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

时间:2023-03-09 09:55:25
对于"单链表逆置和递归"的问题的理解.

 一. 相关知识要点:

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

  例如: 本题目结构为:

 #define  elem_type  int

 typedef  struct   _single_list {
elem_type data;  //所存的数据元素
_single_list *next;  //所存的指针元素
}ListNode;

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

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

说明:

   输入数据: 1  7  2  8  4.

   得到数据: 4  8   2  7  1.

    过程如下:

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

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

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

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

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

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

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

  结果如上:  代码如下:

 void ReverseList_1(ListNode *phead)
{
assert(phead);
ListNode *ptr = phead->next;
int len = ;//作为循环控制变量
while(ptr)
{
++len;
ptr = ptr->next;
} //p和ptr作为中间变换的参量, 一般情况下p = ptr->next;
ListNode *p = NULL; for(int i = ; i < len-; i++)
{
ptr = phead->next;
for(int j = ; j < len-i-; j++)
{
//用内外循环来控制变量的指向.
p = ptr->next;
if(ptr == phead->next)
{
ptr->next = p->next;
p->next = ptr;
phead->next = p;
//在每一次外循环头指针需要维护
continue;
}
ListNode *s = phead->next;
//s的目的是找的ptr的前驱, p为ptr后驱
while(s->next != ptr)
{
s = s->next;
}
ptr->next = p->next;
p->next = ptr;
s->next = p;
//将ptr 放到p的后面
}
}
}

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

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

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

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

    结果如上: 代码如下:

 void ReverseList_2(ListNode *phead)
{
assert(phead);
ListNode *ptr = phead->next; //相当于图上的p指针
ListNode *s = NULL; //一般指向ptr->next 并保存下一个地址
phead->next = NULL;
while(ptr != NULL)
{
/*
* 先将ptr 头指针的先一个位置进行头插.
*/
s = ptr->next;
ptr->next = phead->next;
phead->next = ptr;
ptr = s;
}
}

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

  缺点:  无.

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

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

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

如上所示: 其代码为:

 ListNode* Recursion_1(ListNode *ptr, ListNode *p)
{
if(!p)
{
//递归调用的退出条件
return ptr;
}
else
{
/*
* 递归进入, 并作后续的逆置,
*/
ListNode *s = Recursion_1(ptr->next, p->next);
ptr->next = NULL;
p->next = ptr;
return s;
}
} ListNode* RecursionReverseList_1(ListNode *phead)
{
if(!phead && !phead->next)
{
//判断是否错误
return phead;
}
return Recursion_1(phead->next, phead->next->next);
//这个将来为phead->next = s, 成立
}

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

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

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

 void Recursion_2(ListNode *phead, ListNode *ptr)
{
if(!ptr->next)
{
return;
}
/*
* 进行头插
*/
ListNode *s = ptr->next;
ptr->next = s->next;
s->next = phead->next;
phead->next = s;
Recursion_2(phead, ptr);
} void RecursionReverseList_2(ListNode *phead)
{
if(!phead)
{
return ;
}
Recursion_2(phead, phead->next);
}

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

 #include<stdio.h>
