课程表 II
https://leetcode-cn.com/problems/course-schedule-ii/
思路:拓扑排序。将每一门课看成图的一个顶点,如果想要学习课程 1之前必须完成课程 0,那么可以构造从 0 到 1 的一条有向边。在拓扑排序中得到的序列中,0 一定出现在 1 的前面。因此构造所有课程的邻接表,求出拓扑序列,则可以得到
一种符合要求的课程学习顺序(拓扑序列不是唯一的)。
拓扑排序步骤可以参考王卓老师的PPT
具体实现:广度优先搜索。构造邻接表和入度数组,将入度为0的节点(即没有前驱的节点)放入队列中。每次从队头取出入度为0的节点 i 放入结果数组res当中,让 i 出队并且将 i 的相邻节点 j(i 指向 j)的入度数减去1,如果减去1之后入度数变为0,则把该节点 j 加入队列中。如此循环,直到队列为空为止。最后检查结果数组的顶点数是否和课程总数相等,如果不相等,说明图存在环,即无法实现修完所有课程。
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses,vector<int>());//构建邻接表
vector<int> degree(numCourses,0);//创建一个入度数组
for(int i = 0;i<prerequisites.size();i++){
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
degree[prerequisites[i][0]]++;
}
queue<int> q;
for(int i = 0;i<numCourses;i++){
if(degree[i]==0){
q.push(i);
}
}
vector<int> res;
while(!q.empty()){
int temp = q.front();
q.pop();
res.push_back(temp);
for(int i = 0;i<graph[temp].size();i++){
if(--degree[graph[temp][i]]==0){
q.push(graph[temp][i]);
}
}
}
return (res.size()==numCourses)?res:vector<int>();
}
};
Instrction Arrangement
http://acm.hdu.edu.cn/showproblem.php?pid=4109
思路:关键路径问题用拓扑排序+dp求解。用顶点表示事件,弧表示活动,所以只有当一个事件(级图的顶点)的入度为0 才能开始这个事件,如果一个顶点(设为A)的入度不为1,即n个点(假如多个点用X数组表示)分别用n条弧指向该顶点它,那么最早开始执行时间是 X[i] + W[i](W[i]为A到X活动的消耗时间) 里边的最大值 即当全部指向顶点A的事件和活动(点和弧) 做完了才能开始。因此利用dp数组存储节点i的最迟开始时间,对每个指向它的节点都判断,它的最迟开始时间是什么时候,最后比较所有节点的最迟开始时间,最大的就是完成所有活动所需的最短时间。
#include <iostream>
#include <climits>
#include <cstring>
#include <queue>
using namespace std;
struct node{
int end;
int w;
};
vector<node> graph[1005];
int degree[1005];
int dp[1005];//表示以i为终点的最长路径
int n,m;
int main(){
while(cin>>n>>m){
memset(degree,0,sizeof(degree));
memset(dp,0,sizeof(dp));
for(int i = 0;i<n;i++){
graph[i].clear();
}
int u,v,w;
for(int i = 1;i<=m;i++){
cin>>u>>v>>w;
graph[u].push_back(node{v,w});
degree[v]++;
}
//拓扑排序
queue<int> q;
for(int i = 0;i<n;i++){
if(degree[i]==0){
q.push(i);
dp[i]++;
}
}
while(!q.empty()){
int temp = q.front();
q.pop();
for(int i = 0;i<graph[temp].size();i++){
int end = graph[temp][i].end;
int w = graph[temp][i].w;
dp[end] = max(dp[end],dp[temp]+w);
if(--degree[end]==0){
q.push(end);
}
}
}
int res = 0;
for(int i = 0;i<n;i++){
res = max(res,dp[i]);
}
cout<<res<<endl;
}
}
Borg Maze
http://poj.org/problem?id=3026
思路:BFS+最小生成树。把字母(A或S)看成图的节点,要求代价最小即为图的最小生成树问题。因为给出的迷宫还含有‘#’,所以可以用 BFS(深度优先搜索)求出任意两个节点之间的距离,构造图的邻接矩阵,进而利用Prime算法求解最小代价。
#include <iostream>
#include <climits>
#include <cstring>
#include <queue>
using namespace std;
int t,x,y;
int count;//字母的数量
char graph[55][55];
int num[55][55];//num表示字母的编号
int dis[55][55];//邻接矩阵
int len[55][55];
int vis[55][55];//邻接矩阵的访问数组
struct Point{
int x,y;
};
int dx[4] = {0,0,-1,1};
int dy[4] = {1,-1,0,0};
void BFS(int row,int col){
queue<Point> q;
Point node;
node.x = row;
node.y = col;
q.push(node);
for(int i = 0;i<=y;i++){
for(int j = 0;j<=x;j++){
vis[i][j] = 0;
}
}
vis[row][col] = 1;//标记已经被访问
len[row][col] = 0;
while(!q.empty()){
Point temp = q.front();
q.pop();
int x1 = temp.x;
int y1 = temp.y;
if(num[x1][y1]!=-1){
dis[num[row][col]][num[x1][y1]] = len[x1][y1];//更新邻接矩阵
}
//四个方向寻找
for(int k = 0;k<4;k++){
int x2 = x1+dx[k];
int y2 = y1+dy[k];
if(x2>0 && y2>0 && x2<=y && y2<=x && !vis[x2][y2] &&graph[x2][y2-1]!='#'){
vis[x2][y2] = true;//找到则标记已经访问过了
len[x2][y2] = len[x1][y1]+1;//距离加 1
Point New;
New.x = x2;
New.y = y2;
q.push(New);//把新节点放到队列里面
}
}
}
}
int Prime(int index){
int d[55];
bool v[55];
memset(v,false,sizeof(v));//字母的访问数组
for(int i = 1;i<=count;i++){
d[i] = dis[index][i];
}
v[index] = true;
d[index] = 0;
int cost = 0;
//Prim算法
for(int i = 1;i<=count-1;i++){
int Min = INT_MAX;
int val;
for(int j = 1;j<=count;j++){
if(!v[j]&&d[j]<Min){
Min = d[j];
val = j;
}
}
v[val] = true;
cost += Min;
for(int j = 1;j<=count;j++){
if(!v[j]&&d[j]>dis[val][j]){
d[j] = dis[val][j];
}
}
}
return cost;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&x,&y);
char temp[100];
gets(temp);//去掉空格
for(int i = 1;i<=y;i++){
gets(graph[i]); //构造地图
}
count = 0;
memset(num,-1,sizeof(num));
int index;
for(int i = 1;i<=y;i++){
for(int j = 0;j<x;j++){
if(graph[i][j]=='S'||graph[i][j]=='A')
num[i][j+1] = ++count;//对字母进行编号
if(graph[i][j]=='S')
index = count;
}
}
for(int i = 1;i<=y;i++){
for(int j = 0;j<x;j++){
if(graph[i][j]=='A'||graph[i][j]=='S')
BFS(i,j+1);
}
}
printf("%d\n",Prime(index));
}
return 0;
}
Sorting It All Out
http://poj.org/problem?id=1094
思路:拓扑排序。输入m组关系,每输入一组都要进行一次拓扑排序。存在三种情况:存在环,有矛盾;可以进行拓扑排序且序列唯一;拓扑排序序列不唯一,所以无法判定。注意:如果找到确定顺序,只需要把剩余数据读完即可。存在不确定的顺序时,仍然需要判断是否存在环,如果存在环,则输出矛盾。
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
int n,m;
vector<int> graph[30];
vector<int>degree(26,0);
vector<int> arr(26);
int topo(){
queue<int> q;
vector<int> copy(26,0);
for(int i = 0;i<n;i++){
copy[i] = degree[i];
if(copy[i]==0)
q.push(i);
}
int k = 0;
bool flag = false;//作为标志,判断是不是可以进行拓扑排序,并且只有唯一一种方式
while(!q.empty()){
if(q.size()!=1)//如果每次有多个点得入度为0则最终序列不唯一
flag = true;
int num = q.front();
q.pop();
arr[k++]=num;
int len = graph[num].size();
for(int i = 0;i<len;i++){
if(--copy[graph[num][i]]==0)
q.push(graph[num][i]);
}
}
if(k<n)
return -1;//不能判定
if(flag) //可以进行拓扑排序,有多种情况
return 0;
return 1;//只有唯一一种情况
}
int main(){
int res = 0;
while(scanf("%d%d",&n,&m) && n && m){
string link;
int flag1 = -1;
int flag2 = -1;
for(int j = 0;j<26;j++){
degree[j] = 0;
}
for(int j = 0;j<30;j++){
graph[j].clear();
}
for(int i = 0;i<m;i++){
int left,right;
cin>>link;
left = link[0]-'A';
right = link[2]-'A';
graph[left].push_back(right);
degree[right]++;
if(flag1!=-1||flag2!=-1)
continue;
int res = topo();
if(res==1){
flag1 = i+1;
}else if(res==-1){
flag2 = i+1;
}
}
if(flag2!=-1){
printf("Inconsistency found after %d relations.\n",flag2);
}
else if(flag1 != -1){
printf("Sorted sequence determined after %d relations: ",flag1);
for (int i = 0; i < n; ++i)
putchar('A'+arr[i]);
printf(".\n");
}
else
{
printf("Sorted sequence cannot be determined.\n");
}
}
return 0;
}
```