欢迎光临我的Blog,虽然这里还很简陋,但未来一定会焕发生机的!

855学习记录数据结构图(1)基础、拓扑排序与最小生成树——炎泽汐$de$ Blog

数据结构 yanzexi 1年前 (2023-10-24) 241次浏览 已收录 0个评论 扫描二维码
文章目录[隐藏]

存储表示

存储与基本操作


      图的邻接表(稀疏图,一般$\left| E \right|<\ \left| V \right|\log \left| V \right|$时视为稀疏图)数组实现、初始化与插入函数(y 总版本的):

#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
#define mem(vis, num) memset(vis, num, sizeof(vis));
 
using namespace std;

const int maxn = 10001;
int h[maxn], ne[maxn], e[maxn], w[maxn];

void get(int a, int b, int q){
	ne[idx] = h[a];
	e[idx] = b;
	w[idx] = q;
	h[a] = idx++;
}

int idx = 0;
mem(h, -1);

遍历

深度优先搜索


//DFS
int st[maxn];
mem(st, 0);
int dfs(int u){
    st[u] = 1; // st[u] 表示点 u 已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if (!st[j]){
                dfs(j);
        }
    }
}
广度优先搜索


//BFS
int st[maxn];
mem(st, 0);

queue<int> q;
st[1] = 1; // 表示 1 号点已经被遍历过
q.push(1);

while (!q.empty()){
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i]){
        int j = e[i];
        if (!st[j]){
            st[j] = 1; // 表示点 j 已经被遍历过
            q.push(j);
        }
    }
}
优先级搜索


      前面两种遍历在功能上的差异,主要体现为每一步迭代中对新顶点的选取策略不同。比如,BFS 搜索会优先考查更早被发现的顶点,而 DFS 搜索则恰好相反,会优先考查最后被发现的顶点。每一种选取策略都等效于,给所有顶点赋予不同的优先级,而且随着算法的推进不断调整;而每一步迭代所选取的顶点,都是当时的优先级最高者。按照这种理解,包括 BFS 和 DFS 在内的几乎所有图搜索,都可纳入统一的框架。鉴于优先级在其中所扮演的关键角色,故亦称作优先级搜索($priority$-$first$ $search$, $PFS$),或最佳优先搜索($best$-$first$ $search$, $BFS$)。

      $PFS$搜索的基本过程和功能与常规的图搜索算法一样,也是以迭代方式逐步引入顶点和边,最终构造出一棵遍历树(或者遍历森林)。如前所述,每次都是引入当前优先级最高(优先级数最小)的顶点$s$,然后按照不同的策略更新其邻接顶点的优先级数。

重连通分量(双连通域分解)


      在无向连通图$G$中,当且仅当删去$G$中的顶点$v$及所有依附于$v$的所有边后,可将图分割成两个或两个以上的连通分量,则称顶点$v$为关节点。没有关节点的连通图叫做重连通图。在重连通图上, 任何一对顶点之间至少存在有两条路径, 在删去某个顶点及与该顶点相关联的边时, 也不破坏图的连通性。一个连通图$G$如果不是重连通图,那么它可以包括几个重连通分量。在一个无向连通图$G$中, 重连通分量可以利用深度优先生成树找到。

      从某点出发,深度优先遍历图,各节点的访问次序记为深度优先数$dfn$。其中,深度优先树的祖先节点的$dfn$一定小于子孙节点。原图中未被深度优先搜索访问的边叫做回边。为求解关节点,再定义一个最小深度优先数$low$值,计算方式为:

$$
low\left[ u \right] =\min \left( dfn\left[ u \right] ,\min \left( low\left[ w \right] ,\min \left( dfn\left[ x \right] \right) \right) \right)
$$

      其中$w$为$u$的子女,$(u,x)$为点$u$的回边。那么$u$为关节点的充要条件是要么$u$为树根有两个子女;要么$u$不为树根但有一子女$w$使得$low\left[ w \right] \ge dfn\left[ u \right] $成立。

拓扑排序

拓扑排序


      拓扑排序的前提是有向无环图,同一有向图的拓扑排序未必唯一,拓扑排序也未必存在。有向无环图的拓扑排序必然存在;反之亦然。这是因为,有向无环图对应于偏序关系,而拓扑排序则对应于全序关系。在顶点数目有限时,与任一偏序相容的全序必然存在。

      时间复杂度$O(n+m)$,$n$表示点数,$m$表示边数:

//拓扑排序
bool topsort(){
    int hh = 0, tt = -1;

    // d[i] 存储点 i 的入度
    for (int i = 1; i <= n; i ++ ){
        if (!d[i]){
            q[++tt] = i;
        }
    } 

    while (hh <= tt){
        int t = q[hh++];

        for (int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];
            if (--d[j] == 0)
                q[++tt] = j;
        }
    }

    // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
    return tt == n - 1;
}

最小生成树

      连通图$G$的某一无环连通子图$T$若覆盖$G$中所有的顶点,则称作$G$的一棵支撑树或生成树。就保留原图中边的数目而言,支撑树既是“禁止环路”前提下的极大子图,也是“保持连通”前提下的最小子图。

$prim$算法


      时间复杂度是$O(n^2+m)$,$n$表示点数,$m$表示边数。

//prim 算法
int n;      	// n 表示点数
int g[N][N];    // 邻接矩阵,存储所有边
int dist[N];	// 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回 INF(值是 0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim(){
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    for (int i = 0; i < n; i++ ){
        int t = -1;
        for(int j = 1; j <= n; j ++ ){
            if(!st[j] && (t == -1 || dist[t] > dist[j])){
                t = j;
            } 
        } 
	//t 是不在已有生成树中的距离最小点 
        if(i && dist[t] == INF){
            return INF;
        } 

        if(i){
            res += dist[t]
        } 
        st[t] = 1;

        for(int j = 1; j <= n; j ++ ){
            dist[j] = min(dist[j], g[t][j]);
        } 
    }

    return res;
}
$kruskal$算法


      时间复杂度是$O(mlogm)$,$m$表示边数。

//kruskal 算法 
int n, m;       // n 是点数,m 是边数
int p[N];       // 并查集的父节点数组

struct Edge{     // 存储边
    int a, b, w;

    bool operator< (const Edge &W)const{
        return w < W.w;
    }
}edges[M];

int find(int x){     // 并查集核心操作
    if(p[x] != x){
        p[x] = find(p[x])
    } 
    return p[x];
}

int kruskal(){
    sort(edges, edges + m);

    for(int i = 1; i <= n; i ++ ){    // 初始化并查集
    	p[i] = i;
    } 

    int res = 0, cnt = 0;
    
    for(int i = 0; i < m; i ++ ){
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a);
		b = find(b);
        if(a != b){     // 如果两个连通块不连通,则将这两个连通块合并
            p[a] = b;
            res += w;
            cnt++ ;
        }
    }

    if (cnt < n - 1){
    	return INF;
    }
    return res;
}
喜欢 (1)
[炎泽汐de收款码]
分享 (0)

您必须 登录 才能发表评论!