【问题标题】:How to swap positions of two nodes in a single-linked list, modifying only pointers?如何交换单链表中两个节点的位置,只修改指针?
【发布时间】:2018-12-29 02:15:03
【问题描述】:

我正在尝试将给定基于 0 的索引的单链表的两个节点交换到其中。在我的代码中,我处理了很多情况,但这种方法仅在j-i<=2 时有效。如果ij之间有3个或更多的差异,我无法处理。
请帮助我纠正我的方法。

node* swap_nodes(node *head,int i,int j)
{
    if(j<i)
    {
        int temp;
        temp=i;
        i=j;
        j=temp;
    }
    node *prev1,*prev2,*temp1,*temp2;

    if(i==j)
    {
        return head;
    }

    if(i==0  && abs(i-j)!=1)
    {
        int n=0;
        node *temp=head;
        prev1=head;
        temp1=prev1->next;

        while(n<j-1)
        {
            temp=temp->next;
            n++;
        }

        prev2=temp;
        temp2=prev2->next;
        prev2->next=temp1;
        temp1->next=prev1;
        prev1->next=temp2->next;
        temp2->next=prev2;

        return temp2;
    }
    else if(abs(i-j)==1 && i!=0 )
    {
        int n=0;
        node *temp=head;
        while(n<i-1)
        {
            temp=temp->next;
            n++;
        }

        prev1=temp;
        temp1=prev1->next;
        n=0;

        while(n<j-i+1)
        {
            temp=temp->next;
            n++;
        }

        temp2=temp;
        prev1->next=temp2;
        temp1->next=temp2->next;
        temp2->next=temp1;

        return head;
    }
    else if(i==0 && j==1)
    {
        temp1=head;
        temp2=head->next;
        node *temp3=temp2->next;
        temp2->next=temp1;
        temp1->next=temp3;

        return temp2;
    }
    else
    {
        int n=0;
        node *temp=head;

        while(n<i-1)
        {
            temp=temp->next;
            n++;
        }

        prev1=temp;
        temp1=prev1->next;
        n=0;

        while(n<j-i)
        {
            temp=temp->next;
            n++;
        }

        prev2=temp;
        temp2=prev2->next;
        prev1->next=temp2;
        temp1->next=temp2->next;
        temp2->next=prev2;
        prev2->next=temp1;

        return head;
    }
}

【问题讨论】:

  • 您的算法似乎太复杂了。不应该有任何 abs() 需要。首先查找i 之前的节点和j 之前的节点(如果它们存在或者head 是ij
  • 我认为您是在谈论单链表并希望根据它们的索引交换两个节点?
  • 如果交换两个节点,则数据将与节点交换交换,因此,如果您不想交换数据,则只需移动指针而不影响列表中的数据(即不计算指针是没有净可见效果的),那么你为什么想要这个?
  • @crashmstr 因为在大节点中交换数据(例如:半兆字节的图像数据)比指针竞争更昂贵。交换几个指针比交换半兆节点数据要快得多。
  • 所以,要明确一点:您想通过交换两个条目的位置来更改链接列表的顺序,而不是将数据物理复制到新节点中?一种方法是在节点中添加交换方法:-)

标签: c++ c++11 linked-list singly-linked-list


【解决方案1】:

在链表中交换节点的指针争夺的基础很简单:

  • 在列表中找到指向要交换的节点的指针。这些指针之一可以是head 指针,但至少有一个是列表中的某个next 指针。请记住,这些是指向您正在交换的节点的指针。
  • 交换那些指针
  • 交换这些节点的next 指针以恢复列表的剩余顺序。
  • 就是这样。

要做到这一点,最简单的方法是使用指向指针的指针。这避免了必须完全围绕prev 指针,这使得算法比它需要的复杂得多。

您的方法很勇敢,但也非常复杂。坚持上面的算法,它会变得更清楚需要什么。找到一些指向你想要交换的东西的指针,交换它们,然后交换回它们内部的 next 指针。


考虑到所有这些,算法实现如下(保持您只扫描一次列表以找到两个要交换的节点的愿望)。关于算法如何与代码匹配的评论是内联的:

node *swap_nodes(node *head, int i, int j)
{
    if (i < 0 || j < 0 || i == j)
        return head;

    // order i and j
    if (j < i)
        std::swap(i,j);

    // find the pointer pointing to i'th node.
    node **pp1 = &head;
    while (*pp1 && i-- > 0)
    {
        pp1 = &(*pp1)->next;
        --j;
    }

    // finish finding the pointer pointing to the j'th node
    node **pp2 = pp1;
    while (*pp2 && j-- > 0)
        pp2 = &(*pp2)->next;

    // if either index was out of range, at least one of these will
    //  be null, and if that's the case, no swap will happen
    if (*pp1 && *pp2)
    {
        // swap the pointers
        std::swap(*pp1, *pp2);

        // and swap *back* their next members
        std::swap((*pp1)->next, (*pp2)->next);
    }

    return head;
}

