这是在一次采访中向我提出的一个问题。
"内存中有一个单独的链表。你必须删除一个节点。你需要写一个函数来删除那个节点,它只接受要删除的节点的地址作为输入,而不接受其他内容(包括头)"
我给出的答案与下面文章中的答案类似——将下一个节点的内容复制到要删除的节点中,然后删除下一个。
当指向前一个节点的指针不可用时,从单个链接列表中删除中间节点
但面试官又问我,如果我通过了最后一个节点的地址怎么办。我告诉他,由于下一个将是NULL,请将该NULL与地址一起复制到数据字段中,该地址也是NULL的下一个节点。然后他告诉我会有一个悬挂指针的问题。。。我一点也不明白。有人能解释一下这个问题吗?有通用的解决方案吗?
更新(两天后):有点额外。考虑到列表末尾没有特殊节点。最后一个节点指向NULL,如果该节点作为输入,如何使最后一个之前的节点指向NULL。还是不可能?
简单地说:如果一个节点被作为函数的输入,如何使引用它的指针指向NULL
步骤:
- 将数据从节点(i+1)复制到节点(i)
- 将第二个节点(i+1)的NEXT复制到一个临时变量中
- 现在删除第二个节点(i+1)//它不需要指向上一个节点的指针
功能:
void delete_node(node* node)
{
node->Data = node->Next->Data;
node* temp = node->Next->Next;
delete(node->Next);
node->Next = temp;
}
挂起指针:
(http://en.wikipedia.org/wiki/Dangling_reference)
计算机编程中的挂起指针和野生指针不指向适当类型的有效对象的指针。这些是内存安全违规的特殊情况。
当对象被删除或解除分配时,出现挂起指针,而不修改指针的值,以便指针仍然指向已释放内存的内存位置。作为系统如果原始程序然后取消引用(现在)悬挂指针,不可预测的行为可能会产生,因为内存现在可能包含完全不同的数据。
在您的回答中,要删除给定的节点,您实际上删除了下一个节点,该节点可能被指针引用。这就是悬空指针问题产生的原因。
(1) 正如您在一份说明中所澄清的,该列表没有外部参考。(2) 正如面试官所说,可能会出现指针摆动的问题。
(1)和(2)不能同时正确。这意味着某个地方存在误解。
关于删除最后一个节点:
但面试官再次问我,如果我通过了最后一个节点。我告诉他,由于下一个将是NULL,所以复制该NULL与下一个节点的地址一起写入数据字段也为NULL。
我认为您混淆了这两件事:(1)指向NULL的指针p,(2)数据字段中有NULL的链表节点。
假设数据结构为CCD_ 1。将NULL写入d的数据字段不会神奇地使c在其next
字段中具有NULL指针。
如果链表总是有一个永远不会删除的特殊最后节点,则可以删除最后一个节点。例如,a -> b -> c -> d -> LAST
,其中LAST在其数据字段中有一个特殊值,表示它实际上是最后一个元素。现在要删除d,可以删除LAST并在d的数据字段中写入特殊值。
也许这些正是你在面试中试图告诉你的,在这种情况下,你和面试官之间一定存在一些沟通错误。
然后应该在程序中检查给定节点是否是最后一个节点。
void delete_node(node* node1) {
node* search=head;
if(node1==head) {
head=head->next;
search->next=NULL;
node1->next=NULL;
}
while(search->next != node1)
search=search->next;
if(node1->next==NULL) {
search->next=NULL;
} else {
search->next=node1->next;
node1->next=NULL;
}
delete node1;
}
如果有其他元素指向下一个节点,这些元素将被复制到当前节点,然后删除,那么此操作将引入一个错误。因此,在您的回答中,您应该强调,只有在没有外部引用列表的情况下,您的解决方案才有效。
只有当数据结构添加了特定的"最后一个节点"元素时,您的解决方案才能使用最后一个结点。(如果你使用Smalltalk,你可以写self become: nil
。没有其他语言有类似的东西)
不,如果存在对列表的外部引用,则没有通用解决方案。我想面试官想看看你是真的对这个主题很了解,还是只是在重复一个记住的答案。
可能您的链表遍历可能需要假设任何指向null
的节点都是null
节点,而不管值是多少。。。
a->b->c->d->NULL
所以CCD_ 7是CCD_。通过这种方式,您可以节省使用特殊值的时间,因为它们在一般意义上是不正确的。除此之外,您将无法通过任何其他方式使上一个节点指向null
。
当给出最后一个节点时,不是删除它,而是将其分配给一个虚拟节点,在显示数据时,我们可以检查ptr->下一个作为伪节点并在那里终止。
在悬挂指针的情况下,我相信当指针被释放(释放)时,它将变成悬挂指针,因此在释放后将其分配给NULL。
下面是我针对这个问题的代码片段:
dummy-> data = NULL;
dummy-> next = NULL;
FUNC(del_ptr)
{
if (del_ptr->next == NULL )
{
del_ptr = dummy;
return;
}
struct node *next = del_ptr ->next;
del_ptr -> data = next -> data;
del_ptr -> next = next -> next;
free(next);
next=NULL;
}
假设:a -> b -> c -> d
0引用了节点指针。
没有while循环的简单解决方案。在deleteNode()
中,if case处理头和中间节点的删除,else处理最后一个节点的删除。
#include <iostream>
using namespace std;
class Node{
public:
int data;
Node* next;
Node(int d): data(d){}
};
void insert(Node* &head, int data){
Node *node = new Node(data);
node->next = head;
head = node;
}
void print(Node *head){
Node *temp = head;
while(temp !=NULL){
cout << temp->data <<" ";
temp = temp->next;
}
cout << endl;
}
void deleteNode(Node *&node){
cout << "Deleting "<<node->data<<endl;
if(node->next != NULL){
Node *t = node->next;
node->data = node->next->data;
node->next = node->next->next;
delete t;
}else{
delete node;
node = NULL;
}
}
int main(int argc, char const *argv[]) {
Node *head = NULL;
insert(head, 10);
insert(head, 20);
insert(head, 30);
insert(head, 40);
insert(head, 50);
print(head);
deleteNode(head->next); //delete 40
deleteNode(head->next->next->next); //delete last node 10
print(head);
return 0;
}
public void removeNode(Node node){
/* if no node return null */
if(node==null) return;
/* if only 1 node then delete node */
if(node.getNext()==null) {
node = null;
return ;
}
/* copy next node data to this node */
node.data = node.getNext().data();
/* store the next next node */
Node second = node.getNext().getNext();
/* remove next node */
removeNode(node.getNext());
/* set the copied node as next */
node.setNext(second);
}