#include<string.h>
#include<assert.h>
#include<malloc.h> #define ElemType int
typedef struct SList
{
ElemType data;
SList *next;
}ListNode;
/*
*购买内存
* */
ListNode *buyNode()
{
ListNode *p = (ListNode*)malloc(sizeof(ListNode));
assert(p);
memset(p, '\0', sizeof(ListNode));
return p;
}
/*
*删除节点
* */
void DeleteNode(ListNode *ptr)
{
if(NULL == ptr)
{
return ;
}
free(ptr);
ptr = NULL;
}
/*
*普通的删除单链表
* */
void DeleteList(ListNode *phead)
{
ListNode *ptr = phead->next;
if(NULL == ptr)
{
return ;
}
ListNode *p = NULL;
while(ptr)
{
p = ptr->next;
DeleteNode(ptr);
ptr = p;
}
}
/*
*递归删除单链表
* */
void RecursionDeleteList(ListNode *ptr)
{
if(NULL == ptr->next)
{
return ;
}
RecursionDeleteList(ptr->next);
DeleteNode(ptr->next);
ptr->next = NULL;
}
/*
*打印单链表
* */
void PrintList(ListNode *phead)
{
ListNode *ptr = phead->next;
while(ptr)
{
printf(" %d", ptr->data);
ptr = ptr->next;
}
}
/*
*头插法建立单链表
* */
void PushFront(ListNode *phead, ElemType key)
{
ListNode *p = buyNode();
p->data = key;
p->next = phead->next;
phead->next = p;
}
/*
*方法_1
* */
void ReverseList_1(ListNode *phead)
{
assert(phead);
ListNode *ptr = phead->next;
int len = ;//作为循环控制变量
while(ptr)
{
++len;
ptr = ptr->next;
} //p和ptr作为中间变换的参量, 一般情况下p = ptr->next;
ListNode *p = NULL; for(int i = ; i < len-; i++)
{
ptr = phead->next;
for(int j = ; j < len-i-; j++)
{
//用内外循环来控制变量的指向.
p = ptr->next;
if(ptr == phead->next)
{
ptr->next = p->next;
p->next = ptr;
phead->next = p;
//在每一次外循环头指针需要维护
continue;
}
ListNode *s = phead->next;
//s的目的是找的ptr的前驱, p为ptr后驱
while(s->next != ptr)
{
s = s->next;
}
ptr->next = p->next;
p->next = ptr;
s->next = p;
//将ptr 放到p的后面
}
}
}
/*
*方法_2
* */
void ReverseList_2(ListNode *phead)
{
assert(phead);
ListNode *ptr = phead->next; //相当于图上的p指针
ListNode *s = NULL; //一般指向ptr->next 并保存下一个地址
phead->next = NULL;
while(ptr != NULL)
{
/*
* 先将ptr 头指针的先一个位置进行头插.
*/
s = ptr->next;
ptr->next = phead->next;
phead->next = ptr;
ptr = s;
}
}
/*
*方法_3
* */
ListNode* Recursion_1(ListNode *ptr, ListNode *p)
{
if(!p)
{
//递归调用的退出条件
return ptr;
}
else
{
/*
* 递归进入, 并作后续的逆置,
*/
ListNode *s = Recursion_1(ptr->next, p->next);
ptr->next = NULL;
p->next = ptr;
return s;
}
} ListNode* RecursionReverseList_1(ListNode *phead)
{
if(!phead && !phead->next)
{
//判断是否错误
return phead;
}
return Recursion_1(phead->next, phead->next->next);
//这个将来为phead->next = s, 成立
} void Recursion_2(ListNode *phead, ListNode *ptr)
{
if(!ptr->next)
{
return;
}
/*
* 进行头插
*/
ListNode *s = ptr->next;
ptr->next = s->next;
s->next = phead->next;
phead->next = s;
Recursion_2(phead, ptr);
}
/*
*方法_2R
* */
void RecursionReverseList_2(ListNode *phead)
{
if(!phead)
{
return ;
}
Recursion_2(phead, phead->next);
}
/*
* 打印地址和数据, 目的是为了更好的测试
*/
void PrintAddrData(ListNode *phead)
{
assert(phead);
ListNode *p = phead->next;
printf("phead = %p\t, phead->next = %p\n", phead, phead->next);
int i = ;
while(p)
{
i++;
printf("\ttimes = %d\t Address = %p\t, data = %d\n", i, p, p->data);
p = p->next;
}
} int main()
{ /*
*初始化过程
*/
ListNode phead;
memset(&phead, '\0', sizeof(phead));
int arr[] = {, , , , , , , };
//int arr[] = {1, 7, 2, 8, 4};
int len = sizeof(arr)/sizeof(arr[]); for(int i = ; i < len; ++i)
{
PushFront(&phead, arr[i]);
}
printf("\nprintList(phead), test\n");
PrintList(&phead); /*
* 测试text
*/
printf("\n_test_1 \tReverseList_1(phead)\n");
ReverseList_1(&phead);
PrintAddrData(&phead); printf("\n_test_2 \t ReverseList_2(phead)\n");
ReverseList_2(&phead);
PrintAddrData(&phead); printf("\n_text_3 \tRecursionReverseList_1(phead)\n");
phead.next = RecursionReverseList_1(&phead);
PrintAddrData(&phead); printf("\n_test_2R \tRecursionReverseList_2(phead)\n");
RecursionReverseList_2(&phead);
PrintAddrData(&phead);
/*
* 删除单链表
*/
RecursionDeleteList(&phead);
return ;
}

  测试结果为:  在linux下 gcc编译:

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

 在vs下:

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

结果如上图所示.

总结:

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

同时希望大家可以指正.