算法中经常用到的除了排序算法之外,还有一类是用来求最优的算法。五大常用求最优的算法是:分治法,回溯法,贪心法,动态规划法,分支限界法。从求解思想,求解过程,算法实例的过程让大家复习一下几个算法。
1)求解思想
把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题。直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。各个击破,分而治之。
适用于:问题可分解为规模较小的相同问题,最重要的是问题分解可以合并。
2)求解过程
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 合并:将各个子问题的解合并为原问题的解。
3)算法实例
归并排序是应用分治法的一个完美的例子,归并排序的基本例子是将元素分半,对子序列排序,在将排好的子序列进行合并。下面是C代码。
#include<stdlib.h> typedef int RecType;//要排序元素类型 void Merge(RecType *R,int low,int m,int high) { //将两个有序的子文件R[low..m)和R[m+1..high]归并成一个有序的子文件R[low..high] int i=low,j=m+1,p=0; //置初始值 RecType *R1; //R1是局部向量 R1=(RecType *)malloc((high-low+1)*sizeof(RecType)); if(!R1) { return; //申请空间失败 } while(i<=m&&j<=high) //两子文件非空时取其小者输出到R1[p]上 { R1[p++]=(R[i]<=R[j])?R[i++]:R[j++]; } while(i<=m) //若第1个子文件非空,则复制剩余记录到R1中 { R1[p++]=R[i++]; } while(j<=high) //若第2个子文件非空,则复制剩余记录到R1中 { R1[p++]=R[j++]; } for(p=0,i=low;i<=high;p++,i++) { R[i]=R1[p]; //归并完成后将结果复制回R[low..high] } } void MergeSort(RecType R[],int low,int high) { //用分治法对R[low..high]进行二路归并排序 int mid; if(low<high) { //区间长度大于1 mid=(low+high)/2; //分解 MergeSort(R,low,mid); //递归地对R[low..mid]排序 MergeSort(R,mid+1,high); //递归地对R[mid+1..high]排序 Merge(R,low,mid,high); //组合,将两个有序区归并为一个有序区 } }
1)求解思想
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。通过解局部最优策略。
适用于:局部最优策略可以导致全局最优。
2)求解过程
- 把求解的问题分成若干个子问题
- 对每一子问题求解,得到子问题的局部最优解
- 把子问题的解局部最优解合成原来解问题的一个解
3)算法实例
背包问题:与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n。
#include "stdafx.h" #include <iostream> using namespace std; const int N = 3; void Knapsack(int n,float M,float v[],float w[],float x[]); int main() { float M = 50;//背包所能容纳的重量 //这里给定的物品按单位价值减序排序 float w[] = {0,10,20,30};//下标从1开始 float v[] = {0,60,100,120}; float x[N+1]; cout<<"背包所能容纳的重量为:"<<M<<endl; cout<<"待装物品的重量和价值分别为:"<<endl; for(int i=1; i<=N; i++) { cout<<"["<<i<<"]:("<<w[i]<<","<<v[i]<<")"<<endl; } Knapsack(N,M,v,w,x); cout<<"选择装下的物品比例如下:"<<endl; for(int i=1; i<=N; i++) { cout<<"["<<i<<"]:"<<x[i]<<endl; } return 0; } void Knapsack(int n,float M,float v[],float w[],float x[]) { //Sort(n,v,w);//这里假定w[],v[]已按要求排好序 int i; for (i=1;i<=n;i++) { x[i]=0;//初始化数组x[] } float c=M; for (i=1;i<=n;i++)//物品整件被装下,x[i]=1 { if (w[i]>c) { break; } x[i]=1; c-=w[i]; } //物品i只有部分被装下 if (i<=n) { x[i]=c/w[i]; } }
1)求解思想
将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。在动态规划算法中,还要考察每个最优决策序列中是否包含一个最优决策子序列,即问题是否具有最优子结构性质。
2)求解过程
- 分析问题最优解,找出特性,刻画结构特征。
- 递归的定义最优解。
- 自底向上计算问题最优解。
- 找到最优决策子序列。
3)算法实例
0-1背包问题:给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
#include<stdio.h> int c[10][100];/*对应每种情况的最大价值*/ int knapsack(int m,int n) { int i,j,w[10],p[10]; printf("请输入每个物品的重量,价值:\n"); for(i=1;i<=n;i++) scanf("%d,%d",&w[i],&p[i]); for(i=0;i<10;i++) for(j=0;j<100;j++) c[i][j]=0;/*初始化数组*/ for(i=1;i<=n;i++) for(j=1;j<=m;j++) { if(w[i]<=j) /*如果当前物品的容量小于背包容量*/ { if(p[i]+c[i-1][j-w[i]]>c[i-1][j]) /*如果本物品的价值加上背包剩下的空间能放的物品的价值*/ /*大于上一次选择的最佳方案则更新c[i][j]*/ c[i][j]=p[i]+c[i-1][j-w[i]]; else c[i][j]=c[i-1][j]; } else c[i][j]=c[i-1][j]; } return(c[n][m]); }
1)求解思想
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回重新选择的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
适用于:复杂,规模较大。
2)求解过程
- 定义问题的解空间
- 确定易于搜索的空间结构
- 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索
3)算法实例
#include <iostream> #include <vector> using namespace std; class PackBackTrack { protected: vector<int> m_p; //N个背包的价格 vector<int> m_w; //N个背包的重量 int m_c; //背包的容量 int m_num; //物品的件数 int bestValue; //背包最大价值 int currentValue; //当前背包中物品的价值 int currentWeight; //当前背包中物品的重量 private: //辅助函数,用于回溯搜索 void BackTrack(int depth) { if(depth >= m_num) //达到最大深度 { if(bestValue < currentValue) //保存最优解 bestValue = currentValue; return ; } if(currentWeight +m_w[depth] <= m_c) //是否满足约束条件 { currentWeight += m_w[depth]; currentValue += m_p[depth]; //选取了第i件物品 BackTrack(depth+1); //递归求解下一个结点 //恢复背包的容量和价值 currentWeight -= m_w[depth]; currentValue -= m_p[depth]; } //不取第i件物品 BackTrack(depth+1); } public: //构造函数 PackBackTrack(); PackBackTrack(vector<int>& p,vector<int>& w, int c,int n) :m_p(p),m_w(w),m_c(c),m_num(n) { bestValue =0; currentValue =0; currentWeight =0; } //获取背包内物品的最大值 int GetBestValue() { BackTrack(0); return bestValue; } }; int main(void) { //测试程序 int n; int c; cout << "请输入物品的件数" << endl; cin >>n; cout << "请输入背包的容量" << endl; cin >>c; vector<int> w(n); vector<int> p(n); cout << "请输入物品的重量:" << endl; for(int i=0;i<n;++i) cin >> w[i]; cout << "请输入物品的价格:" << endl; for(int j=0;j<n;++j) cin >> p[j]; PackBackTrack pack(p,w,c,n); int bestValue = pack.GetBestValue(); cout << "背包内的物品的最大价值为:" << bestValue << endl; return 0; }
应对软考中的算法题,很多是在循环体中出一个填空,弄清楚这几层的循环很容易就可以蒙几道题上去的。求最优解系列的算法了解基本思想,做题的时候一定要先想一边基本思路,应该可以蒙对几个的....
排序算法的总结,请移步上一篇:《软考系列——排序算法盘点》