示例

如果没有实际示例,这将是不公平的。下面将构建一个包含十个元素的有序列表,编号为 1..10。然后它使用上面基于零索引的交换例程来交换各种元素,特别是交换头节点、尾节点和一些内部节点的东西,然后通过反转交换来撤消所有这些,以到达我们开始的列表与。

#include <iostream>

struct node
{
    int data;
    struct node *next;

    node(int x)
        : data(x)
        , next(nullptr)
    {
    }
};

void ll_print(node const *p)
{
    while (p)
    {
        std::cout << p->data << ' ';
        p = p->next;
    }
    std::cout << '\n';
}

void ll_free(node **head)
{
    while (*head)
    {
        node *tmp = *head;
        *head = tmp->next;
        delete tmp;
    }
}

node *swap_nodes(node *head, int i, int j)
{
    if (i < 0 || j < 0 || i == j)
        return head;

    // order i and j
    if (std::min(i,j) == j)
        std::swap(i,j);

    // find the pointer pointing to i'th node.
    node **pp1 = &head;
    while (*pp1 && i-- > 0)
    {
        pp1 = &(*pp1)->next;
        --j;
    }

    // finish finding the pointer pointing to the j'th node
    node **pp2 = pp1;
    while (*pp2 && j-- > 0)
        pp2 = &(*pp2)->next;

    // if either index was out of range, at least one of these will
    //  be null, and if that's the case, no swap will happen
    if (*pp1 && *pp2)
    {
        // swap the pointers
        std::swap(*pp1, *pp2);

        // and swap *back* their next members
        std::swap((*pp1)->next, (*pp2)->next);
    }

    return head;
}

int main ()
{
    // build a forward-chained linked list of ten items

    node *head = NULL, **pp = &head;
    for (int i=1; i<=10; ++i)
    {
        *pp = new node(i);
        pp = &(*pp)->next;
    }

    // print the list
    ll_print(head);

    // swap the first and second nodes
    printf("Swapping 0,1\n");
    head = swap_nodes(head, 0, 1);
    ll_print(head);

    // swap the first and last nodes
    printf("Swapping 0,9\n");
    head = swap_nodes(head, 0, 9);
    ll_print(head);

    // swap two internal nodes
    printf("Swapping 3,6\n");
    head = swap_nodes(head, 3, 6);
    ll_print(head);
    ////////////////////////////////////////

    // this shoudl swap everything back, so it should give us
    //  what we originally had.

    // swap two internal nodes
    printf("Swapping 3,6\n");
    head = swap_nodes(head, 3, 6);
    ll_print(head);

    // swap the first and last nodes
    printf("Swapping 0,9\n");
    head = swap_nodes(head, 0, 9);
    ll_print(head);

    // swap the first and second nodes
    printf("Swapping 0,1\n");
    head = swap_nodes(head, 0, 1);
    ll_print(head);

    // release the list
    ll_free(&head);
}

输出

1 2 3 4 5 6 7 8 9 10 
Swapping 0,1
2 1 3 4 5 6 7 8 9 10 
Swapping 0,9
10 1 3 4 5 6 7 8 9 2 
Swapping 3,6
10 1 3 7 5 6 4 8 9 2 
Swapping 3,6
10 1 3 4 5 6 7 8 9 2 
Swapping 0,9
2 1 3 4 5 6 7 8 9 10 
Swapping 0,1
1 2 3 4 5 6 7 8 9 10 

总结

如果您还记得自己想要做什么,那么您试图避免的大多数边缘情况都会消失:交换指针,而不是节点。诀窍是找到指向要交换的节点的指针(不是它们的值;实际的指针),并交换这些指针的值

【讨论】:

  • 嗯,当一个简单的比较就足够了,为什么还要使用std::min()?否则,如果有人以理智的方式做到这一点,那就太好了。
  • @Deduplicator 是的,这是个好问题。它是最初用 C 语言完成的三元表达式的替代品(实际上,看起来实际的算法最初是用 C 语言编写的,然后被改进为 C++)。我只是隔开并放下自然替代品。一句话; doh =P.感谢您了解这一点;固定。
  • 另外,头指针不应该通过引用传递,所以如果需要可以交换开始处的节点?
  • @Deduplicator 我会这样做,但是 OP 提供的接口将(可能修改的)头指针作为函数结果返回(就像我说的,看起来像 C 端口)。我为 OP 保留了那个界面。我个人会使用引用,但就个人而言,我会使用标准容器并保护所有这些。
  • 而且那个标准容器会有一个迭代器——而不是一个基于索引的接口,是的。我记得有一个std::forward_list
