【问题标题】:Swapping Nodes on a single linked list在单个链表上交换节点
【发布时间】:2010-12-04 21:25:47
【问题描述】:

我正在尝试创建一个swapNode 函数,它可以获取任意两个节点并交换它们。我已经制定了一个算法,如果它们至少相距 2 个节点,则该算法可以工作,但我似乎无法想出一个算法,如果它们彼此靠近,则可以工作。

这是我到目前为止写的内容:

void swapNode(call * &head, call * &first, call * &second){
    call * firstPrev = NULL;
    call * secPrev = NULL;
    call * current = head;

    //set previous for first
    while((current->next != first) ){
        current = current->next;
    }

    firstPrev = current;
    current = head;

    //set previous for second
    while((current->next != second) ){
        current = current->next;
    }

    secPrev = current;
    current = second->next;

    //set firstPrev-> next to second
    firstPrev->next = second;
    //set secPrev->next to first
    secPrev->next = first;
    //set second->next = first->next
    second->next = first->next;
    //set first->next to current
    first->next = current;

    current = head;
    while(current->next != NULL){
        cout << current->number << endl;
        current = current->next;
    }

    cout << current->number << endl;
}

编辑: 我现在将它作为我的交换部件,但它似乎仍然无法正常工作

//swap firstPrev-> next with second->next
tmp = firstPrev->next;
second->next = firstPrev->next;
second->next = tmp;
//swap swap first->next with second->next
tmp = first->next;
second->next = first->next;
second->next = tmp;

编辑2: 这个似乎也不起作用,我遇到了段错误。

    //swap previous's->next
    tmp =firstPrev->next;
    secPrev->next = firstPrev->next;
    secPrev->next = tmp;
    //swap swap first->next with second->next
    tmp = first->next;
    second->next = first->next;
second->next = tmp;

【问题讨论】:

  • 嗨 Reti,我在回复中回复了 edit1。如果遇到段错误,请检查您是否正确分配和删除了 tmp 变量。

标签: c++ linked-list swap


【解决方案1】:

假设我们有:

Node1 -> Node2 -> Node3 -> Node4 -> Node5

要交换两个节点,您需要交换每个节点之前的 next 值,以及要交换的节点的 next 值。

因此,要交换 Node2 和 Node3,您实际上必须将 Node1-&gt;nextNode2-&gt;next 交换,并将 Node2-&gt;nextNode3-&gt;next 交换。这将起作用,即使它们彼此相邻(或者即使它是同一个节点)。例如:

交换Node1-&gt;nextNode2-&gt;next

Node1->next = Node3
Node2->next = Node2

Node3-&gt;next交换Node2-&gt;next

Node2->next = Node4
Node3->next = Node2

结果如下:

Node1 -> Node3 -> Node2 -> Node4 -> Node5

交换了!

正如 cmets 部分中的 unwind 所述,如果将 Node1 与任何东西交换,则必须为链表设置一个新的头。


针对问题的编辑:

您的代码交换几乎正确。但是,您需要将 firstPrev 与 secPrev 交换。在我的示例中恰好发生了两次交换节点的next 值之一,因为它们彼此相邻。但从逻辑上讲,我们要交换前两个的nexts,然后交换实际节点的nexts。试试这个:

//swap firstPrev-> next with secPrev->next
tmp = firstPrev->next;
secPrev->next = firstPrev->next;
secPrev->next = tmp;
//swap swap first->next with second->next
tmp = first->next;
second->next = first->next;
second->next = tmp;

如果您遇到段错误,请检查 tmp 变量 - 这可能是某处的分配或删除错误。你从哪里得到段错误?

