【问题标题】:Doubly Linked List sorting function in CC中的双向链表排序功能
【发布时间】:2017-09-23 07:21:26
【问题描述】:

我正在使用冒泡排序来更改节点位置。我知道如果节点 1 位于列表的开头,有几种情况需要注意。如果节点 2 在列表的末尾。如果 node1 在开头, node2 在结尾。所以我相信问题是当我交换邻居节点时,如 node1->next_ = node2;因为如果我进行常规交换,我最终会得到 node2->next_ = node2。

我想知道我是否走上了正轨,因为我尝试了类似于我写的东西并最终无限循环。我认为有些东西我不明白,就像我在某处丢失了指针一样。

我认为这是正确的,除非 2 个节点是链表中的邻居。

编辑明确的链接命名。恢复到原来的。

void swap(struct student_record_node** node1, struct student_record_node** node2)
{

  struct student_record_node *p1, *x1, *n1, *p2,*x2,*n2, *temp;

  p1 = (*node1)->prev_;
  x1 = *node1;
  n1 = (*node1)->next_;

  p2 = (*node2)->prev_;
  x2 = *node2;
  n2 = (*node2)->next_;

  /* swap next_  */

  if (p1 == NULL && n2 == NULL)
  {



  /* step one swap nodes */
    temp = *node1;
    *node1 = *node2;
    *node2 = temp; 
    /* step two swap node1 prev to be node2 prev */
    (*node2)->prev_ = NULL;
    /* step three swap node1 next to be node 2 next */
    (*node2)->next_ = n1;
    /* step four swap node1 next prev to be node2 next prev */
    (*node2)->next_->prev_ = x2;

    /* step 5 swap node2 next to be node1 next */
    (*node1)->next_ = NULL;
    /* step 6 swap node2 prev to be node1 prev */
    (*node1)->prev_ = n2;
    /* step 7 swap node2 prev next to be node1 prev next */
    (*node1)->prev_->next_ = x1;


  }
  else if (p1 == NULL)
  {
   /* step one swap nodes */
    temp = *node1;
    *node1 = *node2;
    *node2 = temp; 
     /* step two swap node1 prev to be node2 prev */
    (*node2)->prev_ = NULL;
    /* step three swap node1 next to be node 2 next */
    (*node2)->next_ = n1;
    /* step four swap node1 next prev to be node2 next prev */
    (*node2)->next_->prev_ = x2;

    /* step 5 swap node2 next to be node1 next */
    (*node1)->next_ = n2;
    /* step 6 swap node2 prev to be node1 prev */
    (*node1)->prev_ = p2;
    /* step 7 swap node2 prev next to be node1 prev next */
    (*node1)->prev_->next_ = x1;
    /* step 8 swap node2 next prev to be node1 next prev */
    (*node1)->next_->prev_ = x1;



  }
  else if(n2 == NULL)
  {
     /* step one swap nodes */
    temp = *node1;
    *node1 = *node2;
    *node2 = temp; 
     /* step two swap node1 prev to be node2 prev */
    (*node2)->prev_ = p1;
    /* step three swap node1 next to be node 2 next */
    (*node2)->next_ = n1;
    /* step four swap node1 next prev to be node2 next prev */
    (*node2)->next_->prev_ = x2;
        /* step 5 node1 prev next swapped with node2 prev next */
    (*node2)->prev_->next_ = x2;


    /* step 6 swap node2 next to be node1 next */
    (*node1)->next_ = NULL;
    /* step 7 swap node2 prev to be node1 prev */
    (*node1)->prev_ = p2;
    /* step 8 swap node2 prev next to be node1 prev next */
    (*node1)->prev_->next_ = x1;


  }
  else
  {
       /* step one swap nodes */
    temp = *node1;
    *node1 = *node2;
    *node2 = temp; 
     /* step two swap node1 prev to be node2 prev */
    (*node2)->prev_ = p1;
    /* step three swap node1 next to be node 2 next */
    (*node2)->next_ = n1;
    /* step four swap node1 next prev to be node2 next prev */
    (*node2)->next_->prev_ = x2;
        /* step 5 node1 prev next swapped with node2 prev next */
    (*node2)->prev_->next_ = x2;


    /* step 6 swap node2 next to be node1 next */
    (*node1)->next_ = n2;
    /* step 7 swap node2 prev to be node1 prev */
    (*node1)->prev_ = p2;
    /* step 8 swap node2 prev next to be node1 prev next */
    (*node1)->prev_->next_ = x1;
    /* step 9 swap node2 next prev to be node1 next prev */
    (*node1)->next_->prev_ = x1;


  }
  /* swap surrounding */

}

【问题讨论】:

  • 我将从使用有意义的变量名开始。通过认识到,例如,如果 (*node1)->prev_ 为 NULL,那么您绝对不允许访问(*node1)->prev_->next_(提示:您)。当然,如果你使用调试器,你会准确找到崩溃的那一行,你就不需要在这里问了。
  • 啊我忘了我做了那个改变。当我在这里发帖时,我试图让它更容易阅读。我想这是违反直觉的。是的,我不明白如何让 gdb 同时获取我的程序和 sample.txt 文件。
  • 是的..你需要驯服你的调试器。没有它,您基本上无法开发软件。
  • 如果您的数据不是超重的,那么通过交换数据而不是指针进行排序可能更容易。这有点像“作弊”,但如果它导致软件更简单,错误更少,那很好。另一种方法可能是将数据收集到一个数组中,对其进行排序,然后通过并行迭代数组和列表来覆盖列表。不过,这可能有点极端。
  • 另一个选项是临时添加一个虚拟头尾节点到列表中。这样你就知道 all 你的指针是有效的。然后你只需在最后删除它们。进一步的简化是在对列表进行排序时只管理单个链接,然后进行最后一次设置所有prev_ 指针。

