数据结构编程笔记二十二:第七章 图 拓扑排序算法的实现

来源:互联网 发布:ios开发数据存储 编辑:程序博客网 时间:2024/06/18 08:12

上次我们介绍了图的最短路径算法的实现,这次介绍基于邻接表的拓扑排序算法的实现。

还是老规矩:

程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git

本次拓扑排序程序共用到以下源文件,有一些已经在之前的文章介绍过。还是和以前一样,所有源文件需要放在同一目录下编译。
my_constants.h 各种状态码定义
LinkedList.cpp 不带头结点单链表各种基本操作的实现
ALGraph.h 图的邻接表存储结构表示定义
ALGraph.cpp 基于邻接表的图的基本操作实现
Stack.cpp 顺序栈的基本操作实现
TopologicalSort.cpp 拓扑排序算法的实现
拓扑排序测试.cpp 主函数,调用算法完成拓扑排序程序的演示

为了操作实现方便,我使用了单链表程序来简化部分操作,但是这个单链表与《 数据结构编程笔记四:第二章 线性表 单链表的实现》一文介绍的单链表有所区别,本文用到的单链表不带头结点,很多操作与带头结点的单链表有区别。望读者注意。具体程序参见《数据结构编程笔记十九:第七章 图 图的邻接表存储表示及各基本操作的实现》;

邻接表在《数据结构编程笔记十九:第七章 图 图的邻接表存储表示及各基本操作的实现》一文有所介绍,my_constants.h、ALGraph.h 、ALGraph.cpp和LinkedList.cpp四个源文件与此文中的同名源文件内容完全一致,没有修改。这里不再重复贴了(否则文章会很长,不能突出重点),但在码云上你可以下载到全部源文件,我会把它放在一个目录下。

拓扑排序操作在实现过程中使用了栈。我引入了已经实现的顺序栈来辅助完成拓扑排序。
顺序栈在《 数据结构编程笔记八:第三章 栈和队列 顺序栈和进位制程序的实现》一文中已经有所介绍,除了修改栈的数据类型为int,源代码其余部分我都没有改变。需要的读者可以参考这篇文章。我会在总结后面贴出这篇代码。

本次只贴拓扑排序的核心代码和主函数:

源文件:TopologicalSort.cpp

