图论
图论
模板
拓扑排序
- 所有入度为0的点入队
- 遍历队列中所有入度为0的点,并将它扩展出去的点的度数-1,如果减去后入度为0则入队。
- 统计队列中点的个数,如果所有点都入队则存在拓扑排序,否则不可进行拓朴排序。
int q[maxn], front = 1, rear = 0;
void topsort() {
for (int i = 1; i <= n; i++) {
if (!r[i]) {
q[++rear] = i;
}
}
while (front <= rear) {
int u = q[front++];
for (auto v : edge[u]) {
if (!(--r[v])) {
q[++rear] = v;
}
}
}
if (rear == n) {
for (int i = front; i <= rear; i++) {
printf("%d%c",q[i]," \n"[i == rear]);
}
} else {
printf("No topsort!");
}
}
Dijkstra
基于贪心思想,适用于所有边的长度都是非负数的图,当边长都是非负数二队时候,全局最小值不可能再被其他节点更新。
朴素版本(邻接图存图)
时间复杂度\(O(n^2+m)\),其中n是点数,m是边数,多用于稠密图。
- 将距离数组除起点外所有点置无穷大,表示目前每个点互不连通。
- 循环n次,每次找到距离源点最近且没有被确定过的点。
- 用这个点更新其他点的距离。
- 标记这个点,表明已经确定该点最短路径。
int g[maxn][maxn], dist[maxn];
bool vis[maxn];
int dijkstra(int Start, int End) {
memset(dist, 0x3f, sizeof dist);
memset(vis, false, sizeof vis);
dist[Start] = 0;
for (int i = 1; i <= n; i++) {
int ver = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && (dist[ver] > dist[j] || ver == -1)) {
ver = j;
}
}
for (int j = 1; j <= n; j++) {
dist[j] = min(dist[j], dist[ver] + g[ver][j]);
}
vis[ver] = true;
}
return dist[End];
}
堆优化版
时间复杂度\(O(mlogn)\),m是边数,n是点数,多用于稀疏图。
优化的是找点的过程,将寻找出距离源点最近且未必确定的点从\(O(n^2)\)优化到\(O(n)\)
void add(int a, int b, int c) {
g[a].push_back({b,c});
}
int dijskra(int Start, int End) {
memset(dist, 0x3f, sizeof dist);
memset(vis, false, sizeof vis);
dist[Start] = 0;
priority_queue<PII,vector<PII>, greater<PII>> heap;
heap.push({0,Start});
while (!heap.empty()) {
PII u = heap.top();
heap.pop();
if (vis[u.second] == true) {
continue;
}
vis[u.second] = ture;
for (auto v : g[u]) {
if (dist[v.first] > u.first + v.second) {
dist[v.first] = u.first + v.second;
heap.push({dist[j], v.first});
}
}
}
return dist[End];
}
Bellman_ford
用来处理负权边和有边数限制的最短路算法,时间复杂度\(O(nm)\),n是点数,m是边数。
- for 1 -> n
- for 所有边
- 松弛操作
有边数限制的条件下,backup的存在可以防止一次更新多条边的情况出现。
for (int i = 0; i < n; i++) {
memcpy(backup, dist, sizeof dist);
for (int j = 0; j < m; j++) {
int u = g[j].a, v = g[j].b, w = g[j].w;
dist[b] = min(dist[a], backup[a] + w);
}
}
SPFA
可以处理负权边图和判断负权环,时间复杂度为\(O(m)\),最坏情况会退化到\(O(nm)\)。
该算法是对Bellman-ford算法的队列优化版本,解决了Bellman-ford算法中过多的无用操作。
int spfa()
{
memset( dis,0x3f,sizeof dis);
dis[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while( q.size() )
{
int t = q.front();
q.pop();
st[t] = false;
for( int i = h[t] ; i != -1 ; i = ne[i] )
{
int j = e[i];
if( dis[j]>dis[t]+w[i] )
{
dis[j] = dis[t] + w[i];
if( !st[j] )
{
q.push( j );
st[j] = true;
}
}
}
}
return dis[n];
}
判断有无负权边,若有会在里面打转转,如果一个点遍历次数大于n次,则说明有负权边。
void add(int a, int b, int c) {
g[a].push_back({b,c});
}
bool spfa(int Start, int End) {
queue<PII> q;
memset(dist, 0x3f, sizeof dist);
dist[Start] = 0;
q.push({0,Start});
vis[Start] = true;
while (!q.empty()) {
PII u = q.front();
q.pop();
vis[u.second] = false;
for (auto v : g[u]) {
if (dist[v.first] > dist[u.second] + v.second) {
dist[v.first] = dist[u.second] + v.second;
cnt[v.first] = cnt[u.second] + 1;
if (cnt[v.first] >= n) {
return true;
}
if (!vis[v.first]) {
q.push({dist[v.first], v.first});
vis[v.first] = true;
}
}
}
}
return false;
}
Floyd
解决多源汇最短路问题。
本质是动态规划,K是阶段,必须置于外层循环当中。
时间复杂度为\(O(n^3)\)
void floyd() {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dist[i][j] = min(dist[i][k]+dist[k][j], dist[i][j]);
}
}
}
}
最小生成树
Prim
基于一种贪心策略,每次将距离集合最近的点加入到集合当中来,使得集合内的连通部分逐渐增大,最后连通整个图并且使得边权和最小。时间复杂度为\(O(n^2)\),多用于稠密图。
int sum;
bool prim() {
memset(dist, 0x3f, sizeof dist);
sum = 0;
for (int i = 0; i < n; i++) {
int ver = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && (ver == -1 || dist[ver] > dist[j])) {
ver = j;
}
}
if (i && dist[ver] == inf) {
return false;
}
if (i) {
sum += dist[ver];
}
vis[ver] = true;
for (int j = 1; j <= n; j++) {
dist[j] = min(dist[j], g[ver][j]);
}
}
return true;
}
结构体邻接表+堆优化写法
成立条件是tot == n,集合内有图的所有点,表示连通,否则表示不连通。
const int N = 200015, inf = 123456789;
typedef pair<int,int> pii;
int h[N], dist[N], idx, n, m, tot, ans;
bool vis[N];
struct Node {
int v, w, ne;
}E[N<<1];
priority_queue<pii, vector<pii>, greater<pii>> q;
inline void add(int a, int b,int c) {
E[idx] = {b,c, h[a]}, h[a] = idx++;
}
void prim() {
memset(dist, 127, sizeof dist);
dist[1] = 0;
q.push(make_pair(0, 1));
while (!q.empty() && tot < n) {
pii t = q.top();
int d = t.first, ver = t.second;
q.pop();
if (vis[ver]) continue;
vis[ver] = true;
tot++, ans += d;
for (int i = h[ver]; ~i; i = E[i].ne) {
if (dist[E[i].v] > E[i].w)
{
dist[E[i].v] = E[i].w;
q.push(make_pair(dist[E[i].v], E[i].v));
}
}
}
}
Kruskal
时间复杂度为\(O(mlogm)\),m为边数。
- 对所有边权从小到大进行排序。
- 枚举每条边,如果边的两点还未进行联通,则将其联通并将边加入集合中。
需要并查集进行合并操作,当集合内边数少于n-1条时,图不连通。
n个结点的图要进行连通,至少需要n-1条边。
const int N = 200015, inf = 123456789;
typedef pair<int,int> pii;
int dist[N], idx, n, m, tot, ans;
int f[N];
bool vis[N];
struct Node {
int u, v, w;
bool operator < (const Node &T) const {
return w < T.w;
}
}E[N<<1];
inline int find(int x) {
if (x != f[x]) f[x] = find(f[x]);
return f[x];
}
inline bool kruskal() {
sort(E,E+m, cmp);
for (int i = 1; i <= n; i++) {
f[i] = i;
}
ans = 0, tot = 0;
for (int i = 0; i < m; ++i) {
int u = E[i].u, v = E[i].v;
ru = find(u), rv = find(v);
if (ru != rv) {
ans += E[i].w;
f[ru] = rv;
++tot;
if (tot == n-1) {
break;
}
}
}
if (tot < n-1) {
return false;
} else {
return true;
}
}

浙公网安备 33010602011771号