算法中经常用到的除了排序算法之外,还有一类是用来求最优的算法。五大常用求最优的算法是:分治法,回溯法,贪心法,动态规划法,分支限界法。从求解思想,求解过程,算法实例的过程让大家复习一下几个算法。
1)求解思想
把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题。直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。各个击破,分而治之。
适用于:问题可分解为规模较小的相同问题,最重要的是问题分解可以合并。
2)求解过程
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 合并:将各个子问题的解合并为原问题的解。
3)算法实例
归并排序是应用分治法的一个完美的例子,归并排序的基本例子是将元素分半,对子序列排序,在将排好的子序列进行合并。下面是C代码。
#include
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
1)求解思想
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。通过解局部最优策略。
适用于:局部最优策略可以导致全局最优。
2)求解过程
- 把求解的问题分成若干个子问题
- 对每一子问题求解,得到子问题的局部最优解
- 把子问题的解局部最优解合成原来解问题的一个解
3)算法实例
背包问题:与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n。
#include "stdafx.h"
#include
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<<"背包所能容纳的重量为:"<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
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
#include
using namespace std;
class PackBackTrack
{
protected:
vector m_p; //N个背包的价格
vector 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& p,vector& 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 w(n);
vector p(n);
cout << "请输入物品的重量:" << endl;
for(int i=0;i> w[i];
cout << "请输入物品的价格:" << endl;
for(int j=0;j> p[j];
PackBackTrack pack(p,w,c,n);
int bestValue = pack.GetBestValue();
cout << "背包内的物品的最大价值为:" << bestValue << endl;
return 0;
}
应对软考中的算法题,很多是在循环体中出一个填空,弄清楚这几层的循环很容易就可以蒙几道题上去的。求最优解系列的算法了解基本思想,做题的时候一定要先想一边基本思路,应该可以蒙对几个的....
排序算法的总结,请移步上一篇:《软考系列——排序算法盘点》