当指向前一个节点的指针不可用时,从单个链表中删除中间节点

Posted

技术标签:

【中文标题】当指向前一个节点的指针不可用时,从单个链表中删除中间节点【英文标题】:Deleting a middle node from a single linked list when pointer to the previous node is not available 【发布时间】:2010-09-09 07:29:34 【问题描述】:

当我们唯一可用的信息是指向要删除的节点的指针而不是指向前一个节点的指针时,是否可以删除单链表中的中间节点?删除后,前一个节点应该指向已删除节点旁边的节点。

【问题讨论】:

那是怎么回事?对我来说,这似乎是一个足够公平的问题。 这是一道经典的面试题。 一个非常干净的答案here 在相关线程上。 【参考方案1】:

这绝对是一个测验而不是一个真正的问题。但是,如果我们允许做一些假设,它可以在 O(1) 时间内解决。为此,列表指向的限制必须是可复制的。算法如下:

我们有一个看起来像这样的列表:... -> Node(i-1) -> Node(i) -> Node(i+1) -> ... 我们需要删除 Node(i)。

    将数据(不是指针,数据本身)从 Node(i+1) 复制到 Node(i),列表将如下所示: ... -> Node(i-1) -> Node(i+1 ) -> 节点(i+1) -> ... 将第二个 Node(i+1) 的 NEXT 复制到一个临时变量中。 现在删除第二个节点(i+1),它不需要指向前一个节点的指针。

伪代码:

void delete_node(Node* pNode)

    pNode->Data = pNode->Next->Data;  // Assume that SData::operator=(SData&) exists.
    Node* pTemp = pNode->Next->Next;
    delete(pNode->Next);
    pNode->Next = pTemp;

迈克。

【讨论】:

不错!我从来没想过。 如果要删除链表中的最后一个节点怎么办? @Aman 这个问题明确说它是一个 middle 节点。 @Aman 问题是有效的。所以这个功能在 LAST NODE 删除时不起作用。有人有答案吗? 不错的解决方案,已纳入我的博客 - k2code.blogspot.in/2010/10/…。再次感谢。【参考方案2】:

让我们假设一个具有结构的列表

A -> B -> C -> D

如果你只有一个指向 B 的指针并想删除它,你可以这样做

tempList = B->next;
*B = *tempList;
free(tempList);

然后列表看起来像

A -> B -> D

但是 B 会保存 C 的旧内容,有效地删除 B 中的内容。如果其他一些代码持有指向 C 的指针,这将不起作用。如果您尝试删除它也将不起作用节点 D。如果你想做这种操作,你需要用一个没有真正使用的虚拟尾节点来构建列表,这样你就可以保证没有有用的节点有一个 NULL 的下一个指针。这对于存储在节点中的数据量很小的列表也更有效。像

这样的结构
struct List  struct List *next; MyData *data; ;

应该没问题,但是有一个地方

struct HeavyList  struct HeavyList *next; char data[8192]; ;

会有点累。

【讨论】:

+1:你不仅显然在这个答案上击败了米哈伊尔,而且你已经解释了一些危险......奇怪的是你得到了他回答的 10% 的选票......跨度> 【参考方案3】:

不可能。

有一些技巧可以模仿删除。

但这些都不会真正删除指针指向的节点。

删除跟随节点并将其内容复制到要删除的实际节点的流行解决方案如果您有外部指针 指向链表中的节点,在这种情况下,指向下一个节点的外部指针会悬空。

【讨论】:

【参考方案4】:

我很欣赏这个解决方案的独创性(删除下一个节点),但它没有回答问题的问题。如果这是实际的解决方案,那么正确的问题应该是“从链表中删除包含在给定指针的节点中的 VALUE”。但是,当然,正确的问题会给你一个解决方案的提示。 提出的问题是为了迷惑人(这实际上发生在我身上,特别是因为面试官甚至没有提到节点在中间)。

【讨论】:

如果你问我,链表中的节点是由它的数据和它在列表中的位置而不是它在内存中的位置来识别的,所以我认为 2 个投票最多的答案是完美的解决方案【参考方案5】:

一种方法是为数据插入一个空值。每当您遍历列表时,您都会跟踪前一个节点。如果找到空数据,则修复列表,然后转到下一个节点。

