数据结构与算法——有向无环图的拓扑排序C++实现

拓扑排序简介:

拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从Vi到Vj的路径,那么在排序中Vi在Vj的前面。

如果图中含有回路,那么拓扑排序是不可能的。此外,拓扑排序不必是唯一的,任何合理的排序都可以。

对于上面的无环图:v1,v2,v5,v4,v3,v7,v6和v1,v2,v5,v4,v7,v3,v6都是合理的拓扑排序。

一个简单的求拓扑排序的思路:

1、先找出任意一个没有入边的顶点

2、然后显出该点,并将它和它邻接的所有的边全部删除。

3、然后,对图中其它部分做同样的处理。

图用邻接表表示法来存储:

参考博客:数据结构与算法——图的邻接表表示法类的C++实现

左边的数组此时就有用了,用来保存每个顶点的信息,该数组中每个元素的数据结构为:

//保存每个顶点信息的数据结构
struct GraphNode{
    int vertex;//当前顶点的标号
    int inDegree;//当前顶点的入度
    int topNum;//当前顶点的拓扑排序的顺序标号
};

图的邻接表示法的类的接口:

/*******************************************************
*  类名称: 邻接表图
********************************************************/
class Graph{
    private:
        int edge_num;//图边的个数
        int vertex_num;//图的顶点数目
        list<Node> * graph_list;//邻接表
        vector<GraphNode> nodeArr;//保存每个顶点信息的数组

    public:
        Graph(){}
        Graph(char* graph[], int edgenum);
        ~Graph();
        void print();
        vector<int> topoSort();//拓扑排序
    private:
        vector<int> get_graph_value(char* graph[], int columns);
        void addEdge(char* graph[], int columns);
};

拓扑排序成员函数:

/*************************************************
*  函数名称:topoSort()
*  功能描述:对图中的顶点进行拓扑排序
*  参数列表:无
*  返回结果:返回顶点拓扑排序之后的结果
*************************************************/
vector<int> Graph::topoSort()
{
    vector<int> topoSortArr;

    for(int count = 0; count < vertex_num; ++count){
        //找到一个入度为0的顶点
        int i;
        for(i = 0; i < vertex_num; ++i){
            if((nodeArr[i].inDegree == 0)&&(nodeArr[i].vertex != -1))
                break;
        }

        if(i == vertex_num)
            break;

        //此时顶点i的入度为0
        //删除该点和删除与该点相邻的边
        //并将与顶点i相连的顶点的入度减1
        nodeArr[i].inDegree = -1;
        for(list<Node>::iterator it = graph_list[i].begin(); it != graph_list[i].end(); ++it){
            nodeArr[(*it).vertex].inDegree--;
        }

        topoSortArr.push_back(i);
    }

    return topoSortArr;
}

测试函数:

1、读取图文件中的数据,图中的数据格式为下面所示:

0,0,1,1
1,0,2,2
2,0,3,1

第1列是边号,第2列是边的起点,第3列是边的终点,第4列是边的权重。

/****************************************************************
*   函数名称:read_file
*   功能描述: 读取文件中的图的数据信息
*   参数列表: buff是将文件读取的图信息保存到buff指向的二维数组中
*             spec是文件中图最大允许的边的个数
*             filename是要打开的图文件
*   返回结果:无
*****************************************************************/
int read_file(char ** const buff, const unsigned int spec, const char * const filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("Fail to open file %s, %s.\n", filename, strerror(errno));
        return 0;
    }
    printf("Open file %s OK.\n", filename);

    char line[MAX_LINE_LEN + 2];
    unsigned int cnt = 0;
    while ((cnt < spec) && !feof(fp))
    {
        line[0] = 0;
        fgets(line, MAX_LINE_LEN + 2, fp);
        if (line[0] == 0)   continue;
        buff[cnt] = (char *)malloc(MAX_LINE_LEN + 2);
        strncpy(buff[cnt], line, MAX_LINE_LEN + 2 - 1);
        buff[cnt][4001] = 0;
        cnt++;
    }
    fclose(fp);
    printf("There are %d lines in file %s.\n", cnt, filename);

    return cnt;
}