【讨论】:

  • ... 除了 Node1 参与交换的特殊情况,因为这会改变列表的头部。
  • 为什么要设置 node2->next 一个接一个的不同值?
  • @reti:是的,在这种情况下,这似乎是一个毫无意义的步骤,并且可能可以做得更快。但是,如果节点相距较远,则需要所有步骤。正如我在回答中所说,要交换两个节点,您需要交换每个节点之前的 next 值,以及要交换的节点的 next 值。如果节点彼此相邻(假设 #2 和 #3,如我的示例所示),那么“#3 之前的一个” #2。所以我们更改#2 的next 值一次,因为#2 是交换的一部分,再一次因为#2 是另一个节点之前的节点(#3)。这是一个特例。
  • @Smashery 我明白你在说什么,但我对它的一般形式有疑问。如果您查看 OP 中的 edit2,您会看到我的尝试,但是当我尝试运行它时出现段错误。
  • gdb 说我在这里遇到了段错误:secPrev->next = firstPrev->next;我在本地静态创建了 tmp,这很好。
【解决方案2】:

在大多数现实生活场景中,交换值将是最佳解决方案:

void swapNode(call * &head, call * &first, call * &second) {
    // swap values, assuming the payload is an int:
    int tempValue = first->value;
    first->value = second->value;
    second->value = tempValue;
}

如果不允许这样做,那么您希望在 ->next 而不是 ->value 组件上进行类似样式的交换。然后对 firstPrev->next 和 secondPrev->next 组件进行另一次交换。注意第一个或第二个 == head 的特殊情况。

【讨论】:

  • 非常简洁实用的解决方案
【解决方案3】:

虽然我不能 100% 确定答案是否应该涉及对节点指针(或指向指针的指针)的引用,并且这应该处理其中一个节点也是列表头的情况。

void swapNodes(node *&first, node *&second)
{
  node *t = second->next;
  second->next = first->next;
  first->next = t;
  t = second;
  second = first;
  first = t;
}

那么你就可以调用例如:

swapNodes(head, secPrev->next);

swapNodes(firstPrev->next, head);

swapNodes(firstPrev->next, secPrev->next)

它应该自动工作。

编辑:

swapNodes 可能更具可读性:

void swapNodes(node *&first, node *&second)
{
  std::swap(first->next, second->next);
  std::swap(first, second);
}

【讨论】:

    【解决方案4】:

    经验法则:“始终将数据与指针分开,从不交换指针,只交换数据!”。在不使用 memcpy() 的情况下进行显式交换,从而可以避免对齐问题。它不会在算法复杂性方面造成性能损失,但会使您的代码更具可读性和安全性。

    【讨论】:

      【解决方案5】:
      void swap()
      {
       struct node *temp=0,*nxt,*ptr;
       ptr=head;
       int count=0;
       while(ptr)
       {
         nxt=ptr->link;
         if(nxt)
       {
        if(count==0)
          head=nxt;
          count++;
         ptr->link=nxt->link;
         nxt->link=ptr;
         if(temp!=NULL)
         temp->link=nxt;
         temp=ptr;
         if(ptr->link==NULL)
         break;
         ptr=nxt->link->link;
       }
      

      } }

      【讨论】:

      • 虽然此代码可能会解决所提出的问题,但最好在代码中添加一些描述级别以解释它如何解决问题。
      【解决方案6】:

      这里p1是第一个要交换的节点,p2是第二个要交换的节点。而prevnode是p2的前一个节点

              temp=head;
              while(temp!=NULL){
              if(temp->link==p1){
                 temp->link=p2;
      
                 prevnode->link=p2->link; 
                 p2->link=p1->link;
                 t=p1->link;
                 while(t!=prevnode)
                    t=t->link;
                    cout<<" YES";
                 cout<<"["<<t->num<<"] ";
                 p1->link=prevnode->link;
                 prevnode->link=p1;   
      
                 temp=p1;
              }//if_
      
              cout<<" "<<temp->num;              
              temp=temp->link;   
          }
      

      【讨论】:

        【解决方案7】:

        您还必须交换前一个节点的next 组件,否则链表将不会保持连接在一起。请注意,我的结构称为node

        int swapNode( node *&head * &first, node * &second)
        {
             //first we will declare the 
             //previous of the swapping nodes
             node *firstprev=NULL;
             node*secprev=NULL;
             node*current=head;
             //set previous first
             while(current->next!=first)
             {
                current=current->next;
             }
             firstprev=current;
             //seting 2nd previous
             while(current->next!=second)
             {
                current=current->next;
             }
        
            // swap values, assuming the payload is an int:
            int tempValue = first->value;
            first->value = second->value;
            second->value = tempValue;
            //swaping next of the nodes
            firstprev->next=second;
            secprev->next=first;
            return; 
        }
        

        【讨论】:

          【解决方案8】:

          谢谢大家的回答!我确实意识到这个问题几乎是在两年前提出的,并且答案早已被接受,但我对答案有点困惑。因此,尽管提问者可能不关心新答案,但我想添加我的版本,以防其他读者也感到困惑,并记录我自己的方法。其中一些可能更适合作为 cmets,但我还没有评论的声誉。

          首先,无论我多久查看一次 - 在白板或调试器中 - 如果我不使用条件即使使用抽象步骤或 Smashery 当前接受的答案中的具体代码,也可以区分节点是否相邻的情况。我在 Internet 上环顾了一下,以找到当前接受的答案风格的代码以避免这种条件,但对我来说,避免这种情况非常棘手,而且我的搜索没有找到这样的解决方案(除非我错了关于提议的一个可能不正确)。如果有一些巧妙的表达式可以在它们相邻时产生第一个节点的地址,而在它们不相邻时产生第一个节点的后继地址,那么就不需要该条件,因为(显然)需要找出第二个节点的新后继那个条件。

          其次,关于在接受的答案中对相同变量的连续分配,我与其他评论员有同样的问题。我希望我在这里不是非常密集,而是按顺序为同一个变量分配不同的值,除非可能有副作用,对我来说,似乎永远不会给变量留下除最后一次赋值之外的任何其他值,不管我考虑的节点配置是什么,因此之前的分配显然是多余的。如果我对此有误,并且该方法实际上可以解决此问题,那么我将能够消除下面代码中的最后一个条件,当我第一次在互联网上寻找解决方案时,我试图摆脱它在没有特殊情况的相邻节点的情况下交换节点。我不太确定,但听起来 Smashery 故意将那些重复的任务排除在逻辑严谨性之外并更好地说明程序 - 不过我可能误解了。

          第三,在这个网站和其他网站上,我经常看到其他答案中重复出现的声明,即最好交换节点的内容,而不是指针。当然,在简单整数的情况下,就像到目前为止的示例一样,这显然会产生更短、更简单的代码。但是,当我们讨论带有包含整数的节点的链表时,它通常是作为更复杂和通用容器的支持数据结构的替身。因此,我不认为交换节点的内容真的那么容易,至少如果数据结构的实现不能对容器项的复制语义做出假设的话。此外,能够像这样交换节点的内容意味着链表对这些节点的内容拥有所有权,因为否则链表方法之外的代码可能会引用这些节点中的对象,其值在他们下面突然改变。

          不过,我承认这可能取决于容器的语义。对于一个数组,交换方法可能会改变引用下面的值,使其指向该数组的某个索引。这意味着引用并不意味着指向特定对象,而是指向容器中可以索引的位置。如果我们将链表视为仅对一组对象进行排序的一种方式,这些对象在链表之外使用,用户可能会期望交换操作仅交换位置,而不是内容。

          例如,假设链表表示“汽车”类型的对象。每辆车都有一个车主,车主通过指向它的指针来引用他们的车。现在假设链表表示一组汽车计划在汽车经销商处进行维修以供检查的顺序。如果我们交换两个节点的内容,以便交换两辆汽车的时间表,并通过交换它们的内容来做到这一点,那么服务实际上会以新的、正确的顺序发生——但人们也会突然拥有不一样的车! (不过我不介意换特斯拉,因为我只开卡罗拉。)

          如果链表(如数组示例)基于索引语义,则节点中的位置可能仅表示汽车装载到船上进行运输的顺序。在这一点上,汽车没有任何所有者,我们真的只关心它们在哪个插槽。然后,我想换掉汽车真的没有什么坏处,即节点引用的对象的内容。

          最后是代码。正如我上面所说,我无法避免相邻节点的特殊情况。

          一、辅助方法的定义:

          int find_node(int v, node* root, node** n, node** pn) {
              int i = 0;
              for (*n = root; *n != NULL && (*n)->v != v; ++i) {
                  *pn = *n;
                  *n = (*n)->next;
              }
              return i;
          }
          

          此方法通过其值查找节点。返回的整数是链表中节点的从零开始的位置(如果愿意,可以将其称为索引)。我发现通过位置而不是指针比较来检测邻接更具可读性。列表的开头是根。该方法将 n 设置为指向包含传递值的节点。在pn中,方法存储n的前任。

          以下是实际的交换:

          void swap_nodes(node **root, int v1, int v2) {
              if (v1 == v2) return;
          
              node *n1, *n2, *pn1, *pn2;
          
              int p1 = find_node(v1, *root, &n1, &pn1);
              int p2 = find_node(v2, *root, &n2, &pn2);
          
              if (p1 > p2) {
                  std::swap(n1, n2);
                  std::swap(pn1, pn2);
                  std::swap(p1, p2);
              }
          
              if (p1 == 0) *root = n2; 
              else pn1->next = n2;
          
              node* nn1 = n1->next;
              n1->next = n2->next;
          
              if (p2 - p1 > 1) {
                  n2->next = nn1;
                  pn2->next = n1;
              } else {
                  n2->next = n1;
              }
          }
          

          很抱歉,我稍微更改了 OP 方法的签名。我发现将节点的值传递给交换更方便,而不是节点指针。如果您仅将节点指针传递给各个节点,则必须再次遍历才能找到具有此解决方案的前辈,这对我来说有点尴尬。如果我们无法通过这些值区分节点,例如值不是唯一的,但我们需要指向节点的指针。

          和上面对 find_node 的解释一样,我们首先找到通过 v1 和 v2 传递给 swap_nodes 的节点值的位置、节点和前驱。如果要交换的第二个节点出现在第一个节点之前,则第一个和第二个节点的值将全部交换。这样做的代码并不多,减少了特殊的大小写,并使其更易于可视化。

          现在,我们只剩下两个条件句了,这两个条件句似乎都不需要避免。如果第一个节点位于链表的头部,即位置为零,则根需要指向第二个节点。否则,第一个节点的前任将指向第二个节点。

          需要记住第一个节点的后继节点的先前值,以防节点不相邻。然后,将第一节点的后继节点设置为第二节点的当前后继节点。这是适用于所有情况的唯一更改:第一个节点的新后继节点是第二个节点的旧后继节点是唯一确定的并且有助于在开始时记住指针操作及其在实现此交换时的顺序.

          最后,如果节点的位置相差一个以上,则它们不相邻。然后,第二个节点的新后继节点成为第一个节点的旧后继节点 - 在上面保存 - 而第二个节点的前任现在指向第一个节点。如果它们相邻,则在要交换的节点之间没有需要更新的节点,因此只需将第二个节点链接到第一个节点即可。

          【讨论】:

            猜你喜欢
            • 2017-05-05
            • 1970-01-01
            • 2013-02-25
            • 2014-04-17
            • 1970-01-01
            • 2015-05-25
            • 2017-12-07
            相关资源
            最近更新 更多