图论算法

一、并查集
 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
例如:若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
算法:
(1) 开始时,为每个人建立一个集合Set(x);
(2) 得到一个关系后a,b,合并相应集合Union(a,b);

题目描述:某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
输入描述:
测试输入包含若干测试用例。每个测试用例的第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
#include
using namespace std;
int roads[1001];
int main(){
    while (true){
        int n, m;
        cin >> n;
        if (n == 0){
            return 0;
        }
        cin >> m;
        for (int i = 1; i <= n; i++){
            roads[i] = -1;
        }
        for (int i = 0; i < m; i++){
            int a, b;
            cin >> a >> b;
            int max = roads[a] > roads[b] ? roads[a] : roads[b];
            if (max == -1){
                roads[a] = b;
            }
            else{
                int ai = a;
                int bi = b;
                while (roads[ai] != -1){
                    ai = roads[ai];
                }
                while (roads[bi] != -1){
                    bi = roads[bi];
                }
                if (ai != bi){
                    roads[bi] = ai;
                }
            }
        }
        int sum = 0;
        for (int i = 1; i <= n; i++){
            if (roads[i] == -1){
                sum++;
            }
        }
        cout << sum - 1 << endl;
    }
    return 0;
}

二、最小生成树
 根据Kruskal算法可以生成最小生成树,即每次选取最小权重的边,若不成环,则加入集合。而检测是否成环可以根据并查集进行计算。

题目描述:
 某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
输入描述:
 测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
 当N为0时,输入结束,该用例不被处理。
输出描述:
 对每个测试用例,在1行里输出最小的公路总长度。
示例输入:
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
输出:
3
5

备注:
采用头文件中的sort函数,可以进行递增排序。
根据需要重载 < :
bool operator < (const Template &A) const{} 
#include
#include
using namespace std;
int roads[101];
struct Node{
    int a, b;
    int cost;
    bool operator < (const Node &A) const {
        return cost < A.cost;
    }
}node[5000];
int findroot(int a){
    if (roads[a] == -1)
        return a;
    int i = a;
    while (roads[i] != -1){
        i = roads[i];
    }
    //roads[a] = i;
    return i;
}
bool unionset(Node nd){
    int aroot = findroot(nd.a);
    int broot = findroot(nd.b);
    if (aroot != broot){
        roads[aroot] = broot;
        return true;
    }
    else{
        return false;
    }
}
int main(){
    while (true){
        int n;
        cin >> n;
        if (n == 0){
            return 0;
        }
        int times = n*(n - 1) / 2;
        for (int i = 1; i <= n; i++){
            roads[i] = -1;
        }
        for (int i = 0; i < times; i++){
            cin >> node[i].a >> node[i].b >> node[i].cost;
        }
        sort(node,node+times);
        int iter = 1;
        int sum = 0;
        for (int i = 0; iter < n; i++){
            if (unionset(node[i])){
                iter++;
                sum += node[i].cost; 
            }
        }
        cout << sum << endl;
    }
    return 0;
}

三、最短路径

题目描述:
 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?
输入描述:
 输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
 输入保证至少存在1条商店到赛场的路线。
输出描述:
 对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间
示例输入:
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
输出:
3
2

#include
using namespace std;
const int NOWAY = -1;
const int VNUM = 101;
// 邻接矩阵
int cost[VNUM][VNUM];

// Floyd算法
// cost[k][i][j] = min { cost[k-1][i][j], 
//      cost[k-1][i][k] + cost[kNOWAY][k][j] }
//  从i到j且中间节点最大不超过k
//  第一维数组可优化
void floyd(int n){
    for (int k = 1; k <= n; k++){
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                if (cost[i][k]!=NOWAY && cost[k][j]!=NOWAY){
                    if (cost[i][j] == NOWAY || cost[i][j] > cost[i][k] + cost[k][j]){
                        cost[i][j] = cost[i][k] + cost[k][j];
                    }
                }
            }
        }
    }
    if (cost[1][n] != NOWAY){
        cout << cost[1][n] << endl;
    }
}
// Dijkstra算法
// 从一个节点出发,不断迭代最短距离。
// 从length[]中选取最小长度对应的结点J,加入新结点j。
// 更新length[]。 a[k] = min { a[j] + cost[j][k] }
// 循环N次
bool mark[VNUM]; // 记录节点是否已加入
int length[VNUM];
void dijkstra(int n, int begin,int end){
    for (int i = 1; i <= n; i++){
        length[i] = -1;
        mark[i] = false;
    }
    // 加入开始节点
    mark[begin] = true;
    length[begin] = 0;
    int newp = begin;

    // 不断加入新节点,循环加入n次
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++){

            // 根据新加的节点的边,更新距离数组
            if (mark[j] || cost[newp][j] == -1){
                continue;
            }
            // 如果节点j不可达,但是newp可达;或者
            // length[j]>length[newp]+cost[newp][j] 更新。
            if (length[j]==-1||length[j]>length[newp]+cost[newp][j]){
                length[j] = length[newp] + cost[newp][j];
            }
        }
        // 寻找未加入的节点中最短路对应的节点,当作新加入的节点。
        int min = 999999999;
        for (int k = 1; k <= n; k++){
            if (!mark[k] && length[k] != -1 && length[k] < min){
                min = length[k];
                newp = k;
            }
        }
        mark[newp] = true;
    }
    cout << length[end] << endl;
}
int main(){
    while (true){
        int n, m;
        cin >> n >> m;
        if (n == 0 && m == 0){
            return 0;
        }
        int a, b, c;
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                cost[i][j] = NOWAY;
            }
            cost[i][i] = 0;
        }
        for (int i = 0; i < m; i++){
            cin >> a >> b >> c;
            cost[a][b] = c;
            cost[b][a] = c;
        }
        // floyd(n);
        dijkstra(n, 1, n);
    }
    return 0;

}

