数据结构-图及最小生成树

好久没更了 其实摸鱼摸太久了,当然也是最近太多事,一直没有时间去打理博客,趁着周末有空,来整理下图部分的内容

这里来总结下无向图、最小生成树(prim和Dijkstra)算法

无向图

结构
const int maxSize = 100;
int visited[maxSize] = {0}; // 到后面发现,visited在无向图中设计的是真的巧妙
template <class DataType>
class MGraph {
    public: 
     MGraph(DataType a[], int n, int e); // 构造函数,构造有n个顶点e条边的图
     ~MGraph() {}
     void DFS(int); // 深搜
     void BFS(int); // 广搜
    
    private:
     DataType vertex[maxSize]; // 存放图中顶点的数组
     int edge[maxSize][maxSize]; // 存放图中边的数组
     int vertexNum, edgeNum; // 图中的顶点数和变数
};
构造函数
template <class DataType>
MGraph<DataType>::MGraph(DataType a[], int n, int e) {
    int i, j, k;
    vertexNum = n;
    edgeNum = e;
    for (i = 0; i < vertexNum; i++) {
        vertex[i] = a[i]; // 储存顶点
    }
    for (i = 0; i < vertexNum; i++) {
        for (j = 0; j < vertexNum; j++) {
            edge[i][j] = 0; // 初始化邻接矩阵
        }
    }
    for (k = 0; k < edgeNum; k++) {
        cin >> i >> j; // 以此输入每条边依附的两个顶点的编号
        edge[i][j] = 1; // 对输入的边做标记(无向图,双向标记)
        edge[j][i] = 1;
    }
}
广搜和深搜
template <class DataType>
void MGraph<DataType>::DFS(int v) {
    cout << vertex[v];
    visited[v] = 1;
    for (int j = 0; j < vertexNum; j++) {
        if (edge[v][j] == 1 && visited[j] == 0) 
            DFS(j);
        // 递归的妙处会在后面讲到
    }
}

template <class DataType>
void MGraph<DataType>::BFS(int v) {
    int w, j, queue[maxSize] = {0}; // queue数组记录的是访问矩阵第几行的顺序
    for (int i = 0; i < vertexNum; i++) {
        visited[i] = 0;
    }
    int front = -1, rear = -1;
    cout << vertex[v];
    visited[v] = 1;
    queue[++rear] = v;
    while (front != rear) {
        v = queue[++front];
        for (j = 0; j < vertexNum; j++) {
            if (edge[v][j] == 1 && visited[j] == 0) {
                cout << vertex[j];
                visited[j] = 1;
                queue[++rear] = j;
            }
        }
    }
}
主函数
int main() {
    char ch[] = {'a', 'b', 'c', 'd', 'e', 'f'};
    MGraph<char> MG(ch, 6, 6);
    for (int i = 0; i < maxSize; i++) {
        visited[i] = 0;
    }
    cout << "DFS order: \n";
    MG.DFS(0);
    cout << "\n================\n";
    cout << "BFS order: \n";
    MG.BFS(0);
}
测试用例及其邻接矩阵

测试数据

0 1
0 2
0 5
1 2
1 4
3 4

邻接矩阵

   0 1 2 3 4 5 
0'   1 1     1 '
1' 1   1   1   '
2' 1 1         '
3'         1   '
4'   1   1     ' 
5' 1           '
// 以横轴为x,竖轴为y       

借用这个样例来说一下深搜和广搜

深搜

已知无向图的邻接矩阵是关于对角线对称的,深搜从第一行开始搜索,搜索到(1,0)时进入递归,进入递归后,首先对visited[v]进行标记,通过观察可以知道,上一轮DFS传入的j和下一轮DFS的v在矩阵中关于对角线对称 好像是个无用信息,每一轮DFS的visited[v]=1就是为了避免重复访问vertex[v],再加上那条if语句的配合,即可无重复遍历完整个图

广搜

广搜的话其实还是要自己画出一个无向图来并且在debug模式运行一遍才知道大概是怎么个流程。对上面的测试用例来说,在第一轮搜索时,访问的都是和0号这个点有通路的点,第二轮是1号,第三轮是2号,依此类推。如果把最开始输入的0号放在中间,后面输入的数据一圈圈和0号联通,产生关联,那么广搜可以理解为,从搜寻点一圈圈向外扩散找

完整代码
#include <bits/stdc++.h>
using namespace std;
const int maxSize = 100;
int visited[maxSize] = {0};
template <class DataType>
class MGraph {
    public:
     MGraph(DataType a[], int n, int e);
     ~MGraph() {}
     void DFS(int);
     void BFS(int);
    
