算法第4-7章部分答案
第四章
第4题:
想法:
求两个正整数m和n的最小公倍数,由题目给出的提示可以知道,m和n的最小公倍数等于两个数的积除以它们的最大公约数。在第一张的事后要我们就已经用欧几里德算法求过两个数的最大公约数,所以对于题目4,我们就可以直接引用欧几里德算法辅助求最小公倍数。
算法:
输入:两个自然数m和n
输出:m和n的最小公倍数
1.r=m%n;
1.循环直到r=0
1.1m=n;
1.2n=r;
1.3r=m%n;
2.return n
3.调用2输出(m*n)/n
程序:
#include
int CommFactor2(int m, int n);//求两个数的最大公约数
int main()
{
int a, b, r,s;//r表示a,b两个数的最大公约数,s表示a,b的最大公倍数
cout<<"请输入两个自然数:";
cin>>a>>b;
r = CommFactor2(a, b);//调用函数求最大公约数
cout< s=a*b/r;//最大公约数与最大公倍数之间的关系 cout< return 0; } int CommFactor2(int m, int n)//返回两个数的最大公约数 { int r = m % n; while (r != 0) { m = n; n = r; r = m % n; } return n; } 第6题: 想法: 首先要建立一个大根堆,然后实现删除操作,关键是如何实现删除操作,我的想法是将要删除的元素和建立的大根堆的最后一个元素交换,然后再调用建立大根堆的函数将前n-1个函数进行大根堆操作 算法: 输入:要删除的元素的下标 输出:删除后排序好的大根堆 1.构造一个大根堆堆顺序函数SiftHeap() 2.构造一个大根堆函数初始建堆函数HeapSort(),调用函数SiftHeap() 3.建立初始大根堆 4.输入要删除的元素的下标 5.将要删除的元素与最后一个一个元素交换 6.建立前n-1个元素的大根堆 程序: //想法:先将已知序列排列成一个大根堆,删除某个元素后,将最后一个元素赋值给删除节点,然后再进行堆排序(堆排序只是有序排序中的一部分) #include void HeapSort(int r[ ], int n);//建立堆以及堆中元素整体排序 void SiftHeap(int r[ ], int k, int n);//堆排序函数 int main() { int m; int r[]={47,33,35,2,18,71,26,13}; int i,n=8; HeapSort(r, n);//调用函数建立一个大根堆 for( i=0;i<8;i++) cout< cout< cout<<"大根堆中要删除的元素的下标为:"; cin>>m;//输入大根堆中要删除的元素的下标 if(m<0||m>=n) cout<<"要删除的元素的不存在"< else { int temp; temp=r[m];//将该元素的下标与大根堆最后一个元素交换,然后将前n-1个元素进行大根堆排序即可 r[m]=r[n-1]; r[n-1]=temp; } HeapSort(r, n-1); for( i=0;i<7;i++) cout< return 0; } void SiftHeap(int r[ ], int k, int n) { int i, j, temp; i = k; j = 2 * i +1; //置i为要筛的结点,j为i的左孩子 while (j < n) //筛选还没有进行到叶子 { if (j < n-1 && r[j] < r[j+1]) j++; //比较i的左右孩子,j为较大者 if (r[i] > r[j]) //根结点已经大于左右孩子中的较大者 break; else { temp = r[i]; r[i] = r[j]; r[j] = temp; //将被筛结点与结点j交换 i = j; j = 2 * i+1; //被筛结点位于原来结点j的位置 } } } void HeapSort(int r[ ], int n) { int i; for (i = (n-1)/2; i >= 0; i--) //初始建堆,从最后一个分支结点至根结点SiftHeap(r, i, n) ; } 第7题: 想法: 求两个数的乘积,根据已知提示可以采用俄式乘法,即是采用减治法将被乘数一直减 小到1,同时将乘数以2倍的方式增大,同时将减去过程中产生的数累加最后一个求和 算法: 输入:两个正整数 输出:l两个数的乘积 1.构造一个俄式乘法函数fun() 2.输入两个数,对两个数比较大小,将较小的数作为被乘数 3.判断被乘数的奇偶性,若是偶数则减半,若是技术则减1再减半 4.进行while循环做递归处理 5.返回最后的值 6.调用函数输出结果 程序: //俄式乘法算法 #include using namespace std; int fun(int a,int b) { int temp; if(a>b)//大乘小除,减少运算步骤 { temp=b; b=a; a=temp; } int count,s=0;//count用于存放结果,s用于存放计算过程中奇数a减1产生的累加值 while(a>1) { if(a%2==0)//若a为偶数 { a=a/2; b=b*2; } else if(a%2==1)//若a为奇数 { a=(a-1)/2; s+=b;//注意s和b的位置不可以交换哦,因为是b未加倍之前的累加和 b=b*2; } } count=a*b+s; return count; } int main() { int m,n; int sum; cout<<"请输入两个乘数:"; cin>>n>>m; sum=fun(n,m); cout<<"计算结果为:"; cout< return 0; } 第8题: 想法: 对于火柴游戏,如果起初所有火柴的数量是5的倍数,那么第一个人是不能赢的,如果要第一个人赢得游戏,那么,要在火柴数量不是5的倍数的情况下进行,不管第二个人拿几根火柴,只要第一个人拿的数量是n%5的模值即可赢得游戏! 算法: 输入:火柴的数量和第二个让人拿的火柴数量 输出:第一个拿的火柴数量 1.构造fun()函数,让A拿的火柴数是第二个人拿完后的数量模5的值 2.判断n%5的值 3.如果模值不等于0,调用fun()函数 4.进行递归调用fu n知道火柴拿完为止 程序: //火柴问题,如果火柴根数是5的倍数,那么只要B不是傻瓜A就输定了!反之如果不是5的倍数,那么A每次只要拿n%5的模值就一定能赢 #include using namespace std; int fun(int n) { while(n>0) { int i,j; i=n%5; cout<<"A:"< n=n-i; if(n>0)//当A拿完之后还有火柴时B才可以拿 { cout<<"B:"; cin>>j; if(j<1||j>4) cout<<"出错了!您只能拿1—4根火柴:"< else { n=n-j; return fun(n);//递归调用函数 } } } } int main() { cout<<"请输入火柴根数:"; int n; cin>>n; if(n%5==0) cout<<"放弃吧!在B不是傻瓜的情况下A输定了!"< else fun(n); return 0; } 第10题: 想法: 这个想法是基于已知假币的轻重的情况下进行的,我假设假币比真币轻来进行试验,也可以假设假币比真币重,只需要稍微改写一下程序即可。首先,我将硬币分成3堆,用数组来存放硬币的重量,随便假设一个数组元素为假,用累加判断大小的方式来判断假币出现在哪一堆分组中,对可能出现的三种情况进行递归调用Coin()函数来进行循环判断,直到找到假币为止 算法: 输入:一个数组用来存放硬币的重量 输出:假币的位置 1.构造Coin()函数,将硬币分成三堆,如果硬币数量是3的倍数则均分成3组,如果不是 则前两组分成n/3+1 2.累加比较重量来判断假币出现的地方 3.递归循环调用Coi n()函数 4.返回假币的下标 5.在主函数中调用Coi n()函数,输出结果 程序: #include const int N = 12; //假设求解12枚硬币问题 int a[N] = {2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2}; int Coin(int low, int high, int n); int main() { int i=Coin(0,11,12); cout<<"假币是第"< return 0; }//真币的重量是2,假币的重量是1 int Coin(int low, int high, int n) //在a[low]~a[high]中查找假币 { int i, num1, num2, num3; // num1、num2和num3存储3组硬币的个数 int add1 = 0, add2 = 0; //add1和add2存储前两组硬币的重量和 if (n == 1) //递归结束的条件 return low + 1; //返回的是序号,即下标加1 if (n % 3 == 0) //3组硬币的个数相同 num1 = num2 = n / 3; else num1 = num2 = n / 3 + 1; //前两组有n / 3 + 1 枚硬币 num3 = n - num1 - num2; for (i = 0; i < num1; i++) //计算第1组硬币的重量和 add1 = add1 + a[low + i]; for (i = num1; i < num1 + num2; i++) //计算第2组硬币的重量和add2 = add2 + a[low + i]; if (add1 < add2) //在第1组查找,下标范围是low~low+num1-1 return Coin(low, low + num1 - 1, num1); else if (add1 > add2) //在第2组查找,下标范围low+num1~low+num1+num2-1 return Coin(low + num1, low + num1 + num2 - 1, num2); else //在第3组查找,下标范围low+num1+num2~high Coin(low + num1 + num2, high, num3); } 第五章 第6题: 想法: 对于第6题,用分治法进行求解的话,若是采用循环赋值的方式,如果字符的个数和左移的位数成倍数关系,那么算法就比较容易实现,但是如果不成比例关系,算法写起来就比较麻烦,所以,我用了另一种方式,进行三次对称交换就可以完成算法。 算法: 1.输入:一个数组和左移位数 2.编写两个函数,一个是对称交换函数1,另一个是调用函数2,用函数2三次调用函 数1,完成整个对称交换过程。 3.调用函数2 4.输出:左移后的数组 程序: /*(升级版2)这个是用分治法将一个字符串(abcdefgh)左移3位(defghabc),具体用到的方法是对称交换和调用函数*/ #include using namespace std; void fun(char *a,int m,int k)//(函数1)对称交换函数(注意函数1和函数2书写的顺序,不要写反了,先有函数1,再写函数2) { int temp; int i,j; for(i=m,j=k;i<=k;i++,j--) { if(i<=(m+k)/2) { temp=a[i]; a[i]=a[j]; a[j]=temp; } } } void xun(char a[],int k,int n)//(函数2)第二个调用函数,调用函数1 { fun(a,0,k-1);//第一次调用函数1,将(abc)对称交换得到(cba),最后a[]为(cbadefgh) fun(a,k,n-1);//第二次调用函数1,将(defgh)对称交换得到(hgfed),最后a[]为(cbahgfed) fun(a,0,n-1);//第三次调用函数1,将(cbadefgh)对称交换得到(defghabc),即最后a[]为(defghabc) } int main() { char a[1000]; cout<<"请输入一个数组:"< gets(a);//用cin>>a;也可以哟,不过配套的比较好看! int k; cout<<"请输入左移位数:"< cin>>k; int n=strlen(a); xun(a,k,n); puts(a);//用cout< return 0; } 想法: 采用二分查找的方式进行递归查找,找到符合要求的数就输出 算法: 1.编写递归函数,用if---else语句,返回符合要求的函数下标 2.调用递归函数,寻找符合要求的值 3.输出找到的值 程序: /*用分治的方式查找有序序列中序列号等于元素的元素,此处可以采用二分查找的方式*/ #include using namespace std; int fun(int a[],int small,int big)//fun函数用于递归 { int equ1,equ2,mid,equ; if(a[small]==small+1) return small; else if(a[big]==big+1) return big; else { mid=(small+big)/2;//二分查找的方式, if(a[mid]>=mid+1)//因为序列式有序的,所以如果a[mid]>mid,那么要查找的元素不可能存在后一半元素中 { big=mid; equ1=fun(a,small,big);//递归调用fun函数 return equ1; } else { small=mid+1; equ2=fun(a,small,big); return equ2; } } } int main() { int equ; int a[]={-6,-2,3,5,6,7,67,89,102};//给出一个有序序列 equ=fun(a,0,8);//调用函数 cout< } 第六章 习题6: 第二题: 想法: 根据6.2图问题中的动态规划法,对于多段图的最短路径问题,将它划分成K段,每一段包含定点的一个子集,每个子集中的定点互不相邻,将其建立成一个加权矩阵,一段一段的进行局部最优求解,并且逐步覆盖,得到最短路径长度,最后再进行回溯,找到路径。算法: 输入:多段图的加权矩阵 输出:最短路径长度和路径 1.调用函数循环填表 2.考察定点的所有入边,选择最小的路径并保存下来 3.输出左端路径的长度 4.回溯,输出最短路径 程序: #include const int N = 20; const int MAX = 1000; int arc[N][N]; //存储弧上的权值 int Backpath(int n);//n代表顶点个数 int creatGraph(); int main() { int n = creatGraph( );//调用函数 int pathLen = Backpath(n);//调用函数 cout<<"最短路径的长度是:"< return 0; } int creatGraph() { int i, j, k; int weight; int vnum, arcnum; cout<<"请输入顶点的个数和边的个数:"; cin>>vnum>>arcnum; for (i = 0; i < vnum; i++) for (j = 0; j < vnum; j++) arc[i][j] = MAX; for (k = 0; k < arcnum; k++)//初始化多段图 { cout<<"请输入边的两个顶点和权值:"; cin>>i>>j>>weight; arc[i][j] = weight; } return vnum;//返回顶点个数 } int Backpath(int n)//求个顶点的多段图最短路径的函数,返回最短路径长度 { int i, j, temp; int cost[N];//存放路径长度 int path[N];//存放路径 for(i = 0; i < n; i++) { cost[i] = MAX; path[i] = -1; } cost[0] = 0;path[0]=-1;//顶点0为起点 for(j = 1; j < n; j++)//执行填表工作 { for(i = j - 1; i >= 0; i--)//考察所有入边 { if (arc[i][j] + cost[i] < cost[j])//比较路径长度,短路径覆盖长路径(但是程序是将前面的所有都点都遍历一次,太过复杂,可以优化) { cost[j] = arc[i][j] + cost[i]; path[j] = i; } } } cout< i = n-1; while (path[i] >= 0)//依次输出path[i] { cout<<"<-"< i = path[i];//求得路径上顶点i的前一个顶点 } cout< return cost[n-1];//返回最短路径长度 } 第四题: 想法: 根据6.3.2最长公共子序列问题的分析,将该问题分解成两个矩阵问题进行求解,一个矩阵L(i, j)记载当前位置的最长公共子序列的长度,另一个矩阵S(i , j)记载求解过程中的状 态变化。两个矩阵都要分情况进行计算,最后再对S矩阵进行回溯,求解出公共子序列。算法: 1.初始化矩阵L的0行0列 2.填充矩阵L和S 3.建立一个数组Z存储公共子序列 4.输出最长公共子序列以及长度 程序: #include int L[10][10],S[10][10];//L(m,n)用于存放X={x1,x2,...,xm}和Y={y1,y2,...,yn}的最长公共子序列的长度 int CommonOrder(char x[ ], int m, char y[ ], int n, char z[ ]);//S(i,j)用于记载求解过程中的状态变化 int main() { //char x[]={'a', 'b', 'c', 'b', 'd', 'b'}; char A[]={'x', 'z', 'y', 'z', 'z', 'y','x'};//程序需要优化,因为输出的结果为1个,但是实际的结果可能有多个?????? //char y[]={'a','c', 'b', 'b', 'a', 'b', 'd', 'b', 'b'}; char B[]={'z','x', 'y', 'y', 'z', 'x', 'z'}; char z[10]; cout<<"长度为:"< return 0; } int CommonOrder(char x[ ], int m, char y[ ], int n, char z[ ]) { int i, j, k; for (j = 0; j <= n; j++) //初始化第0行 L[0][j] = 0; for (i = 0; i <= m; i++) //初始化第0列 L[i][0]=0; for (i = 1; i <= m; i++)//建立矩阵L(m,n)和S(i,j) { for (j = 1; j <= n; j++) { if (x[i-1] == y[j-1]) { L[i][j] = L[i-1][j-1] + 1; S[i][j] = 1; } else if (L[i][j-1] >= L[i-1][j]) { L[i][j] = L[i][j-1]; S[i][j] = 2; } else { L[i][j] = L[i-1][j]; S[i][j] = 3; } } } i = m; j = n; k = L[m][n]; //将公共子序列存储到数组z[k]中 while (i >0 && j >0) { if (S[i][j] == 1) { z[k] = x[i-1]; k--; i--; j--; }//分三种情况进行回溯 else if (S[i][j] == 2) j--;//搜索S(i,j-1) else i--;//其它情况即是S[i][j] == 3时搜索S(i-1,j) } for (k =1; k <=L[m][n]; k++) //输出最长公共子序列 cout< cout< return L[m][n]; //返回公共子序列长度 } 第八题: 想法: 我觉得这个类似于最长公共组序列问题,可以用同样的方式进行求解。同样建立一个矩阵L来存储需要的货币的最小数量。算法的关键在于需要货币数量的算法设计,即是怎样来填充L的算法。 算法: 输入:货币种类数,货币价值,(这里要求重0开始输入,只是为了方便计算,0并不作它用)以及需要兑换的货币值 输出:兑换的最小货币个数 1.输入x value[i] z 2.比较j和value[i]的大小 2.1.当value[j]==i 将a[i][j]=1; 2.2.当value[j]>i 将a[i][j]=a[i][j-1]; 2.3.当value[j] 3.输出需要的最小货币 程序: //感觉有点类似于最长公共子序列问题 #include #define N 100000 #define M 20 int a[N][M]; //用于存放兑换的最小货币数量 int value[M]; using namespace std; int main()//假设货币分成4中,分别为1,5,10,20元,要兑换的是16元 { while(true) { int value[10];//自己修改的 int i,j,k; int x,y,z; cout<<"请输入货币种类的个数:"< cin>>x; cout<<"请从小到大输入货币的价值,其中第一个必须为0:"< for(i=0;i<=x;i++)//x为货币种类的个数 { cout<<"value["< //cin>>y;//不能用一个值进行输入,这样货币的value[]值会被最后一个值覆盖所以导致结果建立的矩阵为全1矩阵,不能达到算法目的 //value[y]; cin>>value[i];//自己修改的 } cout<<"请输入要兑换的钱的价值:"< cin>>z;//z为钱 for(j=0;j<=z;j++)//x+1列z+1行的矩阵 a[j][0]=0;//为首列赋值 for(k=0;k<=x;k++) a[0][k]=0;//为首行赋值 for(i=1;i<=z;i++)//这是整个算法的关键所在 { for(j=1;j<=x;j++) { if(value[j]==i)//货币值与要兑换值刚好相等,相当于将乘法换成加法,即碰到一个钱数等于兑换货币的自身价值时,返回到钱数减去该货币值的地方,其值再加1 a[i][j]=1;//货币数量加1,a[1][1]=1; else if(value[j]>i)//货币值大于要兑换值,如,要兑换的是3元,而已有货币面值是5元 a[i][j]=a[i][j-1]; else if(value[j] { int s; s=i-value[j]; a[i][j]=a[s][j]+1; }//货币值小于要兑换值,a[2][1]=2;(这里没有执行,为什么呢?) cout< }