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

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

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

方法一:使用指针


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

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

代码是:

  1. void reverse(Node *&head)  
  2. {  
  3.     if (head == NULL || head->next==NULL)  //空或者只有一个元素不用逆置  
  4.         return;  
  5.     Node *pre, *cur, *rear;  
  6.     pre = head;  
  7.     cur = head->next;  
  8.     while (cur)  
  9.     {  
  10.         rear = cur->next;  
  11.         cur->next = pre;  
  12.         pre = cur;  
  13.         cur = rear;  
  14.     }  
  15.     //以下两步,很重要   
  16.     head->next = NULL;   //这一步会使新的尾节点的链域置空  
  17.     head = pre;   //head指针指向新的一头  
  18. }  
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指针指向新的一头
}

写一个完整的测试用例

  1. #include<stdio.h>     
  2. #include<stdlib.h>   
  3. typedef struct node  //节点类型定义    
  4. {  
  5.     int data;  
  6.     struct node *next;  
  7. }Node;  
  8. void printlist(Node *head)   //打印链表    
  9. {  
  10.     Node *p = head;  
  11.     while (p->next)  
  12.     {  
  13.         printf("%-4d->", p->data);  
  14.         p = p->next;  
  15.     }  
  16.     printf("%-4d\n", p->data);  
  17. }  
  18. void reverse(Node *&head)   //逆置    
  19. {  
  20.     if (head == NULL || head->next == NULL)  //空或者只有一个元素不用逆置    
  21.         return;  
  22.     Node *pre, *cur, *rear;  
  23.     pre = head;  
  24.     cur = head->next;  
  25.     while (cur)  
  26.     {  
  27.         rear = cur->next;  
  28.         cur->next = pre;  
  29.         pre = cur;  
  30.         cur = rear;  
  31.     }  
  32.     //以下两步,很重要     
  33.     head->next = NULL;  
  34.     head = pre;  
  35. }  
  36. int main()  
  37. {  
  38.     Node p9={ 9, NULL };  
  39.     Node p7={ 7, &p9 };  
  40.     Node p5={ 5, &p7 };  
  41.     Node p3={ 3, &p5 };  
  42.     Node p1={ 1, &p3 };  
  43.     Node *head = &p1;  
  44.     printf("原表是\n");  
  45.     printlist(head);  
  46.     printf("逆置\n");  
  47.     reverse(head);  
  48.     printlist(head);  
  49.     system("pause");  
  50.     return 0;  
  51. }  
#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)


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

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

直接给一个测试用例:

  1. #include<stdio.h>   
  2. #include<stdlib.h>   
  3. typedef struct node  
  4. {  
  5.     int data;  
  6.     struct node *next;  
  7. }Node;  
  8. void printlist(Node *head)  
  9. {  
  10.     Node *p = head;  
  11.     while (p->next)  
  12.     {  
  13.         printf("%-4d->", p->data);  
  14.         p = p->next;  
  15.     }  
  16.     printf("%-4d\n", p->data);  
  17. }  
  18. void reverseWithRecursion(Node *&head, Node *cur)    //递归逆置  
  19. {  
  20.     if (cur->next == NULL)   //最后一个元素是递归终止条件  
  21.     {  
  22.         head = cur;  
  23.         return;  
  24.     }  
  25.     Node *rear = cur->next;  
  26.     reverseWithRecursion(head, rear);  
  27.     rear->next = cur;  
  28.     //cur->next = NULL;    //句一,这一句可以注释掉   
  29. }  
  30. void reverse(Node *&head)  
  31. {  
  32.     if (head == NULL || head->next == NULL)   //为空或只有一个元素就结束   
  33.         return;  
  34.     Node *cur=head;  
  35.     reverseWithRecursion(head, cur);  
  36.     cur->next = NULL;    //句二,这一句可以注释掉,不过句一和句二必须保留一句  
  37. }  
  38. int main()  
  39. {  
  40.     Node *head = NULL;  
  41.     Node p8{ 8, NULL };  
  42.     Node p6{ 6, &p8 };  
  43.     Node p4{ 4, &p6 };  
  44.     Node p2{ 2, &p4 };  
  45.     Node p0{ 0, &p2 };  
  46.     head = &p0;  
  47.     printf("原链表\n");  
  48.     printlist(head);  
  49.     printf("逆置\n");  
  50.     reverse(head);  
  51.     printlist(head);  
  52.     system("pause");  
  53.     return 0;  
  54. }  
#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指向该曾经的尾节点(也就是新链表的第一个节点)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值