图论总结

图论总结


算法这东西,容易忘记, 捡起来很快, 但也要时间, 因此,做一些简短的记录!

图是什么?

树可以存线, 图可以存树,树是一种无环图, 线是最简单的数据结构, 图是较为复杂的一种数据结构!

图的分类

有向无向
有权有向有权图无向有权图
无权有向无权图无向无权图

图的表示

  • 邻接矩阵
vaecotr<vector<int> > Graph4;

i点到j点的距离! 空间复杂度: V^2 , 求一个点的临接点: V

  • 邻接表(链表)

只存每个点能到达的点

空间复杂度: O(V+E ),建图: O(E*V),求一个点的临接点:(O(V))

可以用 hash 或者 红黑树 替换 链表

const int maxn = 1e5 + 1;
struct Node{
    int to, cost;
}
// 无向无权图
vector<int> Graph1[maxn];
// 无向有权图
vector<Node> Graph2[maxn];
// hash 找很快! 红黑树  to  cost  
map<int, int>Graph3[maxn];  // map 默认为0, 

图的遍历

DFS

分为 前序, (二叉树)中序, 后序, 不用记录遍历过的点

分为 先序,后续(基本不用),要记录每一个每一个点是否遍历过, visited 记录

模版:

// 递归
void dfs41(int index, bool *visited) {
  visited[index] = 1;
  cout << index << " -> ";  // 前序遍历*************
  for (int i = 0; i < len; i++) {
    if (!visited[i] && Graph4[index][i] != 0) {
      dfs41(i, visited);
    }
  }
  // cout << index << " -> ";后序遍历*****************
}
// 非递归
void dfs42(int start) {
  stack<int> stack;
  stack.push(start);
  bool visited[len] = {0};
  while (!stack.empty()) {
    int temp = stack.top();
    stack.pop();
    visited[temp] = 1;
    cout << temp << " -> ";
    for (int i = 0; i < len; i++)
      if (!visited[i] && Graph4[temp][i] != 0) {
        stack.push(i);
        visited[i] = 1;
      }
  }
}
BFS

树的BFS和图的BFS是一样的!
无权图的最短路径

// 广度优先
void bfs4(int start) {
  queue<int> q;
  q.push(start);
  bool visited[len] = {0};
  while (!q.empty()) {
    int temp = q.front();
    q.pop();
    visited[temp] = 1;
    cout << temp << " -> ";
    for (int i = 0; i < len; i++)
      if (!visited[i] && Graph4[temp][i]) {
        visited[i] = 1;
        q.push(i);
      }
  }
}
比较

做题最好用dfs, 基于递归不用容器(自带的栈)存储太多东西!

栈(DFS),队列(BFS), 随机容器(迷宫生成!)

typedef vector<vector<int> > vvint;
typedef vector<int> vint;
int rs_len = 2000;
vint cap(rs_len); //容器
int cap_len = 0; 
//自己造一个随机容器!
bool cap_empty() { return cap_len == 0 ? 1 : 0; }
void cap_put(int value) { cap[cap_len++] = value; }
int cap_get() {
  if (cap_empty()) {
    cout << "error: out of index!\n";
    return -1;
  }
  srand(time(NULL));  // 随机获取一个数
  int index = rand() % cap_len;
  // cout << "random" << index << " " << cap_len << endl;
  int ret = cap[index];
  for (int i = index; i < cap_len - 1; i++) cap[i] = cap[i + 1];
  cap_len--;
  return ret;
}
void gen_map(vvint map);
// @param 迷宫的长度
void rs4(int len) {
  rs_len = len;
  vvint map(rs_len * rs_len, vint(rs_len * rs_len));
  for (int i = 0; i < rs_len; i++) {
    for (int j = 0; j < rs_len - 1; j++) {
      map[j + i * rs_len][j + i * rs_len + 1] = 1;
      map[j + i * rs_len + 1][j + i * rs_len] = 1;
      map[j * rs_len + i][(j * rs_len + i) + rs_len] = 1;
      map[j * rs_len + i + rs_len][j * rs_len + i] = 1;
    }
  }
  // 从 0 开始  随机搜索, 迷宫入口
  cap_put(0);
  bool visited[rs_len * rs_len] = {0};
  visited[0] = 1;
  while (!cap_empty()) {
    int temp = cap_get();
    for (int i = 0; i < rs_len * rs_len; i++)
      if (!visited[i] && map[temp][i] == 1) {
        //cout << temp << "  " << i << endl;
        map[temp][i] = map[i][temp] = 2;
        cap_put(i);
        visited[i] = 1;
      }
  }
  // traverse4(map);
  gen_map(map);
}
// 把遍历的经过转换成地图
void gen_map(vvint map) {
  vvint temp(rs_len * 2, vint(rs_len * 2));
  for (int i = 0; i < rs_len; i++) {
    for (int j = 0; j < rs_len - 1; j++) {
      if (map[j + i * rs_len][j + i * rs_len + 1] == 2) {  // 每一行
        //cout << i << " * " << j;
        //cout << " " << j + i * rs_len << " " << j + i * rs_len + 1 << endl;
        temp[i * 2][j * 2] = temp[i * 2][(j + 1) * 2] = temp[i * 2][j * 2 + 1] =
            1;
      }
      if (map[j * rs_len + i][(j * rs_len + i) + rs_len] == 2) {  // 每一列
        //cout << j << " / " << i;
        //cout << " " << j * rs_len + i << " " << j * rs_len + i + rs_len << endl;
        temp[j * 2][i * 2] = temp[(j + 1) * 2][i * 2] = temp[j * 2 + 1][i * 2] =
            1;
      }
    }
  }
  for (int i = 0; i < rs_len * 2 - 1; i++) {
    for (int j = 0; j < rs_len * 2 - 1; j++) {
      int out = (temp[i][j] == 1) ? 1 : 0;
      cout << out << "  ";
    }
    cout << endl;
  }
} 
应用
  • 求联通分量(图分成了多少块!?)
