Swapping Nodes on a single linked list

前端 未结 8 907
说谎
说谎 2020-12-03 16:11

I am trying to make a swapNode function that can take any two nodes and swap them. I\'ve made an algorithm that works if they\'re at least 2 nodes away, but I c

相关标签:
8条回答
  • 2020-12-03 16:27

    Here p1 is the 1st node to be swaped, p2 is 2nd node to be swaped. And prevnode is the node that is previous of 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;   
        }
    
    0 讨论(0)
  • 2020-12-03 16:31

    Rule of thumb: "Always separate data from pointers and never swap pointers, only the data!". Make swap explicit without using memcpy(), thus you can avoid alignment problems. It causes no performance penalty in terms of algorithmic complexity, but makes your code more readable and safe.

    0 讨论(0)
  • 2020-12-03 16:36

    While I am not 100% sure the answer should involve references to node pointer (or pointers to pointers) and this should then handle a case when one of the nodes is the head of list as well.

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

    Then you can call it for example:

    swapNodes(head, secPrev->next);
    

    or

    swapNodes(firstPrev->next, head);
    

    or

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

    and it should work automagically.

    EDIT:

    swapNodes could be even more readable:

    void swapNodes(node *&first, node *&second)
    {
      std::swap(first->next, second->next);
      std::swap(first, second);
    }
    
    0 讨论(0)
  • 2020-12-03 16:37

    Say we have:

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

    To swap two nodes, you need to swap the next values of the ones before each of them, and also the next values of the nodes you want to swap.

    So to swap, say, Node2 and Node3, you effectively have to swap Node1->next with Node2->next, and Node2->next with Node3->next. That will work, even if they're right next to each other (or even if it's the same node). For example:

    Swap Node1->next and Node2->next

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

    Swap Node2->next with Node3->next

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

    This comes out as:

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

    Swapped!

    As unwind noted in the comments section, if swapping Node1 with anything, you'll have to set a new head for the linked list.


    In response to the edit of the question:

    Your code for swapping almost right. However, you need to swap the firstPrev with secPrev. It just so happened in my example that we were swapping one of the node's next values twice, because they were next to each other. But logically, we want to swap the nexts of the two previous ones, and then swap the nexts of the actual nodes. Try this:

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

    If you're getting a segfault, check the tmp variable - it could be an error of allocation or deletion somewhere. Where do you get the segfault?

    0 讨论(0)
  • 2020-12-03 16:40
    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;
     }
    

    } }

    0 讨论(0)
  • 2020-12-03 16:40

    Thank you everyone for your answers! I do realize that this question has been asked almost two years ago and an answer long been accepted, but I have been a bit confused by the answers. Thus, despite the questioner probably not caring about new answers, I would like to add my version, in case other readers were confused as well and to document my own approach. Some of this may have been more fitting as comments, but I do not have the reputation to comment, yet.

    First, no matter how often I look at it - on the whiteboard or in the debugger - I cannot seem to avoid ending up with a loop in the first node, i.e. pointing to itself, if I do not use a conditional to distinguish between cases of nodes being adjacent and not, even with the abstract steps or the concrete code from the currently accepted answer by Smashery. I have looked around a bit on the Internet to find code in the style of the currently accepted answer to avoid such a conditional, but to me it is surprisingly tricky to avoid and my searches have not turned up such a solution (unless I am wrong about the proposed one possibly being incorrect). If there was some clever expression that would yield the first node's address when they are adjacent and the first node's successor's address when they are not, then that conditional would not be needed, because figuring out the second node's new successor is what (apparently) necessitates that conditional.

    Second, I have the same question about the consecutive assignments to the same variables in the accepted answer as other commentators. I hope I am not being extremely dense here, but assigning different values to the same variable in sequence, unless perhaps in case of side-effects, to me just never seems to leave the variable with any other value than the last assignment, no matter what configuration of nodes I consider, and thus makes the previous assignments apparently redundant. If I am wrong about that and that approach would in fact solve this problem then I would have been able to eliminate the last conditional in the code below, which I was trying to get rid off when I first looked on the Internet for a solution to swapping nodes without special-casing adjacent nodes. I am not quite sure, but it sounded like Smashery purposefully left those repeated assignments out of logical rigor and to better illustrate the procedure - I may have misunderstood, though.

    Third, on this and other sites I have often seen the statement from other answers repeated that it is better to swap the content of the nodes, rather than the pointers. Of course, in the case of simple integers, as in the examples so far, that does apparently yield shorter, simpler code. However, when we discuss linked lists with nodes containing integers it is usually as a stand-in for the backing data structure of a more complex and generic container. As such, I don't think swapping the contents of the nodes is really that easy, at least if the implementation of the data structure cannot make assumptions about the copy semantics of the container's items. Also, being able to swap around the contents of nodes like that implies to me that the linked list has ownership of the contents of those nodes, because otherwise code outside of the linked list's method might hold references to the objects in those nodes, whose values suddenly change underneath them.

    I do admit, though, that this might depend on the semantics of the container. For an array, a swap method may be expected to change the value underneath references to a certain index of that array. That would mean that the references are not meant to refer to a specific object, but to a position in a container that one can index. If we consider a linked list as a means to only order a set of objects, which have their use outside of the linked list, a user would probably expect a swap operation to only exchange the position, not the contents.

    Imagine, for example, that the linked list represents objects of type "car". Each car has an owner and that owner references their car through a pointer to it. Now suppose the linked list represents the order a set of cars are scheduled to be serviced for inspection at a car dealership. If we swapped the contents of two nodes, in order to exchange the schedule slots for two cars, and did it by exchanging their contents, then the servicing would in fact happen in the new, right order - but people would also end up suddenly owning different cars! (I wouldn't mind swapping with a Tesla, though, because I am only driving a Corolla.)

    If the linked list was, as in the example of the array, based on indexing semantics then the position in the node might simply represent the order in which the cars are loaded onto a ship for transport. At this point, the cars don't have any owners and we really only care what slot they are in. Then, I suppose it really doesn't hurt to swap out cars, i.e. the contents of the objects referenced by the nodes.

    Finally to the code. As I said above, I haven't been able to avoid the special-casing for adjacent nodes.

    First, the definition of an auxiliary method:

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

    This method finds a node by its value. The integer returned is the zero-based position (call it its index if you like) of the node in the linked list. I found detecting adjacency through position, rather than pointer comparisons, more readable. The start of the list is root. The method sets n to point to the node containing the passed value. In pn, the method stores the predecessor of n.

    The following is the actual swap:

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

    I'm sorry that I have changed the signature of the OP's method a bit. I found it more convenient to pass in the values of the nodes to swap, as opposed to node pointers. If you pass in node pointers to the respective nodes only, you would have to do another traversal to find the predecessors with this solution, which felt a bit awkward to me. If we cannot distinguish nodes by these values, e.g. values are not unique, we will need pointers to the nodes, though.

    As with the explanation for find_node above, we first find the positions, nodes, and predecessors for the node values passed to swap_nodes through v1 and v2. The values for the first and second nodes are all swapped if the second node to swap appears before the first. It's not much code to do so, reduces special casing, and makes it a bit easier to visualize.

    Now, we are left with just two more conditionals, neither of which seemed trivial to avoid. If the first node is at the head of the linked list, i.e. at position zero, the root needs to point to the second node. Otherwise, the first node's predecessor will point to the second node.

    The previous value of the first node's successor needs to be remembered, in case the nodes are not adjacent. Then, the first node's successor is set to the current successor of the second node. This is the only change that applies to all cases: the new successor of the first node being the old successor of the second node is the only certainty and helpful to start off with in order to remember the pointer operations and their sequence when implementing this swap.

    Last, if the positions of the nodes differ by more than one, they are not adjacent. Then, the second node's new successor becomes the first node's old successor - saved away above - and the second node's predecessor now points to the first node. If they are adjacent, there are no nodes between the nodes to swap that need updating, so simply linking the second node to the first is all that is left to do.

    0 讨论(0)
提交回复
热议问题