前言
图的难点在于图的表示方法很多,要在每种表示方法上实现各种算法。解决办法:将所有图转换成自己最熟悉的表示方法,然后在该方法上是实现图的各种算法。
题目给的图—>熟悉的图—>各种算法(提前实现)。
1.图的存储方式
(1)邻接表法:
(2)邻接矩阵法:
(3)图的数据结构
class Node;//两个类相互包含,需要提前声明一下
class Edge {
public:
int weight;
Node* from;//有向图
Node* to;
Edge(int weight, Node* from, Node* to) {
this->weight = weight;
this->from = from;
this->to = to;
}
};
class Node {
public:
int val;
int in;//入度
int out;//出度
vector<Node*> nexts;
vector<Edge*> edges;//又当前点发散出去的边
Node(int val) {
this->val = val;
in = 0;
out = 0;
}
};
class Graph {
public:
unordered_map<int, Node*>nodes;//key:点的编号;value:实际的点
unordered_set<Edge*>edges;
};
2.图的遍历
1.引入库
图可能优化,需要一个机制使得不会变成死循环
(1)图的宽度优先遍历
对于3,用一个set来记录已经进入过队列的点,有时候也可以用数组来替换set(常熟时间更快)
代码实现:
//宽度优先遍历
void bfs(Node* n) {
if (n == nullptr) {
return;
}
queue<Node*>q;
unordered_set<Node*>st;
q.push(n);
st.insert(n);
while (!q.empty()) {
Node* cur = q.front();
q.pop();
cout << cur->val << endl;
for (Node* next : cur->nexts) {
if (st.find(next) == st.end()) {
q.push(next);
st.insert(next);
}
}
}
}
(2)图的深度优先遍历
bfs时是在出队列时打印(处理),dfs是在入栈时打印(处理)
代码实现:
//深度优先遍历
void dfs(Node* n) {
if (n == nullptr) {
return;
}
stack<Node*>sk;
unordered_set<Node*>st;
sk.push(n);
st.insert(n);
cout << n->val << endl;
while (!sk.empty()) {
Node* cur = sk.top();
sk.pop();
for (Node* next : cur->nexts) {
if (st.find(next) == st.end()) {
sk.push(cur);
sk.push(next);
st.insert(next);
cout << next->val << endl;
break;
}
}
}
}
3.拓扑排序算法
适用范围:要求有向图,且有入度为0的节点,且没有环
先找入度为0的点A,将A和A的影响擦除,再找剩下入度为0的点……
代码实现:
//拓扑排序
vector<Node*> sortedTopology(Graph gra) {
unordered_map<Node*, int>inMap;//记录点的剩余入度
queue<Node*>zeroIn;//入度为0的点
for (pair<int,Node*> n : gra.nodes) {
inMap.insert({ n.second,n.second->in });
if (n.second->in == 0) {
zeroIn.push(n.second);
}
}
vector<Node*>res;
while (!zeroIn.empty()) {
Node* cur = zeroIn.front();
zeroIn.pop();
res.push_back(cur);
for (Node* next : cur->nexts) {
inMap[next]--;
if (inMap[next] == 0) {
zeroIn.push(next);
}
}
}
return res;
}
4.生成最小生成树算法
生成最小生成树:保持联通,整体边的权值最小
(1)kruskal算法
适用范围:要求无向图
从边的角度来思考,每次寻找权值最小的边,看加上该边是否形成环,没有形成环就可以加到生成树中,否则不能加入
怎么判断是否形成环?
首先,假设所有的点独自在一个集合中,考虑新加入边是否形成环则看这条边的两个顶点是否在一个集合中,如果不在一个集合中则可以加入该边,之后将边的两个顶点所在的集合合并。集合的查询和合并—并查集
代码实现:
//(1)kruskal算法——并查集实现
class Cmp {
public:
bool operator()(Edge* e1,Edge* e2){
return e1->weight > e2->weight;
}
};
int findFather(vector<int>& fathers, int i) {
if (fathers[i] != i) {
fathers[i] = findFather(fathers, fathers[i]);
}
return fathers[i];
}
void unionFather(vector<int>fathers, int i, int j) {
int fatherOfI = fathers[i];
int fatherOfJ = fathers[j];
if (fatherOfI != fatherOfJ) {
fathers[fatherOfI] = fatherOfJ;
}
}
unordered_set<Edge*> kruskalMST(Graph gra) {
vector<int>fathers(gra.nodes.size());//fathers[i]:i编号节点的父节点的编号
for (pair<int, Node*> n : gra.nodes) {
fathers[n.first] = n.first;
}
priority_queue<Edge*, vector<Edge*>, Cmp>p;
for (Edge* e : gra.edges) {
p.push(e);
}
unordered_set<Edge*>res;
while (!p.empty()) {
Edge* e = p.top();
p.pop();
if (findFather(fathers, e->from->val) != findFather(fathers, e->to->val)) {
res.insert(e);
unionFather(fathers, e->from->val, e->to->val);
}
}
return res;
}
(2)prim算法
适用范围:要求无向图
从点的角度来思考,从任意一个点A开始,A的边被解锁,选择其中权值最小的边(已经被选择,后续不在考虑),来到该边的另一个点B,B的边被解锁,从所有已解锁的边中选择权值最小的边(该边的两个点都没有被加进来),来到该边的另一个点F,……
代码实现:
//(2)prim算法
//可能边重复进队列,当不影响结果
unordered_set<Edge*> primMST(Graph gra) {
priority_queue<Edge*, vector<Edge*>, Cmp>p;//存放已解锁的边
unordered_set<Node*>st;//存放已拉入进来的点
unordered_set<Edge*>res;
for (pair<int, Node*> n : gra.nodes) {处理森林的问题
if (st.find(n.second) != st.end()) {
st.insert(n.second);
for (Edge* e : n.second->edges) {
p.push(e);
}
while (!p.empty()) {
Edge* e = p.top();
p.pop();
if (st.find(e->to) != st.end()) {
st.insert(e->to);
res.insert(e);
for (Edge* nextE : e->to->edges) {
p.push(nextE);
}
}
}
}
}
return res;
}
5.单元最短路径算法—Dijkstra算法
适用范围:不存在累加权值为负的环,如果有这样的环,每次增益为负,不停转圈圈,到任何节点的距离都能任意小
规定一个出发点,从该出发点到其他点的最短距离
初始化一个节点到其他节点距离表,该表到自己的距离为0,到其他节点的距离都为正无穷,每一次在表中选择距离最小的点A,考察从A出发的边能否使得起始点到其他点的距离变小,如果存在这样的边,则更新距离表,使用完A之后,A到起始点的距离确定,不再修改;再选择次小的点……
代码实现:
//dijkstral
Node* getMinNode(unordered_map<Node*, int>distanceMap, unordered_set<Node*>selectedNodes) {
Node* minNode = nullptr;
for (pair<Node*, int>n : distanceMap) {
if (selectedNodes.find(n.first) == selectedNodes.end()) {
if (minNode != nullptr) {
minNode = n.second < distanceMap[minNode] ? n.first : minNode;
}
else {
minNode = n.first;
}
}
}
return minNode;
}
unordered_map<Node*, int>dijkstral(Node* head) {
unordered_map<Node*, int>distanceMap;//其他节点到head的最小距离
unordered_set<Node*>selectedNodes;//已经锁死的节点
distanceMap.insert({ head, 0 });
Node* minNode = getMinNode(distanceMap, selectedNodes);
while (minNode != nullptr) {
int minDistance = distanceMap[minNode];
for (Edge* e : minNode->edges) {
if (selectedNodes.find(e->to) == selectedNodes.end() && (distanceMap.find(e->to)==distanceMap.end()||distanceMap[e->to] > minDistance + e->weight)) {
distanceMap[e->to] = minDistance + e->weight;
}
}
selectedNodes.insert(minNode);
minNode = getMinNode(distanceMap, selectedNodes);
}
return distanceMap;
}