链表常见操作:逆置(反转)

来源:互联网 发布:注册会计师软件哪个好 编辑:程序博客网 时间:2024/06/05 22:56

链表中的一个很常见的操作是:链表的逆置,也叫链表的反转。

如:1->3->5->7->9 反转后是 9->7->5->3->1

方法一:使用指针


红色的箭头是新的变换,明白了操作原理就很好写代码了。

使用了三个指针:pre(前驱) cur(当前) rear(后继),经过以上的四步变换,目地是,使cur指向的节点成功逆置(反转)指向pre所指向的节点。后面的节点的逆置,是同样的。

关键代码是:

void reverse(Node *&head){if (head == NULL || head->next==NULL)  //空或者只有一个元素不用逆置return;Node *pre, *cur, *rear;pre = head;cur = head->next;while (cur){rear = cur->next;cur->next = pre;pre = cur;cur = rear;}//以下两步,很重要head->next = NULL;   //这一步会使新的尾节点的链域置空head = pre;   //head指针指向新的一头}

写一个完整的测试用例

#include<stdio.h>  #include<stdlib.h>typedef struct node  //节点类型定义  {int data;struct node *next;}Node;void printlist(Node *head)   //打印链表  {Node *p = head;while (p->next){printf("%-4d->", p->data);p = p->next;}printf("%-4d\n", p->data);}void reverse(Node *&head)   //逆置  {if (head == NULL || head->next == NULL)  //空或者只有一个元素不用逆置  return;Node *pre, *cur, *rear;pre = head;cur = head->next;while (cur){rear = cur->next;cur->next = pre;pre = cur;cur = rear;}//以下两步,很重要  head->next = NULL;head = pre;}int main(){Node p9{ 9, NULL };Node p7{ 7, &p9 };Node p5{ 5, &p7 };Node p3{ 3, &p5 };Node p1{ 1, &p3 };Node *head = &p1;printf("原表是\n");printlist(head);printf("逆置\n");reverse(head);printlist(head);system("pause");return 0;}


运行:



思考:我们知道链表一般是带有头节点的,而这里我们没有使用头节点。那么,我们如何对一个带有头节点的链表进行逆置呢?逆置后的链表也是要带有头节点的哦。大家可以动手试试,不妨把代码写在评论里,互相参考下,看有什么细节的不同。(楼主的一个写法,在一楼,欢迎不吝赐教,thanks)


方法二:在递归中逆置(反转)

思路:在对当前节点逆置时,先递归地逆置其后继节点,然后将后继节点指向当前节点。

直接给一个测试用例:

#include<stdio.h>#include<stdlib.h>typedef struct node{int data;struct node *next;}Node;void printlist(Node *head){Node *p = head;while (p->next){printf("%-4d->", p->data);p = p->next;}printf("%-4d\n", p->data);}void reverseWithRecursion(Node *&head, Node *cur)    //递归逆置{if (cur->next == NULL)   //最后一个元素是递归终止条件{head = cur;return;}Node *rear = cur->next;reverseWithRecursion(head, rear);rear->next = cur;//cur->next = NULL;    //句一,这一句可以注释掉}void reverse(Node *&head){if (head == NULL || head->next == NULL)   //为空或只有一个元素就结束 return;Node *cur=head;reverseWithRecursion(head, cur);cur->next = NULL;    //句二,这一句可以注释掉,不过句一和句二必须保留一句}int main(){Node *head = NULL;Node p8{ 8, NULL };Node p6{ 6, &p8 };Node p4{ 4, &p6 };Node p2{ 2, &p4 };Node p0{ 0, &p2 };head = &p0;printf("原链表\n");printlist(head);printf("逆置\n");reverse(head);printlist(head);system("pause");return 0;}

运行:



对递归逆置函数的理解是最重要的,也是最难理解的。难点有:

  1. 句一的作用。这个初看很难让人明白,甚至误解。我们知道原链表的第一个节点的链域在最后要置空,但这里每次递归都置空,是否会出问题?画个图后,发现不会。我们对递归的过程要理解,并且得明白次的递归函数中的rear指向的正是这一次的cur,(它俩指向同一节点)。所以即使被置空了,当递归返回上一层时,该链域依然会被修改。当然,除了原链表的第一个节点(想想为什么?),这也是这句话的真实目地:原链表的第一个节点的链域在最后被置空。
  2. 由上一点的分析,我们可以想到:为何不直接修改原链表第一个节点的链域呢?何必费心一路递归修改,这样会造成很多的操作是没必要的。事实上的确可以,这就是句二的作用,那就是说不用句一,只用句二就可以了。但它俩至少要有一个,从效率上看,推荐只使用句二。
  3. 第一个参数被设计成引用类型,它的意义:在递归到原链表的尾节点时,把head指向该曾经的尾节点(也就是新链表的第一个节点)。


专栏目录:

  • 数据结构与算法目录
  • c指针



2 0
原创粉丝点击