简体   繁体   English

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

[英]C++: incorrect swapping of nodes in linked list

I have 2 simple structures: 我有2个简单的结构:

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

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

The Person structure forms the Linked list where new elements are added to the beginning of the list. Person结构形成Linked列表,其中新元素被添加到列表的开头。 What I want to do is to sort them by fullName . 我想要做的是按fullName排序。 At first I tried to swap links, but I lost the beginning of the list and as a result my list was sorted partially. 起初我尝试交换链接,但是我丢失了列表的开头,因此我的列表被部分排序。 Then I decided to sort list by swapping the values of nodes. 然后我决定通过交换节点的值来对列表进行排序。 But I get strange results. 但我得到了奇怪的结果。 For a list with names: Test3, Test2, Test1 , I get Test3, Test3, Test3 . 对于名称为Test3, Test2, Test1的列表,我得到Test3, Test3, Test3

Here is my sorting code: 这是我的排序代码:

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);
}

After the sorting completion, nodes values are a little bit strange. 排序完成后,节点值有点奇怪。

UPDATED 更新

This is how I swapped links: 这是我交换链接的方式:

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;
}

In order to swap nodes in a singly-linked list, you need more than just the two nodes you're swapping. 为了交换单链表中的节点,您需要的不仅仅是交换的两个节点。 There are at least three pointers to modify. 至少有三个要修改的指针。

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

To swap node1 and node2 , you need to swap node1->next and node2->next , and then point before->next at node2 . 要交换node1node2 ,你需要换node1->nextnode2->next ,再点before->nextnode2 Of course, in order to modify before->next , you need to know before . 当然,为了改变before->next ,你需要知道before (And if the nodes aren't adjacent, you'd have to update node2 's parent as well.) (如果节点不相邻,您还必须更新node2的父节点。)

Now, if you're just swapping the data rather than the pointers, you'd be OK with just the two nodes you're swapping. 现在,如果您只是交换数据而不是指针,那么只需要交换的两个节点就可以了。 But you're doing two things horribly wrong, if that's the goal. 但如果这是目标,那么你做了两件可怕的事情。

  1. temp_node == node2 . temp_node == node2 That means they're both pointing at the same object . 这意味着他们都指向同一个对象 So when you copy data from *node1 into *node2 , you're modifying the same object temp_node points to. 因此,当您将数据从*node1复制到*node2 ,您将修改相同的对象temp_node指向。 Then when you copy from *temp_node into *node1 , you're actually copying that data you've just overwritten. 然后,当您从*temp_node复制到*node1 ,您实际上正在复制刚刚覆盖的数据。 End result: all nodes' address objects contain the same bits. 最终结果:所有节点的地址对象包含相同的位。

    You need temp_node to point to an independent object, or simply let it be a node object rather than a pointer, or copy the data into a buffer or something. 您需要temp_node指向一个独立的对象,或者只是让它成为节点对象而不是指针,或者将数据复制到缓冲区或其他东西中。

  2. Swapping the pointers and swapping the data are mutually exclusive if you want to get something done. 如果你想完成某些事情,交换指针和交换数据是互斥的。 Here, you swap the node pointers, and then you swap data pointers...? 在这里,您交换节点指针,然后交换数据指针......? Kinda defeats the purpose, doesn't it? 有点打败了目的,不是吗?

    Watch: 看:

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

    So far, so good. 到现在为止还挺好。 The two pointers are swapped. 这两个指针是交换的。 And that means node1->address sees the second node's address. 这意味着node1->address可以看到第二个节点的地址。

    But wait: Then you swap address pointers. 但是等一下:然后你交换地址指针。

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

    So now, node1->address points at address1 . 所以现在, node1->address指向address1 You have unswapped the data. 您已经取消了数据。

    So decide whether you want to swap pointers or data. 因此,决定是否要交换指针或数据。 If you swap the data, you only need the two nodes' pointers, and don't modify the next pointers. 如果交换数据,则只需要两个节点的指针,不要修改next指针。 If you're swapping whole nodes, on the other hand, you need the "before" node, and you don't touch the data pointers -- the whole point of swapping the pointers is to rearrange the data. 另一方面,如果要交换整个节点,则需要“之前”节点,而不要触摸数据指针 - 交换指针的重点是重新排列数据。

As you are using single linked list to swap two elements you should run it from the beginning till the last element to swap. 当你使用单个链表来交换两个元素时,你应该从头开始运行它到最后一个要交换的元素。 While running it is necessary to find two previous pointers to these two elements which have to be swapped (one previous pointer may be null if element is the first in the list). 在运行时,有必要找到两个先前指向这两个必须交换的元素的指针(如果元素是列表中的第一个,则前一个指针可能为null)。 Then this loop finished (at the seconds element to swap) you must have four: first element to swap (first), previous to first element (first_prev; may be null), second element (second) and the previous element to swap (second_prev; may be null). 然后这个循环完成(在秒元素交换)你必须有四个:第一个元素交换(第一个),第一个元素(first_prev;可能是null),第二个元素(第二个)和前一个要交换的元素(second_prev) ;可能是null)。 Then you need just to swap them in way like this: 然后你需要像这样交换它们:

  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;
  }

