算法基础——图论(二)

算法基础——图论(二)

目录:

  1. 基础知识
    1. 最短路径
    2. 拓扑排序
    3. 关键路径
  2. 应用实例
    1. 畅通工程续【2008浙大研究生复试热身赛(2)——全真模拟 hdu1874】
    2. 最短路径问题【浙江大学】
    3. 最短路径【上海交通大学】
    4. I Wanna Go Home【北京大学】
    5. Legal or Not【HDOJ Monthly Contest – 2010.03.06 hdu 3342】
    6. 确定比赛名次【杭电ACM集训队训练赛(VII) hdu 1285】
    7. Instruction Arrangement【2011 Alibaba-Cup Campus Contest hdu 4109】

一、基础知识

1、最短路径(Shortest Path)

  • 定义:求图G(V, E)中某两个特定顶点间最短的路径长度。
  • 常见算法:
    • Dijkstra算法(贪心策略 + 优先队列)
      • 集合S:已确定的顶点集合,初始只含源点s。
      • 集合T:尚未确定的顶点集合。
      • 算法反复从集合T中选择当前到源点s最近的顶点u,将u加入集合S,然后对所有从u发出的边进行松弛操作。
      • 维护一个优先队列,将集合T中的顶点到源点s的距离,作为这些点的优先级,距离越低,优先级越高。那么只要从优先队列中取出队首元素,即可获得当前离源点s最近的顶点。

2、拓扑排序(Topological Sort)

  • 定义:设有一个含有多条有向边(u, v)的有向无环图(Directed Acyclic Graph,DAG),将图中所有顶点排成一个线性序列,使得在该序列中顶点u都排在顶点v之前。满足该要求的顶点序列,称为满足拓扑序列。
  • 方法:
    • 从DAG图中选择入度为0的顶点,并输出。
    • 从图中删除该入度为0的顶点及所有以它为起点的边。
    • 重复上述过程直到当前图为空,或者图不存在入度为0的顶点。前者输出的序列就是拓扑序列;后者说明图中有环,不存在拓扑序列。

3、关键路径(Critical Path)

  • 定义:
    • AOE网(Activity On Edge Network):
      • 顶点:事件
      • 有向边:活动
      • 边上权值:该活动持续的时间
      • 源点:工程的开始顶点
      • 汇点:工程的结束顶点
    • 关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径。
    • 关键活动:关键路径上的活动。
  • 方法:
    • 一个活动的全部先序活动的最晚完成时间便是该活动的最早开始时间。
    • 一个活动的全部后序活动的最早开始时间减去该活动需要花费的时间,便是该活动的最晚开始时间。
    • 根据拓扑序列逐一求出每个活动的最早开始时间,再根据拓扑序列的逆序列求出每个活动的最晚开始时间。所有活动中最早开始时间和最晚开始时间相同的活动称为关键活动,而所有活动中最早开始时间的最大值便是关键路径的长度。

二、应用实例

