数据结构—拓扑排序

拓扑排序指的是将有向无环图(又称“DAG”图)中的顶点按照图中指定的先后顺序进行排序。

数据结构—拓扑排序_第1张图片
图 1 有向无环图.png

例如,图 1 中的两个图都是有向无环图,都可以使用拓扑排序对图中的顶点进行排序,两个图形的区别是:左图中的 V2 和 V3 之间没有明确的前后顺序;而右图中任意两个顶点之间都有前后顺序。

所以,左图中顶点之间的关系被称为“偏序”关系;右图中顶点之间的关系被称为”全序“关系。

在有向无环图中,弧的方向代表着顶点之间的先后次序,例如从 V1 指向 V2 的弧表示在进行排序时 V1 在前, V2 在后。

全序是偏序的一种特殊情况。对于任意一个有向无环图来说,通过拓扑排序得到的序列首先一定是偏序,如果任意两个顶点都具有前后顺序,那么此序列是全序。

拓扑排序的方法

对有向无环图进行拓扑排序,只需要遵循两个原则:
1.在图中选择一个没有前驱的顶点 V;
2.从图中删除顶点 V 和所有以该顶点为尾的弧。

例如,在对图 1 中的左图进行拓扑排序时的步骤如图 2 所示:


数据结构—拓扑排序_第2张图片
图 2 拓扑排序.png

有向无环图如果顶点本身具有某种实际意义,例如用有向无环图表示大学期间所学习的全部课程,每个顶点都表示一门课程,有向边表示课程学习的先后次序,例如要先学《程序设计基础》和《离散数学》,然后才能学习《数据结构》。所以用来表示某种活动间的优先关系的有向图简称为“AOV网”。

进行拓扑排序时,首先找到没有前驱的顶点 V1,如图 2(1)所示;在删除顶点 V1 及以 V1 作为起点的弧后,继续查找没有前驱的顶点,此时, V2 和 V3 都符合条件,可以随机选择一个,例如图 2(2) 所示,选择 V2 ,然后继续重复以上的操作,直至最后找不到没有前驱的顶点。

所以,针对图 2 来说,拓扑排序最后得到的序列有两种:

  • V1 -> V2 -> V3 -> V4
  • V1 -> V3 -> V2 -> V4

如果顶点之间只是具有偏序关系,那么拓扑排序的结果肯定不唯一;如果顶点之间是全序关系,那么拓扑排序得到的序列唯一。

拓扑排序的C语言实现

在编写程序解决拓扑排序的问题时,大致思路为:首先通过邻接表将 AOV 网进行存储,由于拓扑排序的整个过程中,都是以顶点的入度为依据进行排序,所以需要根据建立的邻接表统计出各顶点的入度。

在得到各顶点的入度后,首先找到入度为 0 的顶点作为拓扑排序的起始点,然后查找以该顶点为起始点的所有顶点,如果入度为 1,说明如果删除前一个顶点后,该顶点的入度为 0,为拓扑排序的下一个对象。

实现代码:

#include 

typedef enum{false,true} bool;
typedef struct ArcNode{
    int adjvex;//邻接点在数组中的位置下标
    struct ArcNode * nextarc;//指向下一个邻接点的指针
}ArcNode;


typedef struct VNode{
    int data;//顶点的数据域
    ArcNode * firstarc;//指向邻接点的指针
}VNode,AdjList[20];//存储各链表头结点的数组


typedef struct {
    AdjList vertices;//图中顶点及各邻接点数组
    int vexnum,arcnum;//记录图中顶点数和边或弧数
}ALGraph;
//找到顶点对应在邻接表数组中的位置下标
int LocateVex(ALGraph G,int u){
    int i;
    for(i=0; ivexnum),&((*G)->arcnum));


    int i;
    for(i=0; i<(*G)->vexnum; i++) {
        scanf("%d",&((*G)->vertices[i].data));
        (*G)->vertices[i].firstarc=NULL;
    }
    int initial,end;
    for (i=0; i<(*G)->arcnum; i++) {
        scanf("%d,%d",&initial,&end);


        ArcNode *p=(ArcNode*)malloc(sizeof(ArcNode));
        p->adjvex=LocateVex(*(*G), end);
        p->nextarc=NULL;


        int locate=LocateVex(*(*G), initial);
        p->nextarc=(*G)->vertices[locate].firstarc;
        (*G)->vertices[locate].firstarc=p;
    }
}
//结构体定义栈结构
typedef struct stack{
    int data;
    struct stack * next;
}stack;
//初始化栈结构
void initStack(stack* *S){
    (*S)=(stack*)malloc(sizeof(stack));
    (*S)->next=NULL;
}
//判断链表是否为空
bool StackEmpty(stack S){
    if (S.next==NULL) {
        return true;
    }
    return false;
}
//进栈,以头插法将新结点插入到链表中
void push(stack *S,int u){
    stack *p=(stack*)malloc(sizeof(stack));
    p->data=u;
    p->next=NULL;
    p->next=S->next;
    S->next=p;
}
//弹栈函数,删除链表首元结点的同时,释放该空间,并将该结点中的数据域通过地址传值给变量i;
void pop(stack *S,int *i){
    stack *p=S->next;
    *i=p->data;
    S->next=S->next->next;
    free(p);
}
//统计各顶点的入度
void FindInDegree(ALGraph G,int indegree[]){
    int i;
    //初始化数组,默认初始值全部为0
    for (i=0; iadjvex]++;
            p=p->nextarc;
        }
    }
}
void TopologicalSort(ALGraph G){
    int indegree[G.vexnum];//创建记录各顶点入度的数组
    FindInDegree(G,indegree);//统计各顶点的入度
    //建立栈结构,程序中使用的是链表
    stack *S;
    initStack(&S);


    int i;
    //查找度为0的顶点,作为起始点
    for (i=0; inextarc) {
            int k=p->adjvex;
            if (!(--indegree[k])) {
                //顶点入度为0,入栈
                push(S, k);
            }
        }
    }
    //如果count值小于顶点数量,表明该有向图有环
    if (count

例如使用上述完整代码解决下图的有向无环图时,拓扑排序的结果为:


数据结构—拓扑排序_第3张图片
image.png
数据结构—拓扑排序_第4张图片
image.png

你可能感兴趣的:(数据结构—拓扑排序)