我正在阅读Robert Sedwick关于C ++算法的书中的哈希
我们可能正在使用标头节点来简化插入代码 到有序列表,但我们可能不想使用M头节点 单独列出的单独列表。的确,我们甚至可以消除 M通过列表中的第一个节点链接到列表 包括表格
class ST
{
struct node
{
Item item;
node* next;
node(Item x, node* t)
{ item = x; next = t; }
};
typedef node *link;
private:
link* heads;
int N, M;
Item searchR(link t, Key v)
{
if (t == 0) return nullItem;
if (t->item.key() == v) return t->item;
return searchR(t->next, v);
}
public:
ST(int maxN)
{
N = 0; M = maxN/5;
heads = new link[M];
for (int i = 0; i < M; i++) heads[i] = 0;
}
Item search(Key v)
{ return searchR(heads[hash(v, M)], v); }
void insert(Item item)
{ int i = hash(item.key(), M);
heads[i] = new node(item, heads[i]); N++; }
};
我对上述文本的两个问题是作者的意思
&#34;我们甚至可以通过让列表中的第一个节点构成表来消除列表的M链接。&#34;我们如何为此修改上述代码?
&#34;我们可能不希望在单独的链接中为单个列表使用M头节点。&#34;这句话是什么意思。
答案 0 :(得分:1)
“我们甚至可以通过让列表中的第一个节点构成表来消除列表的M链接。”
考虑Node* x[n]
vs Node x[n]
:前者需要为每个非空元素的头Node
分配额外的指针和插入内存,并为每个哈希提供额外的间接寻址表操作,而后者消除了n
指针,但要求任何未使用的元素将能够置于某种可辨别的非使用状态(跟踪哪些可能需要或可能不需要额外的内存),以及sizeof(Node)
大小大于sizeof(Node*)
,无论如何都可能更浪费内存。内存使用的差异也会影响缓存使用的效率:如果表具有较高的元素与桶的比率,则Node[]
将节点数据放入较少的连续内存页中,如果您正在迭代(以未排序的顺序)那么它的缓存效率非常高,而Node*[]
将跳转到可能遍布整个地方的单独的内存分配(或者另一方面,实际上可能在一些实际上非常有用的内容中非常接近:例如,如果两种访问模式和动态内存分配地址与对象创建的时间顺序时间相关。
我们如何为此修改上述代码?
首先,你现有的代码有一个问题:heads[i] = new node(item, heads[i]);
覆盖哈希表中的一个条目,而不首先检查它是否为空...如果有任何东西那么你应该添加到列表中,而不是覆盖数组
讨论的设计变更需要:
link* heads;
......改为......
node* head;
你会像这样初始化它:
head = new node[M];
哪个需要额外的node
构造函数(如果item
有一个等效的默认构造函数,你可以在下面省略它的初始化)
node() : item(nullItem), next(nullptr) { }
然后,您可以轻松完成其他代码的更改。基本上,你正在摆脱一层指针。
“我们可能不希望在单独的链接中为单个列表使用M头节点。”这句话是什么意思。
我没有写它所以不能权威地说,但它似乎在说,在设计列表代码时,可能已经决定在一个空列表中有一个初始节点,因为这简化了代码用于多个列表操作。虽然额外的无数据节点在考虑列表的“通常”使用时似乎是合理的价格,但是哈希表是不寻常的,因为您希望大多数链接到桶的列表具有0或1个元素,并且指数少的应该是越来越长。因此,这样的列表实现非常适合在哈希表中使用。