四、拓扑排序

题目描述:
 有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。
输入描述:
 输入有若干组,每组中的第一行为二个数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

// 不断寻找入度为0的顶点。
#include
using namespace std;
const int VNUM = 501;
int cost[VNUM][VNUM];
int in[VNUM];
int ans[VNUM];
void topo(int n){
    int index = 1;
    for (int k = 0; k < n; k++){
        for (int i = 1; i <= n; i++){
            if (in[i] == 0){
                ans[index] = i;
                for (int j = 1; j <= n; j++){
                    if (cost[i][j] == 1){
                        in[j]--;
                    }
                }
                in[i] = -1;
                index++;
                break;
            }
        }
    }
    for (int i = 1; i < n; i++){
        cout << ans[i] << " ";
    }
    cout <> n >> m){
        int a, b;
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                cost[i][j] = 0;
            }
            in[i] = 0;
        }
        for (int i = 0; i < m; i++){
            cin >> a >> b;
            // 防止重边
            if (cost[a][b] == 0)
                in[b]++;
            cost[a][b] = 1;
        }
        topo(n);
    }
    return 0;
}

五、广度优先搜索和深度优先搜索

问题描述:(胜利大逃亡)
 Ignatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会.
 魔王住在一个城堡里,城堡是一个ABC的立方体,可以被表示成A个B*C的矩阵,刚开始Ignatius被关在(0,0,0)的位置,离开城堡的门在(A-1,B-1,C-1)的位置,现在知道魔王将在T分钟后回到城堡,Ignatius每分钟能从一个坐标走到相邻的六个坐标中的其中一个.现在给你城堡的地图,请你计算出Ignatius能否在魔王回来前离开城堡(只要走到出口就算离开城堡,如果走到出口的时候魔王刚好回来也算逃亡成功),如果可以请输出需要多少分钟才能离开,如果不能则输出-1.

图论算法_第1张图片

输入:
 输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第0块,然后是第1块,第2块......),每块输入数据有B行,每行有C个正整数,代表迷宫的布局,其中0代表路,1代表墙.(如果对输入描述不清楚,可以参考Sample Input中的迷宫描述,它表示的就是上图中的迷宫)
特别注意:本题的测试数据非常大,请使用scanf输入,我不能保证使用cin能不超时.在本OJ上请使用Visual C++提交.

输出:
 对于每组测试数据,如果Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出-1.

Sample Input

1
3 3 4 20
0 1 1 1
0 0 1 1
0 1 1 1
1 1 1 1
1 0 0 1
0 1 1 1
0 0 0 0
0 1 1 0
0 1 1 0

Sample Output
11