    private:
     int vertex[maxSize];
     int edge[maxSize][maxSize];
     int vertexNum, edgeNum;
};

template <class DataType>
MGraph<DataType>::MGraph(DataType a[], int n, int e) {
    int i, j, k;
    vertexNum = n;
    edgeNum = e;
    for (i = 0; i < vertexNum; i++) {
        vertex[i] = a[i];
    }
    for (i = 0; i < vertexNum; i++) {
        for (j = 0; j < vertexNum; j++) {
            edge[i][j] = 0;
        }
    }
    for (k = 0; k < edgeNum; k++) {
        cin >> i >> j;
        edge[i][j] = 1;
        edge[j][i] = 1;
    }
}

template <class DataType>
void MGraph<DataType>::DFS(int v) {
    cout << vertex[v];
    visited[v] = 1;
    for (int j = 0; j < vertexNum; j++) {
        if (edge[v][j] == 1 && visited[j] == 0)
            DFS(j);
    }
}

template <class DateType>
void MGraph<DataType>::BFS(int v) {
    int w, j, queue[maxSize] = {0};
    for (int i = 0; i < vertexNum; i++) {
        visited[i] = 0;
    }
    int front = -1, rear = -1;
    cout << vertex[v];
    queue[++rear] = v;
    while (front != rear) {
        v = queue[++front];
        for (j = 0; j < vertexNum; j++) {
            if (edge[v][j] == 1 && visited[j] == 0) {
                cout << vertex[j];
                visited[j] = 1;
                queue[++rear] = j;
            }
        }
    }
}

int main() {
    char ch[] = {'a', 'b', 'c', 'd', 'e', 'f'};
    int i;
    MGraph<char> MG(ch, 6, 6);
    for (i = 0; i < maxSize; i++) {
        visited[i] = 0;
    }
    cout << "DFS order: \n";
    MG.DFS(0);
    cout << "\n================\n";
    cout << "BFS order: \n";
    MG.BFS(0);
}

prim

结构
const int maxSize = 100;
int visited[maxSize] = {0};
template <class DataType>
class MGraph {
    public:
     MGraph(DataType a[], int n, int e);
     ~MGraph() {}
     void DFS(int);
     void BFS(int);
     void Prim(int);
     int minEdge(int[], int);
    
    private:
     DataType vertex[maxSize];
     int edge[maxSize][maxSize];
     int vertexNum, edgeNum;
};
构造函数
template <class DataType>
MGraph<DataType>::MGraph(DataType a[], int n, int e) {
    int i, j, w = 0;
    vertexNum = n;
    edgeNum = e;
    for (i = 0; i < vertexNum; i++) {
        vertex[i] = a[i];
    }
    for (i = 0; i < vertexNum; i++) {
        for (j = 0; j < vertexNum; j++) {
            if (i == j) 
                edge[i][j] = 0; // 这里忽略自环
            else
                edge[i][j] = 100;
            // 这里初始化权值,赋比较大的数即可
        }
    }
    for (int k = 0; k < edgeNum; k++) {
        cout << "input two points of the edge: ";
        cin >> i >> j;
        cout << "input the weight of the edge: " ;
        cin >> w;
        edge[i][j] = w;
        edge[j][i] = w;
    }
}
prim代码实现

将图中顶点(V)分两部分,最小生成树的点集为U,其余顶点在集合(V-U)

    1. 首先任取一个点作为起点
    1. 在V-U中找和起点之间权值最小的边
    1. adjVex记录上一轮找最小值的位置,cost记录到各顶点的距离
    1. 然后上一轮找到的权值最小的边的另一个点作为起点,不断重复步骤2,3
