Algorithm 为什么使用双链表删除哈希表的元素是O(1)?
在CLRS的教科书“算法简介”中,第258页有这样一段 如果列表是双链接的,我们可以在O(1)时间内删除一个元素。(请注意,CHAINED-HASH-DELETE以元素x而不是其键k作为输入,因此我们不必首先搜索x。如果哈希表支持删除,则其链接列表应为双链接,以便我们可以快速删除项目。如果列表仅为单链接,则要删除元素x,我们首先必须在列表中找到x因此,我们可以更新x的前一个属性。对于单链表,删除和搜索将具有相同的渐近运行时间)Algorithm 为什么使用双链表删除哈希表的元素是O(1)?,algorithm,hashtable,doubly-linked-list,Algorithm,Hashtable,Doubly Linked List,在CLRS的教科书“算法简介”中,第258页有这样一段 如果列表是双链接的,我们可以在O(1)时间内删除一个元素。(请注意,CHAINED-HASH-DELETE以元素x而不是其键k作为输入,因此我们不必首先搜索x。如果哈希表支持删除,则其链接列表应为双链接,以便我们可以快速删除项目。如果列表仅为单链接,则要删除元素x,我们首先必须在列表中找到x因此,我们可以更新x的前一个属性。对于单链表,删除和搜索将具有相同的渐近运行时间) 让我困惑的是,这个大家长,我没有理解它的逻辑。对于双链表,仍然需要找
让我困惑的是,这个大家长,我没有理解它的逻辑。对于双链表,仍然需要找到x才能删除它,这与单链表有什么不同?请帮助我理解它 一般来说,您是正确的-您发布的算法将元素本身作为输入,而不仅仅是其键: 请注意,CHAINED-HASH-DELETE将元素x而不是它的 按k键,这样我们就不必先搜索x了
你有元素x——因为它是一个双链接列表,你有指向前置和后继的指针,所以你可以在O(1)中修复这些元素——只有一个链接列表才有后继元素,所以你必须在O(n)中搜索前置元素。教科书是错误的。列表的第一个成员没有可用的“previous”指针,因此,如果元素恰好是链中的第一个元素,则需要额外的代码来查找和取消链接该元素(如果N=M,则通常30%的元素是其链的头部,(当将N个项目映射到M个插槽时;每个插槽都有一个单独的链。) 编辑: 使用反向链接更好的方法是使用指向我们的链接的指针(通常是列表中上一个节点的->下一个链接) 然后删除变为:
*(p->pppar) = p->nxt;
这种方法的一个很好的特点是,它同样适用于链上的第一个节点(其pppar指针指向某个不是节点一部分的指针)
更新2011-11-11
因为人们看不到我的观点,我将尝试举例说明。例如,有一个哈希表表(基本上是一个指针数组)
一组节点一个
,两个
,三个
其中一个必须删除
struct node *table[123];
struct node *one, *two,*three;
/* Initial situation: the chain {one,two,three}
** is located at slot#31 of the array */
table[31] = one, one->next = two , two-next = three, three->next = NULL;
one->prev = NULL, two->prev = one, three->prev = two;
/* How to delete element one :*/
if (one->prev == NULL) {
table[31] = one->next;
}
else {
one->prev->next = one->next
}
if (one->next) {
one->next->prev = one->prev;
}
现在很明显,obove代码是O(1),但有一点很糟糕:它仍然需要数组
,索引31
,因此在大多数情况下节点是“自包含的”,并且指向某个节点的指针足以将其从其链中删除,但当该节点恰好是其链中的第一个节点时,除外;然后需要其他信息来查找表
和31
接下来,将指针指向指针的等价结构作为一个反向链接。
struct node {
struct node *next;
struct node **ppp;
char payload[43];
};
struct node *table[123];
struct node *one, *two,*three;
/* Initial situation: the chain {one,two,three}
** is located at slot#31 of the array */
table[31] = one, one-next = two , two-next = three, three->next = NULL;
one->ppp = &table[31], two->ppp = &one->next, three->ppp = &two-next;
/* How to delete element one */
*(one->ppp) = one->next;
if (one->next) one->next->ppp = one->ppp;
注意:没有特殊情况,也不需要知道父表(考虑有多个哈希表但具有相同节点类型的情况:删除操作仍然需要知道节点应该从哪个表中删除)
通常,在{PREV,NEX}场景中,通过在双链表的开始添加一个哑节点来避免特殊情况;但是这也需要分配和初始化。这里提出的问题是:考虑到你在看一个哈希表的一个特定元素。删除它有多高?
假设您有一个简单的链表:
v ----> w ----> x ----> y ----> z
|
you're here
现在,如果您删除x
,则需要将w
连接到y
以保持列表的链接。您需要访问w
,并告诉它指向y
(您希望有w-->y
)。但是您不能从x
访问w
,因为它是简单链接的!因此您必须遍历所有列表,在O(n)操作中找到w
,然后告诉它链接到y
。这很糟糕
然后,假设您是双重链接的:
v <---> w <---> x <---> y <---> z
|
you're here
vwxyz
|
你来了
很酷,你可以从这里访问w和y,这样你就可以在O(1)操作中连接这两个(wy
)了!在我看来,这其中的哈希表部分主要是一个小问题。真正的问题是:“我们可以在固定时间内从链表中删除当前元素吗?如果可以,如何删除?”
答案是:这有点棘手,但实际上是的,我们可以——至少通常是这样。我们(通常)不必遍历整个链表来查找上一个元素。相反,我们可以在当前元素和下一个元素之间交换数据,然后删除下一个元素
唯一的例外是当/如果我们需要/想要删除列表中的最后一项时。在这种情况下,没有下一个元素可以交换。如果确实必须这样做,就没有真正的方法可以避免找到上一个元素。但是,通常有一些方法可以避免这种情况——一种是使用sentinel inst终止列表空指针的ead。在这种情况下,由于我们从不删除具有sentinel值的节点,因此我们永远不必删除列表中的最后一项。这就给我们留下了相对简单的代码,如下所示:
template <class key, class data>
struct node {
key k;
data d;
node *next;
};
void delete_node(node *item) {
node *temp = item->next;
swap(item->key, temp->key);
swap(item->data, temp->data);
item ->next = temp->next;
delete temp;
}
模板
结构节点{
键k;
数据d;
节点*下一步;
};
void delete_节点(节点*项){
节点*temp=项目->下一步;
交换(项目->键,临时->键);
交换(项目->数据,临时->数据);
项目->下一步=临时->下一步;
删除临时文件;
}
假设您要删除一个元素x,通过使用双链接列表,您可以轻松地将x的上一个元素连接到x的下一个元素。因此,无需遍历所有列表,它将位于O(1)中。查找(x)
通常是O(1)表示链式哈希表,这无关紧要
template <class key, class data>
struct node {
key k;
data d;
node *next;
};
void delete_node(node *item) {
node *temp = item->next;
swap(item->key, temp->key);
swap(item->data, temp->data);
item ->next = temp->next;
delete temp;
}
unordered_map<value,node*>mp;