解题思路:DFS,每次向六个方向扩展,时间加1,采用queue数据结构,第一次遍历到出口 时即最短时间。
问题:第一段是网上答案,第二段是我的代码。我的代码一直出现内存超限,暂未找到原因。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
bool mark[50][50][50];
bool m[50][50][50];
int t;
int go[][3] =
{
    1, 0, 0,
    -1, 0, 0,
    0, 1, 0,
    0, -1, 0,
    0, 0, 1,
    0, 0, -1
};
struct e
{
    int x, y, z;
    int t;
};
queue  q;
int bfs(int a, int b, int c)
{
    while (!q.empty())
    {
        e now;
        now = q.front();
        q.pop();
        if (now.t >= t) {
            return  -1;
        }
        for (int i = 0; i<6; i++)
        {
            int nx = now.x + go[i][0];
            int ny = now.y + go[i][1];
            int nz = now.z + go[i][2];
            if (nx<0 || nx >= a || ny<0 || ny >= b || nz<0 || nz >= c) continue;
            if (mark[nx][ny][nz] == 1) continue;
            if (m[nx][ny][nz] == 1) continue;
            m[nx][ny][nz] = 1;
            e tmp;
            tmp.x = nx;
            tmp.y = ny;
            tmp.z = nz;
            tmp.t = now.t + 1;
            if (tmp.t 
#include
#include
using namespace std;
const int MAX = 50;
int map[MAX][MAX][MAX];
bool mark[MAX][MAX][MAX];
int A, B, C, T;
struct Dot{
    int a;
    int b;
    int c;
    int t;
};
queue Q;
void BFS(){
    while (Q.empty() == false){
        Dot dot = Q.front();
        int a = dot.a;
        int b = dot.b;
        int c = dot.c;
        int t = dot.t;
        if (a == A - 1 && b == B - 1 && c == C - 1 && !mark[a][b][c]){
            mark[a][b][c] = true;
            if (t <= T)
                cout << t << endl;
            else
                cout << -1 << endl;
            return;
        }
        if (t > T){
            cout << -1 << endl;
            return;
        }
        mark[a][b][c] = true;
        Dot temp;
        if (a > 0 && !mark[a - 1][b][c] && map[a - 1][b][c] == 0){
            temp.t = t + 1;
            temp.a = a - 1;
            temp.b = b;
            temp.c = c;
            Q.push(temp);
        }
        if (a 0 && !mark[a][b - 1][c] && map[a][b - 1][c] == 0){
            temp.t = t + 1;
            temp.a = a;
            temp.b = b-1;
            temp.c = c;
            Q.push(temp);
        }
        if (b 0 && !mark[a][b][c - 1] && map[a][b][c - 1] == 0){
            temp.t = t + 1;
            temp.a = a;
            temp.b = b;
            temp.c = c-1;
            Q.push(temp);
        }
        if (c> map[i][j][k];
                    scanf_s("%d", &map[i][j][k]);
                    mark[i][j][k] = 0;
                }
            }
        }
        while (!Q.empty()){
            Q.pop();
        }
        Dot d;
        d.a = 0;
        d.b = 0;
        d.c = 0;
        d.t = 0;
        map[0][0][0] = 0;
        Q.push(d);
        BFS();
        if (!mark[A - 1][B - 1][C - 1])
            cout << -1 << endl;
    }
    return 0;
}

Problem Description(非常可乐)
 大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出"NO"。

Input
 三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以"0 0 0"结束。

Output
 如果能平分的话请输出最少要倒的次数,否则输出"NO"。

Sample Input
7 4 3
4 1 3
0 0 0

Sample Output
NO
3

#include
#include
using namespace std;
struct Liquid{
    int n;
    int m;
    int s;
    int times;
};
bool mark[101][101][101];
queue Q;
int N, M, S;
void AtoB(int &a, int &b, int max){
    if (a + b > max){
        a = a + b - max;
        b = max;
    }
    else{
        b = a + b;
        a = 0;
    }
}
void push_back(int n, int m, int s, int t){
    if (n > N || m > M || s > S){
        return;
    }
    Liquid next ;
    if (mark[n][m][s] == false){
        next.n = n;
        next.m = m;
        next.s = s;
        next.times = t + 1;
        Q.push(next);
    }
}
int main(){
    while (true){
        cin >> S >> N >> M;
        bool flag = false;
        if (S == 0 && N == 0 && M == 0){
            return 0;
        }
        if (S % 2 != 0){
            cout << "NO" << endl;
            continue;
        }
        for (int i = 0; i <= N; i++){
            for (int j = 0; j <= M; j++){
                for (int k = 0; k <= S; k++)
                    mark[i][j][k] = 0;
            }
        }
        while (!Q.empty()){
            Q.pop();
        }
        Liquid temp;
        temp.n = 0;
        temp.m = 0;
        temp.s = S;
        temp.times = 0;
        Q.push(temp);
        while (Q.empty() == false){
            Liquid now = Q.front();
            Liquid next;
            Q.pop();
            int n, m, s, t;
            n = now.n;
            m = now.m;
            s = now.s;
            t = now.times;

            mark[n][m][s] = true;
            if ((n == S / 2 && m == S / 2) || (n == S / 2 && s == S / 2) || (m == S / 2 && s == S / 2)){
                cout << t << endl;
                flag = true;
                break;
            }
            AtoB(n, m, M);
            push_back(n, m, s, t);


            n = now.n;
            m = now.m;
            s = now.s;
            t = now.times;
            AtoB(n, s, S);
            push_back(n, m, s, t);


            n = now.n;
            m = now.m;
            s = now.s;
            t = now.times;
            AtoB(m, n, N);
            push_back(n, m, s, t);


            n = now.n;
            m = now.m;
            s = now.s;
            t = now.times;
            AtoB(m, s, S);
            push_back(n, m, s, t);


            n = now.n;
            m = now.m;
            s = now.s;
            t = now.times;
            AtoB(s, m, M);
            push_back(n, m, s, t);


            n = now.n;
            m = now.m;
            s = now.s;
            t = now.times;
            AtoB(s, n, N);
            push_back(n, m, s, t);


        }
        if (flag)
            continue;
        cout << "NO" << endl;
    }
    return 0;
}
此类题目应先确认状态的转移关系。
(x,y,z...) ---> (xx,yy,zz...).。
其次找清判断结果的条件。

六、A*算法(拓展)
综合了DFS/BFS和启发式寻路算法。

你可能感兴趣的:(图论算法)