C ++:链接列表中节点的交换错误

时间:2013-11-11 15:42:46

标签: c++ pointers

我有两个简单的结构:

struct Address
{
    char city[255];
};
typedef Address* AddressPtr;

struct Person
{
    char fullName[255];
    Address* address;
    Person* next;
};
typedef Person* PersonPtr;

Person结构形成Linked列表,其中新元素被添加到列表的开头。我想要做的是按fullName对它们进行排序。起初我尝试交换链接,但是我丢失了列表的开头,因此我的列表被部分排序。然后我决定通过交换节点的值来对列表进行排序。但我得到了奇怪的结果。对于名称为Test3, Test2, Test1的列表,我得到Test3, Test3, Test3

这是我的分类代码:

void sortByName(PersonPtr& head)
{
    TaskPtr currentNode, nextNode;
    for(currentNode = head; currentNode->next != NULL; currentNode = currentNode->next)
    {
        for(nextNode = currentNode->next; nextNode != NULL; nextNode = nextNode->next)
        {
            if(strcmp(currentNode->fullName, nextNode->fullName) > 0)
            {
                swapNodes(currentNode, nextNode);
            }
        }

    }
}

void swapNodes(PersonPtr& node1, PersonPtr& node2)
{
    PersonPtr temp_node = node2;
    strcpy(node2->fullName, node1->fullName);
    strcpy(node1->fullName, temp_node->fullName);

    strcpy(node2->address->city, node1->address->city);
    strcpy(node1->address->city, temp_node->address->city);
}

排序完成后,节点值有点奇怪。

已更新

这是我交换链接的方式:

void swapNodes(PersonPtr& node1, PersonPtr& node2)
{
    PersonPtr temp_person;
    AddressPtr temp_address;

    temp_person = node2;
    node2 = node1;
    node1 = temp_person;

    temp_address = node2->address;
    node2->address = node1->address;
    node1->address = temp_address;
}

4 个答案:

答案 0 :(得分:1)

为了交换单链表中的节点,您需要的不仅仅是交换的两个节点。至少有三个指针需要修改。

+--------+ next +---------+ next +---------+ next +---------+
| before |----->|  node1  |----->|  node2  |----->|  after  |
+--------+      +---------+      +---------+      +---------+

要交换node1node2,您需要交换node1->nextnode2->next,然后将before->next指向node2。当然,要修改before->next,您需要知道before。 (如果节点不相邻,您也必须更新node2的父节点。)

现在,如果您只是交换数据而不是指针,那么只需要交换的两个节点就可以了。但如果这是目标,那么你做了两件可怕的错事。

  1. temp_node == node2。这意味着他们都是指向同一个对象。因此,当您将数据从*node1复制到*node2时,您需要修改相同的对象temp_node。然后,当您从*temp_node复制到*node1时,您实际上正在复制刚刚覆盖的数据。最终结果:所有节点的地址对象包含相同的位。

    您需要temp_node指向一个独立的对象,或者只是让它成为节点对象而不是指针,或者将数据复制到缓冲区或其他东西。

  2. 如果你想完成某些事情,交换指针和交换数据是互斥的。在这里,您交换节点指针,然后交换数据指针......?有点打败了目的,不是吗?

    观看:

        //    node1             node2
        //    +----------+ next +----------+
        //    |   node   |----->|   node   |
        //    +----------+      +----------+
        //          | address        | address
        //          v                v
        //    +----------+      +----------+
        //    | address1 |      | address2 |
        //    +----------+      +----------+
    
        temp_person = node2;
        node2 = node1;
        node1 = temp_person;
    
        //    node2             node1
        //    +----------+ next +----------+
        //    |   node   |----->|   node   |
        //    +----------+      +----------+
        //          | address        | address
        //          v                v
        //    +----------+      +----------+
        //    | address1 |      | address2 |
        //    +----------+      +----------+
    

    到目前为止,这么好。这两个指针是交换的。 这意味着node1->address会看到第二个节点的地址。

    但等等:然后你交换地址指针。

        temp_address = node2->address;
        node2->address = node1->address;
        node1->address = temp_address;
    
        //    node2             node1
        //    +----------+ next +----------+
        //    |   node   |----->|   node   |
        //    +----------+--+   +----------+
        //           address|        | address
        //          +-------|--------+
        //          v       |
        //    +----------+  |   +----------+
        //    | address1 |  +-->| address2 |
        //    +----------+      +----------+
    

    现在,node1->address指向address1。您已经取消了数据。

    因此决定是否要交换指针或数据。如果交换数据,则只需要两个节点的指针,不要修改next指针。另一方面,如果要交换整个节点,则需要“之前”节点,并且不要触摸数据指针 - 交换指针的重点是重新排列数据。

    < / LI>

答案 1 :(得分:1)

当您使用单个链接列表交换两个元素时,您应该从头开始运行它,直到交换的最后一个元素。在运行时,有必要找到两个先前指向这两个必须交换的元素的指针(如果元素是列表中的第一个,则前一个指针可能为null)。然后这个循环完成(在秒元素交换)你必须有四个:第一个元素交换(第一个),第一个元素(first_prev;可能是null),第二个元素(第二个)和前一个要交换的元素(second_prev) ;可能是null)。然后你需要像这样交换它们:

  temp_first_next = first->next;
  temp_second_next = second->next;

  /* put first element at place of the second */
  first->next = temp_second_next;
  if (second_prev) {
    second_prev->next = first;
  }

  /* put second element at place of the first */
  second->next = temp_first_next;
  if (first_prev) {
    first_prev->next = next;
  }

