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