hello!俺又来给大家送干货了呢!
今天给大家带来
一种新的算法介绍!
贪婪算法
1
基本思路从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。
当达到某算法中的某一步不能再继续前进时,算法停止。
即在贪婪算法中采用逐步构造最优解的方法。
在每个阶段,都作出一个看上去最优的决策(在一定的标准下)。决策一旦作出,就不可再更改!
当每一阶段的解都为最优解时,总体问题的解可粗略认为时最优解。作出贪婪决策的依据称为贪婪准则。
ps:(Prim算法(DJP算法)和Kruskal算法是贪婪算法中比较成功的两个算法,这两个算法一般都应用于最小生成树问题的求解)。
2
算法步骤
1从问题的某一初始解出发,
逐步逼近给定的目标,
以尽可能快的地求得更好的解。
2求出可行解的一个解元素,
当达到某算法中的某一步不能再继续前进时,
算法停止。
最终由所有解元素组合成问题的一个可行解!3
例题求解思路
害!
说了这么多!
咱还是手上见真章吧!
给大家伙个例题,大家思考一下哈!
例如:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边!
不同算法的
解析思路
1Kruskal算法
K r u s k a l算法每次选择n-1条边,所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。
注意到所选取的边若产生环路则不可能形成一棵生成树。
K r u s k a l算法分e 步,其中e 是网络中边的数目。按耗费递增的顺序来考虑这e 条边,每次考虑一条边。当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。
2Prim算法
与Kruskal算法类似,Prim算法通过每次选择多条边来创建最小生成树。
选择下一条边的贪婪准则是:从剩余的边中,选择一条耗费最小的边,并且它的加入应使所有入选的边仍是一棵树。最终,在所有步骤中选择的边形成一棵树。
相反,在Kruskal算法中所有入选的边集合最终形成一个森林。
Prim算法从具有一个单一顶点的树T开始,这个顶点可以是原图中任意一个顶点。然后往T中加入一条代价最小的边( u , v)使TÈ{ (u , v) }仍是一棵树,这种加边的步骤反复循环直到T中包含n- 1条边。注意对于边( u , v),u、v 中正好有一个顶点位于T中。
4
算法编程(C++)
1Kruskal算法
#include
#include
#include
using namespace std;
/*边的定义*/
struct edge
{
int u, v; //边的两个端点编号
int cost; //边权
edge(int x,int y, int c):u(x),v(y),cost(c){}
};
/*边的比较函数*/
bool cmp(edge a, edge b)
{
return a.cost < b.cost;
}
/*并查集查询函数,返回x所在集合的根结点*/
int findFather(vector father, int x)
{
int a = x;
while (x != father[x])
x = father[x];
while (a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
/*Kruskal算法求无向图的最小生成树*/
int Kruskal(int n, int m, vector& E)
{
/*
Param
n: 图的顶点个数
m: 图中边的个数
E: 边的集合
*/
vector father(n); //并查集数组
int ans = 0; //所求边权之和
int NumEdge = 0; //记录最小生成树边数
for (int i = 0; i < n; i++) //初始化并查集
father[i] = i;
sort(E.begin(), E.end(), cmp); //所有边按边权从小到大排序
for (int i = 0; i < m; ++i) //枚举所有边
{
int faU = findFather(father, E[i].u); //查询端点u所在集合的根结点
int faV = findFather(father, E[i].v); //查询端点v所在集合的根结点
if (faU != faV) { //如果不在一个集合中
father[faU] = faV; //合并集合(相当于把测试边加入到最小生成树)
ans += E[i].cost;
NumEdge++; //当前生成树边数加1
if (NumEdge == n - 1) //边数等于顶点数减1,算法结束
break;
}
}
if (NumEdge != n - 1) //无法连通时返回-1
return -1;
else
return ans; //返回最小生成树边权之和
}
int main()
{
vector E = { edge(0,1,4),edge(1,2,1),edge(2,3,6),edge(3,4,5),edge(0,4,1),
edge(0,5,2),edge(1,5,3),edge(2,5,5),edge(3,5,4),edge(4,5,3) };
int n = 6;
int m = 10;
int res = Kruskal(n, m, E);
cout << res << endl;
}
2Prim算法
//最小生成树的prim算法
#include
#include
using namespace std;
#define MAX 100
#define MAXCOST 0x7fffffff
int graph[MAX][MAX];
int prim(int graph[][MAX], int n,int begin)//begin作为初始顶点,n总的顶点数目
{
int lowcost[MAX];
int mst[MAX];
int sum = 0;
for (int i = 1;i <= n;i++) //初始化
{
if (i == begin)
continue;
lowcost[i] = graph[begin][i];//顶点1到其他定点的代价
mst[i] = begin;//初始都指向顶点begin
}
mst[begin] = 0;//顶点begin在MST集合里
lowcost[begin] = 0;
for (int i = 1;i <= n;i++)
{
if (i == begin)
continue;
int min = MAXCOST;
int minid = 0;
for (int j = 1;j <= n;j++)//寻找lowcost中最小的边
{
if (j == begin)
continue;
if (lowcost[j] < min&&lowcost[j] != 0)
{
min = lowcost[j];
minid = j;
}
}
lowcost[minid] = 0;
cout << "V" << mst[minid] << "-V" << minid << "=" << min << endl;//输出找到的最小一条边
sum = sum + min;
for (int j = 1;j <= n;j++)//更新其他定点(主要变化的就是以minid为起始的边)
{
if (j == begin)
continue;
if (graph[minid][j] < lowcost[j])
{
lowcost[j] = graph[minid][j];
mst[j] = minid;
}
}
}
return sum;
}
int main()
{
ifstream in("input.txt");
int m, n;
int begin = 2;
in >> m >> n;
for (int i = 1;i <= m;i++)
{
for (int j = 1;j <= m;j++)
graph[i][j] = MAXCOST;
}
for (int k = 1;k <= n;k++)
{
int i, j, cost;
in >> i >> j >> cost;
graph[i][j] = cost;
graph[j][i] = cost;
}
int cost = prim(graph, m, begin);
cout << "最小权值和=" << cost << endl;
system("pause");
return 0;
}
5
世界上没有绝对完美的算法!
同样,
贪婪算法也存在缺陷!
1不能保证求得的最后解是最佳的
2不能用来求最大或最小解问题
3只能求满足某些约束条件的可行解的范围
以上就是本期的全部内容了!
希望对大家的算法理解有所帮助
爱你!么么哒~
文案|学术部 李彦甫编辑| 庞煜阳 宋艳霏
责编| 全慧鸽 董文浩