在听完左神 的算法课后,历时三天,用C++实现了图结构的相关算法,代码中尽可能包含C++11标准的新功能,尽可能使用STL模板库以达到熟悉C++精髓的目的。
代码分为三部分 :图的CODE,一个简单的并查集CODE,主调函数CODE示例;
/*****************Code(Graph.h)*******************/
#pragma once
//IO_lib
#include<iostream>
#include<stdio.h>
//Containers
#include<unordered_set>//哈希表
#include<unordered_map>
#include<list>//链表
#include<set>//有序表
#include<queue>//队列 /优先级队列(小根堆 大根堆)
#include<stack>//堆栈
//Other
#include<cstdint>//用INT16_MIN等宏
#include<memory> //未用到,可以用智能指针 就不需要在重写析构函数了
#include<algorithm>//用了sort
//一个幼稚的并查集 用于最小生成树K算法
#include"UnionFind_easy.h"
using namespace std;
class Graph {
public:
class Edge;//declaration for Class Edge
class Node {
public:
int value;
int in = 0;
int out = 0;
list<Node*> nexts;//不需要新建 直接就是动态的 也可以理解为 初始化之后 就是一个空指针 按照指针增减数据
list<Edge*> edges;
Node(int v) :value(v) {}
};
class Edge
{
public:
Node* from = nullptr; //指针 指向的Node 归(unordered_map<int, Node*>*) nodes管理
Node* to = nullptr;
int weight = 0;
Edge() {};
Edge(Node* f, Node* t,int w):from(f),to(t),weight(w){}
};
//Node and Edge在编译阶段不能给出实例化 因此unordered_set/map需要用指针的形式
unordered_map<int, Node*>* nodes;
unordered_set<Edge*>* edges;
//Graph(unordered_map<int, Node*>& n, unordered_set<Edge*>& e) :nodes(n), edges(e) {}
Graph() {
nodes = new unordered_map<int, Node*>;
edges = new unordered_set<Edge*>;
}
~Graph() {
for (auto p : *nodes)
delete p.second;
for (auto p : *edges)
delete p;
delete nodes;
delete edges;
}
//接口构造函数
Graph(vector<vector<int> >& arr, bool turned=false);
static inline void clc() { while (getchar() != '\n')continue; }
void input_node(int value = INT16_MIN);
void input_edge(int from = INT16_MIN, int to = INT16_MIN, int weight = INT16_MIN);
void input_TurnedEdge(int from = INT16_MIN, int to = INT16_MIN, int weight = INT16_MIN);
friend ostream& operator<<(ostream& os, Graph& g);
friend ostream& operator<<(ostream& os, Edge* e);
class CMP {
bool up;//是否升序
public:
CMP(bool c=true) :up(c) {};
bool operator()(Edge* p1, Edge* p2) {
if(up) return p1->weight < p2->weight;
else return p1->weight > p2->weight;
}
};
void EdgeSort(vector<Edge*>& ordered_edges);
/*algorithm for Graph*/
void DepthFirst_Traveral(int begin);
void BreadthFirst_Traveral(int begin);
void Topu();
class prior {//优先级队列 是大根堆 小于为true是正向断言 因此最小生成树 要用大于为ture
public:
bool operator()(Graph::Edge* p1, Graph::Edge* p2) {
return p1->weight > p2->weight;
}
};
void MiniScanTree_Prmi();
void MiniScanTree_Kruskal();
void MiniPath_Dijkstra(int begin);
};
/*****************Code(Graph.cpp)*******************/
//重载图的输出运算符
ostream& operator<<(ostream& os, Graph& g) {
/*点集输出*/
os << "Nodes:\n";
for (auto p : (*g.nodes)) {
os << "node( " << p.first << " )\tin: " << p.second->in << " \tout: " << p.second->out << "\n\tnexts:";
for (auto q : p.second->nexts)
os << '\t' << q->value;
os << "\n\n";
}
/*边集输出*/
os << "Edges:\n";
int i = 1;
for (auto p : (*g.edges))
os << "edge( " << i++ << " )\tfrom " << p->from->value << \
" \tto " << p->to->value << "\tweight:\t" << p->weight << '\n';
return os;
}
//边按权重排序(用Edge*向量容器装排序结果) 用于最小生成树K算法
void Graph::EdgeSort(vector<Edge*>& ordered_edges) {
ordered_edges = vector<Edge*>(edges->begin(), edges->end());
sort(ordered_edges.begin(), ordered_edges.end(), CMP(true));
/*结果可视化*/
//int i = 1;
//puts(" ");
//for (auto p : ordered_edges)
// cout << "ordered_edge( " << i++ << " )\tfrom " << p->from->value << \
// " \tto " << p->to->value << "\tweight:\t" << p->weight << '\n';
/*结果可视化*/
}
//图 节点输入 参数缺省时 键入
void Graph::input_node(int value) {
if (value == INT16_MIN) {
puts("input node_value: [Quit(-1)]");
while ((!scanf("%d", &value) || value < 0) && value != -1) {
clc();
puts("bad input,try again:");
}
clc();
}
if (nodes->find(value) != nodes->end()){
//printf("Node: %d is existed!\n", value);
}
else {
Node* pn = new Node(value);
nodes->emplace(value, pn);
//printf("Node: %d is added!\n", value);
}
}
//图 有向边输入 参数缺省时 键入
void Graph::input_TurnedEdge(int from, int to, int weight) {
if (weight == INT16_MIN) {
{//from
puts("input from node value: [Quit(-1)]");
while (!scanf("%d", &from) || from < -1) {
clc();
if (from == -1)return;
puts("bad input,try again:");
}
clc();
}
{//to
puts("input to node value: [Quit(-1)]");
while (!scanf("%d", &to) || to < -1) {
clc();
if (to == -1)return;
puts("bad input,try again:");
}
clc();
}
{//weight
puts("input weight: [Quit(-1)]");
while (!scanf("%d", &weight)) {
clc();
puts("bad input,try again:");
}
clc();
}
}
if (nodes->find(from) == nodes->end())
input_node(from);
if (nodes->find(to) == nodes->end())
input_node(to);
Node* f = nodes->at(from);
Node* t = nodes->at(to);
Edge* pe = new Edge(f, t, weight);
//因为hash map (edges)中存放的是指针,且指针在函数结束后销毁,
//因此不能用指向相同类的指针去查询是否存在指向该类的指针,因为指向相同
//类的两个指针不同,因此遍历指针,对比指向的数据
bool exist = false;
for (auto& p :(*edges)) {
if (p->weight == weight && p->from == f && p->to == t) {
exist = true;
break;
}
}
if (!exist) {// 如果pe存在 则不会插入
edges->emplace(pe);
f->edges.push_back(pe);
f->nexts.push_back(t);
f->out++;
t->in++;
//printf("Edge: from %d to %d is added! ( weight=%d )\n", from, to, weight);
}
//else printf("Edge: from %d to %d is existed! ( weight=%d )\n", from, to, weight);
}
//图 无向边输入 参数缺省时 键入
void Graph::input_edge(int from, int to, int weight) {
if (weight == INT16_MIN) {
{//from
puts("input from node value: [Quit(-1)]");
while (!scanf("%d", &from) || from < -1) {
clc();
if (from == -1)return;
puts("bad input,try again:");
}
clc();
}
{//to
puts("input to node value: [Quit(-1)]");
while (!scanf("%d", &to) || to < -1) {
clc();
if (to == -1)return;
puts("bad input,try again:");
}
clc();
}
{//weight
puts("input weight: [Quit(-1)]");
while (!scanf("%d", &weight)) {
clc();
puts("bad input,try again:");
}
clc();
}
}
if (nodes->find(from) == nodes->end())
input_node(from);
if (nodes->find(to) == nodes->end())
input_node(to);
Node* f = nodes->at(from);
Node* t = nodes->at(to);
Edge* pe = new Edge(f, t, weight);
bool exist = false;
for (auto& p:(*edges) ) {
if (p->weight == weight && (p->from == f && p->to == t ||
p->from == t && p->to == f)) {
exist = true;
break;
}
}
if (!exist) {// 如果pe存在 则不会插入
edges->emplace(pe);
f->edges.push_back(pe);
f->nexts.push_back(t);
f->out++;
f->in++;
t->edges.push_back(pe);
t->nexts.push_back(f);
t->out++;
t->in++;
//printf("Edge: from %d to %d is added! ( weight=%d )\n", from, to, weight);
}
//else printf("Edge: from %d to %d is existed! ( weight=%d )\n", from, to, weight);
}
//图 二维数组/链表 to 点集/边集 的接口构造函数
Graph::Graph(vector<vector<int> >& arr, bool turned) :Graph() {
if (turned)
for (auto p : arr)
input_TurnedEdge(p[1], p[2], p[0]);
else
for (auto p : arr)
input_edge(p[1], p[2], p[0]);
}
//图 广度优先遍历
void Graph::BreadthFirst_Traveral(int begin) {
puts("\nBreadthFirst_Traveral:");
auto f = nodes->at(begin);
queue<Node*> q;
unordered_set<Node*> s;
q.push(f);//出发节点进队列
s.insert(f);//出发节点进hashset 防止重复处理
while (!q.empty()) {
Node* n = q.front();
q.pop();
printf("node(%d)\t", n->value);
for (auto next : n->nexts)
if (s.find(next) == s.end()) {
q.push(next);
s.insert(next);
}
}
puts(" ");
}
//图 深度优先遍历
void Graph::DepthFirst_Traveral(int begin) {
puts("\nDepthFirst_Traveral:");
auto f = nodes->at(begin);//检查存在
stack<Node*> q;
unordered_set<Node*> s;
q.push(f);//出发节点进堆栈
s.insert(f);//出发节点进hashset 防止重复处理
printf("node(%d)\t", f->value);//第一次进栈时候打印
while (!q.empty()) {
Node* n = q.top();
q.pop();
for (auto next : n->nexts)
if (s.find(next) == s.end()) {
q.push(n);//如果当前节点有next 则把当前节点放回
q.push(next);
s.insert(next);
printf("node(%d)\t", next->value);
break;
}
}
puts(" ");
}
//图 拓扑排序
void Graph::Topu() {
puts("\nTopu sort:");
Node* begin = nullptr;
unordered_map<Node*, int> inmap;
queue<Node*> zeroin;
for (auto& p :(*nodes)) {
inmap.emplace(p.second, p.second->in);//保存每个节点的入度
if (p.second->in == 0)
zeroin.push(p.second);
}
while (!zeroin.empty()) {
Node* cur = zeroin.front();
zeroin.pop();
printf("node(%d)\t", cur->value);
for (auto next : cur->nexts) {
inmap[next]= inmap[next] - 1;
if (!inmap[next])
zeroin.push(next);
}
}
puts("");
}
//重载<< 打印Edge*指向的边 (用来查看最小生成树的结果)
ostream& operator<<(ostream& os, Graph::Edge* e) {
os << "edge from\t" << e->from->value << "\tto\t" << e->to->value << endl;
return os;
}
//最小生成树 Prim
void Graph::MiniScanTree_Prmi() {
puts("\nMiniScanTree_Prmi:");
Node* B = (*nodes->begin()).second;
unordered_set<Node*> s;
priority_queue<Edge*, vector<Edge*>, prior> e;
s.insert(B);
for (auto p : B->edges)
e.push(p);
while (!e.empty()) {
Edge* minE = e.top();//当前节点的最小权重边
e.pop();
if (s.find(minE->from) == s.end() || s.find(minE->to) == s.end()) {//查看当前边是否冗余 from to 至少有一个是第一次到达的节点
Node* next = s.find(minE->from) == s.end() ? minE->from : minE->to;
s.insert(next);
cout << minE;
for (auto p : next->edges)//解锁minE->to的边(不包含已经使用过的minE) 因为同一个节点只能用一次
if (p != minE) //因此 同一条边只能被添加两次 pop掉第一次 跳过第二次 就相当于永久删除了这个边
e.push(p);
}
}
}
//最小生成树 Kruskal
void Graph::MiniScanTree_Kruskal() {
puts("\nMiniScanTree_Kruskal:");
UnionFind_easy<Node*> U(nodes);//创建了并查集
vector<Edge*> E;
EdgeSort(E);//获取边的权重升序排列
for (Edge* e : E) {//考察边
Node* from = e->from;
Node* to = e->to;
if (!U.IsSameSet(from, to)) {
cout << e;
U.Union(from, to);
}
}
}
//最短路径 参数:起始node
void Graph::MiniPath_Dijkstra(int begin) {//any edge not in a nagetive value loop
puts("\nMiniPath_Dijkstra:");
if (nodes->find(begin) == nodes->end())return;
Node* B = nodes->operator[](begin); //重载运算符是可以显示调用的
unordered_map<Node*, int> distance;
distance[B]= 0;
for (auto& x :(*nodes))
distance.emplace(x.second, INT16_MAX);// 这个地方需要用emplace方法,如果用[ ]会覆盖已经存在的pair,[]用来查询和修改更好
//至此 除了出发节点之外的节点 distance都是INT16_MAX
unordered_set<Node*> lock;
queue<Node*> q;
q.push(B);
while (!q.empty()) {
Node* cur = q.front();
q.pop();
if (lock.find(cur) == lock.end()) {//弹出的点没处理过
for (auto next : cur->nexts)//把这个点后面的点 放进队列(可能重复 没关系)
if (lock.find(next) == lock.end())
q.push(next);
int myD = distance[cur];
for (auto edge : cur->edges) {
Node* to = edge->from == cur ? edge->to : edge->from;//无向边 选出不是自己的那一边
if (lock.find(to) == lock.end()) {//如果没上锁 (边是可能指向处理过的点的 有了这句 避免做无用功)
int yourD = distance[to];
if (myD + edge->weight < yourD) //找到去出发节点更近的路 就更新距离
distance[to] = myD + edge->weight;
}
}
}
lock.insert(cur);
}
for (auto& x :distance)
cout << "node(" << x.first->value << ")\tdistance:\t" << x.second << endl;
}
/****UnionFind_Easy***/
#pragma once
#include<unordered_set>
#include<unordered_map>
using namespace std;
template <class T> //
class UnionFind_easy
{
unordered_map<T, unordered_set<T>*>* setmap;//每个节点Node* 对应其所在的 Node* 集合
public:
UnionFind_easy(){
setmap = new unordered_map<T, unordered_set<T>*>;
}
UnionFind_easy(unordered_map<int,T>* m):UnionFind_easy<T>(){
for (auto& x :(*m)) {
unordered_set<T>* s = new unordered_set<T>;
s->insert(x.second);
setmap->emplace(x.second, s);
}
}
~UnionFind_easy() {
unordered_set<unordered_set<T>*> d;//防止重复删除(并查集 setmap的map值中 会有重复的集合)
for (auto& x : (*setmap)) {
if (d.find(x.second) == d.end()) {
d.emplace(x.second);
delete x.second;
}
}
delete setmap;
}
bool IsSameSet(T n1, T n2) {
return setmap->operator[](n1) == setmap->operator[](n2);
}
void Union(T n1, T n2) {
auto m = *setmap;
unordered_set<T>* s1 =m[n1]; //假设 S1 {n1 n5} n1->S1 n5->S1
unordered_set<T>* s2 = m[n2]; // S2 {n2 n4} n2->S2 n4->S2
//for (auto x = s2->begin(); x != s2->end(); x++)
// s1->insert(*x); //S1{n1 n2 n4 n5} n1->S1 n5->S1
//setmap->erase(n2); //n1->S1 n5->S1 n2->S1 but n5?
/*修改*/
for (auto& x :(*s2)) {//把 s2中的每个node* 都复制到s1 并在map中将其指向s1
s1->insert(x);
setmap->operator[](x)= s1;
}
delete s2;
}
};
/*********MAIN*********/
#include "map.h"
int main() {
//接口函数测试数据 weight from to /最小生成树测试 no turned , reslut:1-3 2-3 2-5 3-6 4-6
vector<vector<int> > data(10, vector<int>(3));
data[0] = { 6,1,2 };
data[1] = { 1,1,3 };
data[2] = { 5,1,4 };
data[3] = { 5,2,3 };
data[4] = { 3,2,5 };
data[5] = { 5,3,4 };
data[6] = { 6,3,5 };
data[7] = { 4,3,6 };
data[8] = { 2,4,6 };
data[9] = { 6,5,6 };
//Topu数据 weight from to , turned result:4 2 3 1 5
vector<vector<int> > topu(7, vector<int>(3));
topu[0] = { 0,4,2 };
topu[1] = { 0,1,5 };
topu[2] = { 0,4,3 };
topu[3] = { 0,2,3 };
topu[4] = { 0,2,1 };
topu[5] = { 0,2,5 };
topu[6] = { 0,3,1 };
//最短路径测试数据 no turned result(begin=1):1 - 5{0 3 5 9 19}
vector<vector<int> > path(7, vector<int>(3));
path[0] = { 3,1,2 };
path[1] = { 15,1,3 };
path[2] = { 9,1,4 };
path[3] = { 2,2,3 };
path[4] = { 200,2,5 };
path[5] = { 7,4,3 };
path[6] = { 16,4,5 };
path[6] = { 14,3,5 };
//深度优先遍历 result(begin=1):1 2 5 7 6 3 4 ; 宽度优先遍历 result(begin=1): 1 2 3 4 5 6 7
//turned 深度优先遍历 result(begin=1):1 2 5 7 3 6 4
vector<vector<int> > scan(8, vector<int>(3));
scan[0] = { 0,1,2 };
scan[1] = { 0,1,3 };
scan[2] = { 0,1,4 };
scan[3] = { 0,4,3 };
scan[4] = { 0,2,5 };
scan[5] = { 0,3,6 };
scan[6] = { 0,5,7 };
scan[7] = { 0,6,7 };
Graph d(data, false);
Graph p(path,false);
Graph t(topu, true);
Graph s(scan, false);
d.MiniScanTree_Prmi();
d.MiniScanTree_Kruskal();
p.MiniPath_Dijkstra(1);
t.Topu();
s.DepthFirst_Traveral(1);
s.BreadthFirst_Traveral(1);
}