图的邻接表表示及拓扑排序

图的定义

  • 图(graph) G = ( V ,   E ) ,由顶点(vertex)集 V 和边(edge)集 E 组成。每条边为一个点对 ( v ,   w )

图的表示

  • ① 邻接矩阵表示法:使用一个二维数组A,对每条边(u, v), A[u][v]=1;否则为0。总空间大小为 O ( | v | 2 )
    邻接矩阵表示法很多情况下浪费空间。所以引出,
  • ②邻接表表示法:每个顶点使用一个表来存放其所有邻接的点。空间需求为 O ( | V | + | E | ) 。邻接表是图的标准表示方法。

创建图类及其顶点类

  • 首先定义一个顶点类,简单起见,其属性包含:顶点关键字值、顶点的入度、顶点的邻接表

顶点类模板

template<class T>
class Vertex {      // 创建一个顶点类表示图的顶点
public:
    T data;         // 顶点包含的T类型的关键字值
    vector<Vertex<T>> adj_list; // 顶点的邻接表,用向量表示
    int getIndegree() { return Indegree; }          // 获取顶点的入度

    int Indegree;   // 顶点的入度
    Vertex(int data = 0, int Indegree = 0): data(data), Indegree(Indegree){}    // 默认构造函数
};
  • 图类包含一个点集合(用Vertex类型数组表示)以及其他成员函数
template<class T>
class Graph {       // 创建一个图类
public:
    Vertex<T>* Vlist;       // 包含顶点对象的数组

    Graph(int n);           // 构造函数
    Graph(const Graph<T> &g);   // 复制构造函数

    Graph<T>& operator = (const Graph<T> &g);       // 重载=运算符
    void DeleteVertex(int index);   // 删除某个顶点

    int findZeroIndegreeVertexIndex();  // 寻找入度为0的顶点在图中的下标

    void Topsort();     // 图的拓扑排序实现方式1
    void Topsort1();    // 图的拓扑排序实现方式2,借助队列

private:
    int size;           // 图的顶点个数
};

图的拓扑排序

  • 拓扑排序是对有向无圈图的顶点的一种排序,如果存在一条点 v i , v j 的路径,则拓扑排序使得 v i 出现在 v j 之前。
  • 一个简单的求拓扑排序的方法:先任意找一个入度为0的顶点。然后将该顶点与和它相连的边从图中删除。继续在余图中进行。
  • 例如:
  • 这里写图片描述
  • 上图中,第一个入度为0的顶点为“0”,把该顶点及与之相连的边删除,则入度为0的点为“1”,依次进行……
  • 最终该图的拓扑排序为:0->1->4->3->2->6->5或0->1->4->3->6->2->5

图的拓扑排序实现方法1

template<class T>
void Graph<T>::Topsort()
{
    int index;  // 临时变量表示入度为0顶点下标
    for (int i = 0;i < size;i++)
    {
        index = findZeroIndegreeVertexIndex();  

        cout <<"V"<< Vlist[index].data << "->"; // 打印拓扑排序
        // 使用迭代器遍历向量即W的邻接表
        for (vector<Vertex<T>>::iterator iter = Vlist[index].adj_list.begin(); iter != Vlist[index].adj_list.end();iter++)  
        {
            Vlist[iter->data].Indegree--;       // W的邻接表的入度都减1
        }
        //for (int i = 0;i < Vlist[index].adj_list.size();i++)      // 或者这样实现
        //{
        //  Vlist[Vlist[index].adj_list[i]].Indegree--;
        //}
        Vlist[index].Indegree--;    // 这里把当前入度为0顶点的入度变为负数,实现"假删除"
    }
    cout << endl;
}

借助队列实现拓扑排序

  • 首先,将入度为0的顶点入队列。当队列不为空时,顶点出队列,遍历该顶点的邻接表,并将与之相邻的点的入度都减1。
  • 如果相邻点入度减1之后为0,则相邻点入队列……这样,出队列的顺序就是拓扑排序的顺序。

图的拓扑排序实现方法2

template<class T>
void Graph<T>::Topsort1()
{
    Vertex<T> W;
    queue<Vertex<T>> VertexQueue;       // 借助队列模板创建一个存放图的顶点的队列
    for (int i = 0;i < size;i++)
    {
        if (Vlist[i].Indegree == 0)
            VertexQueue.push(Vlist[i]);     // 入度为0的顶点先入栈
    }
    while (VertexQueue.size()!=0)           // 当队列不为空
    {
        W = VertexQueue.front();            // 出队列
        VertexQueue.pop();
        cout << "V" << W.data << "->";  // 打印拓扑排序

        for (int i = 0;i < W.adj_list.size();i++)   // 遍历W的邻接表
        {
            if (--Vlist[W.adj_list[i].data].Indegree == 0)  //  如果W邻接点的入度减1为0,则入队列(注意这里每个顶点的数据域等于其在图中顶点数组的下标,所以方便了操作)
                VertexQueue.push(Vlist[W.adj_list[i].data]);
        }
    }
    cout << endl;
}

附图的邻接表示及拓扑排序C++实现代码

#include<iostream>
#include<vector>
#include<queue>
using namespace std;

template<class T>
class Vertex {      // 创建一个顶点类表示图的顶点
public:
    T data;         // 顶点包含的T类型的关键字值
    vector<Vertex<T>> adj_list; // 顶点的邻接表,用向量表示
    int getIndegree() { return Indegree; }          // 获取顶点的入度