【讨论】:

【参考方案6】:

最好的做法还是把next节点的数据拷贝到要删除的节点中,将该节点的next指针设置为next节点的next指针,然后删除next节点。

外部指针指向要删除的节点的问题虽然是真的,但也适用于下一个节点。考虑以下链表:

A->B->C->D->E->F 和 G->H->I->D->E->F

如果您必须删除节点 C(在第一个链表中),通过上述方法,您将在将内容复制到节点 C 后删除节点 D。这将导致以下列表:

A->B->D->E->F 和 G->H->I->悬空指针。

如果您完全删除 NODE C,结果列表将是:

A->B->D->E->F 和 G->H->I->D->E->F.

但是,如果你要删除节点D,并且你使用之前的方法,外部指针的问题仍然存在。

【讨论】:

【参考方案7】:

最初的建议是改造:

a -> b -> c

到:

a ->, c

如果您保留信息,例如从节点地址到下一个节点地址的映射,那么您可以在下一次修复链以遍历列表。如果需要在下一次遍历之前删除多个项目,那么您需要跟踪删除的顺序(即更改列表)。

标准解决方案是考虑其他数据结构,例如跳过列表。

【讨论】:

【参考方案8】:

也许做一个软删除? (即在节点上设置“已删除”标志)如果需要,您可以稍后清理列表。

【讨论】:

不过,一个问题是维护该标志会增加每个项目的内存。【参考方案9】:

如果您想保持列表的可遍历性,则不是。您需要更新上一个节点以链接到下一个节点。

无论如何,你是怎么落到这种境地的?你想做什么让你问这个问题?

【讨论】:

【参考方案10】:

您必须沿着列表前进才能找到上一个节点。这将使删除通常为 O(n**2)。如果您是唯一执行删除操作的代码,则可以通过缓存前一个节点并在那里开始搜索来做得更好,但这是否有帮助取决于删除的模式。

【讨论】:

向下移动列表仍然需要 O(n) 才能删除一个项目。 确实如此。我指的是(随机)删除整个列表。【参考方案11】:

给定

A -> B -> C -> D

还有一个指向 B 项的指针,您可以通过 将其删除 1. 释放属于 B 成员的所有内存 2. 将 C 的内容复制到 B 中(这包括它的“next”指针) 3.删除整个C项

当然,您必须小心边缘情况,例如处理一个项目的列表。

现在有 B 的地方有了 C,并且释放了曾经是 C 的存储空间。

【讨论】:

【参考方案12】:

考虑下面的链表

1 -> 2 -> 3 -> 空

指向节点 2 的指针被指定为“ptr”。

我们可以有如下所示的伪代码:

struct node* temp = ptr->next;
ptr->data = temp->data;
ptr->next = temp->next;
free(temp);

【讨论】:

【参考方案13】:

是的,但您不能取消链接。如果您不关心损坏内存,请继续;-)

【讨论】:

【参考方案14】:

是的,但您的列表将在您删除后损坏。

在这种特定情况下,再次遍历列表并获取该指针!一般来说,如果您问这个问题,您所做的事情可能存在错误。

【讨论】:

面试时有人问我这个问题。【参考方案15】:

为了到达上一个列表项,您需要从头开始遍历列表,直到找到一个带有指向当前项的next 指针的条目。然后,您将拥有一个指向您必须修改以从列表中删除当前项目的每个项目的指针 - 只需将 previous->next 设置为 current->next,然后删除 current

edit:Kimbo 以不到一分钟的时间击败了我!

【讨论】:

【参考方案16】:

您可以延迟取消链接,您可以使用标志将节点从列表中取消链接,然后在下一次正确遍历时将其删除。设置为断开链接的节点需要由爬取列表的代码正确处理。

我想您也可以从头开始再次遍历列表,直到找到指向列表中您的项目的东西。几乎不是最优的,但至少比延迟断开要好得多。

一般来说,您应该知道指向您刚刚来自的项目的指针,并且应该传递它。

