Bootstrap

算法基础——图论(一)

算法基础——图论(一)

目录:

  1. 基础知识
    1. 邻接矩阵
    2. 邻接表
    3. 并查集
    4. 最小生成树
  2. 应用实例
    1. 畅通工程【浙江大学】
    2. 连通图【吉林大学】
    3. Is It A Tree?【北京大学】
    4. 找出直系亲属【浙江大学】
    5. 统计图的连通分支数【上海交通大学】
    6. Head of a Gang【浙江大学】
    7. 还是畅通工程【浙江大学】
    8. 继续畅通工程【浙江大学】
    9. Freckles【北京大学】
    10. Jungle Roads【北京大学】

一、基础知识

1、邻接矩阵(adjacency matrix)

  • 定义:用一个二维矩阵来表示图的信息,二维矩阵中的每个单元表述一对顶点之间的邻接关系。
    • 带权图:matrix[u][v]的值来表示u和v之间边的权值,对不存在的边一般取值为∞。
    • 无权图:matrix[u][v]为1或0来表示u和v之间是否有边。
  • 缺点:遍历与某顶点相邻的所有顶点,则需要依次遍历二维数组中某行的所有元素,通过其值判断是否相邻。
  • 适用场景:稠密图,且需要频繁判断某特定顶点对是否相邻时。

2、邻接表(adjacency list)

  • 定义:为图中的每个顶点建立一个单链表,单链表中保存与该顶点相邻的所有顶点及其相关信息。
  • 缺点:当邻接表需要判断顶点u和v之间是否存在关系时,需要遍历u和v的所有邻接顶点,才能判定它们之间是否存在关系。
  • 适用场景:存在大量遍历邻接顶点操作而较少判断两个特定顶点的关系时。

3、并查集(Union Find)

  • 定义:用于处理一些不交集的合并和查询问题的数据结构。
  • 判断任意两个元素是否属于同一个集合:
    • 将集合在逻辑上表示为树结构,每个结点都指向其父结点,而树中的元素并无顺序之分,只要在同一棵树上,便说明在同一个集合中。
  • 查找与合并:
    • 查找:不断向上查找,直到找到它的根结点,之后根据根结点是否相同来判断两个元素是否属于同一集合。
    • 合并:将两个子集合并成同一个集合。将一棵树作为另一棵树的子树,从而使得两棵树编程一棵更大的树。
  • 优化方案:
    • 路径压缩:在查找某个特定结点的根结点的同时,将其与根结点之间的所有结点都直接指向根结点。
    • 合并时,将高度较低的树作为高度较高的树子树。

4、最小生成树(Minimum Spanning Tree,MST)

  • 定义:
    • 连通图:在一个无向图G中,若顶点u到顶点v有路径相连,则称u和v是连通的。若图中任意两点都是连通的,则图被称为连通图。
    • 连通分量:无向图G中的一个极大连通子图称为G的一个连通分量。
      • 连通图只有一个连通分量,即其自身;非连通的无向图有多个连通分量。
    • 生成树:在一个无向连通图G(V, E)中,如果存在一个连通子图,它包含所有顶点和部分边,且这个子图不存在回路,那么就称这个子图为原图的一棵生成树。
    • 最小生成树:在带权无向连通图中,所有生成树中边权的和最小的那一棵(或几棵)被称为该无向图的最小生成树。
  • 常见算法:
    • Kruskal算法 + 并查集
      • 步骤:
        • 初试时所有顶点属于孤立的集合。
        • 按照边权递增顺序遍历所有边,若遍历到的边的两个顶点仍分属不同的集合(该边即为连通这两个集合的边中权值最小的那条),则确定该边为最小生成树上的一条边,并将该边两个顶点分属的集合合并。
        • 遍历完所有边后,若原图连通,则被选取的边和所有顶点构成最小生成树;若原图不连通,最小生成树不存在。
    • Prim算法

二、应用实例

1、题目描述:某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?【浙江大学】

  • 输入格式:测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
    注意:两个城市之间可以有多条道路相通,也就是说
    3 3
    1 2
    1 2
    2 1
    这种输入也是合法的
    当N为0时,输入结束,该用例不被处理。
  • 输出格式:对每个测试用例,在1行里输出最少还需要建设的道路数目。
  • 样例输入:
    • 4 2
    • 1 3
    • 4 3
    • 3 3
    • 1 2
    • 1 3
    • 2 3
    • 5 2
    • 1 2
    • 3 5
    • 999 0
    • 0
  • 样例输出:
    • 1
    • 0
    • 2
    • 998

示例代码:

#include <iostream>

using namespace std;

const int MAX_N = 1000;

int father[MAX_N];
int height[MAX_N];

void Initial(int n){
	for(int i = 0; i <= n; i++){
		father[i] = i;
		height[i] = 0;
	}
}

int Find(int x){
	while(x != father[x]){
		x = father[x];
	}
	return father[x];
}

void Union(int x, int y){
	x = Find(x);
	y = Find(y);
	if(x != y){
		if(height[x] < height[y]){
			father[x] = y;
		}else if(height[x] > height[y]){
			father[y] = x;
		}else{
			father[y] = x;
			height[x]++;
		}
	}
}

int main(){
	int n, m;
	while(cin >> n && n != 0){
		cin >> m;
		Initial(n);
		int x, y;
		for(int i = 0; i < m; i++){
			cin >> x >> y;
			Union(x, y);
		}
		int count = -1;
		for(int i = 1; i <= n; i++){
			if(Find(i) == i){
				count++;
			}
		}
		cout << count << endl;
	}
	return 0;
}

2、题目描述:给定一个无向图和其中的所有边,判断这个图是否所有顶点都是连通的。【吉林大学】

  • 输入格式:每组数据的第一行是两个整数 n 和 m(0<=n<=1000)。n 表示图的顶点数目,m 表示图中边的数目。随后有 m 行数据,每行有两个值 x 和 y(0<x, y <=n),表示顶点 x 和 y 相连,顶点的编号从 1 开始计算。输入不保证这些边是否重复。
  • 输出格式:对于每组输入数据,如果所有顶点都是连通的,输出"YES",否则输出"NO"。
  • 样例输入:
    • 4 3
    • 1 2
    • 2 3
    • 3 2
    • 3 2
    • 1 2
    • 2 3
    • 0 0
  • 样例输出:
    • NO
    • YES

示例代码:

#include <iostream>

using namespace std;

const int MAX_N = 1001;

int height[MAX_N];
int father[MAX_N];

void Initial(int n){
	for(int i = 0; i <= n; i++){
		height[i] = 0;
		father[i] = i;
	}
}

int Find(int x){
	while(x != father[x]){
		x = father[x];
	}
	return father[x];
}

void Union(int x, int y){
	x = Find(x);
	y = Find(y);
	if(x != y){
		if(height[x] < height[y]){
			father[x] = y;
		}else if(height[x] > height[y]){
			father[y] = x;
		}else{
			father[y] = x;
			height[x]++;
		}
	}
}

int main(){
	int n, m;
	while(cin >> n >> m && n != 0){
		Initial(n);
		int x, y;
		for(int i = 0; i < m; i++){
			cin >> x >> y;
			Union(x, y);
		}
		int result = -1;
		bool flag = true;
		for(int i = 1; i <= n; i++){
			if(i == father[i]){
				result++;
				if(result > 0){
					flag = false;
					break;
				}
			}
		}
		if(flag){
			cout << "YES" << endl;
		}else{
			cout << "NO" << endl;
		}
	}
	return 0;
}

3、题目描述:A tree is a well-known data structure that is either empty (null, void, nothing) or is a set of on

;