/*    函数:FindInDegree    参数:ALGraph G 图G(邻接表存储结构)           int indegree[] 存储顶点入度的数组     返回值:无    作用:求顶点的入度*/void FindInDegree(ALGraph G, int indegree[]) {    //工作指针p     ArcNode *p;    //对存储入度的indegree数组赋初值0    for(int i = 0; i < G.vexnum; i++) {        indegree[i] = 0;     }//for    //扫描每个顶点后面的弧链表     for(int i = 0; i < G.vexnum; i++) {        //p指向顶点i后面弧链表的首元结点         p = G.vertices[i].firstarc;        //顶点v的弧链表没有扫描完毕         while(p) { //while(p) <=> while(p != NULL)             //每找到一个弧结点,对应邻接点的入度+1             indegree[p->data.adjvex]++;            //p指向下一个弧结点             p = p->nextarc;        }//while     }//for}//FindInDegree/*    函数:TopologicalSort    参数:ALGraph G 有向图G(邻接表存储结构)     返回值:状态码,操作成功返回OK,操作失败返回ERROR     作用:若G无回路,则输出G的顶点的一个拓扑序列并返回OK,          否则返回ERROR。*/Status TopologicalSort(ALGraph G) {    //count为已输出顶点数,初值为0    int i, k, count = 0;    //入度数组,存放各顶点当前入度数    int indegree[MAX_VERTEX_NUM];    //声明栈S     Stack S;    //工作指针p,指向弧结点     ArcNode *p;     //对各顶点求入度并存入数组indegree[]    FindInDegree(G, indegree);    //初始化零入度顶点栈S    InitStack(S);    //扫描所有顶点    for(i = 0; i < G.vexnum; ++i) {        //若其入度为0,则将i压入零入度顶点栈S        //if(!indegree[i]) <=> if(indegree[i] == 0)         if(!indegree[i]) {             Push(S, i);         }//if    }//for    //当零入度顶点栈S不空    //while(!StackIsEmpty(S)) <=> while(StackIsEmpty(S) == FALSE)    while(!StackIsEmpty(S)) {        //从栈中弹出一个零入度顶点的序号,并将其保存到i        Pop(S, i);        //输出i号顶点        printf(" %d ", G.vertices[i].data);        //已输出顶点数+1        ++count;        //扫描i号顶点的每个邻接顶点        for(p = G.vertices[i].firstarc; p; p = p->nextarc) {             //i号顶点的邻接点序号为k            k = p->data.adjvex;            //k的入度减1,若减为0,则将k入栈S            //if(!(--indegree[k])) <=> if((--indegree[k]) == 0)            if(!(--indegree[k])) {                 Push(S, k);            }//if        }//for    }//while    //零入度顶点栈S已空,图G还有顶点未输出    if(count < G.vexnum) {        printf("此有向图有回路\n");        return ERROR;    }//if    else {        return OK;    }//else }//TopologicalSort

源文件:拓扑排序测试.cpp

//**************************引入头文件*****************************#include <stdio.h>   //使用了标准库函数 #include <stdlib.h>  //使用了动态内存分配函数 #include "my_constants.h"  //引入自定义的符号常量,主要是状态码 #include "ALGraph.h"       //引入图的邻接表存储结构定义 #include "LinkedList.cpp"  //引入单链表实现,用到其中的插入、删除等操作 #include "ALGraph.cpp"     //引入图的邻接表存储结构基本操作实现#include "Stack.cpp"       //引入顺序栈的基本操作实现 #include "TopologicalSort.cpp" //引入图的拓扑排序算法实现 int main() {    printf("----------------图的拓扑排序(邻接表)演示程序------------------\n\n");     //图的邻接表存储方式     ALGraph G;    //创建图g并打印初始状态     printf("->测试图G的创建:(拓扑排序需要图的类型为有向图,请输入0)\n");     CreateGraph(G);    printf("图G创建成功!\n");    printf("->打印创建后的图G:\n");     Display(G);    //测试拓扑排序     printf("\n->拓扑排序结果:");     TopologicalSort(G);    //测试销毁图G    printf("\n->测试销毁图G:");     DestroyGraph(G);    printf("销毁成功!");    printf("演示结束");}//main

测试采用书上P182页图7.28。请读者自行验证拓扑排序算法的正确性。

程序运行过程中的输入和输出:

----------------图的拓扑排序(邻接表)演示程序------------------->测试图G的创建:(拓扑排序需要图的类型为有向图,请输入0)请输入图的类型(有向图输入0, 有向网输入1,无向图输入2,无向网输入3): 0请输入图的顶点数,边数: 6,8请输入6个顶点的值(用空格隔开):1 2 3 4 5 6请输入每条弧(边)的弧尾和弧头(以逗号作为间隔):1,21,31,43,23,54,56,46,5图G创建成功!->打印创建后的图G:此图为有向图!图中共有6个顶点,8条弧(边),它们分别是:+----+-----------------------------------------------|顶点|   邻接顶点(和权值)+----+-----------------------------------------------|  1 | →4  →3  →2+----+-----------------------------------------------|  2 |+----+-----------------------------------------------|  3 | →5  →2+----+-----------------------------------------------|  4 | →5+----+-----------------------------------------------|  5 |+----+-----------------------------------------------|  6 | →5  →4+----+------------------------------------------------>拓扑排序结果: 6  1  3  2  4  5->测试销毁图G:销毁成功!演示结束--------------------------------Process exited with return value 0Press any key to continue . . .

总结:
拓扑排序的过程就是从图中找出入度为0的顶点入栈,然后从栈中弹出顶点,然后找出与该顶点邻接的新的入度为0的顶点入栈,再弹出,如此循环,直到图中没有入度为0的顶点时看看顶点是否全部输出,若没有则说明图中存在环,拓扑排序失败,否则,拓扑排序成功。

下次的文章会介绍图这一章最后一个程序:关键路径。感谢大家一直以来的关注和支持。再见!

附:修改栈元素类型后的顺序栈源代码。

源文件:Stack.cpp

#define STACK_INIT_SIZE 20 //栈的初始大小 #define STACKINCREMENT 5   //每次扩容增加多少 //-------------------栈的顺序存储表示------------------------- typedef int SElemType;   //栈的元素为int类型 typedef struct {          //栈的顺序存储表示                            SElemType *base;            //栈底指针,在栈构造之前和销毁之后,base的值为NULL     SElemType *top;             //栈顶指针    int stacksize;              //当前已分配的存储空间,以元素为单位 }Stack; //--------------------------栈的相关函数(供非递归后序遍历使用)----------------------------/*    函数:InitStack_Sq    参数:Stack &S 顺序栈引用      返回值:状态码,OK表示操作成功     作用:构造一个空的顺序栈 */Status InitStack(Stack &S){    //动态申请顺序栈的内存空间,并检查内存空间是否成功分配    //if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))))    //这句代码相当于以下两行代码:    //S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));    //if(!S.base)  <=>  if(S.base == NULL)     if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)))){        printf("内存分配失败,程序即将退出!\n");        exit(OVERFLOW);    }//if    //由于刚动态分配完的栈是空栈,所以栈顶指针和栈底指针都指向栈底       S.top = S.base;    //栈的大小就是栈的初始化大小参数STACK_INIT_SIZE     S.stacksize = STACK_INIT_SIZE;    //操作成功     return OK; }//InitStack_Sq/*    函数:DestoryStack_Sq    参数:Stack &S 顺序栈引用      返回值:状态码,OK表示操作成功     作用:释放顺序栈S所占内存空间 */Status DestoryStack(Stack &S){    //栈底指针保存的是顺序栈内存空间的首地址     free(S.base);    //操作成功     return OK; }//DestoryStack_Sq/*    函数:StackIsEmpty_Sq    参数:Stack S 顺序栈S     返回值:若顺序栈S是空栈返回1,否返回0     作用:判断顺序栈S是否为空栈*/Status StackIsEmpty(Stack S){    //栈顶指针和栈底指针都指向栈底表示此栈是空栈     return S.top == S.base; }//StackIsEmpty_Sq/*    函数:ReallocStack_Sq    参数:Stack &S 顺序栈S引用     返回值:状态码,操作成功返回OK,否则返回ERRROR    作用:将栈S扩容,每扩容一次,栈的大小增加STACKINCREMENT*/Status ReallocStack(Stack &S){    //为顺序栈重新分配内存(扩容),扩展的空间大小是STACKINCREMENT    /*if(!(S.base = (SElemType *)realloc(S.base,                 (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType))))      这句代码相当于:      S.base = (SElemType *)realloc(S.base,                         (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType));      if(!S.base) <=> if(S.base == NULL)    */    if(!(S.base = (SElemType *)realloc(S.base,                (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType)))){        printf("内存分配失败,程序即将退出!\n");        exit(OVERFLOW);    }//if    //由于扩容前栈已经满了,所以栈顶指针位置就是栈底指针+原来栈的大小     S.top = S.base + S.stacksize;    //扩容后,栈的大小增加了STACKINCREMENT     S.stacksize += STACKINCREMENT;    //操作成功     return OK; }//ReallocStack_Sq/*    函数:Push_Sq    参数:Stack &S 顺序栈引用          SElemType e 被插入的元素e     返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR    作用:(入栈、压栈)插入元素e为新的栈顶元素*/Status Push(Stack &S, SElemType e){     //入栈时发现栈满了,就要追加存储空间(扩容)     if(S.top - S.base >= S.stacksize) {          //调用扩容函数        ReallocStack(S);    }//if    //插入前,栈顶指针指向当前栈顶元素的下一个位置     //将e赋值给栈顶指针所指存储空间(插入元素e),栈顶指针后移    //*S.top++ = e;  <=>  *(S.top) = e;  S.top++;     *S.top++ = e;}//Push_Sq/*    函数:Pop_Sq    参数:Stack &S 顺序栈引用          SElemType &e 带回被删除的元素值e     返回值:删除成功返回OK,否则返回ERRROR    作用:(出栈,弹栈)若栈不空,则删除S的栈顶元素,用e返回其值*/Status Pop(Stack &S, SElemType &e){     //在空栈中执行出栈操作没有意义,所以要判断栈是否为空    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用     //S.base != NULL判断栈是否为空     if(StackIsEmpty(S)) {          return ERROR;    }//if    //删除前,栈顶指针指向当前栈顶元素的下一个位置    //--S.top;之后,栈顶指针刚好指向被删除元素     //栈顶指针前移,保存被删除的元素值到e    //e=*--S.top;  <=>  --S.top;   e=*(S.top);    e = *--S.top;    //操作成功     return OK; }//Pop_Sq/*    函数:GetTop    参数:Stack S 顺序栈S    返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR    作用:用e返回栈顶元素的值,但是栈顶元素不做出栈操作 */Status GetTop(Stack S, SElemType &e){    //空栈没有栈顶元素,所以要先判断栈是否为空     //注意栈是否为空和栈是否存在不是一个概念,所以不可以用     //S.base != NULL判断栈是否为空     if(StackIsEmpty(S)) {          return ERROR;    }//if    //注意:栈顶指针指向栈顶元素的下一个位置    e = *(S.top - 1);      /*   注意:此处不能使用“e = *(--S.top); ”的原因          1. --S.top自减操作改变了栈顶指针本身的指向,使得该指针向前移动一位,相当于删除了原来栈中的最后一个元素(最后一个元素出栈);          2. S.top-1 仅仅表示栈顶指针的上一个位置,并没有改变S.top的值,*(S.top-1)表示取栈顶指针前一个位置的值,即栈顶元素的值           3. 这两种写法造成的结果是不同的,如果是纯代数运算,两者没有差别,但在指向数组            (顺序结构在C语言中是用一维数组描述的)的指针变量运算中,这两个表达式有特殊含义             在指针运算中,“指针变量-1 ”表示该指针变量所指位置的前一个位置,            这种做法并不改变指针变量本身的值。             --指针变量   不仅使得该指针指向原来所指位置的上一个位置, 还修改了指针变量本身的值            在栈中,栈顶指针和栈底指针所指向的位置有特殊的含义,故两者不等价。            */      //操作成功      return OK; }//GetTop_Sq/*    函数:StackLength_Sq    参数:Stack S 顺序栈S     返回值:若顺序栈S是空栈返回1,否返回0     作用:判断顺序栈S是否为空栈*/Status StackLength(Stack S){    //栈的长度就是栈顶指针和栈底指针之间的元素个数     return (S.top - S.base); }//StackLength_Sq/*    函数:Print    参数:ElemType e 被访问的元素     返回值:状态码,操作成功返回OK,操作失败返回ERROR     作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,          该函数使用时需要配合遍历函数一起使用。 */Status Print_Stack(SElemType e){    printf("%5d  ", e);    return OK;}//Print /*    函数:StackTraverse_Sq    参数:Stack S 顺序栈S           Status(* visit)(SElemType) 函数指针,指向元素访问函数。     返回值:状态码,操作成功返回OK,操作失败返回ERROR     作用:调用元素访问函数按出栈顺序完成顺序栈的遍历,但并未真正执行出栈操作 */Status StackTraverse(Stack S, Status(* visit)(SElemType)) {    //在空栈中执行遍历操作没有意义,所以要判断栈是否为空    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用     //S.base != NULL判断栈是否为空     if(StackIsEmpty(S)) {        printf("此栈是空栈");         return ERROR;    }//if    //调用元素访问函数依次访问栈中的每个元素    for(int i = 0; i < StackLength(S); ++i){        //调用元素访问函数,一旦访问失败则退出          if(!visit(S.base[i])) {            return ERROR;        }//if     }//for    //输出换行,是控制台显示美观     printf("\n");    //操作成功     return OK;}//StackTraverse_Sq
阅读全文
0 0
原创粉丝点击