数据结构和算法是非常难啃的东西 ,以下我会用VS2019可以编译 并且以代码和典型例子为基础 来讲解 几个典型的计算机学生应该掌握并且使用非常熟练的算法以下内容 需要大家有基本的数据结构知识, 如果学过 巩固数据结构基本的一些知识
型特例)
贪心算法思想:
顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。
贪心算法的基本要素:
1.贪心选择性质。所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。
贪心算法的基本思路:
从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到算法中的某一步不能再继续前进时,算法停止。
该算法存在问题:
不能保证求得的最后解是最佳的;
不能用来求最大或最小解问题;
只能求满足某些约束条件的可行解的范围。
实现该算法的过程:
从问题的某一初始解出发;
while 能朝给定总目标前进一步 do
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
```cpp
#include
using namespace std;
#define max 10000
void yuan(int n, int** a, int m)//实现贪心法的函数
{
int* b = new int[n + 1], * s = new int[n + 1];//增加一块空间,b用来存放目的节点到其它节点的最小距离,S用来标记
int i, j;
for (i = 1; i <= n; i++)
{
b[i] = a[m][i]; s[i] = 0;//可以做假设 从第一个开始m=1 放入b数组中,此时是没有更新的距离,不是最短距离都没有标记
}
s[m] = 1;//此时标记本身,自身到自身肯定是最短距离
for (int ii = 1; ii < n; ii++)//最外层循环,以节点为单位 操作每个节点
{
int min = max, w = 1;//所有大于Max的值都算作 无穷大 w=1
for (int k = 1; k <= n; k++)
if (min > b[k] && (!s[k]))
{
w = k; min = b[k];
}//求出节点1相邻的节点中最短的距离
s[w] = 1;//标记这个节点 记住已记住, 以后不再操作
for (i = 1; i <= n; i++)
if (!s[i])//对没有标记的节点进行操作,更新最小值。如此循环
{
if (b[i] > b[w] + a[w][i])
b[i] = b[w] + a[w][i];
}
}
for (j = 1; j <= n; j++)
if (j != m)
cout << m << "-->" << j << " : " << b[j] << endl;
}
int main()
{
int n, m, ** a, i, j;
cout <<"请输入顶点数和起始的顶点号:随后输入顶点 到另几个顶点的长度(包括自身)";
while (cin >> n >> m)
{
a = new int* [n + 1];
for (i = 0; i <= n; i++)
a[i] = new int[n + 1];
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
cin >> a[i][j];
yuan(n, a, m);
}
return 0;
}
``
不懂的可以自己画图 然后用邻接矩阵法画出一个图,试一试;
将整个问题分解成若干小问题后再分而治之。如果分解得到的子问题相对来说还是太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生方便求解的子问题,必要时逐步合并这些子问题的解,从而得到问题的解。
分治算法可以由递归过程来表示,因为分治法就是一种找大规模问题与小规模问题关系的方法,是递归设计的一种具体策略。
步骤
1.分解
将原问题分解为若干规模较小,相互独立,与原问题相同的子问题。
2.解决
若干子问题较小而容易被解决则直接解决,否则再继续分解为更小的子问题,直到容易解决。
3.合并
将已求解的各个子问题的解,逐步合并为原问题的解。
有的问题分解后不需要合并子问题的解,此时就不需要再做第3步了。多数问题需要子问题的解,按照题意使用恰当的方法合并成为整个问题的解。需要具体问题具体分析。
#include
float a[5]={3,6,9,2,5};
int main(){
void maxmin(int i,int j,float &fmax,float &fmin,int &count);//fmax的地址,fmin的地址
float fmax=0,fmin=0;
int count=0;
maxmin(0,4,fmax,fmin,count);
//传递地址是为了能够把值带出来
printf("fmax=%f\nfmin=%f\ncount=%d\n",fmax,fmin,count);
return 0;
}
void maxmin(int i,int j,float &fmax,float &fmin,int &count){
int mid;
int k;
float lmax,lmin,rmax,rmin;
count++;
//递归结束条件(子问题的解)
//当分到只剩一个数
if(i==j){
fmax=a[i];fmin=a[i];
}
else if(i==j-1){//(i,j为下标,在分解过程中,i总在左,j总在右 )假如只剩两个数
if(a[i]>a[j]){
fmax=a[i];
fmin=a[j];
}
else if(a[j]>a[i]){
fmax=a[j];
fmin=a[i];
}
}
else{//其他情况(还剩很多数),则继续递归分解
mid=(i+j)/2;//继续分半
maxmin(i,mid,lmax,lmin);
maxmin(mid+1,j,rmax,rmin);
if(lmax>rmax) fmax=lmax;
else fmax=rmax;
if(lmin<rmin) fmin=rmin;
else fmin=rmin;
}
}
以下是我综合了动态规划的特点给出的动态规划的定义:动态规划是一种多阶段决策最优解模型,一般用来求最值问题,多数情况下它可以采用自下而上的递推方式来得出每个子问题的最优解(即最优子结构),进而自然而然地得出依赖子问题的原问题的最优解。
划重点:
多阶段决策,意味着问题可以分解成子问题,子子问题,。。。,也就是说问题可以拆分成多个子问题进行求解
最优子结构,在自下而上的递推过程中,我们求得的每个子问题一定是全局最优解,既然它分解的子问题是全局最优解,那么依赖于它们解的原问题自然也是全局最优解。
自下而上,怎样才能自下而上的求出每个子问题的最优解呢,可以肯定子问题之间是有一定联系的,即迭代递推公式,也叫「状态转移方程」,要定义好这个状态转移方程, 我们就需要定义好每个子问题的状态(DP 状态),那为啥要自下而上地求解呢,因为如果采用像递归这样自顶向下的求解方式,子问题之间可能存在大量的重叠,大量地重叠子问题意味着大量地重复计算,这样时间复杂度很可能呈指数级上升(在下文中我们会看到多个这样重复的计算导致的指数级的时间复杂度),所以自下而上的求解方式可以消除重叠子问题。
简单总结一下,最优子结构,状态转移方程,重叠子问题就是动态规划的三要素,这其中定义子问题的状态与写出状态转移方程是解决动态规划最为关键的步骤,状态转移方程如果定义好了,解决动态规划就基本不是问题了。
既然我们知道动态规划的基本概念及特征,那么怎么判断题目是否可以用动态规划求解呢,其实也很简单,当问题的定义是求最值问题,且问题可以采用递归的方式,并且递归的过程中有大量重复子问题的时候,基本可以断定问题可以用动态规划求解,于是我们得出了求解动态规划基本思路如下(解题四步曲)
#include
using namespace std;
#include
int main()
{
int w[5] = { 0 , 2 , 3 , 4 , 5 }; //商品的体积2、3、4、5
int v[5] = { 0 , 3 , 4 , 5 , 6 }; //商品的价值3、4、5、6
int bagV = 8; //背包大小
int dp[5][9] = { { 0 } }; //动态规划表
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= bagV; j++) {
if (j < w[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
//动态规划表的输出
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 9; j++) {
cout << dp[i][j] << ' ';
}
cout << endl;
}
return 0;
}
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类同于:
图的深度优先搜索
二叉树的后序遍历
【
分支限界法:广度优先搜索
思想类同于:图的广度优先遍历
二叉树的层序遍历
】
详细描述
详细的描述则为:
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。
回溯法应用
当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。
它有“通用解题法”之美誉。
#include
#include
#define MAX 100
using namespace std;
int n; //城市个数
int a[MAX][MAX]; //城市间距离
int x[MAX]; //记录路径
int bestx[MAX] = {0}; //记录最优路径
int bestp = 63355; //最短路径长
int cp = 0; //当前路径长
void backpack(int t){
if(t>n){
if((a[x[n]][1])&&(a[x[n]][1]+cp<bestp)){
bestp = a[x[n]][1]+cp;
for(int i = 1;i<=n;i++){
bestx[i] = x[i];
}
}
}else{
for(int i = t;i<=n;i++){
/*约束为当前节点到下一节点的长度不为0,限界为走过的长度+当前要走的长度之和小于最优长度*/
if((a[x[t-1]][x[i]])&&(cp+a[x[t-1]][x[i]]<bestp)){
swap(x[t],x[i]);
cp+=a[x[t-1]][x[t]];
backpack(t+1);
cp-=a[x[t-1]][x[t]];
swap(x[t],x[i]);
}
}
}
}
int main(){
cout<<"输入城市个数:"<<endl;
cin>>n; //顶点数
for(int i = 1;i<=n;i++){
x[i] = i;
}
cout<<"输入城市之间的距离(0表示城市间不通):"<<endl;
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
cin>>a[i][j];
}
}
backpack(2);
cout<<"最少旅行费用为: "<<bestp<<endl;
cout<<"旅行路径为:"<<endl;
for(int i = 1;i<=n;i++){
cout<<bestx[i]<<" ";
}
cout<<bestx[1];
return 0;
}