i++;
if(i==j)
break;
a[j]=a[i];
j--;
}
a[i]=temp;
sort(a,left,i-1);
sort(a,i+1,right);
}
intmain()
{
intarr[7]={10,2,3,5,3,6,9};
intvvv[9]={5,532,523,532,87,3124,76,4325,6};
intk;
sort(arr,0,6);
for(k=0;k<7;k++)
printf("%d",arr[k]);
printf("\n");
sort(vvv,0,8);
for(k=0;k<9;k++)
printf("%d",vvv[k]);
printf("\n");
return0;
}
结果展示:
思考问题:
1.递归的关键问题在哪里?
答:
为防止递归的无休止调用,在递归函数中要及时返回,就是结束条件的作用。
从以上的程序我们刻印看到:
在所有的递归函数中都有一个终止递归的条件判断。
2.递归与非递归之间程序的转换?
通常,一个函数在调用另一个函数之前,要作如下的事情:
a)将实在参数,返回地址等信息传递给被调用函数保存;
b)为被调用函数的局部变量分配存储区;
c)将控制转移到被调函数的入口.
从被调用函数返回调用函数之前,也要做三件事情
:
a)保存被调函数的计算结果;
b)释放被调函数的数据区;
c)依照被调函数保存的返回地址将控制转移到调用函数.
所有的这些,不论是变量还是地址,本质上来说都是"数据",都是保存在系统所分配的栈中的.
递归调用时数据都是保存在栈中的,有多少个数据需要保存就要设置多少个栈,而且最重要的一点是:
控制所有这些栈的栈顶指针都是相同的,否则无法实现 同步.
下面来解决第二个问题:
在非递归中,程序如何知道到底要转移到哪个部分继续执行?
回到上面说的树的三种遍历方式,抽象出来只有三种操作:
访问当前结点,访问左子树,访问右子树.这三种操作的顺序不同,遍历方式也不同.如果我们再抽象一点,对这三种操作再进行一个概括,可以得到:
a)访问当前结点:
对目前的数据进行一些处理;
b)访问左子树:
变换当前的数据以进行下一次处理;
c)访问右子树:
再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式).
下面以先序遍历来说明:
voidpreorder_recursive(BitreeT) /*先序遍历二叉树的递归算法*/
{
if(T){
visit(T); /*访问当前结点*/
preorder_recursive(T->;lchild); /*访问左子树*/
preorder_recursive(T->;rchild); /*访问右子树*/
}
}
visit(T)这个操作就是对当前数据进行的处理,preorder_recursive(T->;lchild)就是把当前数据变换为它的左子树,访问右子树的操作可以同样理解了.
现在回到我们提出的第二个问题:
如何确定转移到哪里继续执行?
关键在于以下三个地方:
a)确定对当前数据的访问顺序,简单一点说就是确定这个递归程序可以转换为哪种方式遍历的树结构;
b)确定这个递归函数转换为递归调用树时的分支是如何划分的,即确定什么是这个递归调用树的"左子树"和"右子树"
c)确定这个递归调用树何时返回,即确定什么结点是这个递归调用树的"叶子结点.
三.两个例子
好了上面的理论知识已经足够了,下面让我们看看几个例子,结合例子加深我们对问题的认识:
1)例子一:
f(n)= n+1; (n<2)
f[n/2]+f[n/4](n>=2);
这个例子相对简单一些,递归程序如下:
int f_recursive(intn)
{
intu1,u2,f;
if(n<2)
f=n+1;
else{
u1=f_recursive((int)(n/2));
u2=f_recursive((int)(n/4));
f=u1*u2;
}
returnf;
}
下面按照我们上面说的,确定好递归调用树的结构,这一步是最重要的.首先,什么是叶子结点,我们看到当n<2时f=n+1,这就是返回的语句,有人问为什么不是f=u1*u2,这也是一个返回的语句呀?
答案是:
这条语句是在u1=exmp1((int)(n/2))和u2=exmp1((int)(n/4))之后执行的,是这两条语句的父结点.其次,什么是当前结点,由上面的分析,f=u1*u2即是父结点.然后,顺理成章的u1=exmp1((int)(n/2))和u2=exmp1((int)(n/4))就分别是左子树和右子树了.最后,我们可以看到,这个递归函数可以表示成后序遍历的二叉调用树.好了,树的情况分析到这里,
下面来分析一下栈的情况,看看我们要把什么数据保存在栈中,在上面给出的后序遍历的如果这个过程你没非递归程序中我们已经看到了要加入一个标志域,因此在栈中要保存这个标志域;另外,u1,u2和每次调用递归函数时的n/2和n/4参数都要保存,这样就要分别有三个栈分别保存:
标志域,返回量和参数,不过我们可以做一个优化,因为在向上一层返回的时候,参数已经没有用了,而返回量也
只有在向上返回时才用到,因此可以把这两个栈合为一个栈.
如果对于上面的分析你没有明白,建议你根据这个递归函数写出它的递归栈的变化情况以加深理解,再次重申一点:
前期对树结构和栈的分析是最重要的,如果你的程序出错,那么请返回到这一步来再次分析,最好把递归调用树和栈的变化情况都画出来,并且结合一些简单的参数来人工分析你的算法到底出错在哪里.
2)例子二
快速排序算法
递归算法如下:
voidswap(intarray[],intlow,inthigh)
{
inttemp;
temp=array[low];
array[low]=array[high];
array[high]=temp;
}
int partition(intarray[],intlow,inthigh)
{
int p;
p=array[low];
while(low while(low=p)
high--;
swap(array,low,high);
while(low low++;
swap(array,low,high);
}
returnlow;
}
voidqsort_recursive(intarray[],intlow,inthigh)
{
intp;
if(low p=partition(array,low,high);
qsort_recursive(array,low,p-1);
qsort_recursive(array,p+1,high);
}
}
需要说明一下快速排序的算法:
partition函数根据数组中的某一个数把数组划分为两个部分,左边的部分均不大于这个数,右边的数均不小于这个数,然后再对左右两边的数组再进行划分.这里我们专注于递归与非递归的转换,partition函数在非递归函数中同样的可以调用(其实partition函数就是对当前结点的访问).