图
图
基本分类:有向图、无向图、加权图
本节所有的算法都是针对于图的基本用法阐述的,关于其他的延伸的比如DFS的回溯和剪枝在此不做讲解
并查集
此处介绍的是一种数据结构
解决问题:图的连通性问题
最终版本(find quick、union quick、按秩合并、基于路径压缩我就不在此一一介绍了,直接给出最终的优化版本吧)
class UnionFind {
public:
void UnionFind(int size) {
root = new int[size];
rank = new int[size];
for (int i = 0; i < size; i++) {
root[i] = i;
rank[i] = 1;
}
}
int find(int x) {
if (x == root[x]) {
return x;
}
return root[x] = find(root[x]);
}
void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] > rank[rootY]) {
root[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
root[rootX] = rootY;
} else {
root[rootY] = rootX;
rank[rootX] += 1;
}
}
};
bool connected(int x, int y) {
return find(x) == find(y);
}
private:
int* root;
int* rank;
}
leetcode547
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
if (isConnected.size() == 0) {
return 0;
}
int n = isConnected.size();
UnionFind* uf = new UnionFind(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (isConnected[i][j] == 1) {
uf->unionFunc(i, j);
}
}
}
return uf->getCount();
}
class UnionFind {
public:
UnionFind(int size) {
root = new int[size];
rank = new int[size];
count = size;
for (int i = 0; i < size; i++) {
root[i] = i;
rank[i] = 1;
}
}
int find(int x) {
if (x == root[x]) {
return x;
}
return root[x] = find(root[x]);
}
void unionFunc(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] > rank[rootY]) {
root[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
root[rootX] = rootY;
} else {
root[rootY] = rootX;
rank[rootX] += 1;
}
count--;
}
};
int getCount() {
return count;
}
private:
int* root;
int* rank;
int count;
};
};
DFS(deep first search)
BFS(breath first search)
应用:最短路径、遍历图
遍历
用队列(queue),不做概述
最短路径
找出两点间的最短路径的前提条件:
- 权重相等且为正数
- 最短路径(第一次取到的目的数肯定是最短路径)
DFS也可以找出最短路径,不过其必须递归完所有的路径后才能找出最短路径,相比之倒BFS倒显得上乘了
来看一个BFS求所有路径的题目leetcode797
class Solution
{
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph)
{
vector<vector<int>> res;
queue<vector<int>> q;
q.push({ 0 });
while (!q.empty()) {
vector<int> con = q.front();
q.pop();
for (int i = 0; i < graph[con[con.size() - 1]].size(); i++) {
con.push_back(graph[con[con.size() - 1]][i]);
if (con[con.size() - 1] == graph.size() - 1) {
res.push_back(con);
}
q.push(con);
con.pop_back();
}
}
return res;
}
};
最小生成树
Kruskal
适用场景:加权无向图,求最少权重的全部顶点问题
算法思想:
- 将所有边一次排序
- 依次加入最小生成树,有环则跳过
- 查找选择n-1条边为止
leetcode1584
class Solution {
public:
static bool cmp(const vector<int>& m, const vector<int>& n) {
return m[2] < n[2];
}
int minCostConnectPoints(vector<vector<int>>& points) {
if (points.size() == 0) {
return 0;
}
vector<vector<int>> con;
for (int i = 0; i < points.size(); i++) {
for (int j = i + 1; j < points.size(); j++) {
vector<int> v = {
i, j, (int)fabs(points[i][0] - points[j][0]) + (int)fabs(points[i][1] - points[j][1])
};
con.push_back(v);
}
}
sort(con.begin(), con.end(), cmp);
int ans = 0;
int count = points.size() - 1;
UnionFind* uf = new UnionFind(count + 1);
int i = 0;
while (count > 0 && con.size() > 0) {
if (!uf->isConnected(con[i][0], con[i][1])) {
uf->unionFunc(con[i][0], con[i][1]);
ans += con[i][2];
count--;
}
i++;
}
return ans;
}
class UnionFind {
public:
UnionFind(int n) {
root = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
root[i] = i;
rank[i] = 1;
}
}
int find(int x) {
if (root[x] == x) {
return x;
}
return root[x] = find(root[x]);
}
void unionFunc(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (root[rootX] > root[rootY]) {
root[rootY] = rootX;
}
else if (root[rootY] > root[rootX]) {
root[rootX] = rootY;
}
else {
root[rootY] = rootX;
rank[rootX] += 1;
}
}
}
bool isConnected(int x, int y) {
return find(x) == find(y);
}
private:
int* root;
int* rank;
};
};
Prim
适用场景:加权无向图,以点为中心展开选择最小边
单源最短路径
Dijkstra
针对题型:加权有向图,权值为正
算法思想:以起点为中心,逐步向外扩展并更新其他路径的最短路径,运用贪心思想,运行的每一步都是选择当前已知顶点的最小权重去寻找其他顶点
Bellman-Ford
针对题型:加权有向图,权值任意
算法思想:易知当图中有多条边时,要求两点之间最短路径的最多经过n-1条边,动态规划的思想,当前边n的最短路径由n-1条边的最短路径求得,依次遍历0~n-1,即可求大最短路径
leetcode787
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
if (src == dst) {
return 0;
}
int* previous = new int[n];
int* current = new int[n];
for (int i = 0; i < n; i++) {
previous[i] = INT_MAX;
current[i] = INT_MAX;
}
previous[src] = 0;
for (int i = 1; i < k + 2; i++) {
current[src] = 0;
for (vector<int> con: flights) {
int previous_flight = con[0];
int current_flight = con[1];
int cost = con[2];
if (previous[previous_flight] < INT_MAX) {
current[current_flight] = min(current[current_flight], previous[previous_flight] + cost);
}
}
for (int i = 0; i < n; i++) {
previous[i] = current[i];
}
}
return current[dst] == INT_MAX ? -1 : current[dst];
}
};
拓扑排序
前提条件:有向无环图
Kahn算法
算法思想:让度为0的结点入队,出队后让其指向的结点度减1,如果其度为0入队,直到队列为空
入度:有多少个箭头指向他
leetcode210
class Solution {
public:
vector<int> findOrder(int n, vector<vector<int>>& pre) {
// 邻接表
vector<vector<int>> graph(n, vector<int>());
vector<int> ans, ind(n, 0);
for (auto& i : pre) {
graph[i[1]].push_back(i[0]);
++ind[i[0]];
}
queue<int> st;
for (int i = 0; i < n; ++i) {
if (!ind[i]) st.push(i);
}
// 判断是否有环
int cnt = 0;
while (!st.empty()) {
int k = st.front(); st.pop();
ans.push_back(k);
++cnt;
for (auto i : graph[k]) {
--ind[i];
if (!ind[i]) {
st.push(i);
}
}
}
if (cnt != n) return {};
return ans;
}
};
Summary:
- 并查级:求结点是否连通性
- 最小生成树:所有结点重组成新树,要求边最小
- 单源最短路径:解决有权图的最短路径问题
- 拓扑排序:结点的先后次序
刷题之路艰难险阻,各位珍重。