对于"单链表逆置和递归"的问题的理解.

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对于"单链表逆置和递归"的问题的理解.相关的知识,希望对你有一定的参考价值。

 一. 相关知识要点:

    学习或了解基础数据结构和C语言, 对基础链表知识或相关知识有概况性认识.

  例如: 本题目结构为:

1 #define  elem_type  int
2 
3 typedef  struct   _single_list {
4         elem_type            data;  //所存的数据元素
5         _single_list        *next;  //所存的指针元素
6 }ListNode;

二. 问题的思考过程(本题以3种不同的方法解决):

  <1>类似于我们学习的C语言基础知识中的冒泡排序(参考C程序设计 第四版谭浩强P147)

说明:

   输入数据: 1  7  2  8  4.

   得到数据: 4  8   2  1  7.

    过程如下:

  技术分享

  技术分享

   如图上所示: 第一轮循环结束:   

  第二轮循环将B放到倒数第二的位置: 如图:

  技术分享

  同理: 再循环三次即可完成单链表的逆置: 结果为:

  技术分享

  结果如上:  代码如下:

 1 void ReverseList_1(ListNode *phead)
 2 {
 3     assert(phead);
 4     ListNode *ptr = phead->next;
 5     int len = 0;//作为循环控制变量
 6     while(ptr)
 7     {
 8         ++len;
 9         ptr = ptr->next;
10     }
11     
12     //p和ptr作为中间变换的参量, 一般情况下p = ptr->next;
13     ListNode *p = NULL;
14     
15     for(int i = 0; i < len-1; i++)
16     {
17         ptr = phead->next;
18         for(int j = 0; j < len-i-1; j++)
19         {
20             //用内外循环来控制变量的指向.
21             p = ptr->next;
22             if(ptr == phead->next)
23             {
24                 ptr->next     = p->next;
25                 p->next     = ptr;
26                 phead->next = p;
27                 //在每一次外循环头指针需要维护
28                 continue;
29             }
30             ListNode *s = phead->next;
31             //s的目的是找的ptr的前驱, p为ptr后驱
32             while(s->next != ptr)
33             {
34                 s = s->next;
35             }
36             ptr->next = p->next;
37             p->next   = ptr;
38             s->next   = p;
39             //将ptr 放到p的后面
40         }
41     }
42 }

  优点: 思想上比较好理解, 容易联系课本来解决问题.

  缺点: 代码上仍需几个指针进行控制, 而且三层循环, 不易实现,  效率不是很高.

  <2>利用链表的特性, 进行头插.(参考 数据结构 第二版 严蔚敏 P14) 

   技术分享 

    结果如上: 代码如下:

 1 void ReverseList_2(ListNode *phead)
 2 {
 3     assert(phead);
 4     ListNode *ptr = phead->next; //相当于图上的p指针
 5     ListNode *s   = NULL; //一般指向ptr->next 并保存下一个地址
 6     phead->next   = NULL;
 7     while(ptr != NULL)
 8     {
 9         /*
10         * 先将ptr 头指针的先一个位置进行头插.
11         */
12         s = ptr->next;
13         ptr->next = phead->next;
14         phead->next = ptr;
15         ptr = s;
16     }
17 }

  优点: 联系结构实际, 进行方法调用 , 代码简单, 思想较易.

  缺点:  无.

  <3> 利用递归调用, 来进行逆置, 这个比较难理解(可以忽略...     参考C程序设计 第四版 谭浩强 P184)

     技术分享

    技术分享    

如上所示: 其代码为:

 1 ListNode* Recursion_1(ListNode *ptr, ListNode *p)
 2 {
 3     if(!p)
 4     {
 5         //递归调用的退出条件
 6         return ptr;
 7     }
 8     else
 9     {
10         /*
11         * 递归进入, 并作后续的逆置,
12         */
13         ListNode *s = Recursion_1(ptr->next, p->next);
14         ptr->next   = NULL;
15         p->next = ptr;
16         return s;
17     }
18 }
19 
20 ListNode* RecursionReverseList_1(ListNode *phead)
21 {
22     if(!phead && !phead->next)
23     {
24         //判断是否错误
25         return phead;
26     }
27     return Recursion_1(phead->next, phead->next->next);
28     //这个将来为phead->next = s, 成立
29 }

     优点:可以对递归的整个流程有一个大致的了解, 并对问题的解决有了一个更深的认识.

  缺点:不好想, 代码难实现, 