And if you are really using C++ it is better to switch to using std::string , std::list and use classes for each element. 如果您真的使用C ++,最好切换到使用std :: stringstd :: list并为每个元素使用类。 It will be easier, less pointers. 这将更容易,更少指针。 :) :)

You don't need to use strcpy. 您不需要使用strcpy。 When you swap nodes, you can accomplish that by swapping the .next member variable. 交换节点时,可以通过交换.next成员变量来实现。 The only reason to use linked lists is to copy a bunch of data around. 使用链表的唯一原因是复制一堆数据。

In times like these, it's a good idea to draw out the situation on paper. 在这样的时代,最好在纸上画出情况。 Draw several boxes in a line: 在一行中绘制几个框:

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

Ok, so, we have four nodes, WXY and Z, set up so that W points to X, X points to Y, and Y points to Z. 好的,我们有四个节点,WXY和Z,设置为W指向X,X指向Y,Y指向Z.

Each node has a string value. 每个节点都有一个字符串值。 W's value is 1, X's value is 3, Y's value is 2, and Z's value is 4. W的值为1,X的值为3,Y的值为2,Z的值为4。

We want to sort the nodes so that the string values go in ascending order: 1, 2, 3, 4. 我们想要对节点进行排序,以便字符串值按升序排列:1,2,3,4。

Now, you've been trying to accomplish that by swapping the actual data values around: 现在,您一直在尝试通过交换实际数据值来实现这一目标:

swap(X.data, Y.data)

... resulting in: ... 导致:

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

That would work, but it's not a good idea, for many reasons. 这样可行,但由于很多原因,这不是一个好主意。 It's very bug-prone, for one. 这对于一个人来说非常容易出错。 For two, you give up the main benefit of using linked lists: swapping around the links, rather than the data. 对于两个,您放弃使用链接列表的主要好处:交换链接而不是数据。

Here's what it would look like if you swap the node linkages around: 以下是交换节点链接时的情况:

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

Notice how none of the data moved -- W's value is still 1, X's value is still 3, Y's value is still 2, and Z's value is still 4. But we've rearranged the traversal order: now W leads to Y, which leads to X, which leads to Z, giving the expected result of 1, 2, 3, 4. 注意没有数据移动 - W的值仍然是1,X的值仍然是3,Y的值仍然是2,Z的值仍然是4.但是我们重新排列了遍历顺序:现在W导致Y,导致X,导致Z,给出预期的结果1,2,3,4。

This may sound confusing, but it's simple once you get into the right mindset. 这可能听起来令人困惑,但一旦你进入正确的心态,这很简单。 Let's break down the steps one at a time. 让我们一次分解一步。

What's the difference between our initial state... 我们的初始状态有什么不同......

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

... and our desired state? ......和我们想要的状态?

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

Aha, the difference is that we've swapped the middle two nodes. 啊哈,区别在于我们交换了中间的两个节点。 So that's the operation we need to figure out how to do: How do you swap two adjacent nodes? 这就是我们需要弄清楚如何操作的操作:如何交换两个相邻的节点?

Well, let's look at it like this. 好吧,让我们这样看。 W used to lead to X, which lead to Y, which lead to Z. W曾经导致X,导致Y,导致Z.

We want W to lead to Y; 我们希望W导致Y; we want Y to lead to X; 我们希望Y导致X; we want X to lead to Z. 我们希望X通向Z.