1、题目描述:某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。【2008浙大研究生复试热身赛(2)——全真模拟 hdu1874】

  • 输入格式:本题目包含多组数据,请处理到文件结束。每组数据第一行包含两个正整数N和M(0
  • 输出格式:对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.
  • 样例输入:
    • 3 3
    • 0 1 1
    • 0 2 3
    • 1 2 1
    • 0 2
    • 3 1
    • 0 1 1
    • 1 2
  • 样例输出:
    • 2
    • -1

示例代码:

#include 
#include 
#include 
#include 

using namespace std;

const int MAX_INT = 0x7fffffff;
const int MAX_N = 200;

struct Edge{
	int to;
	int length;
	Edge(int t, int l):to(t), length(l){};
};

struct Node{
	int number;
	int distance;
	Node(int n, int d):number(n), distance(d){};
	bool operator<(const Node &n)const;
};

int dist[MAX_N];//源点到各点的距离
vector graph[MAX_N];

bool Node::operator<(const Node &n)const{
	return distance < n.distance;
}

void Dijkstra(int s){
	priority_queue myQueue;
	dist[s] = 0;
	myQueue.push(Node(s, dist[s]));
	while(!myQueue.empty()){
		int u = myQueue.top().number;
		myQueue.pop();
		for(int i = 0; i < graph[u].size(); i++){
			int v = graph[u][i].to;
			int tmp = dist[u] + graph[u][i].length;
			if(dist[v] > tmp){
				dist[v] = tmp;
				myQueue.push(Node(v, dist[v]));
			}
		}
	}
}


int main(){
	int n, m;
	while(cin >> n >> m){
		memset(graph, 0, sizeof(graph));
		fill(dist, dist + n, MAX_INT);
		int from, to, length;
		for(int i = 0; i < m; i++){
			cin >> from >> to >> length;
			graph[from].push_back(Edge(to, length));
			graph[to].push_back(Edge(from, length));
		}
		int source, finish;
		cin >> source >> finish;
		Dijkstra(source);
		if(dist[finish] == MAX_INT){
			cout << -1 << endl;
		}else{
			cout << dist[finish] << endl;
		}
	}
	return 0;
}

2、题目描述:给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。【浙江大学】

  • 输入格式:输入n,m,点的编号是1~n,然后是m行,每行4个数 a,b,d,p,表示a和b之间有一条边,且其长度为d,花费为p。最后一行是两个数 s,t;起点s,终点t。n和m为0时输入结束。(1
  • 输出格式:输出一行有两个数, 最短距离及其花费。
  • 样例输入:
    • 3 2
    • 1 2 5 6
    • 2 3 4 5
    • 1 3
    • 0 0
  • 样例输出:
    • 9 11

示例代码:

#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 1001;
const int MAX_INT = 0x7fffffff;

struct Edge{
	int to;
	int length;
	int cost;
	Edge(int t, int l, int c):to(t), length(l), cost(c){};
};

struct Node{
	int number;
	int distance;
	Node(int n, int d):number(n), distance(d){};
	bool operator<(const Node &n)const;
};

vector graph[MAX_N];
int dist[MAX_N];
int cost[MAX_N];

bool Node::operator<(const Node &n)const{
	return distance < n.distance;
}

void Dijkstra(int s){
	priority_queue myQueue;
	dist[s] = 0;
	cost[s] = 0;
	myQueue.push(Node(s, 0));
	while(!myQueue.empty()){
		int u = myQueue.top().number;
		myQueue.pop();
		for(int i = 0; i < graph[u].size(); i++){
			int v = graph[u][i].to;
			int tmpDist = graph[u][i].length + dist[u];
			int tmpCost = graph[u][i].cost + cost[u];
			if(dist[v] == tmpDist && tmpCost < cost[v] 
				|| dist[v] > tmpDist){
				dist[v] = tmpDist;
				cost[v] = tmpCost;
				myQueue.push(Node(v, dist[v]));
			}
		}
	}
}

int main(){
	int n, m;
	while(cin >> n >> m && n != 0 && m != 0){
		memset(graph, 0, sizeof(graph));
		fill(dist, dist + n + 1, MAX_INT);
		fill(cost, cost + n + 1, MAX_INT);
		int from, to, length, c;
		for(int i = 0; i < m; i++){
			cin >> from >> to >> length >> c;
			graph[from].push_back(Edge(to, length, c));
			graph[to].push_back(Edge(from, length, c));
		}
		int startPoint, endPoint;
		cin >> startPoint >> endPoint;
		Dijkstra(startPoint);
		if(cost[endPoint] == MAX_INT || dist[endPoint] == MAX_INT){
			cout << "不可达" << endl;
		}
		cout << dist[endPoint] << " " << cost[endPoint] << endl;
	}
	return 0;
}

3、题目描述:N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离【上海交通大学】

  • 输入格式:第一行两个正整数N(2<=N<=100)M(M<=500),表示有N个城市,M条道路接下来M行两个整数,表示相连的两个城市的编号
  • 输出格式:N-1行,表示0号城市到其他城市的最短路,如果无法到达,输出-1,数值太大的以MOD 100000 的结果输出。
  • 样例输入:
    • 4 4
    • 1 2
    • 2 3
    • 1 3
    • 0 1
  • 样例输出:
    • 8
    • 9
    • 11

示例代码:

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 100;
const int MAX_INT = 0x7fffffff;
const int MOD = 100000;

struct Edge{
	int to;
	int length;
	Edge(int t, int l):to(t), length(l){};
};

struct Node{
	int number;
	int distance;
	Node(int n, int d):number(n), distance(d){};
	bool operator<(const Node &n)const{
		return distance < n.distance;
	}
};

int dist[MAX_N];
int height[MAX_N];
int father[MAX_N];
vector graph[MAX_N];

void Dijkstra(int s){
	priority_queue myQueue;
	dist[s] = 0;
	myQueue.push(Node(s, 0));
	while(!myQueue.empty()){
		int u = myQueue.top().number;
		myQueue.pop();
		for(int i = 0; i < graph[u].size(); i++){
			int v = graph[u][i].to;
			int len = graph[u][i].length + dist[u]; 
			if(len < dist[v]){
				dist[v] = len;
				myQueue.push(Node(v, dist[v]));
			}
		}
	}
}

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

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

int main(){
	int n, m;
	while(cin >> n >> m){
		memset(graph, 0, sizeof(graph));
		for(int i = 0; i < MAX_N; i++){
			father[i] = i;
			height[i] = 0;
			dist[i] = MAX_INT;
		}
		int from, to, mul;
		for(int i = 0; i < m; i++){
			cin >> from >> to;
			if(i == 0){
				mul = 1;
			}else{
				mul = (mul * 2) % MOD;
			}
			if(Union(from, to)){
				graph[from].push_back(Edge(to, mul));
				graph[to].push_back(Edge(from, mul));
			}
		}
		Dijkstra(0);
		for(int i = 1; i <= n - 1; i++){
			if(dist[i] == MAX_INT){
				cout << -1 << endl;
			}else{
				cout << dist[i] % MOD << endl;
			}
		}
	}
	return 0;
}

4、题目描述:The country is facing a terrible civil war----cities in the country are divided into two parts supporting different leaders. As a merchant, Mr. M does not pay attention to politics but he actually knows the severe situation, and your task is to help him reach home as soon as possible.     "For the sake of safety,", said Mr.M, "your route should contain at most 1 road which connects two cities of different camp."     Would you please tell Mr. M at least how long will it take to reach his sweet home?【北京大学】

  • 输入格式:The input contains multiple test cases.
    The first line of each case is an integer N (2<=N<=600), representing the number of cities in the country.
    The second line contains one integer M (0<=M<=10000), which is the number of roads.
    The following M lines are the information of the roads. Each line contains three integers A, B and T, which means the road between city A and city B will cost time T. T is in the range of [1,500].
    Next part contains N integers, which are either 1 or 2. The i-th integer shows the supporting leader of city i.
    To simplify the problem, we assume that Mr. M starts from city 1 and his target is city 2. City 1 always supports leader 1 while city 2 is at the same side of leader 2.
    Note that all roads are bidirectional and there is at most 1 road between two cities.
    Input is ended with a case of N=0.
  • 输出格式:For each test case, output one integer representing the minimum time to reach home.
    If it is impossible to reach home according to Mr. M's demands, output -1 instead.
  • 样例输入:
    • 2
    • 1
    • 1 2 100
    • 1 2
    • 3
    • 3
    • 1 2 100
    • 1 3 40
    • 2 3 50
    • 1 2 1
    • 5
    • 5
    • 3 1 200
    • 5 3 150
    • 2 5 160
    • 4 3 170
    • 4 2 170
    • 1 2 2 2 1
    • 0
  • 样例输出:
    • 100
    • 90
    • 540

示例代码:

#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 601;
const int MAX_INT = 0x7fffffff;

struct Edge{
	int to;
	int length;
	Edge(int t, int l):to(t), length(l){};
};

struct Node{
	int number;
	int distance;
	bool operator<(const Node &n)const{
		return distance < n.distance;
	};
	Node(int n, int d):number(n), distance(d){};
};

int dist1[MAX_N]; //城市1到各点的距离
int dist2[MAX_N]; //城市2到各点的距离
int camp[MAX_N];
vector graph[MAX_N];

void Dijkstra(int s, int c){
	priority_queue myQueue;
	int *dist = NULL;
	if(c == 1){
		dist = dist1;
	}else{
		dist = dist2;
	}
	dist[s] = 0;
	myQueue.push(Node(s, 0));
	while(!myQueue.empty()){
		int u = myQueue.top().number;
		myQueue.pop();
		for(int i = 0; i < graph[u].size(); i++){
			int v = graph[u][i].to;
			if(c == camp[v]){
				int len = graph[u][i].length + dist[u];
				if(len < dist[v]){
					dist[v] = len;
					myQueue.push(Node(v, dist[v]));
				}
			}
		}
	}
}

void Init(int n){
	for(int i = 0; i <= n; i++){
		dist1[i] = MAX_INT;
		dist2[i] = MAX_INT;
	}
	memset(graph, 0, sizeof(graph));
	memset(camp, 0, sizeof(camp));
}

int main(){
	int n, road;
	while(cin >> n && n != 0){
		cin >> road;
		Init(n);
		int from, to, length;
		for(int i = 1; i <= road; i++){
			cin >> from >> to >> length;
			graph[from].push_back(Edge(to, length));
			graph[to].push_back(Edge(from, length));
		}
		for(int i = 1; i <= n; i++){
			cin >> camp[i];
		}
		Dijkstra(1, camp[1]);
		Dijkstra(2, camp[2]);
		int min = MAX_INT;
		for(int i = 1; i <= n; i++){
			if(dist1[i] == MAX_INT){ //找到第一个该阵营的点
				continue;
			}
			for(int j = 0; j < graph[i].size(); j++){
				if(dist2[graph[i][j].to] == MAX_INT){
					continue;
				}
				if(dist2[graph[i][j].to] + dist1[i] + graph[i][j].length < min){
					min = dist2[graph[i][j].to] + dist1[i] + graph[i][j].length;
				}
			}
		}
		if(min == MAX_INT){
			cout << -1 << endl;
		}else{
			cout << min << endl;
		}
	}
	return 0;
}

5、题目描述:ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmonious that just like a big family. Every day,many "holy cows" like HH, hh, AC, ZT, lcc, BF, Qinz and so on chat on-line to exchange their ideas. When someone has questions, many warm-hearted cows like Lost will come to help. Then the one being helped will call Lost "master", and Lost will have a nice "prentice". By and by, there are many pairs of "master and prentice". But then problem occurs: there are too many masters and too many prentices, how can we know whether it is legal or not?
We all know a master can have many prentices and a prentice may have a lot of masters too, it's legal. Nevertheless,some cows are not so honest, they hold illegal relationship. Take HH and 3xian for instant, HH is 3xian's master and, at the same time, 3xian is HH's master,which is quite illegal! To avoid this,please help us to judge whether their relationship is legal or not.
Please note that the "master and prentice" relation is transitive. It means that if A is B's master ans B is C's master, then A is C's master.【HDOJ Monthly Contest – 2010.03.06 hdu 3342】

  • 输入格式:The input consists of several test cases. For each case, the first line contains two integers, N (members to be tested) and M (relationships to be tested)(2 <= N, M <= 100). Then M lines follow, each contains a pair of (x, y) which means x is y's master and y is x's prentice. The input is terminated by N = 0.
    TO MAKE IT SIMPLE, we give every one a number (0, 1, 2,..., N-1). We use their numbers instead of their names.
  • 输出格式:For each test case, print in one line the judgement of the messy relationship.If it is legal, output "YES", otherwise "NO".
  • 样例输入:
    • 3 2
    • 0 1
    • 1 2
    • 2 2
    • 0 1
    • 1 0
    • 0 0
  • 样例输出:
    • YES
    • NO

示例代码:

#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 100;
vector graph[MAX_N];
queue myQueue;
int indegree[MAX_N];

bool TopoSort(int n){
	int count = 0;
	for(int i = 0; i < n; i++){
		if(indegree[i] == 0){
			myQueue.push(i);
		}
	}
	while(!myQueue.empty()){
		int num = myQueue.front();
		myQueue.pop();
		count++;
		for(int i = 0; i < graph[num].size(); i++){
			indegree[graph[num][i]]--;
			if(indegree[graph[num][i]] == 0){
				myQueue.push(graph[num][i]);
			}
		}
	}
	return n == count;
}

int main(){
	int memberSize, relationSize;
	while(cin >> memberSize >> relationSize && memberSize != 0){
		int from, to;
		memset(graph, 0, sizeof(graph));
		memset(indegree, 0, sizeof(indegree));
		for(int i = 0; i < relationSize; i++){
			cin >> from >> to;
			graph[from].push_back(to);
			indegree[to]++;
		}
		if(TopoSort(memberSize)){
			cout << "YES" << endl;
		}else{
			cout << "NO" << endl;
		}
	}
	return 0;
}

6、题目描述:有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。【杭电ACM集训队训练赛(VII) hdu 1285】

  • 输入格式:输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。
  • 输出格式:给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
  • 样例输入:
    • 4 3
    • 1 2
    • 2 3
    • 4 3
  • 样例输出:
    • 1 2 4 3

示例代码:

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 501;

vector myVector[MAX_N];
priority_queue, greater > myQueue;//小值优先
int indegree[MAX_N];
vector result;

bool TopoSort(int n){
	for(int i = 1; i <= n; i++){
		if(indegree[i] == 0){
			myQueue.push(i);
		}
	}
	while(!myQueue.empty()){
		int num = myQueue.top();
		myQueue.pop();
		result.push_back(num);
		for(int i = 0; i < myVector[num].size(); i++){
			indegree[myVector[num][i]]--;
			if(indegree[myVector[num][i]] == 0){
				myQueue.push(myVector[num][i]);
			}
		}
	}
	return result.size() == n;
}

int main(){
	int n, m;
	while(cin >> n >> m){
		memset(indegree, 0, sizeof(indegree));
		memset(myVector, 0, sizeof(myVector));
		result.clear();
		int from, to;
		for(int i = 0; i < m; i++){
			cin >> from >> to;
			myVector[from].push_back(to);
			indegree[to]++;
		}
		if(TopoSort(n)){
			for(int i = 0; i < result.size(); i++){
				if(i != 0){
					cout << " ";
				}
				cout << result[i];
			}
			cout << endl;
		}
	}
	return 0;
}

7、题目描述:Ali has taken the Computer Organization and Architecture course this term. He learned that there may be dependence between instructions, like WAR (write after read), WAW, RAW.
If the distance between two instructions is less than the Safe Distance, it will result in hazard, which may cause wrong result. So we need to design special circuit to eliminate hazard. However the most simple way to solve this problem is to add bubbles(空指令) (useless operation), which means wasting time to ensure that the distance between two instructions is not smaller than the Safe Distance.
The definition of the distance between two instructions is the difference between their beginning times.
Now we have many instructions, and we know the dependent relations and Safe Distances between instructions. We also have a very strong CPU with infinite number of cores, so you can run as many instructions as you want simultaneity, and the CPU is so fast that it just cost 1ns to finish any instruction.
Your job is to rearrange the instructions so that the CPU can finish all the instructions using minimum time.【2011 Alibaba-Cup Campus Contest hdu 4109】

  • 输入格式:The input consists several testcases.
    The first line has two integers N, M (N <= 1000, M <= 10000), means that there are N instructions and M dependent relations.
    The following M lines, each contains three integers X, Y , Z, means the Safe Distance between X and Y is Z, and Y should run after X. The instructions are numbered from 0 to N - 1.
  • 输出格式:Print one integer, the minimum time the CPU needs to run.
  • 样例输入:
    • 5 2
    • 1 2 1
    • 3 4 1
  • 样例输出:
    • 2
  • 附注:In the 1st ns, instruction 0, 1 and 3 are executed; In the 2nd ns, instruction 2 and 4 are executed. So the answer should be 2.

示例代码:

#include 
#include 
#include 
#include 

using namespace std;

const int MAX_INT = 0x7fffffff;
const int MAX_N = 1000;

struct Edge{
	int to;
	int length;
	Edge(int t, int l):to(t), length(l){};
};

vector topoList;
vector graph[MAX_N];
int earliest[MAX_N]; //最早开始时间
int latest[MAX_N];   //最晚开始时间
int indegree[MAX_N];

void Init(){
	topoList.clear();
	memset(graph, 0, sizeof(graph));
	for(int i = 0; i < MAX_N; i++){
		earliest[i] = 1;
		latest[i] = MAX_INT;
	}
	memset(indegree, 0, sizeof(indegree));
}

void TopoSort(int n){
	queue myQueue;
	for(int i = 0; i < n; i++){
		if(indegree[i] == 0){
			myQueue.push(i);
		}
	}
	while(!myQueue.empty()){
		int u = myQueue.front();
		myQueue.pop();
		topoList.push_back(u);
		for(int i = 0; i < graph[u].size(); i++){
			int v = graph[u][i].to;
			int len = graph[u][i].length;
			earliest[v] = max(earliest[u] + len, earliest[v]);
			indegree[v]--;
			if(indegree[v] == 0){
				myQueue.push(v);
			}
		}
	}
}

void CriticalPath(int n){
	for(int i = topoList.size() - 1; i >= 0; i--){
		int u = topoList[i];
		if(graph[u].size() == 0){
			latest[u] = earliest[u];
			continue;
		}
		for(int j = 0; j < graph[u].size(); j++){
			int v = graph[u][j].to;
			int len = graph[u][j].length;
			latest[u] = min(latest[v] - len, latest[u]);
		}
	}
}

int main(){
	int n, m;
	while(cin >> n >> m){
		Init();
		int from, to, distance;
		for(int i = 0; i < m; i++){
			cin >> from >> to >> distance;
			graph[from].push_back(Edge(to, distance));
			indegree[to]++;
		}
		TopoSort(n);
		CriticalPath(n);
		int answer = 0;
		for(int i = 0; i < n; i++){
			answer = max(answer, earliest[i]);
		}
		cout << answer << endl;
	}
	return 0;
}

参考文献:

[1]杨泽邦、赵霖. 计算机考研——机试指南(第2版). [M]北京:电子工业出版社,2019.11;

你可能感兴趣的:(#,常用算法)