标签: c sorting pointers struct doubly-linked-list


【解决方案1】:

当你交换双链表中的两个节点时,有 8 个指针需要修改。请参见下图中的 8 个箭头。

+---+    +---+    +---+        +---+    +---+    +---+
|   |--->| A |--->|   | ...... |   |--->| B |--->|   |
|   |<---|   |<---|   | ...... |   |<---|   |<---|   |
+---+    +---+    +---+        +---+    +---+    +---+

现在,A 或 B 可能位于列表的开头或结尾。它们也可以紧挨着(我们稍后会解决一个特殊情况)。

这里最简单的做法是首先存储指向这四个未标记节点的指针。这是所有您需要的信息:

struct node *a_prev = a->prev;
struct node *a_next = a->next;
struct node *b_prev = b->prev;
struct node *b_next = b->next;

现在,由于这些指针中的任何一个都可以为 NULL,因此您只需在对它们进行操作之前进行健全性测试:

if (a_prev) a_prev->next = b;  //(1)
if (a_next) a_next->prev = b;  //(2)
if (b_prev) b_prev->next = a;  //(3)
if (b_next) b_next->prev = a;  //(4)

然后您可以从实际节点更新链接:

a->prev = b_prev;  //(5)
a->next = b_next;  //(6)
b->prev = a_prev;  //(7)
b->next = a_next;  //(8)

现在,那个特殊情况呢?有两个版本:

a_prev   b_prev   a_next   b_next
+---+    +---+    +---+    +---+
|   |--->| A |--->| B |--->|   |
|   |<---|   |<---|   |<---|   |
+---+    +---+    +---+    +---+

b_prev   a_prev   b_next   a_next
+---+    +---+    +---+    +---+
|   |--->| B |--->| A |--->|   |
|   |<---|   |<---|   |<---|   |
+---+    +---+    +---+    +---+

让我们拿第一个。哪些代码行会破坏列表的结构?可以看到,由于b_prev == aa_next == b,如下:

if (a_prev) a_prev->next = b;  //(1) OK
if (a_next) a_next->prev = b;  //(2) ERROR b->prev = b 
if (b_prev) b_prev->next = a;  //(3) ERROR a->next = a
if (b_next) b_next->prev = a;  //(4) OK
a->prev = b_prev;  //(5) ERROR a->prev = a
a->next = b_next;  //(6) OK and fixes 3
b->prev = a_prev;  //(7) OK and fixes 2
b->next = a_next;  //(8) ERROR b->next = b

所以有两个语句(5 和 8)会中断。首先,他们应该是什么?

a->prev = b;
b->prev = a;

您还可以看到,如果 A 和 B 颠倒(第二种情况),则会出现相反的问题(5 和 8 会损坏;6 和 7 会修复 1 和 4;2 和 3 会好)。

如果你想要紧凑,你可以用三元运算符扩展这四个最后的语句。不过,这可能会让你有点头疼:

a->prev = (b_prev == a ? b : b_prev);  //(5)
a->next = (b_next == a ? b : b_next);  //(6)
b->prev = (a_prev == b ? a : a_prev);  //(7)
b->next = (a_next == b ? a : a_next);  //(8)

要挤出最后一个分支,您可以在不使用三元运算符的情况下重新排列这四个语句(因为一半的测试是多余的)。或者,您可以重写语句以使用两个测试(而不是我为对称而编写的四个)并依赖编译器进行优化。

我可能不会打扰,但您也可以扩展整个内容:

if (a_next == b)
{
    a->prev = b;       //(5)
    a->next = b_next;  //(6)
    b->prev = a_prev;  //(7)
    b->next = a;       //(8)
}
else if (b_next == a)
{
    a->prev = b_prev;  //(5)
    a->next = b;       //(6)
    b->prev = a;       //(7)
    b->next = a_next;  //(8)
}
else
{
    a->prev = b_prev;  //(5)
    a->next = b_next;  //(6)
    b->prev = a_prev;  //(7)
    b->next = a_next;  //(8)
}

【讨论】:

  • 还有一种特殊情况:a和b可以相邻; (a->next == b; b->prev ==a;)
  • 您能解释一下您正在使用的健全性测试吗?我是指针/双向链表的新手,还没有看到这种语法。是否类似于我只需要查找的三元运算符?感谢您的帮助
  • @wildplasser 我提到了节点相邻的特殊情况(实际上是两个)。在这种情况下,一半的指针分配是多余的,但不会造成任何损害,因为这些指针首先保存在堆栈中。
  • @EthanGlory 你不需要三元运算符。我只是在做一个一元指针测试。声明 if (a_prev)non-zero = truth 的情况,C 标准特别允许它等同于 if (a_prev != NULL)
  • @paddy 另一种特殊情况是什么。我认为挡风玻璃的答案是正确的。如果它们是邻居,则意味着 a-&gt;next == b &amp;&amp; a ==b-&gt;prev 这意味着当我保存初始指针 a_next 时,我不能像在它们之间分隔一个节点时那样使用它。如果我只是制作a-&gt;next = b-&gt;next;,这就是我认为我遇到的问题。我基本上会让a_node-&gt;next 指向a_node 还是完全错误?
【解决方案2】:

您的 swap() 中的问题是 x4 和 y4 ,两者都有 NULL 值,并且您试图取消引用这两个指针,这会导致分段错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-06-17
    • 2011-04-13
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 2016-06-19
    相关资源
    最近更新 更多