首页 > 代码库 > 贪婪技术与Prim算法

贪婪技术与Prim算法

时间:2014.06.06

地点:基地

---------------------------------------------------------------------------

一、简述贪婪技术

  贪婪技术在贪婪过程中所做的每一步选择都满足如三个条件:

1.可行性:满足问题的约束

2局部优先:当前步骤中所有可行选择中最佳的局部选择

3不可取消:即选择一旦做出,在算法的后面步骤中无法改变

找零问题就是一个典型的贪婪技术应用:存在面额d1=25   d2=10  d3=5  d4=1的四种面额硬币,要求用最少硬币数给出找零sum=48的方案。答案是1个d1,2个d2,和3个d4 。在这里贪婪的每一步我们都是力求用满足问题约束的最大面额硬币,以使得余下的找零数额降到最低。对于该找零问题这个解也是最优解,

贪婪技术的核心是通过一系列步骤来构造问题的解,其中每一个步骤都对目前构造的部分解做一个扩展,直到获得问题的完整解为止。

贪婪地每一步,都要求贪婪地选择最佳操作,并希望通过一系列局部最优选择产生一个全局最优解。当然也并不是总是这样对于所有问题都凑效,局部最优并不意味着全局最优,但若关心的是近似解,用贪婪技术还是一样的靠谱。

---------------------------------------------------------------------------

二、Prim算法

  Prim算法是用来求一个恶给定加权连通图的最小生成树的算法。连通图的生成树是一个包含图的所有顶点的连通无环子图,也就是一个树。加权连通图的最小生成树即图的一颗权重最小的生成树,树的权重即所有边的权重的总和。

  Prim算法即是通过一系列不断扩张的子图来构造一颗最小生成树,算法首先从图的顶点集合V中任意选择一个顶点开始,作为一系列贪婪序列中的初始子树T0,每一次迭代都以一种贪婪的方式去扩张当前的生成树,把还不在树中的且离树最近的顶点加入到树中,当图中所有的顶点都包含在构造的树中以后,算法结束。算法每次迭代对树的扩张只添加一个顶点,于是迭代的总次数为n-1(共n个顶点,然后初始化了一个顶点)。在对树进行扩张时我们使用边的集合来表示算法的生成树。

算法伪代码:

  

Prim(G)
//输入:加权连通图G=<V,E>
//输出:E' ,组成G的最小生成树的边的集合
V' <——{ v0 }   //用任意顶点初始化树的顶点集合
E' <——空     // 此时边集还是空的
for i=[1....n-1]    //需n-1次迭代,n为顶点个数
    在所有的与树相连的边中(v,u)中求得权重最小的边 e*=(v*,u*)  //这里的与树相连的边意味着构成边的两个顶点一个在树中V',一个在树外V-V'
    V'<——V' U { u* }     //把新的顶点归入生成树中
    E'<——E' U { e* }    //把这条新的边归入生成树的边集中
return E'

---------------------------------------------------------------------------

三、Prim算法的实现技巧

  为了找出与树相连的边中权重最小的边,我们需要让一个顶点提供两个附加的标记,该顶点连接到树中顶点的名称和对应权重,其中若是与树中任何顶点都不相连,用无穷大去标记。通过这些标记,问题求出加入当前树的下一个顶点的任务其实就是求出集合V-V‘中距离标记最小的顶点即可。

在确定加入树中的顶点u*后,有两个操作

一是 把u*从集合V-V‘中移动到顶点集合V‘中,即上面伪代码中的

<span style="font-size:18px;"> V'<——V' U { u* } </span>

二是 归入新顶点等操作后,对应集合V-V‘ 中剩下的顶点们u ,如果它们能用一条比当前自己和树的距离标记更短的边与刚刚新加入树中的顶点 u*相连,那么要把标记更新为 u* 和与 u*相连的边的权重。

---------------------------------------------------------------------------

五、Prim算法实现

#include<iostream>
#include<limits>
using namespace std;

struct Vertex
{
	int min_weight;    //存放当前离树最小的距离
	int parent_vertex; //存放当前离树最小距离已在树中的顶点
	bool is_in_tree;   //是否已在树中
};

size_t FindNearestVertes(Vertex vertex_set[], size_t size);
//Precondition:  Given a vertex set,finde the nearest vertes which is not in the Tree
//Postcondition: Return the nearest vertex's index
//Notice:        This is a auxiliary function
size_t FindNearestVertes(Vertex vertex_set[], size_t size);
//Precondition:
//Postcondition:
void Print(Vertex vertex_set[], int* graph[], size_t size);
//Precondition:
//Postcondition:

size_t FindNearestVertes(Vertex vertex_set[], size_t size)
{
	int min_weight = INT_MAX, nearest_vertex_index;
	for (size_t i = 0; i < size; ++i)
	{
		if (!(vertex_set[i].is_in_tree) && vertex_set[i].min_weight < min_weight)
		{
			min_weight = vertex_set[i].min_weight,
			nearest_vertex_index = i;
		}
	}
	return nearest_vertex_index;
}
Vertex* Prim(int* graph[], size_t size)
{
	Vertex* vertex_set = new Vertex[size];
	for (size_t i = 0; i < size; ++i)//初始化图的各顶点都离树很远也不再树中
	{
		vertex_set[i].min_weight = INT_MAX;
		vertex_set[i].is_in_tree = false;
	}
	vertex_set[0].min_weight = 0;  //初始化第一个顶点
	vertex_set[0].parent_vertex = -1;//

	int u;
	for (size_t i = 1; i < size; ++i)
	{
		u = FindNearestVertes(vertex_set, size);
		vertex_set[u].is_in_tree = true;
		for (size_t v = 0; v < size; ++v)
		{
			if (graph[u][v] && (!vertex_set[v].is_in_tree) && graph[u][v] < vertex_set[v].min_weight)
			{
				vertex_set[v].min_weight = graph[u][v];
				vertex_set[v].parent_vertex = u;
			}
		}
	}
	return vertex_set;
}
void Print(Vertex vertex_set[], int* graph[],size_t size)
{
	for (size_t i=1; i < size; ++i)
		cout << vertex_set[i].parent_vertex << "__" << i << ":   " << graph[i][vertex_set[i].parent_vertex] << endl;
}
int main()
{
	size_t N;
	cout << "Input vertex number: " << endl;
	cin >> N;
	cout << "Input a graph: " << endl;
	int** graph = new int*[N];  
	{
		for (size_t i = 0; i < N; ++i) 
		{
			graph[i] = new int[N];
			for (size_t j = 0;  j< N; ++j)
				cin >> graph[i][j];
		}
	}
	cout << "The result is: " << endl;
	Vertex* p=Prim(graph,N);
	Print(p, graph, 5);
}