void component() {
  int ans = 0;
  bool visited[len] = {0};
  for (int i = 0; i < len; i++) {
    if(!visited[i]) ans ++;      // 只要有没有访问到的, 就计数加上一
    queue<int> q;				// 其它 更遍历是一样的
    q.push(0);
    while (!q.empty()) {
      int temp = q.front();
      q.pop();
      visited[temp] = 1;
      //cout << temp << " -> ";
      for (int i = 0; i < len; i++)
        if (!visited[i] && Graph4[temp][i]) {
          visited[i] = 1;
          q.push(i);
        }
    }
  }
  cout << "unicom component is : " << ans << endl;
}
  • 路径问题(从一个点到另一个点是否通?)回溯(dfs), 广搜
bool is_connected(int a, int b) {
  bool visited[len] = {0};
  queue<int> q;
  q.push(a);
  while (!q.empty()) {
    int temp = q.front();
    q.pop();
    visited[temp] = 1;
    for (int i = 0; i < len; i++)
      if (Graph4[temp][i]) {
        if (i == b)
          return true;
        else if (!visited[i]) {
          visited[i] = 1;
          q.push(i);
        }
      }
  }
  return false;
}
  • 检测环

  • 二分图检测

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gr16MPZ-1583062762736)(https://raw.githubusercontent.com/Fierygit/picbed/master/20200219211503.png)]

定点V可以分为两部分, 所有边的两个顶点分别属于这两部分!

floodfill算法

把图联通的某一部分填满!

算法思路:

​ 直接遍历!

例题: 游戏开发 -> 扫雷, 最大人工岛屿

桥和隔点

桥: 删除了某一条边,联通分量发生改变!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6TakjUb-1583062762737)(https://raw.githubusercontent.com/Fierygit/picbed/master/20200219215253.png)]

寻找所有桥!只有DFS可以求!(dfs遍历树)

对于每一条边 v - w , 通过w, 能否通过另一条路回到v

隔点: 删除隔点, 图的联通分量产生变化!

类似寻找桥的算法!

欧拉回路和欧拉路径

哈密尔顿回路

从一个点出发, 经过每个点一次,回到原点

欧拉回路

从一条点出发, 经过每一条边一次, 回到原点

// 离散数学知识, 不会考

状态压缩

无权图可以直接使用来存储图

最小生成树

Kruskal 需要用到并查集判断有无环, 因此先列一下!

并查集

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的问题。

example:

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;
随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。
为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。

https://blog.youkuaiyun.com/niushuai666/article/details/6662911

#include  <stdio.h>
using namespace std;
int pre[1010];  //里面全是掌门

int unionsearch(int root) {
  int son, tmp;
  son = root;
  while (root != pre[root])  //寻找掌门ing……
    root = pre[root];
  while (son != root) {  //路径压缩
    tmp = pre[son];
    pre[son] = root;
    son = tmp;
  }
  return root;  //掌门驾到~
}
int main() {
  int num, road, total, i, start, end, root1, root2;
  while (scanf("%d%d", &num, &road) , num) {
    total = num - 1;            //共num-1个门派
    for (i = 1; i <= num; ++i)  //每条路都是掌门
      pre[i] = i;
    while (road--) {
      scanf("%d%d", &start, &end);  //他俩要结拜
      root1 = unionsearch(start);
      root2 = unionsearch(end);
      if (root1 != root2) {  //掌门不同?踢馆!~
        pre[root1] = root2;
        total--;  //门派少一个,敌人(要建的路)就少一个
      }
    }
    printf("%d\n", total);  //天下局势:还剩几个门派
  }
  return 0;
}
Kruskal

首先排序, 从最小的开始选取, 只要不构成环加入!

int isCircle(int root, int pre[]) {
  int tmp, son = root;
  while (pre[root] != root) root = pre[root];
  while (son != root) {
    tmp = pre[son];
    pre[son] = root;
    son = tmp;
  }
  return root;
}
void kruskal() {
  vector<Edge *> edge(edges);
  sort(edge.begin(), edge.end(),
       [](Edge *a, Edge *b) { return a->value < b->value; });
  int pre[len] = {0};  //用作并查集的父节点
  for (int i = 0; i < len; i++) pre[i] = i;
  for (int i = 1; i < len; i++) {
    int a = isCircle(edge[i]->start, pre);
    int b = isCircle(edge[i]->end, pre);
    if (a != b) {  // 不是环
      cout << edge[i]->start << " " << edge[i]->end << endl;
      pre[b] = a;  // 合并
    }
  }
}
prim

选择离 已扩充节点集合 距离最小的作为扩充点!

void prime() {
  int dis[len] = {INF};  // 关键 !!!!!
  int p[len] = {0};
  for (int i = 0; i < len; i++) dis[i] = INF;
  bool visited[len] = {0};
  for (int i = 0; i < len; i++) {
    int index, min = -1;
    for (int j = 0; j < len; j++) {
      if (!visited[j] && (min == -1 || dis[j] < min)) {
        min = dis[j];
        index = j;
      }
    }
    visited[index] = 1;
    cout << p[index] << " " << index << endl;
    if (Graph4[p[index]][index] != 0) addNode(root1, p[index], index);
    for (int j = 0; j < len; j++) {
      if (Graph4[index][j] != 0 && dis[j] > Graph4[index][j]) {
        dis[j] = Graph4[index][j];
        p[j] = index;
      }
    }
  }
  cout << "the tree is : \n";
  printTree();
}

最短路径

dijkstra和单源最短路径

无负数, 一直选取当前未访问的最近的节点作为扩展!

void dijkstra(int start) {
  bool visited[len] = {0};
  int dis[len];
  memset(dis, 0x3f, sizeof(int) * len);
  dis[start] = 0;
  for (int i = 0; i < len; i++) {
    int index = -1;
    for (int j = 0; j < len; j++) 
        //选取当前未访问的最近的点作为扩展
      if (!visited[j] && (index == -1 || dis[index] > dis[j])) 
        index = j;
    visited[index] = 1;
    for (int j = 0; j < len; j++) 
      if (Graph4[index][j] != 0 && dis[index] + Graph4[index][j] < dis[j]) 
        dis[j] = dis[index] + Graph4[index][j];
  }
  cout << "dijkstra: " << endl;
  cout << INF <<endl;
  for (int i = 0; i < len; i++)
    cout << start << " to " << i << " len: " << ((dis[i] == INF)? -1 : dis[i]) << endl;
}
Bellman-Ford

迭代n次, 每次按边来缩短dis, 如果某一次迭代没有变化,停止迭代!

void ford(int start) {
  int dis[len];
  memset(dis, 0x3f, sizeof(int) * len);
  dis[start] = 0;

  for (int i = 0; i < len; i++) {  // 循环  len 次
    for (int j = 0; j < edges.size(); j++) { // 对于每一条边
      if (dis[edges[j]->start] + edges[j]->value < dis[edges[j]->end]) {
        dis[edges[j]->end] = dis[edges[j]->start] + edges[j]->value;
      }
    }
  }
    cout << "ford:\n";
  for (int i = 0; i < len; i++)
    cout << start << " to " << i << " len: " << ((dis[i] == INF) ? -1 : dis[i])
         << endl;
}
Floyed-Warshall

所有点对最短路径, 动态规划, 子的状态直接用在

void floyed() {
  int dis[len][len];
  memset(dis, 0x3f, sizeof(int) * len * len);
  for (int i = 0; i < edges.size(); i++)
    dis[edges[i]->start][edges[i]->end] = dis[edges[i]->end][edges[i]->start] =
        edges[i]->value;
  for (int i = 0; i < len; i++) {
    for (int j = 0; j < len; j++) {
      for (int k = 0; k < len; k++) {
        if (i != j && i != k && j != k) dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
      }
    }
  }
  cout << "floyed:\n";
  for (int i = 0; i < len; i++) {
    for (int j = 0; j < len; j++) {
      if (dis[i][j] != INF)
        cout << i << " to " << j << " is " << dis[i][j] << ";  ";
    }
    cout << endl;
  }
}

拓扑排序

一.定义

对一个有向无环图(Directed Acyclic Graph, DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。

通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。

注意:

1)只有有向无环图才存在拓扑序列;

2)对于一个DAG,可能存在多个拓扑序列;

