图、最小生成树、最短路径、AOV、AOE-数据结构

图的存储

图的类定义

class Graph
{
    // 对象:由一个顶点的非空集合和一个边集合构成,每条边由一个顶点对来表示
    public:
    Graph();
    // 建立一个空的图
    void insertVertex(const T&vertex);
    // 在图中插入一个顶点vertex,该顶点暂时没有边
    void insertEdge(int v1, int v2, int weight);
    // 若构成边的两个顶点v1和v2是图中的顶点,则在图中插入一条边(v1,v2)
    void removeVertex(int v);
    // 若被删顶点是图中的顶点,则删除顶点v和所有关联到它的边
    void removeEdge(int v1, int v2);
    // 若构成边的两个顶点v1和v2是图中的顶点,则在图中删除边(v1,v2)
    bool IsEmpty();
    // 若图中没有边,则函数返回true,否则返回false
    T getWeight(int v1, int v2);
    // 函数返回边(v1,v2)的权值
    int getFirstNeighbor(int v);
    // 给出顶点位置为v的第一个邻接顶点的位置,如果找不到,则函数返回一1
    int getNextNeighbor(int v1, int v2);
    // 给出顶点位置为v1的某邻接顶点v2的下一个邻接顶点的位置,如果找不到,则返回一1
    void printGraph();
    // 打印图的所有顶点和边
    int getVertexCount();
    // 返回图中顶点的数量
    int getEdgeCount();
    // 返回图中边的数量
};

图的模板基类

#define MAX_VERTICES 30
// 最大顶点数
#define MAX_EDGES 40
// 最大边数
#define MAX_WEIGHT 999
// 代表无穷大的值

template <class T, class E>
class Graph {
    // 图的类定义
    public:
        bool GraphEmpty() {
            return (numEdges == 0);
        }

        bool GraphFull() {
            return (numVertices == MAX_VERTICES) && (numEdges == MAX_VERTICES * (MAX_VERTICES - 1) / 2);
        }

        int NumberOfVertices() {
            return numVertices;
        }
        // 返回当前顶点数

        int NumberOfEdges() {
            return numEdges;
        }
        // 返回当前边数

        virtual T getValue(int i) = 0;
        // 取顶点i的值,i不合理返回0

        virtual E getWeight(int v1, int v2) = 0;
        // 取边(v1,v2)上的权值

        virtual int getVertexPos(T vertex) = 0;
        // 给出顶点vertex在图中的位置

        virtual int getFirstNeighbor(int v) = 0;
        // 取顶点v的第一个邻接顶点

        virtual int getNextNeighbor(int v, int w) = 0;
        // 取邻接顶点w的下一个邻接顶点

        virtual bool insertVertex(T vertex) = 0;
        // 插入一个顶点vertex

        virtual bool insertEdge(int v1, int v2, E cost) = 0;
        // 插入边(v1,v2), 权为cost

        virtual bool removeVertex(int v, T& vertex) = 0;
        // 删除顶点v和所有与其相关联的边

        virtual bool removeEdge(int v1, int v2, E& cost) = 0;
        // 在图中删除边(v1,v2)

    protected:
        int numEdges;
        // 当前边数

        int numVertices;
        // 当前顶点数
};

邻接矩阵表示的图

#include "Graph.h"
template <class T,class E>
class Graphmtx : public Graph<T,E> {
    //图的邻接矩阵类定义
    friend istream&operator >>(istream&in,Graphmtx<T,E>&G);
    //输入
    friend ostream& operator<<(ostream& out,Graphmtx<T,E>&G);
public:
    Graphmtx(int sz = DefaultVertices);
    //构造函数
    ~Graphmtx();
    //析构函数
    int getVertexPos(T vertex);
    //给出顶点vertex在图中的位置
    T getValue(int i);
    //取顶点i的值,i不合理返回0
    E getWeight(int v1,int v2);
    //取边(v1,v2)上的权值
    int getFirstNeighbor(int v);
    //取顶点v的第一个邻接顶点
    int getNextNeighbor(int v,int w);
    //取v的邻接顶点w的下一个邻接顶点
    bool insertVertex(T vertex);
    //插入顶点vertex
    bool insertEdge(int v1,int v2,E cost);
    //插入边(v1,v2),权值为cost
    bool removeVertex(int v, T& vertex);
    //删除顶点v和所有与它相关联的边
    bool removeEdge(int v1,int v2,E& cost);
    //在图中删除边(v1,v2)
private:
    T VerticesList[maxVertices];
    //顶点表,定义所有顶点所在的位置
    E Edge[maxVertices][maxVertices];
    //邻接矩阵
};

template <class T,class E>
int Graphmtx<T,E>::getVertexPos(T vertex){
    //给出顶点vertex在图中的位置
    for (int i =0;i<numVertices;i++)
        if (VerticesList[i]==vertex) return i;
    return -1;
}

template <class T,class E>
Graphmtx<T,E>::Graphmtx(int sz) : numVertices(0), numEdges(0) {
    //初始化矩阵
    for (int i=0;i<maxVertices;i++)
        for (int j=0;j<maxVertices;j++)
            Edge[i][j]=   (i==j)?0:maxWeight;
}
template <class T,class E>
int Graphmtx<T,E>::getFirstNeighbor(int v){//第一是指数值的第一第二第三
    //给出顶点位置为v的第一个邻接顶点的位置,如果找不到,则函数返回-1
    if(v!=-1){
        for (int col = 0; col < numVertices; col++)
            if (Edge[v][col]>0 && Edge[v][col]<maxWeight) return col;
    }
    return -1;
}

template <class T,class E>
int Graphmtx<T,E>::getNextNeighbor(int v,int w){
    //给出顶点v的某邻接顶点w的下一个邻接顶点
    if(v!=-1 && w!=-1){
        for (int col = w+1; col < numVertices; col++)
            if (Edge[v][col]>0 && Edge[v][col]<maxWeight) return col;
    }
    return -1;
}

template <class T,class E>
bool Graphmtx<T,E>::insertVertex(T vertex) {
    //插入顶点vertex
    if (numVertices == maxVertices) return false;
    //顶点表满,不插入
    VerticesList[numVertices] = vertex;
    //新加顶点信息
    numVertices++;
    return true;
    //顶点数加1
}

template <class T,class E>
bool Graphmtx<T,E>::removeVertex(int v, T& vertex){
    //删除顶点v和所有与它相关联的边,用最后的顶点跟他互换位置
    if (v < 0 || v >= numVertices) return false;
    //v不在图中,不删除
    if (numVertices == 1) return false;
    //只剩一个顶点,不删除
    
    for (int i = 0; i < numVertices; i++){
        //修改图的边数
        if (Edge[v][i] > 0 && Edge[v][i] < maxWeight) numEdges--;}
    
    vertex = VerticesList[v];
    //顶点表中删除该顶点
    VerticesList[v] = VerticesList[numVertices - 1];
    for (int i = 0; i < numVertices; i++)
        //用最后一列填补第v列
        Edge[i][v] = Edge[i][numVertices - 1];
    for (int i = 0; i < numVertices; i++)
        //用最后一行填补第v行
        Edge[v][i] = Edge[numVertices - 1][i];
    numVertices--;
    return true;
    //顶点数减1
}
template <class T,class E>
bool Graphmtx<T,E>::insertEdge(int v1,int v2,E cost){
    //插入边(v1,v2),权值为cost;插入的是编号不是数字
    if (v1 >-1 && v1<numVertices && v2 >-1 && v2 < numVertices){
        if (Edge[v1][v2]==0 || Edge[v1][v2]==maxWeight){
            numEdges++;
            //插入边后边数加1
            Edge[v1][v2]=Edge[v2][v1]=cost;
            //插入边
            return true;
        }
    }
    return false;
}

template <class T,class E>
bool Graphmtx<T,E>::removeEdge(int v1,int v2,E&cost){
    //在图中删除边(v1,v2)
    if (v1 >-1 && v1 < numVertices && v2 >-1 && v2 < numVertices){
        if (Edge[v1][v2]>0 && Edge[v1][v2]<maxWeight){
            cost = Edge[v1][v2];
            //保存边上权值
            Edge[v1][v2]=Edge[v2][v1]=maxWeight;
            numEdges--;
            return true;
        }
    }
    return false;
}
template <class T,class E>
istream& operator >>(istream& in, Graphmtx<T,E>& G){
    //通过从输入流对象in输入n个顶点信息和e条无向边的信息建立用邻接矩阵表示的图G。邻接
    //矩阵初始化的工作已经在构造函数中完成
    int i, j, k, n, m;
    T e1, e2;
    E weight;
    cout << "输入顶点数、边数" << endl;
    in >> n >> m;
    //输入顶点数n和边数m
    for (i = 0; i < n; i++){
        //建立顶点表数据
        cout << "输入顶点值";
        in >> e1;
        G.insertVertex(e1);
    }
    i = 0;
    while (i < m){
        cout << "输入边信息" << endl;
        in >> e1 >> e2 >> weight;
        //输入端点信息
        j = G.getVertexPos(e1);
        k = G.getVertexPos(e2); //查顶点号
        if(j == -1 || k == -1)
            cout << "边两端点信息有误,重新输入!" << endl;
        else {
            G.insertEdge(j, k, weight);
            i++;
        }
    }
    return in;
}

template <class T,class E>
ostream& operator<<(ostream& out, Graphmtx<T,E>& G){
    //输出图的所有顶点和边的信息
    int i, j, n, m;
    T e1, e2;
    E w;
    n = G.NumberOfVertices();
    m = G.NumberOfEdges();
    out << "顶点数" << n << ",边数" << m << endl;
    //输出顶点数与边数
    out << "所有顶点信息为" << endl;
    for (i = 0; i < n; i++) out << G.getValue(i) << " ";
    out << endl << "所有边的信息为" << endl;
    for (i = 0; i < n; i++)
        for (j = i + 1; j < n; j++){
            w = G.getWeight(i, j);
            //取边上权值
            if (w > 0 && w < maxWeight){
                //有效
                e1 = G.getValue(i);
                e2 = G.getValue(j);
                //取顶点号
                out << "(" << e1 << "," << e2 << "," << w << ")";
                out << endl;
            }
        }
    return out;
}

邻接表表示的图

image-20231209190022844

image-20231209190036865

#include "Graph.h"

template <class T, class E>
struct Edge {
    //边结点的定义
    int dest; //边的另一个顶点位置
    E cost; //边上的权值
    Edge<T, E>* link; //下一条边链指针

    Edge() {} //构造函数
    Edge(int num, E weight) : dest(num), cost(weight), link(NULL) {} //构造函数
    bool operator!=(Edge<T, E>& R) const { //判断边不等否
        return (dest != R.dest) ? true : false;
    }
};

template <class T, class E>
struct Vertex {
    //顶点的定义
    T data; //顶点的名字
    Edge<T, E> adj; //边链表的头指针
};

template <class T, class E>
class Graphlnk : public Graph<T, E> {
    //图的类定义
    friend istream& operator>>(istream& in, Graphlnk<T, E>& G); //输入
    friend ostream& operator<<(ostream& out, Graphlnk<T, E>& G); //输出

public:
    Graphlnk(int sz = DefaultVertices); //构造函数
    ~Graphlnk(); //析构函数
    int getVertexPos(T vertex); //取顶点vertex在结点表中的序号
    T getValue(int i); //取位置为i的顶点中的值
    E getWeight(int v1, int v2); //返回边(v1,v2)上的权值
    bool insertVertex(T vertex); //在图中插入一个顶点vertex
    bool removeVertex(int v, T& vertex); //在图中删除一个顶点v
    bool insertEdge(int v1, int v2, E cost); //在图中插入一条边(v1,v2)
    bool removeEdge(int v1, int v2, E& cost); //在图中删除一条边(v1,v2)
    int getFirstNeighbor(int v); //取顶点v的第一个邻接顶点
    int getNextNeighbor(int v, int w); //取v的邻接顶点w的下一个邻接顶点

private:
    Vertex<T, E> NodeTable[maxVertices]; //顶点表(各边链表的头结点)
};

template<class T, class E>
int Graphlnk<T, E>::getVertexPos(T vertex) {
    //给出顶点vertex在图中的位置
    for (int i = 0; i < numVertices; i++) {
        if (NodeTable[i].data == vertex) {
            return i;
        }
    }
    return -1;
}
template <class T,class E>
Graphlnk<T,E>::Graphlnk() {
    //构造函数:建立一个空的邻接表
    numVertices = 0; numEdges = 0;
    for (int i = 0; i < maxVertices; i++) NodeTable[i].adj = NULL;
}

template <class T,class E>
Graphlnk<T,E>::~Graphlnk() {
    //析构函数:删除一个邻接表
    for (int i = 0; i < numVertices; i++) {
        //删除各边链表中的结点
        Edge<T,E>* p = NodeTable[i].adj;
        //找到其对应边链表的首结点
        while (p != NULL) {
            //不断地删除第一个结点
            NodeTable[i].adj = p->link;
            delete p; 
            p = NodeTable[i].adj;
        }
    }
}

template <class T,class E>
int Graphlnk<T,E>::getFirstNeighbor(int v) {
    //给出顶点位置为v的第一个邻接顶点的位置,如果找不到,则函数返回-1
    if (v != -1) {
        //顶点v存在
        Edge<T,E>* p = NodeTable[v].adj;
        //对应边链表第一个边结点
        if (p != NULL) return p->dest;
        //存在,返回第一个邻接顶点
    }
    return -1;
    //第一个邻接顶点不存在
}

template <class T,class E>
int Graphlnk<T,E>::getNextNeighbor(int v, int w) {
    //给出顶点v的邻接顶点w的下一个邻接顶点的位置,若没有下一个邻接顶点,则函数返回-1
    if (v != -1) {
        //顶点v存在
        Edge<T,E>* p = NodeTable[v].adj;
        //对应边链表第一个边结点
        while (p != NULL && p->dest != w)
            //寻找邻接顶点w
            p = p->link;
        if (p != NULL && p->link != NULL)
            return p->link->dest;
        //返回下一个邻接顶点
    }
    return -1;
    //下一个邻接顶点不存在
}

图的遍历

dfs

深度优先搜索是一个不断探查和回溯的过程。在探查的每一步,算法都有一个当前顶点。最初的当前顶点就是指定的起始顶点。每一步探查过程中,首先对当前顶点进行访问,并立即设置该顶点的访问标志visited[v]=true。接着在v的所有邻接顶点中,找出尚未访问过的一个,将其作为下一步探查的当前顶点。如果当前顶点的所有邻接顶点都已经被访问过,则退回一步,将前一步所访问的顶点重新取出,当作探查的当前顶点。重复上述过程,直到最初指定起始顶点的所有邻接顶点都被访问到。此时连通图中的所有顶点也必然都被访问过了。

image-20231210123230171

template<class T, class E>
void DFS_recur(Graphlnk<T, E>& G, int v, bool visited[]) {
    // 从顶点位置v出发,以深度优先的次序访问所有可到达的尚未被访问过的顶点。算法中用到一个辅助数组visited,对已访问过的顶点做访问标记
    cout << G.getValue(v) << " ";
    // 访问顶点v
    visited[v] = true;
    // 顶点v做访问标记
    int w = G.getFirstNeighbor(v);
    // 找顶点v的第一个邻接顶点w
    while (w != -1) {
        // 若邻接顶点w存在
        if (visited[w] == false) {
            DFS_recur(G, w, visited);
            // 若w未访问过,递归访问顶点w
        }
        w = G.getNextNeighbor(v, w);
        // 取v排在w后的下一个邻接顶点
    }
}

template<class T, class E>
void DFS_recur(Graphlnk<T, E>& G, T v) {
    // 从顶点v出发,对图G进行深度优先遍历的主过程
    int loc, n = G.NumberOfVertices();
    // 取图中顶点个数
z
    // 创建辅助数组visited
    for (int i = 0; i < n; i++) {
        visited[i] = false;
    }
    // 辅助数组visited初始化
    loc = G.getVertexPos(v);
    DFS_recur(G, loc, visited);
    // 从顶点loc开始深度优先搜索
    delete[] visited;
    // 释放visited
}

bfs

与深度优先搜索方法不同,广度优先搜索方法没有探索和回溯的过程,而是一个逐层遍历的过程。在此过程中,图中有多少个顶点就要重复多少步。每步都有一个当前顶点。最初的当前顶点是主过程指定的起始顶点。在每步中,首先访问当前顶点,并设置该顶点的访问标志visited[v]=true。接着依次访问v的各个未曾被访问过的邻接顶点w1,w2,…,然后再顺序访问w1,w2,…的所有还未被访问过的邻接顶点。再从这些访问过的顶点出发,访问它们的所有还未被访问过的邻接顶点,如此做下去,直到图中所有顶点都被访问到为止。

图8.13(a)展示了从顶点A出发进行广度优先搜索的过程。图中各顶点旁边的数字标明了顶点访问的顺序。图8.13(b)展示了经由广度优先搜索得到的广度优先生成树,它由遍历时访问过的n个顶点和遍历时经历的n-1条边组成。

广度优先搜索不是一个递归的过程,其算法也不是递归的。为了实现逐层访问,算法中使用了一个队列,以记忆正在访问的这一层和上一层的顶点,便于向下一层访问。另外,与深度优先搜索过程一样,为避免重复访问,需要一个辅助数组visited[],给被访问过的顶点加标记。程序8.10给出广度优先搜索的算法。

image-20231210183938782

template <class T, class E>
void BFS(Graph<T, E> &G, T v) {
    // 从顶点v出发,以广度优先的次序横向搜索图,算法中使用了一个队列
    int i, w, n = G.NumberOfVertices(); // 取图中顶点个数
    bool *visited = new bool[n]; // visited记录顶点是否被访问过
    for (i = 0; i < n; i++) visited[i] = false; // 初始化
    int loc = G.getVertexPos(v); // 取顶点号
    cout << G.getValue(loc) << ""; // 访问顶点v,做已访问标记
    visited[loc] = true;
    LinkedQueue<int> Q;
    Q.EnQueue(loc); // 顶点进队列,实现分层访问
    while (!Q.IsEmpty()) { // 循环,访问所有结点
        Q.DeQueue(loc); // 从队列中退出顶点loc
        w = G.getFirstNeighbor(loc); // 找顶点loc的第一个邻接顶点w
        while (w != -1) { // 若邻接顶点w存在
            if (visited[w] == false) { // 若未被访问过
                cout << G.getValue(w) << ""; // 访问顶点w
                visited[w] = true;
                Q.EnQueue(w); // 顶点w进队列
            }
            w = G.getNextNeighbor(loc, w); // 找顶点loc的下一个邻接顶点
        } // 重复检测v的所有邻接顶点
    } // 外层循环,判队列空否
    delete[] visited;
}

连通分量

template <class T, class E>
void ConnectCom_BFS(Graphlnk<T, E> &G) {
    // 通过BFS遍历非连通图G:输出求得的所有连通分量
    LinkedQueue<int> Q;
    int i, u, w;
    bool visited[maxVertices]; // 定义访问标志数组
    for (i = 0; i < G.NumberOfVertices(); i++) visited[i] = false;
    for (i = 0; i < G.NumberOfVertices(); i++) { // 对所有顶点检查
        if (visited[i] == false) { // 从顶点i做BFS遍历
            Q.EnQueue(i); 
            visited[i] = true;
            cout << "(" << G.getValue(i);
            while (!Q.IsEmpty()) { // 队列不空
                Q.DeQueue(u);
                w = G.getFirstNeighbor(u);
                while (w != -1) {
                    if (visited[w] == false) {
                        cout << "," << G.getValue(w);
                        Q.EnQueue(w); 
                        visited[w] = true;
                    }
                    w = G.getNextNeighbor(u, w);
                }
            }
            cout << ")" << endl; // 一个连通分量遍历完
        }
    }
}

最小生成树

连通图中的每棵生成树,都是原图的一个极小连通图。也就是说,从生成树中删去任何一条边,生成树就不再连通;反之,在生成树中引入任何一条新边,都会形成(恰好)一个回路。

按照不同的遍历算法,会得到不同的生成树;从不同的顶点出发,得到的生成树也有所不同。对于一个带权图(即网络),不同的生成树所对应的总权值也不尽相同。那么,如何找出总权值最小的生成树呢?在多数可以表示为带权图的实际应用中,都会遇到这一问题。

例如,在规划建立n个城市之间的通信网络时,至少要架设n-1条线路。如果在任何两个城市间建立通信线路的成本已经确定,那么如何使总造价最低?若用顶点表示城市,用边表示城市之间的通信线路,边上的权值表示线路对应的造价,就可以将这一问题表示为一个带权图。为了建立成本最低的通信网络,就要找出该网络的一棵最小(代价)生成树(minimum-cost spanning tree)。

按照定义,若连通网络由n个顶点组成,则其生成树必含n个顶点、n-1条边。因此,构造最小生成树的准则有3条:

  1. 只能使用该网络中的边来构造最小生成树;
  2. 只能使用恰好n-1条边来联结网络中的n个顶点;
  3. 选用的这n-1条边不能构成回路。

构造最小生成树的方法有多种,典型的有两种:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的策略,也称贪心策略:给定带权图N={V,E},V中共有n个顶点。首先构造一个包括全部n个顶点和0条边的森林F={T0,T1,…,Tm-1},然后不断迭代。每经过一轮迭代,就会在F中引入一条边。经过n-1轮迭代,最终得到一棵包含n-1条边的最小生成树。

需要指出的是,同一带权图可能有多棵最小生成树。例如,当有多条边具有相等的权值时,很可能出现这种现象。

Kruskal算法

该算法的基本过程:任给一个有n个顶点的连通网络N={V,E},首先构造一个由这n个顶点组成、不含任何边的图T={V,φ},其中每个顶点自成一个连通分量。不断从E中取出权值最小的一条边(若有多条,任取其一),若该边的两个端点来自不同的连通分量,则将此边加入T中。如此重复,直到所有顶点在同一个连通分量上为止。

Kruskal算法的伪代码描述

T=φ;
//T是最小生成树的边集合,初值为空;E是无向带权图的边集合

while(T包含的边少于n-1&&E不空){
从E中选一条具有最小代价(cost)的边(v,w);
从E中删除(v,w);
如果(v,w)加到T中后不会产生回路,则将(v,w)加入T;否则放弃(v,w);
}

if(T中包含的边少于n-1)cout<<"不是最小生成树"<<endl;

例如,针对图8.17(a)所示的带权图,首先将所有顶点组成一个如图8.17(b)所示的非连通图,然后不断迭代。每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树当中。图8.17(a)共有7个顶点,故经过6轮迭代(见图8.17©~(h)),就可以得到在图8.17(h)中由6条边组成的一棵生成树。

image-20231210190541158

image-20231210190600083

#define maxValue 999
//机器可表示的、问题中不可能出现的大数
#define maxSize 30

template <class T>
struct MSTEdgeNode {
    //最小生成树边结点的类声明
    int tail, head;
    T key;
    //两顶点位置及边上的权值
    MSTEdgeNode() : tail(-1), head(-1), key(0) {} // 构造函数
    bool operator<=(MSTEdgeNode<T>& R) { return key <= R.key; }
    bool operator>(MSTEdgeNode<T>& R) { return key > R.key; }
};

template <class T>
class MinSpanTree {
    //最小生成树的类定义
protected:
    MSTEdgeNode<T> edgevalue[maxSize];
    //用边值数组表示树
    int maxSize, n;
    //数组的最大元素个数和当前个数
public:
    MinSpanTree() { n = 0; }
    int Insert(MSTEdgeNode<T> item);
    void printEdges();
};

template <class T>
int MinSpanTree<T>::Insert(MSTEdgeNode<T> item) {
    edgevalue[n++] = item;
}

template <class T>
void MinSpanTree<T>::printEdges() {
    cout << "依次输出最小生成树的边:" << endl;
    for (int i = 0; i < n; ++i) {
        cout << "(" << edgevalue[i].tail << "," << edgevalue[i].head
             << "," << edgevalue[i].key << ")";
    }
    cout << endl;
}

Kruskal算法的实现

#include "Graphlnk.cpp"
#include "minHeap.cpp"
#include "UFSets.h"
#include "MinSpanTree.cpp"

template <class T, class E>
void Kruskal(Graphlnk<T, E>& G, MinSpanTree<E>& MST) {
    Element<E> e;
    MSTEdgeNode<E> ed;
    int u, v, count;
    int n = G.NumberOfVertices();
    //顶点数
    int m = G.NumberOfEdges();
    //边数
    minHeap<E> H;
    //最小堆,关键码类型为E
    UFSets F;
    //并查集
    for (u = 0; u < n; u++)//堆的初始化
        for (v = u + 1; v < n; v++)
            if (G.getWeight(u, v) > 0 && G.getWeight(u, v) < maxWeight) {
                e.i = u;
                e.j = v;
                //插入堆
                e.key = G.getWeight(u, v);
                H.Insert(e);
            }
    count = 1;
    //最小生成树加入边数计数
    while (count < n) {
        //反复执行,取n-1条边
        H.Remove(e);
        //从最小堆中退出具最小权值的边ed
        u = F.Find(e.i);
        v = F.Find(e.j);
        //取两顶点所在集合的根u与v
        if (u != v) {
            //不是同一集合,说明不连通
            F.Merge(u, v);//并查集的组合
            //合并,连通它们
            ed.tail = e.i;
            ed.head = e.j;
            ed.key = e.key;
            MST.Insert(ed);
            //该边存入最小生成树
            count++;
        }
    }
}

这是Kruskal算法的实现。该算法接受一个Graphlnk对象G和一个MinSpanTree对象MST作为参数。首先,将G中的所有边插入最小堆H。然后,使用并查集F来判断边是否可以添加到最小生成树中。如果边的两个顶点不在同一集合中,将它们合并,并将边添加到最小生成树MST中。算法反复执行,直至最小生成树中包含n-1条边。

prime算法

image-20231210195755614

image-20231210200027808

image-20231210200042482

#include "Graphlnk.cpp"
#include "minHeap.cpp"
#include "MinSpanTree.cpp"

template <class T, class E>
void Prim(Graphlnk<T, E>& G, T u0, MinSpanTree<E>& MST) {
    Element<E> e;
    MSTEdgeNode<E> ed;
    int i, u, v, count;
    int n = G.NumberOfVertices();
    //顶点数
    int m = G.NumberOfEdges();
    //边数
    u = G.getVertexPos(u0);
    //求起始顶点号u
    minHeap<E> H;
    //最小堆,关键码类型为E
    bool* Vmst = new bool[n];
    //最小生成树顶点集合
    for (i = 0; i < n; i++) Vmst[i] = false;
    Vmst[u] = true;
    //u加入生成树
    count = 1;
    do {
        //最小堆的构建
        v = G.getFirstNeighbor(u);
        while (v != -1) {//最小堆的构建
            //重复检测u的所有邻接顶点
            if (Vmst[v] == false) {
                //若v不在生成树,(u,v)加入堆
                e.i = u;
                e.j = v;
                e.key = G.getWeight(u, v);
                //u在树内,v不在树内
                H.Insert(e);
            }
            v = G.getNextNeighbor(u, v);
            //找顶点u的下一个邻接顶点v
        }
        while (H.IsEmpty() == false && count < n) {
            H.Remove(e);
            //从堆中退出具最小权值的边e
            if (Vmst[e.j] == false) {
                ed.tail = e.i;
                ed.head = e.j;
                ed.key = e.key;
                MST.Insert(ed);
                //加入最小生成树
                u = ed.head;
                Vmst[u] = true;
                //u加入生成树顶点集合
                count++;
                break;
            }
        }
    } while (count < n);
    delete[] Vmst;
}

image-20231210201322972

image-20231210201555443image-20231210201602576image-20231210201613515

最短路径

边权全部为整数的算法

image-20231210203207662image-20231210203234142image-20231210203249020

image-20231210203823474image-20231210203830912

Dijkstra求最短路径的算法

#include "DGraphmtx.cpp"

template <class T, class E>//DGraphmtx为有向带权图的邻接矩阵
void ShortestPath(DGraphmtx<T, E>& G, int v, E dist[], int path[]) {
    int n = G.NumberOfVertices();
    bool* S = new bool[n]; //最短路径顶点集
    int i, j, k;
    E w, min;
//在Dijkstra的最短路径算法中,path[]数组用于记录每个顶点的前一个顶点的位置,也就是在最短路径中,从起始顶点到达该顶点的前一个顶点。

//例如,如果我们有一个从顶点A到顶点B的最短路径是A->C->B,那么在path[]数组中,path[B]的值就是C,表示在最短路径中,B的前一个顶点是C。

//通过这个path[]数组,我们可以方便地找出从起始顶点到任何一个顶点的最短路径。只需要从目标顶点开始,沿着path[]数组反向遍历,直到回到起始顶点,就得到了一条最短路径。
    for (i = 0; i < n; i++) {
        dist[i] = G.getWeight(v, i); //数组初始化
        S[i] = false;
        if (i != v && dist[i] < maxWeight) path[i] = v;
        else path[i] = -1;
    }
    S[v] = true;
    dist[v] = 0; //ist[]数组用来存储从起始顶点到图中每一个顶点的最短路径长度。例如,如果我们想要找出从顶点A到顶点B的最短路径长度,我们可以查看dist[B]的值。

    for(i = 0; i < n-1; i++) {
        min = maxWeight; 
        int u = -1; //选不在S中具有最短路径的顶点u

        for (j = 0; j < n; j++) {
            if (S[j] == false && dist[j] < min) {//S[j]表示有没有进行过运算,dist[j]表示和初始点有没有路
                u = j; min = dist[j];
            }
        }
        S[u] = true; //将顶点u加入集合S

        for (k = 0; k < n; k++) {
            w = G.getWeight(u, k);
            if (S[k] == false && w < maxWeight && dist[u] + w < dist[k]) {
                //顶点k未加入S,且绕过u可以缩短路径
                dist[k] = dist[u] + w;
                path[k] = u; //修改到k的最短路径
            }
        }
    }
    delete[] S;
}

这是Dijkstra求最短路径的算法。该算法接受一个DGraphmtx对象G,一个起始顶点v,一个用于存储最短路径长度的数组dist和一个用于存储最短路径的数组path作为参数。算法首先将所有顶点的最短路径长度初始化为无穷大,然后设置起始顶点v的最短路径长度为0。然后,迭代地执行以下步骤:从未加入S的顶点中选择一个最短路径长度最小的顶点u,并将其加入S。然后,更新所有从v出发经过u到达的顶点的最短路径长度。当所有顶点都加入S后,算法终止。

当已经更新了最短路了之后不会对这个进行判断,即跟 v 0 v_0 v0直接邻接的最短边一定是最短路,所以一旦有负数存在,就都无法判断;下图中用dij算法会把0到1的最短路径设置为1

image-20231213165141497

任意权值的单源最短路径

image-20231213165754097

所有顶点之间的最短路径

image-20231213171852679

image-20231213171937829

image-20231213171957906

AOV网络(用顶点标志活动的网络)

image-20231213172954617

image-20231213173027316

image-20231214102322202

image-20231214102732408image-20231214102746748image-20231214102756247

image-20231214104335221image-20231214104347580外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include "DGraphlnk.cpp"
// DGraphlnk是有向图的邻接表表示

template <class T, class E>
void TopologicalSort(DGraphlnk<T, E>& G) {
    int i, j, w, v;
    int top = -1;
    // 入度为零顶点的栈初始化
    int n = G.NumberOfVertices(); // 修正:加上分号
    // 网络中顶点个数
    int* count = new int[n]; // 修正:new int[n],并添加分号
    // 入度数组兼入度为零顶点栈

    // 检查有向图所有的边
    for (i = 0; i < n; i++) {
        j = G.getFirstNeighbor(i);
        while (j != -1) {
            count[j]++;
            // 邻接顶点入度加1
            j = G.getNextNeighbor(i, j);
        }
    }

    for (i = 0; i < n; i++) {
        // 检查网络所有顶点
        if (count[i] == 0) {
            count[i] = top;
            top = i;
        }
        // 入度为零的顶点进栈
    }

    for (i = 0; i < n; i++) {
        // 期望输出n个顶点
        if (top == -1) {
            // 中途栈空,转出
            cout << "网络中有回路!" << endl;
            return;
        } else {
            // 继续拓扑排序
            v = top;
            top = count[top];
            // 退栈v
            cout << G.getValue(v) << " ";
            // 输出
            w = G.getFirstNeighbor(v);
            while (w != -1) {
                // 扫描出边表
                if (--count[w] == 0) {
                    count[w] = top;
                    top = w;
                }
                // 顶点入度减至零,进栈
                w = G.getNextNeighbor(v, w);
            }
        }
    }

    delete[] count; // 修正:添加删除动态数组的语句
}

AOE网络(用边表示活动的网络)

image-20231214105810332image-20231214110231120

image-20231214110248459image-20231214110258658

image-20231214110655637

image-20231214110716550

image-20231214110732712

image-20231210205702477

image-20231210205712062

这tm是有向图?

image-20231210205727723

image-20231210211946009

image-20231210205748888

image-20231210205855165image-20231210205918617

image-20231210205908944

image-20231210205933651

image-20231210205945387image-20231210205957315

image-20231210210015185

image-20231210210027746

image-20231210210043476

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值