2、释放刚才读取的图的信息

/****************************************************************
*   函数名称:release_buff
*   功能描述: 释放刚才读取的文件中的图的数据信息
*   参数列表: buff是指向文件读取的图信息
*             valid_item_num是指图中边的个数
*   返回结果:void
*****************************************************************/
void release_buff(char ** const buff, const int valid_item_num)
{
    for (int i = 0; i < valid_item_num; i++)
        free(buff[i]);
}

3、主测试函数

int main(int argc, char *argv[])
{
    char *topo[5000];
    int edge_num;
    char *demand;
    int demand_num;

    char *topo_file = argv[1];
    edge_num = read_file(topo, 5000, topo_file);
    if (edge_num == 0)
    {
        printf("Please input valid topo file.\n");
        return -1;
    }

    Graph G(topo, edge_num);
    G.print();
    vector<int> topoSortArr = G.topoSort();
    cout << "拓扑排序的结果: ";
    for(unsigned i = 0; i < topoSortArr.size(); ++i)
        cout << topoSortArr[i] << " ";
    cout << endl;

    release_buff(topo, edge_num);

	return 0;
}

图类的源代码:

#ifndef GRAPH_H
#define GRAPH_H

#include <list>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iterator>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

using namespace std;

#define MAX_VERTEX_NUM 600

//保存每个顶点信息的数据结构
struct GraphNode{
    int vertex;//当前顶点的标号
    int inDegree;//当前顶点的入度
    int topNum;//当前顶点的拓扑排序的顺序标号
};

//图节点信息
typedef struct Node{
    int edge_num;//边号
    int src;//源点
    int vertex;//自身
    int weight;//边的权重
}Node; 

/*******************************************************
*  类名称: 邻接表图
********************************************************/
class Graph{
    private:
        int edge_num;//图边的个数
        int vertex_num;//图的顶点数目
        list<Node> * graph_list;//邻接表
        vector<GraphNode> nodeArr;//保存每个顶点信息的数组

    public:
        Graph(){}
        Graph(char* graph[], int edgenum);
        ~Graph();
        void print();
        vector<int> topoSort();//拓扑排序
    private:
        vector<int> get_graph_value(char* graph[], int columns);
        void addEdge(char* graph[], int columns);
};

/*************************************************
*  函数名称:topoSort()
*  功能描述:对图中的顶点进行拓扑排序
*  参数列表:无
*  返回结果:返回顶点拓扑排序之后的结果
*************************************************/
vector<int> Graph::topoSort()
{
    vector<int> topoSortArr;

    for(int count = 0; count < vertex_num; ++count){
        //找到一个入度为0的顶点
        int i;
        for(i = 0; i < vertex_num; ++i){
            if((nodeArr[i].inDegree == 0)&&(nodeArr[i].vertex != -1))
                break;
        }

        if(i == vertex_num)
            break;

        //此时顶点i的入度为0
        //删除该点和删除与该点相邻的边
        //并将与顶点i相连的顶点的入度减1
        nodeArr[i].inDegree = -1;
        for(list<Node>::iterator it = graph_list[i].begin(); it != graph_list[i].end(); ++it){
            nodeArr[(*it).vertex].inDegree--;
        }

        topoSortArr.push_back(i);
    }

    return topoSortArr;
}

/*************************************************
*  函数名称:print
*  功能描述:将图的信息以邻接表的形式输出到标准输出
*  参数列表:无
*  返回结果:无
*************************************************/
void Graph::print()
{
    cout << "******************************************************************" << endl;
    //for(int i = 0 ; i < MAX_VERTEX_NUM; ++i){
    for(int i = 0 ; i < vertex_num; ++i){
        if(graph_list[i].begin() != graph_list[i].end()){
            cout << i << "-->";
            for(list<Node>::iterator it = graph_list[i].begin(); it != graph_list[i].end(); ++it){
                cout << (*it).vertex << "(边号:" << (*it).edge_num << ",权重:" << (*it).weight << ")-->";
            }
            cout << "NULL" << endl;
        }
    }

    cout << "******************************************************************" << endl;
}

