chapter11-图论

1.图的存储方式

首先,关于图的存储方式有2种,一种是邻接矩阵,一种是邻接表,而邻接表适用于1个点对到其他所有点的批处理,实际程序中经常使用。

邻接表会给每一个顶点建立一个单链表,即使那个顶点没有度(无向图),or没有任何出度(有向图)。在程序中,我们并不是使用单链表来存储,而是一个向量数组来表示一个图

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

const int MAXN = 1000;

vector<int> graph[MAXN]; //给每个顶点都分配一个向量

2.并查集

并查集用于实现不相交集合的查找Find和合并Union两种操作。

  • 查找:确定元素属于哪个集合。这种方法的步骤是:不断向上查找,直到找到它的根结点,之后根据根结点是否相同来判断两个元素是否属于同一集合。

  • 合并:将两个子集合并成同一个集合。这种方法的步骤是:将一棵树作为另外一棵树的子树,从而使得两棵树变成一颗更大的树。

无论是查找,还是合并首先都要找到子集的根结点,这个操作的耗时与树高h有关,所以应尽可能保持较低的树高,为此要在查找和合并操作中加入一点约束和优化。

  • 查找-路径压缩:在查找某个特定结点的根结点的同时,将将除了根结点之外的所有先辈结点直接指向根结点。

  • 合并-矮树合并大树:为此在father[MAXN]数组的基础上,要增设height[MAXN],初始化为0,并且当且仅当两棵树的树高相同时,合并后的集合树高会加1,其他情况树高均不会发生变化,从而提高查找效率。

并查集用于判定是否是连通图非常方便,因为只要是连通图,必定只有一个连通分量,如果多于1个连通分量,那么就不是连通图。

判定是否是连通图
//判定连通图-并查集 2024-03-16
#include <iostream>
#include <cstdio>

using namespace std;

const int MAXN = 1000 + 10;

int father[MAXN];
int height[MAXN];

void Initial(int n) {
    for(int i = 0; i <= n; ++i) {
        father[i] = i; //初始每个顶点都是孤立的集合,自己是自己的根结点
        height[i] = 0;
    }
}
//查找元素a属于哪个集合,返回根结点编号
int Find(int a) {
    if(father[a] != a) {
        father[a] = Find(father[a]);
    }
    return father[a];
}
//合并元素x和元素y所在的两个子集合
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[y] < height[x]) {
            father[y] = x;
        } else {
            father[y] = x;
            height[x]++;
        }
    }
}

int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF) {
        Initial(n);
        while(m--) {
            int x, y;
            cin >> x >> y;
            Union(x, y);
        }
        //连通分量是否只有1个
        int component = 0;
        for(int i = 1; i <= n; ++i) {
            if(father[i] == i) {
                ++component;
            }
        }
        if(component == 1) {
            cout << "YES" << endl;
        } else {
            cout << "NO" << endl;
        }
    }
    return 0;
}

3.最小生成树

即无向连通图中的极小连通子图,可能不只一棵,但最小生成树的边权和的值是一样的。**如何求解一个连通图的最小生成树,最常见的有Kruskal算法和Prim算法。

kruskal.jpg

最小生成树的权值和
//最小生成树-kruskal算法,以边为依据逐步加入一个集合当中-并查集 2024-03-16
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 100;

struct Edge {
    int from;
    int to;
    int length;
    bool operator<(const Edge &c) const {
        return length < c.length;
    }
};
Edge edge[MAXN * MAXN];
int father[MAXN];
int height[MAXN];

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

int Find(int x) {
    if(x != father[x]) {
        father[x] = Find(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[y] < height[x]) {
            father[y] = x;
        } else {
            father[y] = x;
            height[x]++;
        }
    }
    return;
}

int Kruskal(int n, int edgeNumber) {
    Initial(n);
    sort(edge, edge + edgeNumber);
    int sum = 0;
    for(int i = 0; i < edgeNumber; ++i) {
        int x = Find(edge[i].from);
        int y = Find(edge[i].to);
        if(x != y) {
            Union(x, y);
            sum += edge[i].length;
        }
    }//其实这里还缺一个判定连通分量是否为1,即是否是无向连通图
    return sum;
}

int main() {
    int n;
    while(cin >> n) {
        if(n == 0)
            break;
        int edgeNumber = n * (n -1) / 2;
        for(int i = 0; i < edgeNumber; ++i) {
            scanf("%d%d%d", &edge[i].from, &edge[i].to, &edge[i].length);
        }
        int answer = Kruskal(n, edgeNumber);
        cout << answer << endl;
    }
    return 0;
}

4.最短路径

Dijkstra算法用于解决单源最短路径问题。

Dijkstra算法在运行过程中将顶点集合V分成两个集合S和T。

(1)S:已确定的顶点集合,起始顶点到当前顶点最短的距离已确定。

(2)T=T - S:尚未确定的顶点集合。

初始所有顶点都在T集合中,然后设置起始顶点到各个顶点的距离值,到自己肯定是0,到其他顶点先设置理论上的最大值INF。算法反复从集合T中选择当前到源点s最近的顶点u,将u加入集合S,然后对所有从u发出的边进行松弛操作

松弛操作举例,假设现在把点U加入集合S,对所有从点U发出的边进行松弛操作为:
\(D(q)= min\{D(q), D(U) + (U, q)\}\)

单源最短路径
//单源最短路径-用优先队列优化每次选择距离起始顶点最短的顶点 2024-03-16
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>

using namespace std;

const int MAXN = 200;
const int INF = INT_MAX;

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

struct Point {
    int index;
    int distance;
    Point(int i, int d): index(i), distance(d) {}
    bool operator< (const Point &p) const {
        return distance > p.distance;
    }
};

vector<Edge> graph[MAXN];
int dis[MAXN];
priority_queue<Point> myPriortyQueue;

void Dijkstra(int start, int n) {
    fill(dis, dis + MAXN, INF);
    dis[start] = 0;
    myPriortyQueue.push(Point(start, dis[start]));
    while(!myPriortyQueue.empty()) {
        int u = myPriortyQueue.top().index;
        myPriortyQueue.pop();

        for(int i = 0; i < graph[u].size(); ++i) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[u] + d < dis[v]) {
                dis[v] = dis[u] + d;
                myPriortyQueue.push(Point(v, dis[v])); //if 更新,就放入优先队列中
            }
        }
    }
}

int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF) {
        memset(graph, 0, sizeof(graph));
        while(m--) {
            int from, to, dis;
            scanf("%d%d%d", &from, &to, &dis);
            graph[from].push_back(Edge(to, dis));
            graph[to].push_back(Edge(from, dis));
        }
        int start, terminal;
        scanf("%d%d", &start, &terminal);
        Dijkstra(start, n);
        if(dis[terminal] == INF) {
            dis[terminal] = -1;
        }
        cout << dis[terminal] << endl;
    }
    return 0;
}
posted @ 2024-03-16 17:06  paopaotangzu  阅读(39)  评论(0)    收藏  举报