我们经常通过链表方式处理问题。链表的 创建、插删、合并、逆置、等等我们都通过非递归方法和递归方法实现过了,但是 冒泡排序 一直是用非递归的循环方法进行处理,这两天突发奇想,为啥不用递归试一试呢?O(∩_∩)O~~然后好好想了一下,恕在下愚钝,最终代码写成这个样子已是极限,。
下面是我的思路。
思考:如何用递归实现冒泡排序?我觉得排序的代码应该是一样的,都是用中间变量t_num来交换数据,而不是通过改变节点地址关系。(如果这个方法真的可以实现的话,那肯定是很厉害的算法⊙o⊙!)既然交换方式是相同的,那么需要解决的就是如何将链表进行 n 次 完整的递归 ,(在我的定义中 n 为全部节点个数,一次 完整的递归 是从当前调用点递归到最后一个节点,然后层层返回到这个调用点的上一个调用点)。
我想了下,如果一个函数中只有一条递归语句,应该很难实现这个过程,因为一次递归的话,跑到最后一个节点就得返回了,只能将链表中的最值排到链表尾部,而整体目标尚未达成。所以在我的sort函数中,有两条递归语句。
既然有两条递归语句,那么一定得为这两条语句设定严格的停止条件,否则函数将无法停止。(这也是所有递归算法都应该有的东西),所以我设定了tag值。
在我的设想中:
第一条递归语句放在sort函数首部。在sort函数被激活之后,应该先通过递归一直调到链表尾部,(第一条递归语句的作用就是递归到链表尾部,并且这条语句只能被执行n-2次,因为最后一个节点没有后继结点。即从第一次开始处理数据之后,这条递归语句就再也不能被执行了)然后每次返回之前都比较当前节点的num与后继结点的num,然后进行交换处理。这样可以保证,链表的处理次序过程是从后向前倒序进行的,但排序过 程还是从前向后的!)这样倒着返回、正着处理的好处是,保证当前处理节点之后的所有节点的信息都是已经排好序的!!第二条递归语句放在sort函数尾部。每一次当前节点(为说明方便,设此节点为king)数据处理完毕后,都会再次递归(若到达倒数第二个节点则不用继续递归),将下一个节点的地址传给sort函数,然后层层递进并且处理数据,(此时第一条递归语句已经被屏蔽不能在执行,tag为0),直到递归到达倒数第二个节点,因为在倒数第二个节点,所以第二条递归语句也不执行。然后层层返回,最终返回到king的前驱结点。接着在处理king前驱结点的数据,然后激活第二条递归语句,将king的地址传给sort函数继续调用,然后边处理数据边递归直到倒数第二个节点,这样就将king前驱节点及其之后所有节点的信息都排序好了,然后层层返回到king前驱的前驱,继续进行这样的重复操作。。。
最终返回到头结点,然后处理头结点的数据(此时头结点之后的数据已经全部排好序了),在层层递归调用,将头结点里的数据放到它应该在的位置上,随后层层返回到主函数。由于没有改变节点的链接关系,所以头结点仍为head,sort函数类型为void。
限于我表达能力有限,说了这么多,也不知道到底有没有成功将我的思路表达而出,下面贴上源代码,作为程序猿的我们,自己理解的效果肯定还是比听别人说的要高得多。总之还望路过的英雄多多指点。
#include<stdio.h> /*利用递归实现:创建链表 、冒泡排序 */
#include<stdlib.h>
#define NULL 0
int tag=1; //tag初始化为1
typedef struct node{
int num;
struct node * next;
}ElemSN;
ElemSN * creatlink(int n);
void out(ElemSN * head);
void sort(ElemSN *p);
int main (void){
ElemSN * head;
printf("请输入十个数据:\n");
head=creatlink(10); //创建了十个节点
printf("\n原链表为:\t\t");
out(head);
sort(head);
printf("\n冒泡排序后的链表为:\t");
out(head);
printf("\n\n");
return 0;
}
void sort(ElemSN *p){
int t;
if(p->next->next&&tag){ //当跑到倒数第二个节点后,tag=0,之后就再也不执行这条语句了*/
sort(p->next); //第一条递归语句
}
else
tag=0;
if(p->num<p->next->num){ //处理节点数据
t=p->num;
p->num=p->next->num;
p->next->num=t;
}
if(p->next->next)
sort(p->next); //第二条递归语句
}
ElemSN * creatlink(int n)
{
ElemSN * head = NULL;
if(n){
head=(ElemSN *)malloc(sizeof(ElemSN));
scanf("%d", &head->num);
head->next = creatlink(n-1);
}
return head;
}
void out(ElemSN * head){
ElemSN *p;
for(p=head;p!=NULL;p=p->next)
printf(" %d",p->num);
}
最后,虽然代码运行成功,但我仍有很多地方还是不清楚。比如代码的时间复杂度是多少,递归次数=(n-2)+1+2+3+······+(n-3)+(n-2)=n*(n-2),但由于用的是递归,所以我不知道到底该怎么算。。
还有代码的优化,其实不用每次都跑到倒数第二个节点的,但是我暂时也没想到该怎么修改。以及其他好多问题。
况且这还只算是最初等的算法了吧,之后有那么多的内容还可以去学习!
所以,路漫修远~
顺便膜拜路过的大神,真诚的求指导与修改,不胜感激!