/*************************************************
*  函数名称:get_graph_value
*  功能描述:将图的每一条边的信息保存到一个数组中
*  参数列表: graph:指向图信息的二维数组
             columns:图的第几条边
*  返回结果:无
*************************************************/
vector<int> Graph::get_graph_value(char* graph[], int columns)
{
    vector<int> v;
    char buff[20];
    int i = 0, j = 0, val;
    memset(buff, 0, 20);

    while((graph[columns][i] != '\n') && (graph[columns][i] != '\0')){
        if(graph[columns][i] != ','){
            buff[j] = graph[columns][i];
            j++;
        }
        else{
            j = 0;
            val = atoi(buff);
            v.push_back(val);
            memset(buff, 0, 20);
        }
        i++;
    }
    val = atoi(buff);
    v.push_back(val);

    return v;
}

/*************************************************
*  函数名称:addEdge
*  功能描述:将图的每一条边的信息加入图的邻接表中
*  参数列表:graph:指向图信息的二维数组
             columns:图的第几条边
*  返回结果:无
*************************************************/
void Graph::addEdge(char* graph[], int columns)
{
    Node node;
    vector<int> v = get_graph_value(graph, columns);

    node.edge_num = v[0];
    node.src = v[1];
    node.vertex = v[2];
    node.weight = v[3];

    //根据顶点的标号,求的总的顶点数目
    if(node.vertex > vertex_num)
        vertex_num = node.vertex;

    //要考虑重复的边,但是边的权重不一样
    for(list<Node>::iterator it = graph_list[node.src].begin(); it != graph_list[node.src].end(); ++it){
        if((*it).vertex == node.vertex){
            if((*it).weight > node.weight){
                (*it).weight = node.weight;
            }
            return;
        }
    }

    //将信息写入到保存每个顶点的数组中
    nodeArr[node.src].vertex = node.src;
    nodeArr[node.vertex].vertex = node.vertex;
    nodeArr[node.vertex].inDegree++;//入度加1 

    graph_list[node.src].push_back(node);
}

/*************************************************
*  函数名称:构造函数
*  功能描述:以邻接表的形式保存图的信息,并保存必须经过的顶点
*  参数列表:graph:指向图信息的二维数组
             edgenum:图的边的个数
*  返回结果:无
*************************************************/
Graph::Graph(char* graph[], int edgenum):nodeArr(MAX_VERTEX_NUM)
{
    edge_num =  edgenum;
    vertex_num = 0;
    graph_list = new list<Node>[MAX_VERTEX_NUM+1];

    //对保存顶点信息的数组进行初始化,如果vertext=-1表示没有该顶点
    for(int i = 0; i < MAX_VERTEX_NUM; ++i){
        nodeArr[i].vertex = -1;
        nodeArr[i].inDegree = 0;
        nodeArr[i].topNum = -1;
    }

    for(int i = 0; i < edgenum; ++i){
        addEdge(graph, i);
    }

    vertex_num++;
}

/*************************************************
*  函数名称:析构函数
*  功能描述:释放动态分配的内存
*  参数列表:无
*  返回结果:无
*************************************************/
Graph::~Graph()
{
    delete[] graph_list;
}

#endif

测试函数的源代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/timeb.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include "graphTopoSort.h"

#define MAX_LINE_LEN 4000

int read_file(char ** const buff, const unsigned int spec, const char * const filename);
void release_buff(char ** const buff, const int valid_item_num);

