图论

图的存储

直接存储

//代码更少的做法
vector<tuple<int,int,int>> e(m);
for(auto &[f, t, w] : e) {
	cin >> f >> t >> w;
//	-- u, -- v;
}
//-----------------------------------------------
struct edge {
    int f, t, w;
};
vector<edge> e(m);
for(int i = 0; i < m; i ++) {
    cin >> e[i].f >> e[i].t >> e[i],w;
}

邻接矩阵

vector<vector<int>> e(n + 1, vector<int> (n + 1, 0));

邻接表

类似哈希表拉链法,对每个点都开一个vector

vector<int> e(n);

链式前向星(快)

链表实现邻接表

int e[N * M], ne[N * M], idx, head[N];
memset(head, - 1, sizeof head);
void add(int f, int t) {
	e[idx] = t, ne[idx] = head[f], head[f] = idx ++;
}

DFS

利用函数栈空间来实现

标记节点是否被遍历过,递归到下一层

void dfs (int x) {
	ban[x] = 1;
    for(auto it : e[x]) {
		if(!ban[it])
        	dfs(it);
    }
}

BFS

手写队列来实现

每次拿出队头元素,搜索每个子节点并标记并将每个子节点放入队列中去

void b (int x) {
	queue<int> q;
    q.push(x);
    ban[x] = 1;
    while(!q.empty()) {
        int t = q.front ();
        q.pop();
		for(auto it : e[t]) {
			if(!ban[it]) {
            	q.push(it);
            	ban[it] = 1;
            }
        }
    }
}

有向图的拓扑排序

排序后,边的方向都由前指向后

具体流程:

1、让所有入度为0的点入队

2、BFS,每次遍历都删去被遍历的边,即让终点的入度--

3、让入度减为0的点出队

4、数组模拟的队列可以直接输出,STL的队列需要将中间出队的点都存下来

vector<int> d(n + 1, 0), q(n);
vector<vector<int>> e(n +);
bool topsort() {
	head = 0, tail = - 1;
    for(int i = 1;i <= n;i ++) {
		if(d[i] == 0) {
			q[++ tail] = i;
        }
    }
    while(tail >= head) {
		int t = q[head ++];
        for(auto it : e[t]) {
            d[it] --;
            if(!d[it])
                q[++ tail] = it;
        }
    }
	return tail == n - 1;
}

for(int i = 0;i < m;i ++) {
    int f, t;
    cin >> f >> t;
	//add(f, t);
    //d[t] ++;
}

最短路

Dijkstra

基于贪心证明,适用于非负权值图

具体流程:

1、创建一个集合\(S\)存放已经确认最短路的点

2、初始化dis数组为$ +\infty$,将源点的dis赋为\(0\)

3、在未被添加到\(S\)集合中的点中选择dis最小的点添加到\(S\)

4、更新每个dis的上限(松弛操作)

5、重复操作3和4,直至所有点都在\(S\)集合中

暴力做法:

注释可以输出路径

时间复杂度\(O(n^2 + m)\)

邻接表:
typedef pair<int, int> PII;
vector<vector<PII>> e;
vector<int> dis;
//vector<int> path;
int n, m;
void Dijkstra(int n, int s) {
    dis.resize(n + 1,0x3f3f3f3f);
    //path.resize(n + 1, 0);
    //int pre = 0;
    dis[s] = 0;
    vector<bool> vis(n + 1, 0);
    for(int i = 1;i <= n;i ++) {
        int u = 0, mind = 0x3f3f3f3f;
        for(int j = 1;j <= n;j ++) {
            if(dis[j] < mind && !vis[j]) {
                u = j, mind = dis[j];
            }
        }
        vis[u] = 1;
        //pre = u;
        for(auto it : e[u]) {
            int v = it.first, w = it.second;
            if(dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                //path[v] = pre;
            }
        }
    }
}
邻接矩阵:
vector<int> dis;
vector<vector<int>> e;
void Dijkstra(int n, int s) {
    dis.resize(n + 1,0x3f3f3f3f);
    //path.resize(n + 1, 0);
    //int pre = 0;
    dis[s] = 0;
    vector<bool> vis(n + 1, 0);
    for(int i = 1;i <= n;i ++) {
        int u = 0, mind = 0x3f3f3f3f;
        for(int j = 1;j <= n;j ++) {
            if(dis[j] < mind && !vis[j]) {
                u = j, mind = dis[j];
            }
        }
        vis[u] = 1;
        //pre = u;
        for(int x = 1;x <= n;x ++) {
            if(dis[x] > dis[u] + e[u][x]) {
                dis[x] = dis[u] + e[u][x];
                //path[v] = pre;
            }
        }
    }
}
堆优化做法:

注释可以输出路径

时间复杂度\(O(m\ log\ m)\)

