算法设计王茂林Word格式.docx
《算法设计王茂林Word格式.docx》由会员分享,可在线阅读,更多相关《算法设计王茂林Word格式.docx(20页珍藏版)》请在冰豆网上搜索。
printf(“%dfrom%cto%c”,n,A,C);
H(B,A,C,n-1);
}
}
-c语言实验代码:
#include<
stdio.h>
voidhanoi(intn,charX,charY,charZ)
if(n==1)
printf("
把%c移动到%c\n"
X,Z);
else
{hanoi(n-1,X,Z,Y);
hanoi(n-1,Y,X,Z);
main()
intm;
请输入盘子的数目:
"
);
scanf("
%d"
&
m);
要移动的盘子执行的步骤为:
%d\n"
m);
hanoi(m,'
A'
'
B'
C'
实验结果:
2、二分查找问题
(1)设a[0:
n-1]是一个已排好序的数组。
请改写二分搜索算法,使得当搜索元素x不在数组中时,返回小于x的最大元素的位置i和大于x的最小元素位置j。
当搜索元素在数组中时,i和j相同,均为x在数组中的位置。
***********************************************************
代码显示:
#include<
intbinarySearch(inta[],intn,intx,int&
i,int&
j)//a[]为要搜索的数组;
n为数组元素的个数;
x为要查询的元素值;
//i为小于x的最大元素位置;
j为大于x的最小元素的位置
intmiddle;
//中值
intright=n-1;
//数组的右边界
intleft=0;
//数组的左边界
while(left<
=right)
middle=(left+right)/2;
if(x==a[middle])
i=j=middle;
returnmiddle;
if(x>
a[middle])
left=middle+1;
else//2016年11月3日测试成功。
right=middle-1;
i=right;
j=left;
return-1;
//查询失败
intmain(void)
inti;
intj;
intb[]={1,2,3,4,5,6,7,8,12,22};
//用来测试的数组
middle=binarySearch(b,10,13,i,j);
//调用二分搜索算法
if(middle!
=-1)//查询成功
thex'
spositionis:
%d,iis%d,jis%d\n"
middle,i,j);
else//查询失败
找不到这个元素:
%d,jis%d\n"
i,j);
return0;
结果显示:
方法二:
如果存在,必须返回下标
iostream>
usingnamespacestd;
constintSIZE=12;
intbinarySearch(intArray[],intx,intn,int&
a,int&
b)
middle=(left+right)/2;
if(x==Array[middle])
{
a=b=middle;
return0;
}
if(x>
Array[middle])
left=middle+1;
else
right=middle-1;
}
a=right;
b=left;
return1;
intmain()//于16年11月3日测试成功
inta,b;
intt;
intArray[SIZE]={1,4,7,10,23,33,36,65,76,87,89,90};
cout<
<
请输入要搜索的元素:
;
cin>
>
t;
if(binarySearch(Array,t,SIZE,a,b)==0)
cout<
查找的元素存在!
endl<
下标为:
a<
endl;
查找的元素不存在!
endl
<
大于X的最小元素的位置为:
小于X的最大元素位置为:
b<
当输入元素存在时的下标显示:
当输入结果不存在时,回馈最大最小元素
(2)设有n个不同的整数排好序后存放于t[0:
n-1]中,若存在一个下标i,0≤i<n,使得t[i]=i,设计一个有效的算法找到这个下标。
要求算法在最坏的情况下的计算时间为O(logn)。
boolBinarySearch(inta[],intn,intx,int&
i,int&
j)
right)
intmid=(left+right)/2;
if(x==a[mid])
i=j=mid;
returntrue;
a[mid])
left=mid+1;
right=mid-1;
returnfalse;
intSearchTag(inta[],intn,intx)
if(x==a[mid])returnmid;
}
*********************************************************************
代码实现:
#defineN10
right-1)
left=mid;
right=mid;
intSearchTag(inta[],intn)
if(mid==a[mid])returnmid;
if(mid<
intmain(intargc)
inta[N];
请输入%d个由小到大排列的整数\n以空格隔开,以回车结束\n"
N);
intk;
for(k=0;
k<
N;
k++)
a+k);
你的输入如下:
\n"
%d\t"
k);
//printf("
a[k]);
if((k=SearchTag(a,N))!
=-1)
第%d个数字满足a[%d]=%d\n"
k,k,k);
没有找到任何一个数字满足a[i]=i\n"
inti,j;
intx;
请输入你想要查找的数字\n"
&
x);
if(BinarySearch(a,N,x,i,j))
找到了:
i和j均在%d\n"
j);
没找到:
j=%d\ti=%d\n"
j,i);
getchar();
getchar();
3、快速排序问题
在快速排序中,记录的比较和交换是从两端向中间进行的,关键字较大的记录一次就能交换到后面单元,关键字较小的记录一次就能交换到前面单元,记录每次移动的距离较大,因而总的比较和移动次数较少。
typedefintType;
voidQuickSort(Typea[],intp,intr)
if(p<
r){
intq=Partition(a,p,r);
QuickSort(a,p,q-1);
//对左半段排序
QuickSort(a,q+1,r);
//对右半段排序
template<
classType>
intPartition(Typea[],intp,intr)
inti=p,j=r+1;
Typex=a[p];
//将<
x的元素交换到左边区域
//将>
x的元素交换到右边区域
while(true){
while(a[++i]<
while(a[--j]>
if(i>
=j)break;
Swap(a[i],a[j]);
a[p]=a[j];
a[j]=x;
returnj;
**********************************************
快速排序代码实现:
voidsort(inta[],intleft,intright)
if(left>
=right)
return;
inti=left;
intj=right;
inttemp=a[left];
while(i!
=j)
while(temp<
a[j]&
&
i<
j)
j--;
if(i==j)
break;
a[i]=a[j];
i++;
while(a[i]<
temp&
i++;
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};
sort(arr,0,6);
k<
7;
k++)
printf("
%d"
arr[k]);
sort(vvv,0,8);
9;
vvv[k]);
return0;
结果展示:
思考问题:
1.递归的关键问题在哪里?
答:
为防止递归的无休止调用,在递归函数中要及时返回,就是结束条件的作用。
从以上的程序我们刻印看到:
在所有的递归函数中都有一个终止递归的条件判断。
2.递归与非递归之间程序的转换?
通常,一个函数在调用另一个函数之前,要作如下的事情:
a)将实在参数,返回地址等信息传递给被调用函数保存;
b)为被调用函数的局部变量分配存储区;
c)将控制转移到被调函数的入口.
从被调用函数返回调用函数之前,也要做三件事情
:
a)保存被调函数的计算结果;
b)释放被调函数的数据区;
c)依照被调函数保存的返回地址将控制转移到调用函数.
所有的这些,不论是变量还是地址,本质上来说都是"
数据"
都是保存在系统所分配的栈中的.
递归调用时数据都是保存在栈中的,有多少个数据需要保存就要设置多少个栈,而且最重要的一点是:
控制所有这些栈的栈顶指针都是相同的,否则无法实现
同步.
下面来解决第二个问题:
在非递归中,程序如何知道到底要转移到哪个部分继续执行?
回到上面说的树的三种遍历方式,抽象出来只有三种操作:
访问当前结点,访问左子树,访问右子树.这三种操作的顺序不同,遍历方式也不同.如果我们再抽象一点,对这三种操作再进行一个概括,可以得到:
a)访问当前结点:
对目前的数据进行一些处理;
b)访问左子树:
变换当前的数据以进行下一次处理;
c)访问右子树:
再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式).
下面以先序遍历来说明:
voidpreorder_recursive(BitreeT)
/*先序遍历二叉树的递归算法*/
if(T){
visit(T);
/*访问当前结点*/
preorder_recursive(T->
lchild);
/*访问左子树*/
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;
partition(intarray[],intlow,inthigh)
p;
p=array[low];
while(low<
high){
high&
array[high]>
=p)
high--;
swap(array,low,high);
array[low]<
low++;
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函数就是对当前结点的访问).