int main(int argc, char *argv[])
{
    char *topo[5000];
    int edge_num;
    char *demand;
    int demand_num;

    char *topo_file = argv[1];
    edge_num = read_file(topo, 5000, topo_file);
    if (edge_num == 0)
    {
        printf("Please input valid topo file.\n");
        return -1;
    }

    Graph G(topo, edge_num);
    G.print();
    vector<int> topoSortArr = G.topoSort();
    cout << "拓扑排序的结果: ";
    for(unsigned i = 0; i < topoSortArr.size(); ++i)
        cout << topoSortArr[i] << " ";
    cout << endl;

    release_buff(topo, edge_num);

	return 0;
}

/****************************************************************
*   函数名称:read_file
*   功能描述: 读取文件中的图的数据信息
*   参数列表: buff是将文件读取的图信息保存到buff指向的二维数组中
*             spec是文件中图最大允许的边的个数
*             filename是要打开的图文件
*   返回结果:无
*****************************************************************/
int read_file(char ** const buff, const unsigned int spec, const char * const filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("Fail to open file %s, %s.\n", filename, strerror(errno));
        return 0;
    }
    printf("Open file %s OK.\n", filename);

    char line[MAX_LINE_LEN + 2];
    unsigned int cnt = 0;
    while ((cnt < spec) && !feof(fp))
    {
        line[0] = 0;
        fgets(line, MAX_LINE_LEN + 2, fp);
        if (line[0] == 0)   continue;
        buff[cnt] = (char *)malloc(MAX_LINE_LEN + 2);
        strncpy(buff[cnt], line, MAX_LINE_LEN + 2 - 1);
        buff[cnt][4001] = 0;
        cnt++;
    }
    fclose(fp);
    printf("There are %d lines in file %s.\n", cnt, filename);

    return cnt;
}

/****************************************************************
*   函数名称:release_buff
*   功能描述: 释放刚才读取的文件中的图的数据信息
*   参数列表: buff是指向文件读取的图信息
*             valid_item_num是指图中边的个数
*   返回结果:void
*****************************************************************/
void release_buff(char ** const buff, const int valid_item_num)
{
    for (int i = 0; i < valid_item_num; i++)
        free(buff[i]);
}

测试用例:

0,1,2,1
1,1,3,1
2,1,4,1
3,2,4,1
4,2,5,1
5,3,6,1
6,4,3,1
7,4,6,1
8,4,7,1
9,5,4,1
10,5,7,1
11,7,6,1

运行结果:

时间: 2024-09-29 03:27:18

数据结构与算法——有向无环图的拓扑排序C++实现的相关文章

有向无环图(DAG)拓扑排序的两种方法

如下图的DAG: 第一种: (1)从AOV网中选择一个没有前驱的顶点并且输出它: (2)从AOV网中删除该顶点,并且上去所有该顶点为尾的弧: (3)重复上述两步,直到全部顶点都被输出,或者AOV网中不存在没有前驱的顶点. 第二种: 使用深度优先搜索(DFS),并标记每一个节点的第一次访问(pre)和最后一次访问时间(post),最后post的逆序就是DAG的拓扑排序,其实也是节点在进行DFS搜索时,出栈的逆序就是拓扑排序. 拓扑序列的结果有: (1) c++,高等数学,离散数学,数据结构,概率论

图的邻接表表示与无环图的拓扑排序

一.  图的最常用的表示方法是邻接矩阵和邻接表. 1,邻接矩阵 邻接矩阵其实就是一个二维数组,对于每条边<u,v>,我们就令A[u][v] = 1,如果图为有权图,我们也可以令A[u][v]等于该权,这么表示的优点是非常简单,但是它的空间需求很大,如果图是稠密的,邻接矩阵是合适的表示方法,如果图是稀疏的,那这种方法就太浪费空间了,下面给出图的邻接矩阵表示例子. 2 邻接表 邻接表是图的常用储存结构之一.邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点.如下图

CSU 1804: 有向无环图(拓扑排序)