额外扩展: 可不可以将方法二转换成"递归"形式: 其代码如下:

 1 void Recursion_2(ListNode *phead, ListNode *ptr)
 2 {
 3     if(!ptr->next)
 4     {
 5         return;
 6     }
 7     /*
 8     * 进行头插
 9     */
10     ListNode *s = ptr->next;
11     ptr->next   = s->next;
12     s->next     = phead->next;
13     phead->next = s;
14     Recursion_2(phead, ptr);
15 }
16 
17 void RecursionReverseList_2(ListNode *phead)
18 {
19     if(!phead)
20     {
21         return ;
22     }
23     Recursion_2(phead, phead->next);
24 }

接下来我们进行测试下代码:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<assert.h>
  4 #include<malloc.h>
  5 
  6 #define  ElemType int
  7 typedef struct SList
  8 {
  9     ElemType data;
 10     SList     *next;
 11 }ListNode;
 12 /*
 13 *购买内存
 14 * */
 15 ListNode *buyNode()
 16 {
 17     ListNode *p = (ListNode*)malloc(sizeof(ListNode));
 18     assert(p);
 19     memset(p, \\0, sizeof(ListNode));
 20     return p;
 21 }
 22 /*
 23 *删除节点
 24 * */
 25 void DeleteNode(ListNode *ptr)
 26 {
 27     if(NULL == ptr)
 28     {
 29         return ;
 30     }
 31     free(ptr);
 32     ptr = NULL;
 33 }
 34 /*
 35 *普通的删除单链表
 36 * */
 37 void DeleteList(ListNode *phead)
 38 {
 39     ListNode *ptr = phead->next;
 40     if(NULL == ptr)
 41     {
 42         return ;
 43     }
 44     ListNode *p = NULL;
 45     while(ptr)
 46     {
 47         p = ptr->next;
 48         DeleteNode(ptr);
 49         ptr = p;
 50     }
 51 }
 52 /*
 53 *递归删除单链表
 54 * */
 55 void RecursionDeleteList(ListNode *ptr)
 56 {
 57     if(NULL == ptr->next) 
 58     {
 59         return ;
 60     }
 61     RecursionDeleteList(ptr->next);
 62     DeleteNode(ptr->next);
 63     ptr->next = NULL;
 64 }
 65 /*
 66 *打印单链表
 67 * */
 68 void PrintList(ListNode *phead)
 69 {
 70     ListNode *ptr = phead->next;
 71     while(ptr)
 72     {
 73         printf(" %d", ptr->data);
 74         ptr = ptr->next;
 75     }
 76 }
 77 /*
 78 *头插法建立单链表
 79 * */
 80 void PushFront(ListNode *phead, ElemType key)
 81 {
 82     ListNode *p = buyNode();
 83     p->data     = key;
 84     p->next     = phead->next;
 85     phead->next = p;
 86 }
 87 /*
 88 *方法_1
 89 * */
 90 void ReverseList_1(ListNode *phead)
 91 {
 92     assert(phead);
 93     ListNode *ptr = phead->next;
 94     int len = 0;//作为循环控制变量
 95     while(ptr)
 96     {
 97         ++len;
 98         ptr = ptr->next;
 99     }
100     
101     //p和ptr作为中间变换的参量, 一般情况下p = ptr->next;
102     ListNode *p = NULL;
103     
104     for(int i = 0; i < len-1; i++)
105     {
106         ptr = phead->next;
107         for(int j = 0; j < len-i-1; j++)
108         {
109             //用内外循环来控制变量的指向.
110             p = ptr->next;
111             if(ptr == phead->next)
112             {
113                 ptr->next     = p->next;
114                 p->next     = ptr;
115                 phead->next = p;
116                 //在每一次外循环头指针需要维护
117                 continue;
118             }
119             ListNode *s = phead->next;
120             //s的目的是找的ptr的前驱, p为ptr后驱
121             while(s->next != ptr)
122             {
123                 s = s->next;
124             }
125             ptr->next = p->next;
126             p->next   = ptr;
127             s->next   = p;
128             //将ptr 放到p的后面
129         }
130     }
131 }
132 /*
133 *方法_2
134 * */
135 void ReverseList_2(ListNode *phead)
136 {
137     assert(phead);
138     ListNode *ptr = phead->next; //相当于图上的p指针
139     ListNode *s   = NULL; //一般指向ptr->next 并保存下一个地址
140     phead->next   = NULL;
141     while(ptr != NULL)
142     {
143         /*
144         * 先将ptr 头指针的先一个位置进行头插.
145         */
146         s = ptr->next;
147         ptr->next = phead->next;
148         phead->next = ptr;
149         ptr = s;
150     }
151 }
152 /*
153 *方法_3
154 * */
155 ListNode* Recursion_1(ListNode *ptr, ListNode *p)
156 {
157     if(!p)
158     {
159         //递归调用的退出条件
160         return ptr;
161     }
162     else
163     {
164         /*
165         * 递归进入, 并作后续的逆置,
166         */
167         ListNode *s = Recursion_1(ptr->next, p->next);
168         ptr->next   = NULL;
169         p->next = ptr;
170         return s;
171     }
172 }
173 
174 ListNode* RecursionReverseList_1(ListNode *phead)
175 {
176     if(!phead && !phead->next)
177     {
178         //判断是否错误
179         return phead;
180     }
181     return Recursion_1(phead->next, phead->next->next);
182     //这个将来为phead->next = s, 成立
183 }
184 
185 void Recursion_2(ListNode *phead, ListNode *ptr)
186 {
187     if(!ptr->next)
188     {
189         return;
190     }
191     /*
192     * 进行头插
193     */
194     ListNode *s = ptr->next;
195     ptr->next   = s->next;
196     s->next     = phead->next;
197     phead->next = s;
198     Recursion_2(phead, ptr);
199 }
200 /*
201 *方法_2R
202 * */
203 void RecursionReverseList_2(ListNode *phead)
204 {
205     if(!phead)
206     {
207         return ;
208     }
209     Recursion_2(phead, phead->next);
210 }
211 /*
212 * 打印地址和数据, 目的是为了更好的测试
213 */
214 void PrintAddrData(ListNode *phead)
215 {
216     assert(phead);
217     ListNode *p = phead->next;
218     printf("phead = %p\\t, phead->next = %p\\n", phead, phead->next);
219     int i = 0;
220     while(p)
221     {    
222         i++;
223         printf("\\ttimes = %d\\t Address = %p\\t, data = %d\\n", i, p, p->data);
224         p = p->next;
225     }
226 }
227 
228 int main()
229 {
230     
231     /*
232     *初始化过程
233     */
234     ListNode phead;
235     memset(&phead, \\0, sizeof(phead));
236     int arr[] = {12, 45, 62, 9, 89, 32, 16, 54};
237     //int arr[] = {1, 7, 2, 8, 4};
238     int len = sizeof(arr)/sizeof(arr[0]);
239 
240     for(int i = 0; i < len; ++i)
241     {
242         PushFront(&phead, arr[i]);
243     }
244     printf("\\nprintList(phead), test\\n");
245     PrintList(&phead);
246     
247     /*
248     * 测试text
249     */
250     printf("\\n_test_1 \\tReverseList_1(phead)\\n");
251     ReverseList_1(&phead);
252     PrintAddrData(&phead);
253 
254     printf("\\n_test_2 \\t ReverseList_2(phead)\\n");
255     ReverseList_2(&phead);
256     PrintAddrData(&phead);
257 
258     printf("\\n_text_3 \\tRecursionReverseList_1(phead)\\n");
259     phead.next = RecursionReverseList_1(&phead);
260     PrintAddrData(&phead);
261     
262     
263     printf("\\n_test_2R \\tRecursionReverseList_2(phead)\\n");
264     RecursionReverseList_2(&phead);
265     PrintAddrData(&phead);
266     /*
267     * 删除单链表
268     */
269     RecursionDeleteList(&phead);
270     return 0;
271 } 

  测试结果为:

技术分享技术分享

结果如上图所示.

总结:

  通过以上分析, 和自己的测试, 对于单链表的逆置递归问题, 我觉得大家应该有一个清晰地认识了. 如果以上有任何的问题和错误,

同时希望大家可以指正.

 









以上是关于对于"单链表逆置和递归"的问题的理解.的主要内容,如果未能解决你的问题,请参考以下文章

单链表的逆置使用递归算法出现-842150451这个值,请求高手予以解决。。

单链表的逆置使用递归算法出现-842150451这个值,请求高手予以解决。。

C++程序设计 编写程序实现单链表逆置功能。

单链表就地逆置的两种方法(递归与普通循环)

单链表逆置

03 单链表逆置