(编辑:Ick,我花了很长时间才打出完整的答案,三亿人几乎涵盖了我要提到的所有要点。:()

【讨论】:

【参考方案17】:

唯一明智的做法是用几个指针遍历列表,直到前导找到要删除的节点,然后使用尾随指针更新下一个字段。

如果您想有效地从列表中删除随机项目,则需要对其进行双重链接。但是,如果您想从列表的头部获取项目并将它们添加到尾部,则不需要双重链接整个列表。单独链接列表,但使列表中最后一项的下一个字段指向列表中的第一项。然后使列表“head”指向尾部项(而不是头部)。然后很容易添加到列表的尾部或从头部删除。

【讨论】:

【参考方案18】:

你是名单的首位,对吧?你只是遍历它。

假设您的列表由“head”指向,而要删除它的节点为“del”。

C 风格的伪代码(点将是 -> 在 C 中):

prev = head
next = prev.link

while(next != null)

  if(next == del)
  
    prev.link = next.link;
    free(del);
    del = null;
    return 0;
  
  prev = next;
  next = next.link;


return 1;

【讨论】:

【参考方案19】:

下面的代码会创建一个LL,n然后要求用户将指针指向要删除的节点。删除后将打印列表 它的作用与通过复制要删除的节点之后的节点,覆盖要删除的节点,然后删除要删除的节点的下一个节点来完成相同的操作。 即

a-b-c-d

将 c 复制到 b 并释放 c 以便结果为

a-c-d

struct node  

    int data;
    struct node *link;
 ;

void populate(struct node **,int);

void delete(struct node **);

void printlist(struct node **);

void populate(struct node **n,int num)


    struct node *temp,*t;
    if(*n==NULL)
    
        t=*n;
        t=malloc(sizeof(struct node));
        t->data=num;
        t->link=NULL;
        *n=t;
    
    else
    
        t=*n;
        temp=malloc(sizeof(struct node));
        while(t->link!=NULL)
            t=t->link;
        temp->data=num;
        temp->link=NULL;
        t->link=temp;
    


void printlist(struct node **n)

    struct node *t;
    t=*n;
    if(t==NULL)
        printf("\nEmpty list");

    while(t!=NULL)
    
        printf("\n%d",t->data);
        printf("\t%u address=",t);
        t=t->link;
    



void delete(struct node **n)

    struct node *temp,*t;
    temp=*n;
    temp->data=temp->link->data;
    t=temp->link;
    temp->link=temp->link->link;
    free(t);
    

int main()

    struct node *ty,*todelete;
    ty=NULL;
    populate(&ty,1);
    populate(&ty,2);
    populate(&ty,13);
    populate(&ty,14);
    populate(&ty,12);
    populate(&ty,19);

    printf("\nlist b4 delete\n");
    printlist(&ty);

    printf("\nEnter node pointer to delete the node====");
    scanf("%u",&todelete);
    delete(&todelete);

    printf("\nlist after delete\n");
    printlist(&ty);

    return 0;

【讨论】:

将代码缩进四个空格以使其格式正确。【参考方案20】:
void delself(list *list)

   /*if we got a pointer to itself how to remove it...*/
   int n;

   printf("Enter the num:");
   scanf("%d",&n);

   while(list->next!=NULL)
   
      if(list->number==n) /*now pointer in node itself*/
      
         list->number=list->next->number;   /*copy all(name,rollnum,mark..)
                             data of next to current, disconnect its next*/
         list->next=list->next->next;
      
      list=list->next;
   

【讨论】:

【参考方案21】:

如果你有一个链表 A -> B -> C -> D 和一个指向节点 B 的指针。如果你必须删除这个节点,你可以将节点 C 的内容复制到 B,将节点 D 复制到 C 并删除D. 但是我们不能在单链表的情况下删除节点,因为如果我们这样做,节点 A 也会丢失。虽然我们可以在双向链表的情况下回溯到 A。

我说的对吗?

【讨论】:

我会说将 NODE B 的 NEXT 部分复制到 temp 中。然后将NODE C的所有内容复制到NODE B中。使用temp变量中的地址删除NODE C。【参考方案22】:
void delself(list *list)

   /*if we got a pointer to itself how to remove it...*/
   int n;

   printf("Enter the num:");

   scanf("%d",&n);

   while(list->next!=NULL)
   
       if(list->number==n) /*now pointer in node itself*/
       
           list->number=list->next->number;
           /*copy all(name,rollnum,mark..) data of next to current, disconect its next*/
           list->next=list->next->next;
       
       list=list->next;
   

【讨论】:

【参考方案23】:

这是我放在一起的一段代码,它可以满足 OP 的要求,甚至可以删除列表中的最后一个元素(不是以最优雅的方式,但它可以完成)。一边学习如何使用链表一边写的。希望对您有所帮助。

#include <cstdlib>
#include <ctime>
#include <iostream>
#include <string>

using namespace std;


struct node

    int nodeID;
    node *next;
;

void printList(node* p_nodeList, int removeID);
void removeNode(node* p_nodeList, int nodeID);
void removeLastNode(node* p_nodeList, int nodeID ,node* p_lastNode);

node* addNewNode(node* p_nodeList, int id)

    node* p_node = new node;
    p_node->nodeID = id;
    p_node->next = p_nodeList;
    return p_node;


int main()

    node* p_nodeList = NULL;
    int nodeID = 1;
    int removeID;
    int listLength;
    cout << "Pick a list length: ";
    cin >> listLength;
    for (int i = 0; i < listLength; i++)
    
        p_nodeList = addNewNode(p_nodeList, nodeID);
        nodeID++;
    
    cout << "Pick a node from 1 to " << listLength << " to remove: ";
    cin >> removeID;
    while (removeID <= 0 || removeID > listLength)
    
        if (removeID == 0)
        
            return 0;
        
        cout << "Please pick a number from 1 to " << listLength << ": ";
        cin >> removeID;
    
    removeNode(p_nodeList, removeID);
    printList(p_nodeList, removeID);


void printList(node* p_nodeList, int removeID)

    node* p_currentNode = p_nodeList;
    if (p_currentNode != NULL)
    
        p_currentNode = p_currentNode->next;
        printList(p_currentNode, removeID);
        if (removeID != 1)
        
            if (p_nodeList->nodeID != 1)
            
                cout << ", ";
            

            cout << p_nodeList->nodeID;
        
        else
        
            if (p_nodeList->nodeID !=2)
            
                cout << ", ";
            
            cout << p_nodeList->nodeID;
        
    


void removeNode(node* p_nodeList, int nodeID)

    node* p_currentNode = p_nodeList;
    if (p_currentNode->nodeID == nodeID)
    
        if(p_currentNode->next != NULL)
        
            p_currentNode->nodeID = p_currentNode->next->nodeID;
            node* p_temp = p_currentNode->next->next;
            delete(p_currentNode->next);
            p_currentNode->next = p_temp;
        
        else
        
            delete(p_currentNode);
        
    
    else if(p_currentNode->next->next == NULL)
    
        removeLastNode(p_currentNode->next, nodeID, p_currentNode);
    
    else
    
        removeNode(p_currentNode->next, nodeID);
    


void removeLastNode(node* p_nodeList, int nodeID ,node* p_lastNode)

    node* p_currentNode = p_nodeList;
    p_lastNode->next = NULL;
    delete (p_currentNode);

【讨论】:

基本上它分配一个节点直到结束。【参考方案24】:
Void deleteMidddle(Node* head)

     Node* slow_ptr = head;
     Node* fast_ptr = head;
     Node* tmp = head;
     while(slow_ptr->next != NULL && fast_ptr->next != NULL)
     
        tmp = slow_ptr;
        slow_ptr = slow_ptr->next;
        fast_ptr = fast_ptr->next->next;
     
     tmp->next = slow_ptr->next;
     free(slow_ptr);
    enter code here


【讨论】:

完全不同的解决方案。他要求删除中间的数字,不一定是中间元素。 :-)

以上是关于当指向前一个节点的指针不可用时,从单个链表中删除中间节点的主要内容,如果未能解决你的问题,请参考以下文章

剑指Offer-代码的完整性面试题18.2:删除链表中的重复节点

链表前哨节点,以避免将指针重新分配给第一个节点

链表和双指针框架

剑指Offer之在O时间删除链表节点

删除有序链表中相同的元素ii

剑指offer18.删除链表中重复节点