    int Indegree;   // 顶点的入度
    Vertex(int data = 0, int Indegree = 0): data(data), Indegree(Indegree){}    // 默认构造函数
};



template<class T>
class Graph {       // 创建一个图类
public:
    Vertex<T>* Vlist;       // 包含顶点对象的数组

    Graph(int n);           // 构造函数
    Graph(const Graph<T> &g);   // 复制构造函数

    Graph<T>& operator = (const Graph<T> &g);       // 重载=运算符
    void DeleteVertex(int index);   // 删除某个顶点

    int findZeroIndegreeVertexIndex();  // 寻找入度为0的顶点在图中的下标

    void Topsort();     // 图的拓扑排序实现方式1
    void Topsort1();    // 图的拓扑排序实现方式2,借助队列

private:
    int size;           // 图的顶点个数
};

// 图类函数成员实现
template<class T>
Graph<T>::Graph(int n)
{
    size = n;               // 图的顶点数量
    Vlist = new Vertex<T> [size];   // 为包含顶点的数组分配空间
}

template<class T>
Graph<T>::Graph(const Graph<T> &g)
{
    this->size = g.size;
    this->Vlist = new Vertex<T>[size];
    for (int i = 0;i < size;i++)        // 深层复制
    {
        this->Vlist[i] = g.Vlist[i];
    }
}

template<class T>
Graph<T>& Graph<T>::operator = (const Graph<T> &g)  // 重载=操作符实现
{
    this->size = g.size;
    this->Vlist = new Vertex<T> [size];
    for (int i = 0;i < size;i++)        
    {
        this->Vlist[i] = g.Vlist[i];
    }
    return *this;
}

template<class T>
int Graph<T>::findZeroIndegreeVertexIndex()
{
    for (int i = 0;i < size;i++)
    {
        if (Vlist[i].Indegree == 0)
            return i;
    }
}

template<class T>
void Graph<T>::Topsort()
{
    int index;  // 临时变量表示入度为0顶点下标
    for (int i = 0;i < size;i++)
    {
        index = findZeroIndegreeVertexIndex();  

        cout <<"V"<< Vlist[index].data << "->"; // 打印拓扑排序
        for (vector<Vertex<T>>::iterator iter = Vlist[index].adj_list.begin(); iter != Vlist[index].adj_list.end();iter++)  // 使用迭代器遍历向量即W的邻接表
        {
            Vlist[iter->data].Indegree--;       // W的邻接表的入度都减1
        }
        //for (int i = 0;i < Vlist[index].adj_list.size();i++)      // 或者这样实现
        //{
        //  Vlist[Vlist[index].adj_list[i]].Indegree--;
        //}
        Vlist[index].Indegree--;    // 这里把当前入度为0顶点的入度变为负数,实现"假删除"
    }
    cout << endl;
}

template<class T>
void Graph<T>::Topsort1()
{
    Vertex<T> W;
    queue<Vertex<T>> VertexQueue;       // 借助队列模板创建一个存放图的顶点的队列
    for (int i = 0;i < size;i++)
    {
        if (Vlist[i].Indegree == 0)
            VertexQueue.push(Vlist[i]);     // 入度为0的顶点先入栈
    }
    while (VertexQueue.size()!=0)           // 当队列不为空
    {
        W = VertexQueue.front();            // 出队列
        VertexQueue.pop();
        cout << "V" << W.data << "->";  // 打印拓扑排序

        for (int i = 0;i < W.adj_list.size();i++)   // 遍历W的邻接表
        {
            if (--Vlist[W.adj_list[i].data].Indegree == 0)  //  如果W邻接点的入度减1为0,则入队列(注意这里每个顶点的数据域等于其在图中顶点数组的下标,所以方便了操作)
                VertexQueue.push(Vlist[W.adj_list[i].data]);
        }
    }
    cout << endl;
}

int main()
{
    int n = 7;
    Graph<int> G(n);        // 初始化创建一个7个节点的图
    Vertex<int> V[] = {Vertex<int>(0,0), Vertex<int>(1,1), Vertex<int>(2,2), Vertex<int>(3,3),
                            Vertex<int>(4,1), Vertex<int>(5,3), Vertex<int>(6,2) }; // 节点对象数组
    V[0].adj_list.push_back(V[1]);
    V[0].adj_list.push_back(V[2]);
    V[0].adj_list.push_back(V[3]);  // 顶点0的邻接表

    V[1].adj_list.push_back(V[3]);
    V[1].adj_list.push_back(V[4]);  // 顶点1的邻接表

    V[2].adj_list.push_back(V[5]);  // 顶点2的邻接表

    V[3].adj_list.push_back(V[2]);  // 顶点3的邻接表
    V[3].adj_list.push_back(V[5]);
    V[3].adj_list.push_back(V[6]);

    V[4].adj_list.push_back(V[3]);  // 顶点4的邻接表
    V[4].adj_list.push_back(V[6]);

    V[6].adj_list.push_back(V[5]);  // 顶点6的邻接表

    for (int i = 0;i < n;i++)
    {
        G.Vlist[i] = V[i];          // 构建图
    }

    Graph<int> G1 = G;             // 复制图

    G.Topsort();                   // 拓扑排序1
    G1.Topsort1();                 // 拓扑排序2
    system("pause");
    return 0;
}
  • 运行结果
V0->V1->V4->V3->V2->V6->V5->
V0->V1->V4->V3->V2->V6->V5->
请按任意键继续. . .

参考资料

Mark Allen Weiss: 数据结构与算法分析

猜你喜欢

转载自blog.csdn.net/weixin_40170902/article/details/80771773