typedef pair<int, int> PII;
vector<vector<PII>> e;
vector<int> dis;
//vector<int> path;
int n, m;
void Dijkstra(int n, int s) {
    dis.resize(n + 1,0x3f3f3f3f);
    //path.resize(n + 1, 0);
    //int pre = 0;
    dis[s] = 0;
    vector<bool> vis(n + 1, 0);
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, s});
    while(!q.empty()) {
        PII u = q.top();
        q.pop();
        int tmpdis = u.first, tmpu = u.second;
        if(vis[tmpu]) continue;
        vis[tmpu] = 1;
        for(auto it : e[tmpu]) {
            int v = it.first, w = it.second;
            if(dis[v] > dis[tmpu] + w) {
                dis[v] = dis[tmpu] + w;
                q.push({dis[v], v});
                //path[v] = pre;
            }
        }
    }
}

Bellman-Ford

可以用来求路径长度不大于k的最短路,可以存在负权边,可以检测负环

时间复杂度\(O(n\times m)\)

具体流程

进行n轮操作(如要求路径长度不大于k则进行k轮操作,并对前一轮的dis进行备份),对每一条边都进行松弛操作
注释表示进行k轮操作(并且不需要flag)

bool Bellmanford(int n, int s) {
	dis.resize(n + 1, 0x3f3f3f3f);
    dis[s] = 0;
    bool flag = 0;
    for(int i = 1; i <= n; i ++) {
        flag = 0;
        //vector<int>tmpdis = dis;
        for(int j = 1; j <= n; j ++) {
            for(auto it : e[j]) {
                int u = it.first, w = it.second;
                if(dis[u] > dis[j] + w) {
                    dis[u] = dis[j] + w;
                    flag = 1;
                }
                //if(dis[u] > tmpdis[j] + w) {
                //    dis[u] = tmpdis[j] + w;
                //    flag = 1;
                //}
            }
        }
    }
    //返回是否存在负环
    return flag;
}

SPFA

基于bellman-ford算法的优化,用队列来维护,将上一次循环更新dis的点push队列,可以存在负权边,可以检测负环

时间复杂度一般\(O(m)\),最坏\(O(n \times m)\)

bool spfa(int n, int s) {
    dis.resize(n + 1, 0x3f3f3f3f);
    dis[s] = 0;
    queue<int> q;
    vector<int> vis(n + 1, 0), cnt(n + 1, 0);
    q.push(s);
    vis[s] = 1;
    while(!q.empty()) {
        int v = q.front();
        q.pop();
        vis[v] = 0;
        for(auto it : e[v]) {
            int u = it.first, w = it.second;
            if(dis[u] > dis[v] + w ) {
                dis[u] = dis[v] + w;
                cnt[u] ++;
                if(cnt[u] >= n) {
                    return 1;
                }
                if(!vis[u]) {
                    q.push({u});
                	vis[u] = 1;
                }
            }
        }
    }
    //返回是否存在负环
	return 0;
}

Floyd

基于动态规划证明,下面给出优化后的代码

时间复杂度\(O(n^3)\)

void floyd() {
    dis.resize(n + 1, vector<int>(n + 1), 0x3f3f3f3f);
    for(int i = 0; i < m; i ++) {
		int v, u, w;
        cin >> v >> u >> w;
        dis[v][u] = min(dis[v][u])
    }
    for(int i = 0; i <= n; i ++) {
        dis[i][i] = 0;
    }
 	for(int k = 1; k <= n; k ++) {
		for(int i = 1;i <= n; i ++) {
			for(in j = 1; j <= n; j ++) {
                dis[i][j] = min(dis[i][j], dis[k][j] + dis[i][k]);
            }
        }
    }   
}

最小生成树

Prim

时间复杂度\(O(n^2)\)

vector<vector<int>> e(n + 1, vector<int>(n + 1));
vector<int> dis;
int prim() {
    int res = 0;
	dis.resize(n + 1, 0x3f3f3f3f);
    vector<bool> vis(n + 1, 0);
    dis[1] = 0;
    for(int i = 0;i < n;i ++) {
        int u = 0, mind = 0x3f3f3f3f, flag = 1;
        for(int j = 1;j <= n;j ++) {
			if(dis[j] < mind && !vis[j]) {
                u = j, mind = dis[j];
                flag = 0;
            }
        }
        if(flag) return 0x3f3f3f3f;
        vis[u] = 1;
        res += dis[u];
        for(int x = 1;x <= n;x ++) {
            dis[x] = min(dis[x], e[u][x]);
        }
    }
    return res;
}

Kruskal

具体流程:

按边权从小到大遍历每条边,询问边的节点是否在同一集合中,如果不在则合并,最终得到的结果就是最小生成树

时间复杂度\(O(m\ log\ m)\)

struct Edge {
    int v, u, w;
    bool operator< (const edge &a) const {
		return w < a.w;
    }
}
vector<Edge> e(m);
for(int i = 0; i <= n; i ++) {
    p[i] = i;
}
vector<int>p(n + 1);
int find(int x) {
	return p[x] == x ? x : p[x] = find(p[x]);
} 
int Kruskal() {
    int res = 0, cnt = 0;
    sort(e.begin(), e.end());
    for(auto it : e) {
        int x = find(it.v), y = find(it.u);
        if(x != y) {
            res += it.w;
            cnt ++;
            p[y] = x;
        }
    }
    if(cnt == n - 1) return res;
    return -1;//表示不存在最小生成树
}
posted @ 2022-02-13 13:49  Muly  阅读(74)  评论(0)    收藏  举报