找到最小值位置
template <class DataType>
int MGraph<DataType>::minEdge(int r[], int n) {
    int index;
    int min = 100; // 图中all权值最大不超过100
    for (int i = 0; i < n; i++) {
        if (r[i] != 0 && r[i] < min) {
            min = r[i];
            index = i;
        }
    }
    return index; // 返回最小值在数组中的位置
}
prim核心代码
template <class DataType>
void MGraph<DataType>::Prim(int v) {
    int adjVex[maxSize], cost[maxSize];
    int i, j, k;
    for (i = 0; i < vertexNum; i++) {
        // 通过起点对adjVex,cost数组初始化
        cost[i] = edge[v][i];
        adjVex[i] = v;
        // 将起点所有有联通的点都录入cost中,找权值最小的边(类似BFS)
    }
    cost[v] = 0; // 将顶点加入u中
    for (k = 1; k < vertexNum; k++) {
        j = minEdge(cost, vertexNum); // 在cost数组找最小值
        // cout << '(' << adjVex[j] << ',' << j << ')' << cost[j] << endl; // 输出的是点的序号
        cout << '(' << vertex[j] << ',' << vertex[adjVex[j]] << ')' << cost[j] << endl;  // 输出的是字符
        // 输出生成最小生成树的过程(都是输出上一轮查找结果)
        cost[j] = 0; // 将最小值的点加入U中(清零当前最小值的权值,防止后面重复遍历)
        for (int p = 0; p < vertexNum; p++) {
            // 这一步,是以第j号为起点,不断寻找和j号联通的最小权值的路线
            if (edge[p][j] < cost[p]) {
                // 从所有与当前最小值临界点出发找到最小值点权值最小的
                cost[p] = edge[p][j];
                adjVex[p] = j; // 记录新加入顶点上一轮迭代的最小值的位置
            }
        }
    }
}
测试用例及邻接矩阵
0 1
34
0 2
46
0 5
19
1 4
12
2 3
17
2 5
25
3 4
38
3 5
25
4 5
26
   0  1  2  3  4  5 
0'    34 46       19 '
1' 34          12    '
2' 46       17    25 '
3'       17    38    '
4'    12    38    26 ' 
5' 19    25    26    '
// 以横轴为x,竖轴为y       

Dijkstra

结构
const int maxSize = 100;
int visited[maxSize] = {0}; // 到后面发现,visited在无向图中设计的是真的巧妙
template <class DataType>
class MGraph {
    public: 
     MGraph(DataType a[], int n, int e); // 构造函数,构造有n个顶点e条边的图
     ~MGraph() {}
     void DFS(int); // 深搜
     void BFS(int); // 广搜
    
    private:
     DataType vertex[maxSize]; // 存放图中顶点的数组
     int edge[maxSize][maxSize]; // 存放图中边的数组
     int vertexNum, edgeNum; // 图中的顶点数和变数
};
构造函数
template <class DataType>
MGraph<DataType>::MGraph(DataType a[], int n, int e) {
    vertexNum = e, edgeNum = n;
    int i, j, w = 0;
    for (i = 0; i < vertexNum; i++) {
        vertex[i] = a[i];
    }
    for (i = 0; i < vertexNum; i++) {
        for (j = 0; j < vertexNum; j++) {
            if (i == j) 
                edge[i][j] = 0;
            else
                edge[i][j] = 100;
        }
        for (int k = 0; k < edgeNum; k++) {
            cout << "input two vertexes of the edge: ";
            cin >> i >> j;
            cout << "input the weight of the edge: ";
            cin >> w;
            edge[i][j] = w;
        }
    }
}
深搜和广搜
template <class DataType>
void MGraph<DataType>::DFS(int v) {
    cout << vertex[v];
    visited[v] = 1;
    for (int i = 0; i < vertexNum; i++) {
        if (edge[v][i] < 100 && visited[i] == 0) 
            DFS(i);
    }
}

template <class DataType>
void MGraph<DataType>::BFS(int v) {
    int queue[maxSize];
    int front = -1, rear = -1;
    cout << vertex[v];
    visited[v] = 1;
    queue[++rear] = v;
    while (front != rear) {
        v = queue[++front];
        for (int j = 0; j < vertexNum; j++) {
            if (edge[v][j] < 100 && visited[j] == 0) {
                cout << vertex[v];
                visited[j] = 1;
                queue[++rear] = 1;
            }
        }
    }
}
Dijkstra
template <class DataType>
void MGraph<DataType>::Dijkstra(MGraph<DataType> mg, int v) {
    int dist[maxSize];
    // dist为起点到各个点的距离,具有临时性
    string path[maxSize];
    string vertex(mg.vertex);
    for (int i = 0; i < mg.vertexNum; i++) {
        dist[i] = mg.edge[v][i];
        // 初始化dist数组,0号顶点到其余各顶点的初始路程
        if (dist[i] != 100) {
            path[i] += vertex[v];
            path[i] += vertex[i];
            // 这里是记录起点可以直达的路径
        } else {
            path[i] = "";
        }
    }
    dist[v] = 0;
    int num = 1;
    while (num < mg.vertexNum) {
        int min = 255, k = 0; // 每一轮重置最小值
        for (int i = 0; i < mg.vertexNum; i++) {
            if (dist[i] != 0 && dist[i] < min) {
                min = dist[i];
                k = i;
                // 找最小值
            }
        }
        // cout << path[k] << ',' << dist[k] << ";\n";
        num++; // 标记这是第几个被访问的点
        for (int i = 0; i < mg.vertexNum; i++) {
            if (dist[i] > dist[k] + mg.edge[k][i]) {
                dist[i] = dist[k] + mg.edge[k][i];
                path[i] = ""; // 重置路径
                path[i] += path[k]; // 加上之前走过的路
                path[i] += vertex[i];
            }
        }
        cout << path[k] << ',' << dist[k] << ";\n";
        dist[k] = 0;
    }
}
测试用例及邻接矩阵
0 1
10
0 3
30
0 4
100
1 2
50
2 4
10
3 2
20
3 4
60
   0   1   2   3   4
