• 单向链表反转算法——递归版和迭代版


      最近在做笔试题时,遇到一道编程题:单向链表反转算法。一时紧张,没写出来就提前交卷了,然而交完卷就想出来了。。。

      最初想出来的是递归版,遗憾的是没能做到尾递归,后来又琢磨出了迭代版。后来用实际编译运行测试了一遍,能正常运行。

      递归版的灵感来源于《Haskell 趣学指南》中非常简洁的快速排序算法的实现,其思想是将单向链表分割头部和尾部。其中头部指是链表的第一个节点,尾部是指除去第一个节点后的子链表。通过递归的方法,将子链表继续分割成头部和尾部,直至尾部指剩下一个节点,无法继续分割,然后将头部和尾部的位置交换,返回尾部,然后将前一个头部接到返回的尾部后面,继续返回新的尾部,依此类推,直到整个递归完成。

      迭代版的思想是,用三个指针(first, now, next)来协助调整顺序。其中每一次迭代(即:每遍历一个节点),都将单链表动态分割成反转后的和反转前的两部分。每一次迭代后,反转后的链表会变长,反转前的链表会变短,直到反转前的链表长度为0,反转完成,返回反转后的链表。整个思想和冒泡排序有点像,把序列(链表)划分成有序区(反转后的部分)和无序区(反转前的部分),但是只需对整个单向链表进行一趟遍历。

      值得注意的是,第一次迭代,是从单向链表的第二个节点开始的。而第一个节点默认就是反转后的节点,而且是反转后的尾节点,为了避免环路导致的死循环,需要将该节点指向的下一个节点的指针设为 NULL 。

      这三个指针的作用如下:

    • first 指针始终指向反转后的链表的首节点;
    • now 指针指向当前遍历到的反转前的链表的首节点;
    • next 指针指向 now 节点的下一个节点,避免在 now->next 指向反转后的链表首节点时,丢失了反转前的链表的引用。

    关键数据结构如下:

    1 typedef struct _Node {
    2     int data;
    3     struct _Node * next;
    4 } Node;

    递归版的反转算法代码如下:

     1 Node * Node_reverse(Node *node) {
     2     if (node == NULL) return NULL;
     3     if (node->next == NULL) return node;
     4     Node * n = Node_reverse(node->next);
     5     if (n != NULL) {
     6         n->next = node;
     7         node->next = NULL;
     8     }
     9     return node;
    10 }

    其实,这个递归版反转算法有个小缺陷:那就是整个递归完成时,返回的节点就是传入的参数。也就是说,如果传入的是反转前的单向链表首节点,那么最终返回的也是这个节点,而这个节点却是反转后的单向链表的尾节点(因为反转了,首尾交换)。因此,使用时,需要做些微不足道的工作来弥补缺陷。即:在使用前需要先对单向链表进行一趟遍历,找到尾节点并用指针引用它。。。其实还能改善一下这段代码来修正这个小缺陷,把这些微不足道的工作交给这个函数来完成,也就是加个 if 分支的事,我懒得写了。迭代版的就没这个问题,而且效率略高。

    迭代版的反转算法代码如下:

     1 Node * Node_reverse_v2(Node* node) {
     2     if (node == NULL) return NULL;
     3     if (node->next == NULL) return node;
     4 
     5     Node *first = node; //总是指向新链表的首部。
     6     Node *now = node->next;
     7     Node *next = now->next;
     8     first->next = NULL; //首节点变成尾节点,尾节点的下一个节点置空,防止环路。
     9     do {
    10         next = now->next;
    11         now->next = first;
    12         first = now;
    13         now = next;
    14     } while (next != NULL);
    15 
    16     return first;
    17 }

    测试代码如下:

     1 void printNode(Node *node) {
     2     if (node != NULL) {
     3         printf("%d ", node->data);
     4     }
     5 }
     6 
     7 int main()
     8 {
     9     LinkedList *l = List_create();
    10     for (int i=0; i<20; i++) {
    11         List_append(l, i);
    12     }
    13     List_foreach(l, printNode);
    14     printf("
    ");
    15     List_foreach(List_reverse(l), printNode);
    16     printf("
    ");
    17     List_foreach(List_reverse_v2(l), printNode);
    18     printf("
    ");
    19     return 0;
    20 }

    输出如下:

    完整代码下载:

    reverse.7z

  • 相关阅读:
    一些认识或对不清楚知识的猜想
    Python 绘图与可视化 seaborn
    Python 绘图与可视化 matplotlib 制作Gif动图
    python numPy模块 与numpy里的数据类型、数据类型对象dtype
    python web开发 编写web框架
    Python 绘图与可视化 matplotlib 散点图、numpy模块的random()、条形图bar
    Python 绘图与可视化 matplotlib 填充fill和fill_between
    Python 绘图与可视化 matplotlib(下)
    Python 绘图与可视化 matplotlib(上)
    Python排序 插入排序
  • 原文地址:https://www.cnblogs.com/zlmdy/p/reverse-linkedlist-algorithm-in-C.html
Copyright © 2020-2023  润新知