http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1804 题意:…… 思路:对于某条路径,在遍历到某个点的时候,之前遍历过的点都可以到达它,因此在这个时候对答案的贡献就是∑(a1 + a2 + a3 + ... + ai) * bv,其中a是之前遍历到的点,v是当前遍历的点. 这样想之后就很简单了.类似于前缀和,每次遍历到一个v点,就把a[u]加给a[v],然后像平时的拓扑排序做就行了. 1 #include <bits/stdc++.h>

47. 蛤蟆的数据结构笔记之四十七的有向无环图的应用排序

47.蛤蟆的数据结构笔记之四十七的有向无环图的应用排序 本篇名言:"君子喻于义 ,小人喻于利. -- 孔丘" 接下去来看下有向无环图. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47110397 1.  有向无环图 一个无环的有向图称做有向无环图(directedacycline praph).简称DAG图.DAG 图是一类较有向树更一般的特殊有向图,图1给出了有向树.DAG图和有向图 有向无环图是描述含有公共

有向无环图的最短路径

我们已经知道了如何通过Dijkstra算法在非负权图中找到最短路径.即使图中有负权边,我们也知道通过Bellman-Ford算法找到一个从 给定的源点到其它所有节点的最短路径.现在我们将看到一个在线性时间内运行得更快的算法,它可以在有向无环图中找到从一个给定的源点到其它所有可达顶点的 最短路径,又名有向无环图(DAG). 由于有向无环图无环所以我们不必担心负环的问题.正如我们已经知道在负环里讨论最短路径是毫无意义的一样,因为我们可以在这些环里不断“循环”,但实际上我们得到的路径将变得越来越短(构

图-&gt;有向无环图-&gt;拓扑排序

文字描述 关于有向无环图的基础定义: 一个无环的有向图称为有向无环图,简称DAG图(directed acycline graph).DAG图是一类较有向树更一般的特殊有向图. 举个例子说明有向无环图的应用.假如有一个表达式: ((a+b)*(b*(c+d))+(c+d)*e)*((c+d)*e), 可以用之前讨论的二叉树来表示,也可以用有向无环图来表示,如下图.显然有向无环图实现了对相同子式的共享,从而比二叉树更节省空间. 关于拓扑排序的基础定义: 由某个集合上的一个偏序得到该集合上的一个全须

图的拓扑排序

定义: 图的拓扑排序是对有向无环图来说的,无向图和有环图没有拓扑排序,或者说不存在拓扑排序,对于一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若图G存在边< u, v >,则u在线性序列中出现在v之前.对一个有向无环图进行拓扑排序产生的线性序列称为满足拓扑次序的序列.一个有向无环图可以表示某种动作或者方案,或者状态,而有向无环图的拓扑序列通常表示某种某案切实可行或者各个成员之间的某种关系. 举个栗子,看上面那个图,v1v2v3v4v5v6v7v8是

【拓扑】【宽搜】CSU 1084 有向无环图 (2016湖南省第十二届大学生计算机程序设计竞赛)

题目链接: http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1804 题目大意: 一个有向无环图(DAG),有N个点M条有向边(N,M<=105),每个点有两个值ai,bi(ai,bi<=109),count(i,j)表示从i走到j的方案数. 求mod 109+7的值. 题目思路: [拓扑][宽搜] 首先将式子拆开,每个点I走到点J的d[j]一次就加上一次ai,这样一个点被i走到的几次就加上几次ai,相当于count(i,j)*ai,最终只要求

【数据结构】拓扑排序、最短路径算法、Dijkstra算法、无环图等等

图的定义 图(graph)G = (V,E)由顶点(vertex)的集V和边(Edge)的集E组成.有时也把边称作弧(arc),如果点对(v,w)是有序的,那么图就叫做有向的图(有向图).顶点v和w邻接(adjacent)当且仅当(v,w)属于E. 如果无向图中从每一个顶点到其他每个顶点都存在一条路径,则称该无向图是连通的(connected).具有这样性质的有向图称为是强连通的(strongly connected).如果有向图不是强连通的,但它的基础图(underlying graph)(也