二.拓扑序列算法思想

(1)从有向图中选取一个没有前驱(即入度为0)的顶点,并输出之;

(2)从有向图中删去此顶点以及所有以它为尾的弧;

重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止。

void DAG() {
  printG2();
  queue<int> q;
  for (int i = 0; i < len; i++)
    if (in_cnt[i] == 0 && Graph2[i].size() != 0) q.push(i);
  int count = 0;
  cout << "start topology:\n";
  while (!q.empty()) {
    int a = q.front();
    q.pop();
    count++;
    cout << a << endl;
    for (int i = 0; i < Graph2[a].size(); i++)
      if (!--in_cnt[Graph2[a][i]->to]) q.push(Graph2[a][i]->to);
  }
  cout << "count: " << count << endl;
}

以下用的比较少, 不做总结!

网络流和最大流

Ford-Fulkerson
Edmonds-Karp
匈牙利算法

BFS

DFS

### 图论算法总结与例题 图论是一门研究图形结构及其性质的学科,在计算机科学中有广泛的应用。以下是几种常见的图论算法以及它们的相关例题。 #### 1. **广度优先搜索 (BFS)** 和 **深度优先搜索 (DFS)** 这两种基本的遍历方法可以用来解决许多基础问题,比如连通分量检测、径寻找等。 - BFS通常适用于找到两点间的最短径(无权图),其核心思想是以层次的方式扩展节点[^2]。 - DFS则常用于探索所有可能的状态或者回溯解决问题。 #### 2. **并查集** 并查集是一种处理动态集合的数据结构,主要用于快速判断两个元素是否属于同一个集合,并支持高效的合并操作。 - 并查集的主要功能包括查询和合并两部分。 #### 3. **最小生成树 (MST)** 最小生成树是指在一个加权连通图中找出一棵总权重最小的生成树。 - Prim算法通过不断选择离当前已构建的部分最近的顶点来逐步扩大这棵树[^1]。 - Kruscal算法则是按照边的权重从小到大依次考虑每边,只要该边不会形成环就将其加入结果集中。 #### 4. **拓扑排序** 对于有向无环图(DAG),可以通过拓扑排序得到一个线性的顺序使得所有的前驱关系都满足。 - 方法通常是先统计入度再利用队列实现零入度节点移除的过程直到完成整个序列排列。 #### 5. **最短径算法** 这些算法旨在计算从某个起点到达其他各点所需的最少成本。 - Dijkstra算法适合于非负权值的情况,它采用贪心策略每次挑选距离源点最近尚未访问过的节点作为新的候选者继续扩散影响范围[^3]。 - Bellman-Ford允许存在负权但需额外注意可能出现的负圈情况;SPFA是对前者的一种改进形式特别针对稀疏图表现更优[^4]。 - Floyd-Warshall能够一次性得出任意两点间最佳线长度非常适合小型密集网络分析需求。 #### 实际应用举例 假设我们要设计一款游戏地图编辑器需要自动规划NPC巡逻线,则可选用A*启发式寻径技术结合实际地形数据综合考量效率与合理性给出解决方案。 ```python import heapq def dijkstra(graph, start): distances = {node: float('infinity') for node in graph} distances[start] = 0 priority_queue = [(0, start)] while priority_queue: current_distance, current_node = heapq.heappop(priority_queue) if current_distance > distances[current_node]: continue for neighbor, weight in graph[current_node].items(): distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances ``` 以上代码片段展示了如何使用Python实现Dijkstra算法以求解给定图上某一点至其余各点之间的最短径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值