所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。
给定一个有向图G=(V,E),s为V中一点,以s为起点,要确定从s出发到V中每一个其他定点的距离(距离的定义是最短路径对应的长度).
初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则 < u,v >正常有权值,若u不是v的出边邻接点,则 < u,v > 权值为∞
从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)
以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权
重复步骤b和c直到所有顶点都包含在S中
const int MAXINT = 32767;
const int MAXNUM = 10;
int dist[MAXNUM];//表示距离
int prev[MAXNUM];//记录每个顶点的前驱顶点(因为最短路径的唯一性,所以每个点的前驱元素都是唯一的)
int A[MAXUNM][MAXNUM];
void Dijkstra(int v0)
{
bool S[MAXNUM]; // 判断是否已存入该点到S集合中
int n=MAXNUM;
for(int i=1; i<=n; i)
{
dist[i] = A[v0][i];
S[i] = false; // 初始都未用过该点
if(dist[i] == MAXINT) //如果该点与源点之间无边
prev[i] = -1;
else
prev[i] = v0;
}
dist[v0] = 0;
S[v0] = true;
for(int i=2; i<=n; i )
{
int mindist = MAXINT;
int u = v0; // 找出当前未使用的点j的dist[j]最小值
for(int j=1; j<=n; j)
if((!S[j]) && dist[j]//如果j点没有被用过而且dist[j]小于mindist
{
u = j; // u保存当前邻接点中距离最小的点的号码
mindist = dist[j];
}
S[u] = true; //将u置为已用
for(int j=1; j<=n; j )//更新u加入已用集合后的未用顶点集合中点到已用集合中点的距离
if((!S[j]) && A[u][j]if(dist[u] A[u][j] < dist[j]) //在通过新加入的u点路径找到离v0点更短的路径
{
dist[j] = dist[u] A[u][j]; //更新dist
prev[j] = u; //记录前驱顶点
}
}
}
}
算法的时间复杂度是senta(n^2)
设G = (V,E)是无向连通带权图,即一个网络。E中的每一条边(v,w)的权为c[v][w]。如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成树上各边权的总和称为生成树的耗费。在G的所有生成树中,耗费最小的生成树称为G的最小生成树。
最小生成树最常见的题就是求解n个城市之间的修路问题.
给定无向连同带权图G = (V,E),V = {1,2,…,n}。Kruskal算法构造G的最小生成树的基本思想是:
将G的n个顶点看成n个孤立的连通分支,将所有的边按权从小大排序。
从第一条边开始,依边权递增的顺序检查每一条边。并按照下述方法连接两个不同的连通分支:当查看到第k条边(v,w)时,如果端点v和w分别是当前两个不同的连通分支T1和T2的端点时,就用边(v,w)将T1和T2连接成一个连通分支,然后继续查看第k 1条边;如果端点v和w在当前的同一个连通分支中,就直接再查看k 1条边。这个过程一个进行到只剩下一个连通分支时为止。
此时,已构成G的一棵最小生成树。
#include
#include
#include
#include
using namespace std;
#define maxn 110 //最多点个数
int n, m; //点个数,边数
int parent[maxn]; //父亲节点,当值为-1时表示根节点
int ans; //存放最小生成树权值
struct eage //边的结构体,u、v为两端点,w为边权值
{
int u, v, w;
}EG[5010];
bool cmp(eage a, eage b) //排序调用
{
return a.w < b.w;
}
int Find(int x) //寻找根节点,判断是否在同一棵树中的依据
{
if(parent[x] == -1) return x;
return Find(parent[x]);
}
void Kruskal() //Kruskal算法,parent能够还原一棵生成树,或者森林
{
memset(parent, -1, sizeof(parent));
sort(EG 1, EG m 1, cmp); //按权值将边从小到大排序
ans = 0;
for(int i = 1; i <= m; i ) //按权值从小到大选择边
{
int t1 = Find(EG[i].u), t2 = Find(EG[i].v);
if(t1 != t2) //若不在同一棵树种则选择该边,合并两棵树
{
ans = EG[i].w;
parent[t1] = t2;
}
}
}
当图的边数为e时,Kruskal算法所需的时间是O(eloge).
设G = (V,E)是连通带权图,V = {1,2,…,n}。构造G的最小生成树,Prim算法的基本思想是:首先置S = {1},然后,只要S是V的真子集,就进行如下的贪心选择:选取满足条件i ∈S,j ∈V – S,且c[i][j]最小的边,将顶点j添加到S中。这个过程一直进行到S = V时为止。在这个过程中选取到的所有边恰好构成G的一棵最小生成树。
/* Prim算法生成最小生成树 */
void MiniSpanTree_Prim(MGraph MG)
{
int min, i, j, k;
int adjvex[MAXVEX];/* 保存相关顶点的父亲节点 */
int lowcost[MAXVEX];/* 保存相关顶点间边的权值 */
lowcost[0] = 0;/* 初始化第一个权值为0,即v0加入生成树 */
/* lowcost的值为0,在这里就是此下标的顶点已经加入生成树 */
adjvex[0] = 0;/* 初始化第一个顶点下标为0 */
cout << "最小生成树的边为:" << endl;
for (i = 1; i < MG.numVertexes; i )
{
lowcost[i] = MG.arc[0][i];/* 将v0顶点与之有边的权值存入数组 */
adjvex[i] = 0;/* 初始化都为v0的下标 */
}
for (i = 1; i < MG.numVertexes; i )
{
min = INFINITY; /* 初始化最小权值为∞, */
j = 1;
k = 0;
while (j < MG.numVertexes)/* 循环全部顶点 */
{
if (lowcost[j] != 0 && lowcost[j] < min)/* 如果权值不为0且权值小于min */
{
min = lowcost[j];/* 则让当前权值成为最小值 */
k = j;/* 将当前最小值的下标存入k */
}
j ;
}
cout << "(" << adjvex[k] << ", " << k << ")" << " "; /* 打印当前顶点边中权值最小的边 */
lowcost[k] = 0;/* 将当前顶点的权值设置为0,表示此顶点已经完成任务 */
for (j = 1; j < MG.numVertexes; j )/* 循环所有顶点 */
{
/* 如果下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 */
if (lowcost[j] != 0 && MG.arc[k][j] < lowcost[j])
{
lowcost[j] = MG.arc[k][j];/* 将较小的权值存入lowcost相应位置 */
adjvex[j] = k;/* 将k设为下标j的父亲节点 */
}
}
}
cout << endl;
}
此算法的时间复杂度为O(n^2)
当e = Ω(n^2)时,Kruskal算法比Prim算法差;
但当e = o(n^2)时,Kruskal算法比Prim算法好得多。