【解决方案2】:

你让你的交换逻辑变得比它需要的更复杂。试试这样的:

node* get_node_at(node *head, int index, node **previous = nullptr)
{
    if (previous) *previous = nullptr;

    if ((!head) || (index < 0)) return nullptr;

    node *temp = head;
    while (index > 0)
    {
        if (!temp) return nullptr;
        if (previous) *previous = temp;
        temp = temp->next;
        --index;
    }

    return temp;
}

void swap_nodes(node *&head, int i, int j)
{
    if ((!head) || (i == j)) return;

    node *previous_i, *previous_j;
    node* temp_i = get_node_at(head, i, &previous_i);
    node* temp_j = get_node_at(head, j, &previous_j);

    if (!temp_i || !temp_j)
        return;

    if (previous_i)
        previous_i->next = temp_j;

    if (previous_j)
        previous_j->next = temp_i;

    node *temp = temp_i->next;
    temp_i->next = temp_j->next;
    temp_j->next = temp;

   if (temp_i == head)
       head = temp_j;
   else if (temp_j == head)
       head = temp_i;
}

Live Demo

【讨论】:

  • 它可能有用,但它让我想起了一点画家什莱米尔
  • 它确实有效(见链接)。如果您担心对get_node_at() 的两次调用都从head 开始并遍历相同的节点两次,您可以通过从第一次调用返回的节点开始优化第二次调用,减去i 之间的距离和来自请求索引的j。然后只需 1 次遍历即可找到两个节点。在那之后,其他一切都是常数时间。
【解决方案3】:

您似乎正在努力处理极端情况,但您的代码仍然无法处理基本情况,那是因为您让它变得艰难。 尝试再次分析它 - 任务是什么以及完成这些任务的要求是什么。

让我帮你:-

  • 任务1 - 通过遍历链表找到第i个索引节点和第j个索引节点。(无需找到它们之间的距离)
  • 任务 2 - 交换两个节点

要求 - 交换一个节点,访问它的前一个节点。(这对你来说似乎是已知的,因为你已经在你的代码中尝试过了)

一些极端情况 -

  • 如果 i==j 或 i>j(由您的代码管理)
  • 如果第 i 个节点没有前一个节点(即它是链表的头)

现在尝试分析您的代码。

参考代码见下方

 node *swap_nodes(node *head,int i,int j)
 {
    if(j<i)
    {
        int temp;
        temp=i;
        i=j;
        j=temp;
    }
    node *prev1=NULL,*prev2=NULL,*temp1,*temp2;
    node *swp;
    int k=0;

    if(i==j)
    {
        return head;
    }
    temp1=head;
    while(k!=i)
    {
        prev1=temp1;
        temp1=temp1->next;
        k++;
    }
    prev2=prev1;
    temp2=temp1;
    while(k!=j)
    {
        prev2=temp2;
        temp2=temp2->next;
        k++;
    }

    // critical part
    prev2->next = temp1;
    swp = temp1->next;
    temp1->next = temp2->next;
    temp2->next = swp;

    // check if prev1 exists 
    if(prev1)
        prev1->next=temp2;
    else
        head=temp2;
    return head;
 }

希望这会有所帮助。

不断询问,不断成长:)

【讨论】:

  • 您在 2 个地方缺少分号 k++。变量 prevc2 也不存在。请更新您的代码。
  • 感谢您的解决方案..但问题是我正在尝试反转整个节点..假设您获得了链表 1 2 3 4 5 6 7 并且如果 i=1 和j=5 那么输出应该是 1 6 5 4 3 2 7..不仅仅是交换两个节点
  • 好的。所以为此,1。创建一个函数,它接受两个节点指针,然后反转这两个指针之间的列表并返回该部分的起始节点,2。将 temp2 之后的节点存储在临时节点指针中, 3. 在创建的函数中传递 temp1 和 temp2 , 4. 将返回值存储在 prev1->next 5. 从该点遍历列表直到 node->next 存在, 6. make到达的 node->next (它是 NULL)指向存储在步骤 2 中的节点。然后你就完成了你的目标。 :)
猜你喜欢
  • 2017-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-25
  • 2015-05-25
  • 1970-01-01
相关资源
最近更新 更多