数据结构之图
一、浅谈
图的内容非常的多,包括各种概念和算法。想要一次性全部拿下是非常难的,尤其是里面的算法,比如最小生成树算法这些,就很像KMP算法,属于一时懂,但后来就很容易忘光的算法。关于图内容的总结,需要较长时间,所以,这篇博文会动态更新。
二、图的种类
把图的全部分类总结如下:
有向图:顶点间的连线是有方向的
无向图:顶点间的连线是无向的
完全图:任意两顶点间都存在连线(有向时则需要来回两条,无向时则只需一条)
连通图:任意两顶点间都存在通路
强连通图:任意两顶点间都存在直接的通路
弱连通图:任意两顶点间都存在通路,但是不一定是直接的通路
AOV网:有向无环图(DAG)中,用顶点表示活动,用有向边表示活动之间的先后顺序的网络(拓扑排序)
AOE网:在AOV网的边上加上权值表示完成该活动所需时间的网络
三、图的操作
(1) 建立、插入和删除
建立一个图,需要记录顶点和边。记录顶点的方式,可以是顺序存储(数组)和链式存储(链表)。
边的记录方式,就比较多了。第一种,可以是直接用邻接矩阵,即表征两个点直接是否有边存在。
第二种,可以用邻接表,就是用一个存链表的数组,数组存的是每个顶点连接的顶点组成的链表的头节点,
第三种,可以用十字链表法,比较复杂,总之就是可以同时表示入度和出度的改进邻接表。
//链表
struct ListNode {
int val;
ListNode* next;
ListNode* tail;
ListNode():val(0),next(nullptr), tail(nullptr) {}
ListNode(int n) :val(n), next(nullptr), tail(this) {}
};
//图
struct Gragh {
//顶点---顺序存储
vector<int>points;
//边---邻接表法
vector<ListNode*>edges;
//构造
Gragh(vector<vector<int>>connects) {
for (int i = 0; i < connects.size(); ++i) {
points.push_back(connects[i][0]);
ListNode* cur = new ListNode(connects[i][0]);
while (i < connects.size() - 1 && connects[i].size()>1&&connects[i][0] == connects[i+1][0]) {
cur->tail->next = new ListNode(connects[i][1]);
cur->tail = cur->tail->next;
++i;
}
if (i < connects.size() - 1 && connects[i].size()>1) {
cur->tail->next = new ListNode(connects[i][1]);
cur->tail = cur->tail->next;
}
edges.push_back(cur);
}
}
//插入
void insert(vector<int>connect) {
int i = 0;
for (i; i < points.size(); ++i) {
if (points[i] == connect[0]) {
edges[i]->tail->next = new ListNode(connect[1]);
edges[i]->tail = edges[i]->tail->next;
break;
}
}
if (i == points.size()) {
points.push_back(connect[0]);
ListNode* cur = new ListNode(connect[0]);
if (connect.size() > 1) {
cur->tail->next = new ListNode(connect[1]);
cur->tail = cur->tail->next;
}
edges.push_back(cur);
}
}
//删除
void deletes(int num) {
for (int i = 0; i < points.size(); ++i) {
if (points[i] == num) {
points.erase(points.begin() + i);
edges.erase(edges.begin() + i);
break;
}
}
for (int i = 0; i < edges.size(); ++i) {
ListNode* cur = edges[i];
while (cur->next) {
if (cur->next->val == num) {
ListNode* tmp = cur->next;
cur->next = tmp->next;
delete tmp;
}
cur = cur->next;
}
}
}
//显示
void show() {
for (int i = 0; i < points.size(); ++i) {
cout << points[i] << ": ";
if (edges[i]) {
ListNode* tmp = edges[i]->next;
while (tmp) {
cout << tmp->val;
if (tmp->next) cout << " -> ";
tmp = tmp->next;
}
}
cout << endl;
}
}
//获取顶点数
int getpoints() {
return points.size();
}
};(2) 遍历
遍历分为两种,一种是DFS遍历,一种是BFS遍历
DFS遍历就是“见异思迁,遇到新的顶点就跑到新的顶点”---表现为类似树的前序遍历
BFS遍历就是“一心一意,把该顶点所有信息遍历完再进入下一个”---表现为类似树的层序遍历
//DFS遍历
void dfs(int num,Gragh g, unordered_set<int>& visited) {
for (int i = 0; i < g.points.size(); ++i) {
if (num == g.points[i]) {
if (visited.find(num) == visited.end()) {
cout << num << " -> ";
visited.insert(num);
}
ListNode* cur = g.edges[i]->next;
while (cur) {
dfs(cur->val, g, visited);
cur = cur->next;
}
break;
}
}
}
//BFS遍历
void bfs( Gragh g, unordered_set<int>& visited) {
for (int i = 0; i < g.points.size(); ++i) {
if (visited.find(g.points[i]) == visited.end()) {
cout << g.points[i] << " -> ";
visited.insert(g.points[i]);
}
ListNode* cur = g.edges[i]->next;
while (cur) {
if (visited.find(cur->val) == visited.end()) {
cout << cur->val << " -> ";
visited.insert(cur->val);
}
cur = cur->next;
}
}
cout << endl;
}三、图的相关算法
(1) 最短路径算法
//建立AOE网---重新定义图结构,用邻接矩阵存储边信息
struct AOE {
vector<int>points;
vector<vector<int>>edges;
AOE(vector<vector<int>>connects) {
for (int i = 0; i < connects.size(); ++i) {
points.push_back(connects[i][0]);
while (i < connects.size() - 1 && connects[i].size()>1 && connects[i][0] == connects[i + 1][0])
++i;
}
edges = vector<vector<int>>(points.size(), vector<int>(points.size()));
for (int i = 0; i < connects.size(); ++i) {
for (int k = 0; k < points.size(); ++k) {
if (points[k] == connects[i][0]) {
for (int j = 0; j < points.size(); ++j) {
if (connects[i].size()>2&&points[j] == connects[i][1]) {
edges[k][j] = connects[i][2];
break;
}
}
break;
}
}
}
}
void show() {
for (int i = 0; i < points.size(); ++i) {
cout << points[i] << ": ";
for (int j = 0; j < points.size(); ++j)
cout <<setiosflags(ios::left)<< setw(10) << edges[i][j] << " ";
cout << endl;
}
}
};1) 弗洛伊德最短路径算法
//1.弗洛伊德算法
void floyd(AOE aoe){
const int n = aoe.points.size();
for (int i = 0; i < n; ++i) {
for(int j=0;j<n;++j){
if (i != j &&aoe.edges[i][j] == 0)
aoe.edges[i][j] = INT32_MAX / 2;
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 0; k < n; ++k) {
if (aoe.edges[i][j] >(aoe.edges[i][k] + aoe.edges[k][j]))
aoe.edges[i][j] = aoe.edges[i][k] + aoe.edges[k][j];
}
}
}
for (int i = 0; i < n; ++i) {
cout <<aoe.points[i] << ": ";
for (int j = 0; j < n; ++j)
cout << setiosflags(ios::left) << setw(10)<<aoe.edges[i][j] << " ";
cout << endl;
}
}1) 迪杰斯特拉最短路径算法
//2.Dijkstra算法
void Dijkstra(AOE aoe,int num) {
const int n = aoe.points.size();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i != j && aoe.edges[i][j] == 0)
aoe.edges[i][j] = INT32_MAX / 2;
}
}
vector<vector<int>>S;
vector<vector<int>>U(n-1,vector<int>(2));
for(int i=0;i<n;++i){
if (aoe.points[i] == num) {
//初始化S,U
S.push_back({ num,aoe.edges[i][i] });
for (int j = 1; j < n; ++j)
U[j - 1] = { aoe.points[j],aoe.edges[i][j] };
//更新S和U
while (!U.empty()) {
//更新S
sort(U.begin(), U.end(), [](vector<int>& a, vector<int>& b) {
return a[1] < b[1];
});
S.push_back(U[0]);
//更新U
for (int u = 1; u < U.size(); ++u) {
for (int j = 0; j < n; ++j) {
if (U[0][0] == aoe.points[j]) {
for (int k = 1; k < n; ++k) {
if (aoe.points[k] == U[u][0]) {
if (U[0][1] + aoe.edges[j][k] < U[u][1])
U[u][1] = U[0][1] + aoe.edges[j][k];
break;
}
}
break;
}
}
}
U.erase(U.begin());
}
break;
}
}
//显示结果
for (int i = 0; i < S.size(); ++i)
cout << num << " 到 " << S[i][0]
<< " 的最小距离为: " << S[i][1] << endl;
}(2)最小生成树算法