0          
1' 10                 '
2'     50      20     '
3' 30                 '
4' 100     10  60     '
// 横轴为x,竖轴为y       
完整代码
#include <bits/stdc++.h>
using namespace std;
const int maxSize = 100;
int visited[maxSize] = {0};

template <class DataType>
struct MGraph {
    public:
     MGraph(DataType a[], int n, int e);
     ~MGraph() {}
     void DFS(int);
     void BFS(int);
     void Dijkstra(MGraph<DataType>, int);
    
    private:
     DataType vertex[maxSize];
     int edge[maxSize][maxSize];
     int vertexNum, edgeNum;
};

template <class DataType>
MGraph<DataType>::MGraph(DataType a[], int n, int e) {
    int i, j, w = 0;
    vertexNum = n, edgeNum = e;
    for (i = 0; i < vertexNum; i++) {
        vertex[i] = a[i];
    }
    for (i = 0; i < vertexNum; i++) {
        for (j = 0; j < vertexNum; j++) {
            if (i == j)
                edge[i][j] = 0;
            else
                edge[i][j] = 100;
        }
    }
    for (int k = 0; k < edgeNum; k++) {
        cout << "input two vertexes of the edge: ";
        cin >> i >> j;
        cout << "input the weight of the edge: ";
        cin >> w;
        edge[i][j] = w;
    }
}

template <class DataType>
void MGraph<DataType>::DFS(int v) {
    cout << vertex[v];
    visited[v] = 1;
    for (int i = 0; i < vertexNum; i++) {
        if (edge[v][i] < 100 && visited[i] == 0)
            DFS(i);
    }
}

template <class DataType>
void MGraph<DataType>::BFS(int v) {
    int queue[maxSize];
    int front = -1, rear = -1;
    cout << vertex[v];
    visited[v] = 1;
    queue[++rear] = v;
    while (front != rear) {
        v = queue[++front];
        for (int j = 0; j < vertexNum; j++) {
            if (edge[v][j] < 100 && visited[j] == 0) {
                cout << vertex[j];
                visited[j] = 1;
                queue[++rear] = j;
            }
        }
    }
}

template <class DataType>
void MGraph<DataType>::Dijkstra(MGraph<DataType> mg, int v) {
    int distance[maxSize];
    string path[maxSize];
    string vertex(mg.vertex);
    for (int i = 0; i < mg.vertexNum; i++) {
        distance[i] = mg.edge[v][i];
        if (dist[i] != 100) {
            path[i] += vertex[v];
            path[i] += vertex[i];
        } else {
            path[i] = "";
        }
    }
    distance[v] = 0;
    int num = 1;
    while (num < mg.vertexNum) {
        int min = 100, k = 0;
        for (int i = 0; i < mg.vertexNum; i++) {
            if (distance[i] != 0 && distance[i] < min) {
                min = distance[i];
                k = i;
            }
        }
        num++;
        for (int i = 0; i < mg.vertexNum; i++) {
            if (distance[i] > distance[k] + mg.edge[v][i]) {
                distance[i] = distance[i] + mg.edge[k][i];
                path[i] = "";
                path[i] += path[k];
                path[i] += vertex[i];
            }
        }
        cout << path[k] << ',' << distance[k] << ";\n";
        distance[k] = 0;
    }
}

int main() {
    char ch[] = {'a', 'b', 'c', 'd', 'e'};
    MGraph<char> mg(ch, 5, 7);
    for (int i = 0; i < maxSize; i++) {
        visited[i] = 0;
    }
    cout << "DFS: ";
    mg.DFS(0);
    cout << endl;
    for (int i = 0; i < maxSize; i++) {
        visited[i] = 0;
    }
    cout << "BFS: ";
    mg.BFS(0);
    cout << "the short path: \n";
    mg.Dijkstra(mg, 0);
}

参考链接

【数据结构】最小生成树Prim算法_zgsdlr的博客-CSDN博客_ java求最小生成树