Do you see? 你有看到? That involves modifying three nodes. 这涉及修改三个节点。 You can't do it if you only know about two of the nodes. 如果您只了解其中两个节点,则无法执行此操作。 You have to be able to modify three in a row. 您必须能够连续修改三个。

Let's get rid of some clutter: 让我们摆脱一些混乱:

A -> B -> C -> D

We want to swap B and C, so that it winds up looking like: 我们想要交换B和C,以便它看起来像:

A -> C -> B -> D

How? 怎么样?

Well, that graph is equivalent to the following: 那么,该图表等同于以下内容:

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

So here's what we can do. 所以这就是我们能做的。 Let's define an operation that takes a node, and swaps the two nodes after it. 让我们定义一个占用节点的操作,并在其后交换两个节点。 So if we use our operation on A, then it will swap B and C around. 因此,如果我们在A上使用我们的操作,那么它将交换B和C. Or if we use our operation on B, then it will swap C and D around. 或者如果我们在B上使用我们的操作,那么它将交换C和D.

Here's how. 这是如何做。

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!
}

And there we go. 我们走了。 Now we have a way of swapping around any two nodes. 现在我们有一种交换任意两个节点的方法。

The reason I decided to explain it this (roundabout) way is to help you understand and visualize what's going on with linked lists. 我决定解释它(环形交叉口)方式的原因是为了帮助您理解和可视化链接列表的内容。 Beginners often have trouble mentally following what happens during the operations. 初学者经常在心理上跟踪操作过程中发生的事情。 Usually they'll just blindly do what a book tells them to do and not really grok why it works. 通常他们只会盲目地做一本书告诉他们要做的事情而不是真正理解为什么它有效。 Whereas with this example, each step is very obvious. 而在这个例子中,每一步都非常明显。

But that "swapAfter" function might not be very useful to you if you try to use it directly. 但是,如果您尝试直接使用它,那么“swapAfter”函数可能对您没有用。 For one thing, it'll crash if you try to use it on the last node, because it has no error checking. 首先,如果您尝试在最后一个节点上使用它,它将崩溃,因为它没有错误检查。 But the point was to give you an understanding of how linked lists work rather than give you a swapNodes function. 但重点是让您了解链接列表的工作方式,而不是为您提供swapNodes函数。 :-) :-)

The main takeaway is that you're not providing enough parameters to your swapNodes function... if you want to swap "curNode" and "nextNode", then at that point you must know which node comes before curNode ("prevNode"). 主要的一点是你没有为你的swapNodes函数提供足够的参数......如果你想交换“curNode”和“nextNode”,那么你必须知道curNode(“prevNode”)之前的哪个节点。 Then you'd do something like: 然后你会做类似的事情:

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;

Note that you'll have to do error checking in order to get this to work completely... eg what if nextNode is NULL? 请注意,您必须进行错误检查才能使其完全正常工作...例如,如果nextNode为NULL,该怎么办? Or what if prevNode is NULL (meaning curNode is the first node?) Just work through each case one by one. 或者如果prevNode为NULL(意味着curNode是第一个节点?),请逐个完成每个案例。

If you want to sidestep all of this complexity, there's really no reason to use linked lists. 如果你想回避所有这些复杂性,那么就没有理由使用链表。 Instead, you can make an array of pointers: 相反,你可以创建一个指针数组:

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

and then you can sort that array by just swapping around its elements: 然后你可以通过交换它的元素来排序该数组:

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

No more dealing with 'next' pointers. 不再处理'下一个'指针。 But now if you want to add more than 4 people to your array, you'll have to reallocate it, since it's a fixed size. 但是现在如果你想为你的数组添加超过4个人,你将不得不重新分配它,因为它是一个固定的大小。 So that's the tradeoff. 这就是权衡。

Anyway, I hope this was somewhat helpful. 无论如何,我希望这有点帮助。 This is my first real Stack Overflow comment, so I apologize if it's a little rough. 这是我的第一个真正的Stack Overflow评论,所以如果它有点粗糙我会道歉。

The problem arises from the fact that you are swapping the elements of the list while iterating through it. 问题来自于您在迭代列表的元素时交换它的事实。

Another approach could be to run through the list and swap objects until it runs through the list once without swapping any elements. 另一种方法可能是遍历列表并交换对象,直到它在列表中运行一次而不交换任何元素。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM