数据结构 图Graph

一些概念:

  • 连通图:在无向图中,若任意两个顶点v与u都有路径相通,则称该无向图为连通图。
  • 强连通图:在有向图中,若任意两个顶点v与u都有路径相通,则称该有向图为强连通图。
  • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
  • 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

图的存储结构

  • 邻接矩阵(Adjacency Matrix)
    若图中有N个确定的顶点,则用一个N x N的数组来存储点之间的关系(边)。
  • 邻接表(Adjacency List)
    代码来自geeksforgeeks owo哇这个stl用的厉害了
// A simple representation of graph using STL
#include
#include
using namespace std;

// A utility function to add an edge in an
// undirected graph.
void addEdge(vector adj[], int u, int v)
{
    adj[u].push_back(v);
    adj[v].push_back(u);
}

// A utility function to print the adjacency list
// representation of graph
void printGraph(vector adj[], int V)
{
    for (int v = 0; v < V; ++v)
    {
        cout << "\n Adjacency list of vertex "
            << v << "\n head ";
        for (auto x : adj[v])
            cout << "-> " << x;
        printf("\n");
    }
}

// Driver code
int main()
{
    const int V = 5;
    vector adj[V];
    addEdge(adj, 0, 1);
    addEdge(adj, 0, 4);
    addEdge(adj, 1, 2);
    addEdge(adj, 1, 3);
    addEdge(adj, 1, 4);
    addEdge(adj, 2, 3);
    addEdge(adj, 3, 4);
    printGraph(adj, V);
    return 0;
}

然后是自己写的

/*图的存储邻接矩阵*/
#include
#define UNVISTED -1;
using namespace std;
class Edge {
public:
    int start, end;
    int weight;//边的权重
    Edge();
    Edge(int st, int en, int w) :start(st), end(en), weight(w) {};//构造边
    bool operator >(Edge oneEdge);//重载运算符比较边的大小
    bool operator <(Edge oneEdge);
};

bool Edge::operator>(Edge oneEdge) {
    return weight > oneEdge.weight;
}
bool Edge::operator<(Edge oneEdge) {
    return weight < oneEdge.weight;
}
class Graph {
public:
    int vetexNum;//顶点数目
    int edgeNum;//边数目
    int *Mark;//标记某顶点是否被访问过
    Graph(int vNum) {//constructor
        vetexNum = vNum;
        edgeNum = 0;
        Mark = new int[vetexNum];
        for (int i = 0; i < vetexNum; i++) {
            Mark[i] = UNVISTED;
        }
    }
    ~Graph() {
        delete[] Mark;
    }
    virtual Edge FirstEdge(int oneVertex) = 0;
    virtual Edge NextEdge(Edge oneEdge) = 0;
    int VertexNum() { return vetexNum; }
    int EdgesNum() { return edgeNum; }
    bool isEdge(Edge oneEdge) {
        if (oneEdge.weight > 0 && oneEdge.weight < INFINITY&&oneEdge.end >= 0) {
            return true;
        }
        else {
            return false;
        }
    }
    virtual void setEdge(int start, int end, int weight) = 0;
    virtual void deleteEdge(int start, int end) = 0;

};
class AdjGraph:public Graph
{
private:
    int **matrix;//指向领接矩阵的指针
public:
    AdjGraph(int vNum) :Graph(vNum) {
        int i, j;
        matrix = (int**) new int*[vetexNum];
        for (i = 0; i < vetexNum; i++) {
            matrix[i] = new int[vetexNum];
            for (j = 0; j < vetexNum; j++) {//初始化领接矩阵的元素
                matrix[i][j] = 0;
            }
        }
    }
    ~AdjGraph()
    {
        for (int i = 0; i < vetexNum; i++) {
            delete[] matrix[i];//释放每个matrix[i]申请的空间
        }
        delete[] matrix;//释放matrix指针指向的空间
    }
    void showGrapgh() {
        for (int i = 0; i < vetexNum; i++) {
            for (int j = 0; j < vetexNum; j++) {
                cout<

图的遍历

BFS
广度优先搜索(类似树的层序遍历,都是用队列实现的)

代码实现:

DFS
深度优先搜索(类似树的前三种遍历方式,用栈来实现,其实树算一种图吧。。。)

最小生成树(Minimum-Cost Spanning Tree,MST)

概念:对于带权无向图(无向网),其所有生成树中,边上权值之和最小的称为最小生成树。
最小生成树生成算法的基本原理-MST性质
MST性质:假设G=(V, E)是一个连通网,U是顶点V的一个非空子集。若(u, v)是满足条件u∈U且v∈V-U的所有边中一条具有最小权值的边,则必存在一棵包含边(u, v)的最小生成树。


1.Prim算法
假设G=(V, E)是连通网,TE是G上最小生成树中边的集合。算法从U={u0} (u0∈V)且TE={}开始,重复执行下列操作:
1.在所有u∈U且v∈V-U的边(u, v) 中找一条权值最小的边(u', v')并入集合TE中,同时v'并入U,直到V=U为止。
2.最后,TE中必有n-1条边。T=(V, TE)就是G的一棵最小生成树。

void Prim(AdjGraph &G,int s) {//s为起始点
    int n = G.VertexNum();//图的顶点数
    Edge* reEdge = new Edge[n];
    int index = 0;
    bool* Mst = new bool[n];//当Mst[i]=true时,表示i在最小生成树集合里
    int *nearest;//nearest[i]表示生成树到i的最小距离
    int *neighbor;//neighbor[i]和点i相连的点
    nearest = new int[n];
    neighbor = new int[n];
    for (int i = 0; i < n; i++) {//初始化
        neighbor[i] = s;
        nearest[i] = INT_MAX;
        Mst[i] = false;
    }
    Mst[s] = true;
    for (Edge e = G.FirstEdge(s); G.isEdge(e); e = G.NextEdge(e)) {
        nearest[e.end] = e.weight;
        neighbor[e.end] = s;
    }
    for (int i = 1; i < n; i++) {
        //找到离最小生成树最近的点
        int min = INT_MAX;
        int v = -1;
        for (int j = 0; j < n; j++) {
            if (nearest[j] < min && Mst[j] == false) {
                min = nearest[j];
                v = j;
            }
        }
        if (v >= 0) {//找到了那个点
            //访问v,把v加入mst中
            Mst[v] = true;
            reEdge[index++] = Edge(neighbor[v], v, min);//加入边集合
            for (Edge e = G.FirstEdge(v); G.isEdge(e);e = G.NextEdge(e)) {
                if (Mst[e.end]==false&&nearest[e.end] > e.weight) {
                    nearest[e.end] = e.weight;
                    neighbor[e.end] = v;
                }
            }
        }
    }
    for (int i = 0; i < n-1; i++) {
        reEdge[i].printEdge();
        cout << endl;
    }
}

2.Kruskal算法
和Prim算法一样也是基于贪心准则(?),先选取权重最小的边,然后判断两个顶点是否属于两个不同的连通分量,是就加入该边到最小生成树T,否则选择下一条权重最小的边,重复以上操作,直至T中所有顶点都在一个连通分量中。


三个核心操作
1.确定权重最小的边<——实现:按权重组成优先队列(最小堆)/直接从小到大给边排序也行呐
2.判断这条边的两个顶点是否在一个连通分量中<——实现:Union-Find(等价类)
3.不是就合并这两个顶点的连通分量。<——同2.

//Union-Find
class UFSets
{
private:
    int n;//等价类中元素个数
    int* root;//root[i]表示元素i所在等价类的编号
    //int* next;//next[i]标书等价类i后面元素的编号
    int* length;//length表示i所代表的等价类的元素个数
public:
    UFSets(int size) {
        n = size;
        root = new int[size];
        length = new int[size];
        for (int i = 0; i < n; i++) {
            root[i] =i;
            length[i] = 1;
        }
    }
    int Find(int v) { 
        return root[v];
    }//v元素所在分量的编号
    void Union(int v, int u) {
        if (root[v] == root[u])return;
        else if (length[root[v]]>length[root[u]]) {//v分量元素更多,u并入v中
            int rt = root[u];
            length[root[v]] += length[root[u]];//把u联通分量加入v连通分量中
            root[rt] = root[v];//把root[u]的根rt连向v的根
            //u分量里的每个元素root都指向root[v]
            for (int i = 0; i < n;i++) {
                if (root[i] == root[u]) {
                    root[i] = root[v];
                }
            }

        }
        else {
            int rt = root[v];
            length[root[u]] += length[root[v]];//把v联通分量加入u连通分量中
            root[rt] = root[u];//把root[v]的根rt连向u的根
                               //v分量里的每个元素root都指向root[u]
            for (int i = 0; i < n; i++) {
                if (root[i] == root[v]) {
                    root[i] = root[u];
                }
            }
        }
    }
};
//Kruskal算法
bool edgeCmp(Edge e1, Edge e2) {//sort函数第三个参数函数,按权重升序排列边
    return e1 < e2;
}
void Kruskal(AdjGraph &G) {
    int n = G.VertexNum();//顶点数目
    int edgeNum = G.EdgesNum();//边数目
    vector sortEdge;//给边排序的边数组
    vector reEdge(n - 1);//储存最小生成树的边
    UFSets set(n);//等价类集合
    for (int i = 0; i < n; i++) {
        for (Edge e = G.FirstEdge(i); G.isEdge(e); e = G.NextEdge(e)) {
            if (e.start < e.end) {
                sortEdge.push_back(e);
            }
        }
    }
    sort(sortEdge.begin(), sortEdge.end(), edgeCmp);
    int curnum = 0;//生成树边个数
    int index = 0;//排序边数组下标
    while (curnum < n - 1) {
        Edge e = sortEdge[index++];
        int sr = e.start;
        int end = e.end;
        if (set.Find(sr) != set.Find(end)) {
            set.Union(sr, end);
            reEdge[curnum++] = e;//加入最小生成树数组
        }
    }
    for (int i = 0; i < n - 1; i++) {//打印边
        reEdge[i].printEdge();
        cout << endl;
    }

}

你可能感兴趣的:(数据结构 图Graph)