我正在进行一项任务,我必须实现自己的HashMap。在赋值文本中,它被描述为一个列表数组,每当你想要添加一个元素时,它最终在数组中的位置由其hashCode决定。在我的例子中,它是电子表格中的位置,所以我刚刚使用了columnNumber + rowNumber,然后将其转换为String,然后转换为int,作为hashCode,然后我将其插入到Array中。它当然以节点(键,值)的形式插入,其中键是单元格的位置,值是单元格的值。
但我必须说我不明白为什么我们需要一个列表数组,因为如果我们最终得到一个包含多个元素的列表,它会不会相当大地增加查找时间?那么它不应该是一个节点数组吗?
我也发现了Java中HashMap的这种实现:
public class HashEntry {
private int key;
private int value;
HashEntry(int key, int value) {
this.key = key;
this.value = value;
}
public int getKey() {
return key;
}
public int getValue() {
return value;
}
}
public class HashMap {
private final static int TABLE_SIZE = 128;
HashEntry[] table;
HashMap() {
table = new HashEntry[TABLE_SIZE];
for (int i = 0; i < TABLE_SIZE; i++)
table[i] = null;
}
public int get(int key) {
int hash = (key % TABLE_SIZE);
while (table[hash] != null && table[hash].getKey() != key)
hash = (hash + 1) % TABLE_SIZE;
if (table[hash] == null)
return -1;
else
return table[hash].getValue();
}
public void put(int key, int value) {
int hash = (key % TABLE_SIZE);
while (table[hash] != null && table[hash].getKey() != key)
hash = (hash + 1) % TABLE_SIZE;
table[hash] = new HashEntry(key, value);
}
}
所以put方法首先在表[hash]中查找是正确的,如果那不是空的,如果那里没有得到密钥,则在put方法中输入,然后继续to table [(hash + 1)%TABLE_SIZE]。但如果它是相同的键,它只是覆盖该值。这是正确理解的吗?是因为get和put方法使用相同的方法查找数组中的位置,给定相同的键它们最终会在数组中的同一位置?
我知道这些问题可能有些基本,但我花了很多时间试图解决这个问题,为什么我们会非常感谢任何帮助!
修改的
所以现在我尝试通过Node类自己实现HashMap 构造一个带有键和相应值的节点,它还有一个getHashCode方法,我只是将这两个值相互连接起来。
我还构建了一个SinglyLinkedList(前一个作业的一部分),我将其用作存储桶。
我的Hash函数只是hashCode%hashMap.length。
这是我自己的实现,你怎么看?
package spreadsheet;
public class HashTableMap {
private SinglyLinkedListMap[] hashArray;
private int size;
public HashTableMap() {
hashArray = new SinglyLinkedListMap[64];
size = 0;
}
public void insert(final Position key, final Expression value) {
Node node = new Node(key, value);
int hashNumber = node.getHashCode() % hashArray.length;
SinglyLinkedListMap bucket = new SinglyLinkedListMap();
bucket.insert(key, value);
if(hashArray[hashNumber] == null) {
hashArray[hashNumber] = bucket;
size++;
}
if(hashArray[hashNumber] != null) {
SinglyLinkedListMap bucket2 = hashArray[hashNumber];
bucket2.insert(key, value);
hashArray[hashNumber] = bucket2;
size++;
}
if (hashArray.length == size) {
SinglyLinkedListMap[] newhashArray = new SinglyLinkedListMap[size * 2];
for (int i = 0; i < size; i++) {
newhashArray[i] = hashArray[i];
}
hashArray = newhashArray;
}
}
public Expression lookUp(final Position key) {
Node node = new Node(key, null);
int hashNumber = node.getHashCode() % hashArray.length;
SinglyLinkedListMap foundBucket = hashArray[hashNumber];
return foundBucket.lookUp(key);
}
}
查找时间应该在O(1)附近,所以我想知道是否是这种情况?如果不是,我在这方面如何改进呢?
答案 0 :(得分:9)
你必须有一些计划来处理哈希冲突,其中两个不同的键落在同一个桶中,即阵列的相同元素。
最简单的解决方案之一是保留每个存储桶的条目列表。
如果你有一个好的哈希算法,并确保桶的数量大于元素的数量,你应该最终得到大多数桶有零个或一个项目,所以列表搜索不应该花费很长时间。如果列表变得太长,则需要重新使用更多存储桶来传播数据。
答案 1 :(得分:1)
这实际上取决于你的hashcode方法有多好。让我们说你试图让它变得尽可能糟糕:你每次都使哈希码返回1。如果是这种情况,您将拥有一个列表数组,但只有该数组的一个元素将包含任何数据。该元素只会在其中有一个巨大的列表。
如果你这样做,你的hashmap会非常低效。但是,如果您的哈希码稍好一些,它会将对象分配到许多不同的数组元素中,因此效率会更高。
最理想的情况(通常无法实现)是使用哈希码方法,无论您放入什么对象,都会返回唯一的数字。如果你能做到这一点,你就不需要一系列的列表。你可以使用一个数组。但由于您的哈希码不是“完美”,因此两个不同的对象可能具有相同的哈希码。您需要能够通过将它们放在同一个数组元素的列表中来处理该场景。
但是,如果您的哈希码方法“非常好”并且很少发生冲突,那么列表中很少会有多于1个元素。
答案 2 :(得分:0)
Lists
通常被称为桶,是处理冲突的一种方式。当两个数据元素具有相同的哈希码mod TABLE SIZE时,它们会发生碰撞,但两者都必须存储。
更糟糕的一种冲突是两个不同的数据点具有相同的key
- 这在哈希表中是不允许的,一个将覆盖其他数据点。如果只是将行添加到列,则(2,1)和(1,2)都将具有3的键,这意味着它们不能存储在同一个哈希表中。如果你在没有分隔符的情况下将字符串连接在一起,则问题在于(12,1)与(1,21)---两者都有键“121”对于分隔符(例如逗号),所有键都是不同的。
如果hashcode是相同的mod TABLE_SIZE,则不同的密钥可以位于同一个buck中。这些列表是将两个值存储在同一个存储桶中的一种方法。
答案 3 :(得分:0)
class SpreadSheetPosition {
int column;
int row;
@Override
public int hashCode() {
return column + row;
}
}
class HashMap {
private Liat[] buckets = new List[N];
public void put(Object key, Object value) {
int keyHashCode = key.hashCode();
int bucketIndex = keyHashCode % N;
...
}
}
比较有N个列表,只有一个列表/数组。为了在列表中搜索,可能必须遍历整个列表。通过使用列表数组,至少可以减少单个列表。甚至可能获得一个或零个元素的列表(null)。
如果hashCode()
尽可能独特,立即找到的机会很高。