一些概念的介绍。
图的表示
- 稀疏图(顶点数大于边数), 用邻接表(邻接链表数组, e.g A[2]里面放的是一个链表,是和节点2相连的节点)
- 稠密图(顶点数小于边数), 用邻接矩阵 (n个接点,nxn个矩阵,假如i和j相连,A【i】【j】, A【j】【i】 为true)
图的分类
- 有向/无向
- 加权/不加权
上面两两组合, 所以一共可以有4种图
图的邻接表 表示
一般给的输入是edges= [[1,0],[2,0],[3,1],[3,2]] 和 节点个数 num
1) 无向图
a) 给了节点个数
vector<vector<int>> adjs(num);
for (auto &edge: edges)
{
int i = edge[0], j = edge[1];
adjs[i].push_back(j);
adjs[j].push_back(i);
}
adjs【i】 是一个vector, 里面存着节点编号为i的 所有边的 除i以外另一个节点编号
b) 没给节点个数
unordered_map<int, vector<int>> adjs;
2)有向图
struct Edge
{
Edge(int val, int from, int to): val(val), from(from), to(to) {}
int val; // 有权 要这个数据成员, 无权图不要
int from, to;
};
a) 给了节点个数用 vector<vector<Edge>> adjs(num);
b) 没给节点个数用 unordered_map<int, vector<Edge>> adjs;
DFS
基本思路:
不撞南墙不回头.寻找离起点最远的顶点,只在碰到死胡同时才回来
用了一个下压栈结构,获取下一个顶点的方式是最晚加入的顶点
用途:
节点A和B之间的可达性, 并且给出一条路径
非递归伪代码
深度优先遍历有「回头」的过程,在树中由于不存在「环」(回路),对于每一个结点来说,每一个结点只会被递归处理一次。而「图」中由于存在「环」(回路),就需要 记录已经被递归处理的结点(通常使用布尔数组或者哈希表),以免结点被重复遍历到。
// 使用邻接表 adjs 表示图的 dfs 递归版代码
void dfs(int cur, vector<vector<int>> &adjs, vector<bool> &visited)
{
visited[cur] = true; // 设置当前节点已经访问过了
for (int i: adjs[cur]) // 遍历cur节点的所有邻接点(就是跟cur会组成边的节点)
{
if (!visited[i])
dfs(i);
}
}
void dfs(int cur, vector<vector<int>> &adjs, vector<bool> &visited)
{
stack<int> stk;
stk.push(cur);
visited[root] = true;
cout << root<<endl; // 一进来就入栈并打印
while (stk.size())
{
cur = stk.top();
stk.pop();
for (int i: adjs[cur])
{
if (!visited[i])
{
cout << i << endl;
stk.push(cur);
stk.push(i);
visited[i] = true;
}
}
}
}
BFS(广度优先搜索)
基本思路:
一圈一圈的扫荡。首先扫荡距离起点为1的点,然后扫荡距离起点为2的点,按照与起点的距离的顺序来遍历所有的顶点
基本实现:
用一个队列,先把起点0入队,标记0 visited[0]=1,然后循环条件当队列不为空的时候, 先出队一个节点, 遍历它的邻接点w,假如没被标记过,标记,入队到队尾。(假设要求最短路径,设置path[w] = v)
用途:
节点A和B之间的可达性, 并且给出最短的路径
void bfs(int cur, vector<vector<int>> &adjs, vector<bool> &visited)
{
queue<int> que;
visited[cur] = true;
queue.push(cur);
while (!que.empty())
{
cur = que.front();
que.pop();
cout << cur<< endl; // 访问该节点
for (int i: adjs[cur]) // 访问该节点的所有邻接点
{
if (!visited[i]) //假如没有被访问过
{
que.push(i);
visited[i] = true;
}
}
}
}
有无环
-
无向图
- 并查集
- 有向图
- 123
拓扑排序
针对有向无环图
概念:
首先必须是有向无环图才能有拓扑排序. 将所有顶点排序,保证所有的有向边都是排在前面的顶点指向后面的顶点。
解决方法:
1)Kahn算法:
用队列实现,入度为0的顶点全都入队(顺序不重要), 然后进行循环,条件队列不为空,第一个元素出队,同时设为已访问且放进一个数组A里面,对这个元素的所有邻接点入度减1,假如入度为0 了,就入队,退出循环之后,遍历所有点,看有没有没访问的,有就是有环,也就没有拓扑排序,没有那就输出数组A,就是拓扑排序.
经典题目:
最小生成树(MST)
针对加权连通无向图。
概念:
首先只有连通图(从图的任意一个顶点能到任意另一个顶点)才存在最小生成树.
生成树是图的一颗含有其所有顶点的无环连通子图,最小生成树,就是权值和最小的生成树
解决方法:
1)prim算法
2)kruskal算法
1) Kruskal算法
基本思路:
1) 所有边由小到大排序
2) 取最小的边,假如不会和已经在最小生成树中的边形成一个环,那就把这条边加到最小生成树里面,,假如会形成环,跳过
3)直到有 v-1个边为止
具体方法:最小堆把边排序,并查集识别会形成环的边,队列保存MST结果
时间复杂度: O(ElogE) E 表示边数。
空间复杂度: O(V)。V 表示顶点数。
2) prim算法
最小路径树
针对加权有向图
解决方法:
1)Dijkstra算法 (限制权值非负)
2)Bellman-Ford算法 (适用于所有情况,权值可以负,可以有环,但别有负权重环,存在负权重环,最短路径树就不存在了)