这一篇博客继续以一些OJ上的题目为载体,对树专题进行整理整理一下。会陆续的更新。。。
一、树的遍历
树的定义:连通的无回路的无向图。
例题
1、POJ 2255 Tree Recovery 树的恢复
题目分析:
这道题抽象一下就是:“由树的前序遍历和中序遍历顺序确定输的后序遍历顺序”。其实我们很早之前就在各种资料上看到过以下这句话,“由输的前序遍历(或后序遍历)和中序遍历序列,我们就能唯一确定一个二叉树”。。但是算法思想到底是怎么样的,实现又怎么实现,我想很多人也就不怎么去探究了,也就是仅仅的记住这句话,当做是定理一样的记住。。。
以下给出由前序遍历序列和中序遍历序列得到后序遍历序列的算法。
描述如下:首先由前序遍历序列的第一个节点确定根节点。然后到中序遍历序列中找到该节点。然后输出该节点前面的元素的个数,接着到前序遍历序列中数同样个数的元素(除开第一个根节点)。。。这时候就得到了根节点、左子树的前序遍历序列和中序遍历虚列、右子树的前序遍历序列和中序遍历序列。 。。接下来干的活就是不断的递归。。。。。
实现如下:
/**
* 由前序序列和中序序列得到后序序列
*/
string solve(string pre,string in){
if(in.length() == 1){//递归的结束条件
return in;
}else if(in.length() == 0){
return "";
}
int m = in.find(pre[0]);//在中序序列中找到根节点的位置
//后序遍历的遍历次序是: 后序遍历左子树、后序遍历右子树、输出根节点的信息
return solve(pre.substr(1,m),in.substr(0,m)) + solve(pre.substr(m+1),in.substr(m+1)) + pre[0];
}
这道题的解法如下:
/*
* POJ_2255_1.cpp
*
* Created on: 2014年6月8日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
/**
* 由前序序列和中序序列得到后序序列
*/
string solve(string pre,string in){
if(in.length() == 1){//递归的结束条件
return in;
}else if(in.length() == 0){
return "";
}
int m = in.find(pre[0]);//在中序序列中找到根节点的位置
//后序遍历的遍历次序是: 后序遍历左子树、后序遍历右子树、输出根节点的信息
return solve(pre.substr(1,m),in.substr(0,m)) + solve(pre.substr(m+1),in.substr(m+1)) + pre[0];
}
int main(){
string back;
string pre;
string in;
while(cin >> pre >> in){
back = solve(pre,in);
cout << back <<endl;
}
return 0;
}
二、图的最小生成树
求无向图的最小生成树主要有两种算法:Prim算法和kruscal算法。以下分别介绍着两种算法。
无向图的最下生成树问题通俗点讲就是:“求n个点连通的最小费用”
1、Prim算法
描述:
1)从图中选一个点当做起始点(一般选“1”这个点)。加入到最小生成树中
2)寻找与该点相连的边中权值最小的那条边,并将该边的另外一个点加入到最小生成树中。
3)不断的重复步骤2)直到所有的点都在最小生成树中。
有人会说,Prim算法和dijkstra算法特别的像。除了最后那一部分基本事实一模一样的
prim写法:
for(j = 1 ; j <= n ; ++j){
if(!s[j] && dis[j] > map[pos][j]){
dis[j] = map[pos][j];
}
}
dijkstra的写法:
for (j = 1; j < maxn; ++j) {
if (!s[j] && dis[j] > dis[pos] + map[pos][j]) {
dis[j] = dis[pos] + map[pos][j];
}
}
其实他们的差异主要是在松弛的部分。prim的是
if(!s[j] && dis[j] > map[pos][j]){
dis[j] = map[pos][j];
}
因为这是求的是最小生树到未在最小生成树中的点的距离(不太准确地讲就是求一棵树都某一个点的距离,而不是dijkstra中的某一个点到未在集合中的另一个点的距离)。
算法实现:
int n, m;
/**
* 使用prim算法生成无向图的最小生成树
*/
int prim() {
int sum = 0;
int i;
for (i = 2; i <= n; ++i) {//初始化
p[i] = false;//所有的点(除1外,默认使用1作为起始点)都不在Va集合中
dis[i] = map[1][i];
pre[i] = 1;
}
dis[1] = 0;
p[1] = true;
for (i = 1; i <= n - 1; ++i) {//循环n-1次,每次加入一个点
int min = inf;
int k = 0;
int j;
for (j = 1; j <= n; ++j) {//求出边权值最小的那个点
if (!p[j] && dis[j] < min) {
min = dis[j];
k = j;
}
}
if(k == 0){//没有电可以拓展,图G不连通
return -10;
}
sum += dis[k];
p[k] = true;//将点k加入到最小生成树中
for (j = 1; j <= n; ++j) {
if (!p[j] && dis[j] > map[k][j]) {
dis[j] = map[k][j];
pre[j] = k;
}
}
}
return sum;
}
时间复杂度:O(n^2)
空间复杂度:O(n^2)
基本结构:
/**
* prim算法的基本结构
*/
int map[maxn][maxn];//map[i][j]: 表示i到j这条边的距离
int p[maxn];//p[i]: 表示idian是否在Va集合(最小生成树的集合)中
int dis[maxn];//最小生成树中包含节点i最短边的权值
int pre[maxn];//最小生成树中包含节点i的最短边
int n, m;
基本条件:
点数、
边数、
以及各边的权值
注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。
Ex:
看了上面一大段文字是不是感觉有点晕啊,为了更好理解我在这里举一个例子,示例如下:
(1)图中有6个顶点v1-v6,每条边的边权值都在图上;在进行prim算法时,我先随意选择一个顶点作为起始点,当然我们一般选择v1作为起始点,好,现在我们设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边,现在状态如下:
U={v1}; TE={};
(2)现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。
通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:
U={v1,v3}; TE={(v1,v3)};
(3)继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。
我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:
U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循环一下直到找到所有顶点为止。
(4)下图像我们展示了全部的查找过程:
2、Kruscal算法
算法思想描述:先对所有的边进行排序。然后遍历所有的边,将该边所连接的点并入到同一个最下生成树中。
实现(使用了链式前向星):
/**
* 链式前向星
*/
struct Edge{
int begin;//边的起点
int end;//边的终点
int weight;//边的权值
int selected;//该边是否被选中
}edge[maxm];
bool cmp(Edge a,Edge b){
if(a.weight != b.weight){
return a.weight < b.weight;
}
if(a.begin != b.begin){
return a.begin < b.begin;
}
return a.end < b.end;
}
int n,m;
/**
* kruscal算法思想:
* 1)对边排序(贪心思想的体现)
* 2)遍历每一条边.加入并将该边所连接的点合并到同一颗最小生成树中
*/
int kruscal(){
int sum = 0;
int k = 0;
sort(edge+1,edge+1+m,cmp);//对边排序...这是kruscal中使用了贪心思想的体现
int i;
for(i = 1 ; i <= n ; ++i){
father[i] = i;
}
for(i = 1 ; i <= m ; ++i){//i在这里是一个计数器..
if(k == n-1){//如果合并了n-1条边,说明最下生成树已经生成,可以返回了
break;
}
/**
* 判断两个点是否已经在最小生成树里面了,如果不在,则将她们合并到最小哦啊生成树中
*/
int x = find(edge[i].begin);
int y = find(edge[i].end);
if(x != y){
merge(x,y);
k++;//已经合并的边数+1
edge[i].selected = true;//将改变标记为选中状态
sum += edge[i].weight;//累加最小生成树的边权值
}
}
return sum;
}
时间复杂度:O(mlgm + m),一部分时间花在边排序上面了。。。
Ex:
需要注意的是使用kruscal算法来解决最小生成树的问题时,输入数据的处理方式。
1)当以边的形式给出图的信息的时候,可以直接往edge[i]里面存。如下所示:
for(i = 1 ; i <= m ; ++i){
scanf("%d%d%d",&edge[i].begin,&edge[i].end,&edge[i].weight);
edge[i].selected = false;
}
2)当以矩阵的形式给出图的信息的时候。这是可以现网map[][]矩阵里面存,然后再从里面取,如下所示:
int i;
int j;
for(i = 1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
scanf("%d",&map[i][j]);
}
}
for(i = 1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
edge[m].begin = i;
edge[m].end = j;
edge[m].weight = map[i][j];
edge[m++].selected = false;
}
}
m -= 1;//这里的处理很重要,如果不加上这个很可能WA。
有的同学可能会觉得求最小生成树的基本条件和求最短路径的基本条件很像。。。是的。。。
求最短路径的算法主要有:dijkstra、floyd、bellman-ford、SPFA.这些算法的基本条件是:
点数、边数、起点、终点、边的信息
求最小生成树的算法主要有:prim、kruscal。这些算法的基本条件是:
点数、边数、边的信息。这比求最短路径所需要的基本条件少了两个(起点、终点),其实这也很好理解。因为最小生成树求的是让n个点连通的最小费用。而最短路径算法求的是某一个点到另外一个点的最短路径
例题:
1、NYOJ 38
题目与分析:
1》这一道题中,抽象一下为:“给出点数、边数、各边的权值”,求“该无向图的最小生成树”..这道题可以使用Prim算法来解决。和最小生成树裸题有点不同的是,他还在最后加了一种“链接外界”的情况。这时候,只需要在求出最小生成树后,将“外界情况”中耗费最下的那个值加上即可。。
2》题目中有2个很重要的条件:
1、把所有的楼都供上电。(让n个点连通)
2、所用电线花费最少。(费用最小)
-----》合起来就是求让n个点连通的最小费用-----》用最小生成树求解。。。。
1)使用Prim算法解决
/*
* NYOJ_38.cpp
*
* Created on: 2014年6月9日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 505;
const int maxm = maxn * maxn / 2;
const int inf = 100000004;
/**
* prim算法的基本结构
*/
int map[maxn][maxn];//map[i][j]: 表示i到j这条边的距离
int p[maxn];//p[i]: 表示idian是否在Va集合(最小生成树的集合)中
int dis[maxn];//最小生成树中包含节点i最短边的权值
int pre[maxn];//最小生成树中包含节点i的最短边
int n, m;
/**
* 使用prim算法生成无向图的最小生成树
*/
int prim() {
int sum = 0;
int i;
for (i = 2; i <= n; ++i) {//初始化
p[i] = false;//所有的点(除1外,默认使用1作为起始点)都不在Va集合中
dis[i] = map[1][i];
pre[i] = 1;
}
dis[1] = 0;
p[1] = true;
for (i = 1; i <= n - 1; ++i) {//循环n-1次,每次加入一个点
int min = inf;
int k = 0;
int j;
for (j = 1; j <= n; ++j) {//求出边权值最小的那个点
if (!p[j] && dis[j] < min) {
min = dis[j];
k = j;
}
}
if(k == 0){//没有电可以拓展,图G不连通
return -10;
}
sum += dis[k];
p[k] = true;//将点k加入到最小生成树中
for (j = 1; j <= n; ++j) {
if (!p[j] && dis[j] > map[k][j]) {
dis[j] = map[k][j];
pre[j] = k;
}
}
}
return sum;
}
void printDis(){
int i;
printf("%d %d %d %d\n",dis[1],dis[2],dis[3],dis[4]);
for(i = 1 ; i <= n ; ++i){
printf("%d ",dis[i]);
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
int i, j;
for (i = 1; i <= n; ++i) {
for (j = 1; j <= n; ++j) {
if(i == j){
map[i][j] = 0;
}else{
map[i][j] = inf;
}
}
}
for (i = 1; i <= m; ++i) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if (map[a][b] > c) {//用来处理重边的情况
map[a][b] = map[b][a] = c;
}
}
int sum = prim();
// printf("n:%d,m:%d\n",n,m);
// printDis();
int mm = inf;
for (i = 1; i <= n; ++i) {
int a;
scanf("%d", &a);
if (mm > a) {
mm = a;
}
}
printf("%d\n", sum+mm);
}
return 0;
}
以下是再次做这道题时的代码:
/*
* NY_38.cpp
*
* Created on: 2014年9月7日
* Author: pc
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 505;
const int inf = 999999;
int s[maxn];
int dis[maxn];
int map[maxn][maxn];
int n,m;
void initial(){
int i;
int j;
for(i =1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
if(i == j){
map[i][j] = 0;
}else{
map[i][j] = inf;
}
}
}
}
int prim(){
int sum = 0;
int i;
int j;
for(i = 1 ; i <= n ; ++i){
s[i] = false;
dis[i] = map[1][i];
}
s[1] = true;
dis[1] = 0;
for(i = 1 ; i < n ; ++i){
int min = inf;
int pos;
for(j = 1 ; j <= n ; ++j){
if(!s[j] && min > dis[j]){
min = dis[j];
pos = j;
}
}
s[pos] = true;
sum += dis[pos];
for(j = 1 ; j <= n ; ++j){
if(!s[j] && dis[j] > map[pos][j]){
dis[j] = map[pos][j];
}
}
}
return sum;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
initial();
int i;
for(i = 1 ; i <= m ;++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(map[a][b] > c){
map[a][b] = map[b][a] = c;
}
}
int min = inf;
for(i = 0 ; i < n ; ++i){
int a;
scanf("%d",&a);
if(min > a){
min = a;
}
}
int sum = prim();
printf("%d\n",sum+min);
}
return 0;
}
2)使用kruscal算法来解决
因为这道题的边的范围在500*500=250000之内。因为kruscal算法的时间复杂度是O(m lgm),所以,还是可以做的。在使用kruscal算法来解决这道题的时候,有一点需要提醒一下的是:当题目以边的形式给出图的信息(而不是以一个邻接矩阵)的时候,这时候我们其实可以不需要邻接矩阵。这时候不需要担心重边的问题,链式前向星能够解决这个问题。
/*
* NYOJ_38_kruscal.cpp
*
* Created on: 2014年6月11日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 505;
const int maxm = maxn*maxn;
const int inf = 99999999;
//在i边的形式给出图的信息的时,如果使用kruscal来解决的话,
//那么其实可以不需要用到邻接矩阵.直接往里面存就行了.因为链式前向星能够处理过重边的问题.
//int map[maxn][maxn];
int father[maxn];
int find(int x) {
if (x == father[x]) {
return x;
}
father[x] = find(father[x]);
return father[x];
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx != fy) {
father[fx] = fy;
}
}
struct Edge{
int begin;
int end;
int weight;
int selected;
}edge[maxm];
bool cmp(Edge a,Edge b){
if(a.weight != b.weight){
return a.weight < b.weight;
}
if(a.begin != b.begin){
return a.begin < b.begin;
}
return a.end < b.end;
}
int n,m;
int kruscal(){
int sum = 0;
int i;
for(i = 1 ; i <= n ; ++i){
father[i] = i;
}
sort(edge+1,edge+1+m,cmp);
int k = 0;
for(i = 1 ; i <= m ; ++i){
if(k == n-1){
break;
}
int x = find(edge[i].begin);
int y = find(edge[i].end);
if(x != y){
merge(x,y);
k++;
edge[i].selected = true;
sum += edge[i].weight;
}
}
return sum;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
int i;
int j;
for(i = 1 ; i <= m ; ++i){
int a,b,c;
scanf("%d%d%d",&edge[i].begin,&edge[i].end,&edge[i].weight);
edge[i].selected = false;
}
int result = kruscal();
int mina = inf;
for(i = 1 ; i <= n ; ++i){
int a;
scanf("%d",&a);
if(mina > a){
mina = a;
}
}
printf("%d\n",result+mina);
}
return 0;
}
2、山东理工大学OJ 2144
题目与分析:
这道题抽象一下还是;“给出点数、边数、各边的情况,求将所有点连接起来的最短路径的边权和”。这道题可以使用Prim算法来做。。
1)使用Prim算法解决
/*
* SDOJ_2144.cpp
*
* Created on: 2014年6月10日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 105;
const int inf = 9999999;
int map[maxn][maxn];
int p[maxn];
int dis[maxn];
int pre[maxn];
int n,m;
int prim(){
int sum = 0;
int i;
for(i = 2 ; i <= n ; ++i){
p[i] = false;
dis[i] = map[1][i];
pre[i] = 1;
}
dis[1] = 0;
p[1] = true;
for(i = 1 ; i <= n-1 ; ++i){//注意,这里不要写成i<=n,因为只需要处理n-1个点(起始点已经处理了)
int min = inf;
int k = 0;
int j;
for(j = 1 ; j <= n ; ++j){
if(!p[j] && dis[j] < min){
min = dis[j];
k = j;
}
}
if(k == 0){
return -10;
}
p[k] = true;
sum += dis[k];
for(j = 1 ; j <= n ; ++j){
if(!p[j] && dis[j] > map[k][j]){
dis[j] = map[k][j];
pre[j] = k;
}
}
}
return sum;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
int i,j;
for(i = 1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
if(i == j){
map[i][j] = 0;
}else{
map[i][j] = inf;
}
}
}
for(i = 1 ; i <= m ; ++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(map[a][b] > c){
map[a][b] = map[b][a] = c;
}
}
int sum = prim();
printf("%d\n",sum);
}
return 0;
}
2)使用kruscal算法来解决
/*
* SDOJ_2144_kruscal.cpp
*
* Created on: 2014年6月11日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 105;
const int maxm = maxn*maxn;
const int inf = 9999999;
int map[maxn][maxn];
int father[maxn];
int find(int x) {
if (x == father[x]) {
return x;
}
father[x] = find(father[x]);
return father[x];
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx != fy) {
father[fx] = fy;
}
}
struct Edge{
int begin;
int end;
int weight;
int selected;
}edge[maxm];
bool cmp(Edge a,Edge b){
if(a.weight != b.weight){
return a.weight < b.weight;
}
if(a.begin < b.begin){
return a.begin < b.begin;
}
return a.end < b.end;
}
int n,m;
int kruscal(){
int sum = 0;
int i;
for(i = 1 ; i <= n ; ++i){
father[i] = i;
}
sort(edge+1,edge+1+m,cmp);
int k = 0;
for(i = 1 ; i <= m ; ++i){
if(k == n-1){
break;
}
int x = find(edge[i].begin);
int y = find(edge[i].end);
if(x != y){
merge(x,y);
k++;
edge[i].selected = true;
sum += edge[i].weight;
}
}
return sum;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
int i;
for(i = 1 ; i <= m ; ++i){
scanf("%d%d%d",&edge[i].begin,&edge[i].end,&edge[i].weight);
edge[i].selected = false;
}
int result = kruscal();
printf("%d\n",result);
}
return 0;
}
3、HDU 1102
题目与分析:
这一道题抽象一下,还是求图的最小边权和的最小生成树问题。这一道题与最裸的最下生成树问题的差别主要由以下几点:
1)之前是以边的形式给出图的信息。这道题是一矩阵的形式给出了整张图的信息
2)引入了“已修建道路的概念”。对于“已修建道路”,我们可以理解为:修建这条道路是费用为0即可。
方法一:Prim算法解决无向图的最小生成树问题
/*
* HDU_1102.cpp
*
* Created on: 2014年6月10日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 105;
const int inf = 9999999;
int map[maxn][maxn];
int p[maxn];
int dis[maxn];
int pre[maxn];
int n, m;
int prim() {
int sum = 0;
int i;
for (i = 2; i <= n; ++i) {
p[i] = false;
dis[i] = map[1][i];
pre[i] = 1;
}
dis[1] = 0;
p[1] = true;
for (i = 1; i <= n - 1; ++i) {
int min = inf;
int k = 0;
int j;
for (j = 1; j <= n; ++j) {
if (!p[j] && dis[j] < min) {
min = dis[j];
k = j;
}
}
if (k == 0) {
return -10;
}
p[k] = true;
sum += dis[k];
for (j = 1; j <= n; ++j) {
if (!p[j] && dis[j] > map[k][j]) {
dis[j] = map[k][j];
pre[j] = k;
}
}
}
return sum;
}
int main() {
while (scanf("%d", &n) != EOF) {
int i, j;
for (i = 1; i <= n; ++i) {
for (j = 1; j <= n; ++j) {
scanf("%d", &map[i][j]);
}
}
scanf("%d", &m);
for (i = 1; i <= m; ++i) {
int a, b;
scanf("%d%d", &a, &b);
map[a][b] = map[b][a] = 0;
}
int sum = prim();
printf("%d\n", sum);
}
return 0;
}
4、HDU 1233
题目与分析:
这道题其实与最裸的最下生成树问题没有太大的区别。都是给出点数、和以边的形式给出图的信息。如果说有区别的话,那便是,之前是随机给定边数,现在是有一个公式给定边数。
1)使用Prim算法解决
/*
* HDU_1233.cpp
*
* Created on: 2014年6月10日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 105;
const int inf = 9999999;
int map[maxn][maxn];
int p[maxn];
int dis[maxn];
int pre[maxn];
int n,m;
int prim(){
int sum = 0;
int i;
for(i = 2 ; i <= n ; ++i){
p[i] = false;
dis[i] = map[1][i];
pre[i] = 1;
}
dis[1] = 0;
p[1] = true;
for(i = 1 ; i <= n-1 ; ++i){
int min = inf;
int k = 0;
int j;
for(j = 1 ; j <= n ; ++j){
if(!p[j] && dis[j] < min){
min = dis[j];
k = j;
}
}
if(k == 0){
return -10;
}
p[k] = true;
sum += dis[k];
for(j = 1 ; j <= n ; ++j){
if(!p[j] && dis[j] > map[k][j]){
dis[j] = map[k][j];
pre[j] = k;
}
}
}
return sum;
}
int main(){
while(scanf("%d",&n),n!=0){
m = n*(n-1)/2;
int i;
int j;
for(i = 1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
if(i == j){
map[i][j] = 0;
}else{
map[i][j] = inf;
}
}
}
for(i = 1 ; i <= m ; ++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(map[a][b] > c){
map[a][b] = map[b][a] = c;
}
}
int sum = prim();
printf("%d\n",sum);
}
return 0;
}
2)使用kruscal算法来解决
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 105;
const int maxm = maxn*maxn;
int map[maxn][maxn];
int father[maxn];
int find(int x) {
if (x == father[x]) {
return x;
}
father[x] = find(father[x]);
return father[x];
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx != fy) {
father[fx] = fy;
}
}
struct Edge{
int begin;
int end;
int weight;
int selected;
}edge[maxm];
bool cmp(Edge a, Edge b){
if(a.weight != b.weight){
return a.weight < b.weight;
}
if(a.begin != b.begin){
return a.begin < b.begin;
}
return a.end < b.end;
}
int n,m;
int kruscal(){
int sum = 0;
int i;
for( i = 1 ; i <= n ; ++i){
father[i] = i;
}
sort(edge+1,edge+1+m,cmp);
int k = 0;
for(i = 1 ; i <= m ; ++i){
if(k == n-1){
break;
}
int x = find(edge[i].begin);
int y = find(edge[i].end);
if(x != y){
merge(x,y);
k++;
edge[i].selected = true;
sum += edge[i].weight;
}
}
return sum;
}
int main(){
while(scanf("%d",&n),n){
m = n*(n-1)/2;
int i;
for(i = 1 ; i <= m ; ++i){
scanf("%d%d%d",&edge[i].begin,&edge[i].end,&edge[i].weight);
edge[i].selected = false;
}
int result = kruscal();
printf("%d\n",result);
}
return 0;
}
5、POJ 1258
题目与分析:
这道题的数据的给出的形式其实上面已经分析过了。“给出点数、以邻接矩阵的形式给出图的信息。求将所有点连接起来的最小边权值之和”。
2)使用kruscal算法来解决
/*
* POJ_1258.cpp
*
* Created on: 2014年6月10日
* Author: Administrator
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 105;
const int maxm = maxn*maxn;
/**
* 并查集的基本结构
*/
int map[maxn][maxn];
int father[maxn];
int find(int x) {
if (x == father[x]) {
return x;
}
father[x] = find(father[x]);
return father[x];
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx != fy) {
father[fx] = fy;
}
}
/**
* 链式前向星
*/
struct Edge{
int begin;
int end;
int weight;
int selected;
}edge[maxm];
bool cmp(Edge a,Edge b){
if(a.weight != b.weight){
return a.weight < b.weight;
}
if(a.begin != b.begin){
return a.begin < b.begin;
}
return a.end < b.end;
}
int n,m;
/**
* kruscal算法思想:
* 1)对边排序(贪心思想的体现)
* 2)遍历每一条边.加入并将该边所连接的点合并到同一颗最小生成树中
*/
int kruscal(){
int sum = 0;
int k = 0;
sort(edge+1,edge+1+m,cmp);//对边排序...这是kruscal中使用了贪心思想的体现
int i;
for(i = 1 ; i <= n ; ++i){
father[i] = i;
}
for(i = 1 ; i <= m ; ++i){//i在这里是一个计数器..
if(k == n-1){//如果合并了n-1条边,说明最下生成树已经生成,可以返回了
break;
}
/**
* 判断两个点是否已经在最小生成树里面了,如果不在,则将她们合并到最小哦啊生成树中
*/
int x = find(edge[i].begin);
int y = find(edge[i].end);
if(x != y){
merge(x,y);
k++;//已经合并的边数+1
edge[i].selected = true;//将改变标记为选中状态
sum += edge[i].weight;//累加最小生成树的边权值
}
}
return sum;
}
int main(){
while(scanf("%d",&n)!=EOF){
m = 1;
int i;
int j;
for(i = 1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
scanf("%d",&map[i][j]);
}
}
for(i = 1 ; i <= n ; ++i){
for(j = 1 ; j <= n ; ++j){
edge[m].begin = i;
edge[m].end = j;
edge[m].weight = map[i][j];
edge[m++].selected = false;
}
}
m -= 1;//这里的处理很重要,如果不加上这个很可能WA。
int result = kruscal();
printf("%d\n",result);
}
return 0;
}