如果您真的使用C ++,最好切换到使用std::stringstd::list并为每个元素使用类。这将更容易,更少指针。 :)

答案 2 :(得分:1)

您不需要使用strcpy。交换节点时,可以通过交换.next成员变量来实现。使用链表的唯一原因是复制一堆数据。

在这些时候,最好在纸上画出情况。在一行中绘制几个框:

 W["1"] -> X["3"] -> Y["2"] -> Z["4"]

好的,我们有四个节点,W X Y和Z,设置为W指向X,X指向Y,Y指向Z.

每个节点都有一个字符串值。 W的值为1,X的值为3,Y的值为2,Z的值为4.

我们希望对节点进行排序,以便字符串值按升序排列:1,2,3,4。

现在,您一直在尝试通过交换实际数据值来实现这一目标:

swap(X.data, Y.data)

...导致:

W["1"] -> X["2"] -> Y["3"] -> Z["4"]

这样可行,但由于很多原因,这不是一个好主意。这对于一个人来说非常容易出错。对于两个,您放弃使用链接列表的主要好处:交换链接而不是数据。

以下是交换节点链接时的情况:

 W["1"] -> Y["2"] -> X["3"] -> Z["4"]

注意没有数据移动 - W的值仍然是1,X的值仍然是3,Y的值仍然是2,Z的值仍然是4.但是我们重新排列了遍历顺序:现在W导致Y,导致X,导致Z,给出预期的结果1,2,3,4。

这可能听起来令人困惑,但一旦你进入正确的心态,这很简单。让我们一次分解一步。

我们的初始状态有什么不同......

 W["1"] -> X["3"] -> Y["2"] -> Z["4"]

......和我们想要的状态?

 W["1"] -> Y["2"] -> X["3"] -> Z["4"]

啊哈,区别在于我们交换了中间的两个节点。这就是我们需要弄清楚如何操作的操作:如何交换两个相邻的节点?

好吧,让我们这样看。 W曾经导致X,导致Y,导致Z.

我们希望W导致Y;我们希望Y导致X;我们希望X通向Z.

你知道吗?这涉及修改三个节点。如果您只了解其中两个节点,则无法执行此操作。你必须能够连续修改三个。

让我们摆脱一些混乱:

A -> B -> C -> D

我们要交换B和C,以便它看起来像:

A -> C -> B -> D

如何?

那么,该图表等同于以下内容:

A -> A.next -> A.next.next -> A.next.next.next

所以这就是我们能做的。让我们定义一个占用节点的操作,并在其后交换两个节点。因此,如果我们在A上使用我们的操作,那么它将交换B和C.或者如果我们在B上使用我们的操作,那么它将交换C和D.

这是怎么做的。

void swapAfter( Node* node ) {
  Node* A = node;
  Node* B = node->next;
  Node* C = node->next->next;
  Node* D = node->next->next->next;

  A->next = C; // Lead A to C
  C->next = B; // Lead C to B
  B->next = D; // Lead b to D

  // Now we have A->C->B->D!
}

然后我们走了。现在我们有一种交换任意两个节点的方法。

我决定解释它(环形交叉口)方式的原因是为了帮助您理解和可视化链接列表的内容。初学者经常在心理上跟踪操作过程中发生的事情。通常他们只会盲目地做一本书告诉他们要做的事情而不是真正理解为什么它有效。而在这个例子中,每个步骤都非常明显。

但是如果您尝试直接使用它,那么“swapAfter”函数可能对您没有用。首先,如果您尝试在最后一个节点上使用它,它将崩溃,因为它没有错误检查。但重点是让您了解链接列表的工作方式,而不是为您提供swapNodes函数。 : - )

主要的一点是你没有为你的swapNodes函数提供足够的参数......如果你想交换“curNode”和“nextNode”,那么你必须知道curNode之前哪个节点(“prevNode” “)。然后你会做类似的事情:

Node* finalNode = nextNode->next;

// right now, we have..
//   prevNode -> curNode -> nextNode -> finalNode

// we want..
//   prevNode -> nextNode -> curNode -> finalNode

// so..
prevNode->next = nextNode;
nextNode->next = curNode;
curNode->next = finalNode;

请注意,您必须进行错误检查才能使其完全正常工作...例如如果nextNode为NULL怎么办?或者如果prevNode为NULL(意味着curNode是第一个节点?),请逐个完成每个案例。

如果你想回避所有这些复杂性,那么就没有理由使用链表。相反,你可以创建一个指针数组:

Person** people = new Person*[4];
for ( int i = 0; i < 4; i++)
  people[i] = new Person();

然后你可以通过交换元素来对该数组进行排序:

if ( strcmp(people[1]->fullName, people[2]->fullName) > 0 )
  swap( people[1], people[2] );

不再处理'下一个'指针。但是现在如果你想为你的数组添加超过4个人,你将不得不重新分配它,因为它是一个固定的大小。这就是权衡。

无论如何,我希望这有点帮助。这是我的第一个真正的Stack Overflow评论,所以如果它有点粗糙,我会道歉。

答案 3 :(得分:0)

问题来自于你在迭代它时交换列表的元素这一事实。

另一种方法可能是运行列表并交换对象,直到它在列表中运行一次而不交换任何元素。