浅谈数据结构-拓扑排序

在生活、工作中进行一项任务,必须有先后顺序,这就是流程图,在流程图中有if分支,也就是下一个活动的展开,可能上一次活动中所有分支执行完毕,这样就形成了拓扑结构。接入项目中比较简单,我们可以通过链表表示项目之间各个活动的关系,当项目流程比较复杂时,我们需要有向图来描述这种关系,而且是不能循环的有向图,我们称之为有向无环图。

在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的有限关系,这样的有向图为顶点表是活动的图,成为AOV网(Activity On Vertex Network)。在AOV网中弧表示活动之间的某种约束关系。

一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行。如图3-6是一个具有三个顶点的回路,由<A,B>边可得B活动必须在A活动之后,由<B,C>边可得C活动必须在B活动之后,所以推出C活动必然在A活动之后,但由<C,A>边可得C活动必须在A活动之前,从而出现矛盾,使每一项活动都无法进行。这种情况若在程序中出现,则称为死锁或死循环,是应该必须避免的。

    在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

一、算法思想

从AOV网中选取一个入度为0的顶点输出,然后删除此顶点,并删除以此顶点为尾的弧,继续重复此操作,直到输出全部顶点或者AOV网中不存在入度为0的顶点为止。 在左图中入度为0的点位c1和c2,如按照拓扑排序算法进行运算结果为:

(1) C1,C8,C9,C2,C3,C5,C4,C7,C6

(2) C2,C1,C3,C5,C4,C6,C8,C9,C7

(3) C1,C2,C3,C8,C9,C5,C4,C6,C7

    由AOV网构造出拓扑序列的实际意义是:如果按照拓扑序列中的顶点次序,在开始每一项活动时,能够保证它的所有前驱活动都已完成,从而使整个工程顺序进行,不会出现冲突的情况。

二、算法分析

  1. 从AOV网络中删除入度为0的顶点;有入度这个概念,则用链表的存储结构储存数据。
    //边表结点
    typedef struct EdgeNode
    {
        int nNodevex;     //邻接点的点表中结点的坐标
        EdgeType nNodeWeight;   //用于网图中边的权值
        EdgeNode* next;       //链域,指向下一个邻接点
    }EdgeNode,*pEdgeNode;
    
    typedef struct VertexNodeAdv
    {
        int in;                  //顶点的度
        VertexType nNodeData;   //顶点表中存储的数据
        pEdgeNode pFirstNode;   //顶点表和边表中关联指针,指向边表头指针
    }VertexNodeAdv,*pVertexNodeAdv,VertexListAdv[MAXVEX];;
    
    
    //图结构
    typedef struct
    {
        VertexListAdv vertexList;
        int numVertess,numEdges;
    }GraphListAdv;
    存储结构
  2. 删除此顶点还有顶点尾的弧;此时删除顶点(输出数据),删除顶点为尾的弧,意味这弧消失,图中一个顶点的入度减1.必须在顶点表中有表示度的变量。
  3. 继续循环,直到不存在度为0的顶点;就是循环停止条件是顶点全部输出。在程序中用堆栈图中顶点。
  4. 入度为0的顶点全部入栈,进入循环后,一顶点出栈,获取这顶点中连接的下一个顶点。

三、例图解释

(1) 在(a)图中v0和v1的入度都为0,不妨选择v0并输出之,接着删去顶点v0及出边<0,2>,得到的结果如(b)图所示。

(2) 在(b)图中只有一个入度为0的顶点v1,输出v1,接着删去v1和它的三条出边<1,2>,<1,3>和<1,4>,得到的结果如(c)图所示。

(3) 在(c)图中v2和v4的入度都为0,不妨选择v2并输出之,接着删去v2及两条出边<2,3>和<2,5>,得到的结果如(d)图所示。

(4) 在(d)图上依次输出顶点v3,v4和v5,并在每个顶点输出后删除该顶点及出边,操作都很简单,不再赘述。

为了利用C++语言在计算机上实现拓扑排序算法,AOV网采用邻接表表示较方便。

四、代码

int GraphData::GetGraphAdvListLocation(GraphListAdv* pList,char chpoint)
{
    GraphListAdv *pGraphList = pList;
    int i = 0;
    for (i = 0;i< pGraphList->numVertess;i++)
    {
        if (pGraphList->vertexList[i].nNodeData == chpoint)
        {
            break;;
        }
    }
    if (i >= pGraphList->numVertess)
    {
        return -1;
    }
    return i;
}
void GraphData::CreateGraphListAdv(GraphListAdv* pList,int numVer,int numEdegs)
{
    int weight = 0;
    GraphListAdv *pGraphList = pList;
    pGraphList->numVertess = numVer;
    pGraphList->numEdges = numEdegs;
    EdgeNode* firstNode,*secondNode;
    //创建顶点表
    for (int i= 0; i < numVer;i++)
    {
        pGraphList->vertexList[i].nNodeData = getchar();
        pGraphList->vertexList[i].pFirstNode = NULL;
        while(pGraphList->vertexList[i].nNodeData == '
')
        {
            pGraphList->vertexList[i].nNodeData = getchar();
        }
    }

    //创建边表    
    for(int k = 0; k < pGraphList->numEdges; k++)
    {
        char p, q;
        printf("输入边(vi,vj)上的下标i,下标j和权值:
");

        p = getchar();
        while(p == '
')
        {
            p = getchar();
        }
        q = getchar();
        while(q == '
')
        {
            q = getchar();
        }
        scanf("%d", &weight);    

        int m = -1;
        int n = -1;
        m = GetGraphAdvListLocation(pGraphList, p);
        n = GetGraphAdvListLocation(pGraphList, q);
        if(n == -1 || m == -1)
        {
            fprintf(stderr, "there is no this vertex.
");
            return;
        }
        //getchar();
        //字符p在顶点表的坐标为m,与坐标n的结点建立联系权重为weight
        firstNode = new EdgeNode();
        firstNode->nNodevex = n;
        firstNode->next = pGraphList->vertexList[m].pFirstNode;
        pGraphList->vertexList[n].in++;
        firstNode->nNodeWeight = weight;
        pGraphList->vertexList[m].pFirstNode = firstNode;

        ////第二个字符second
        //secondNode = new EdgeNode();
        //secondNode->nNodevex = m;
        //secondNode->next = pGraphList->vertexList[n].pFirstNode;
        //pGraphList->vertexList[n].in++;
        //secondNode->nNodeWeight = weight;
        //pGraphList->vertexList[n].pFirstNode = secondNode;

    }
}
void GraphData::TopoLogicalSort(GraphListAdv *pList)
{
    EdgeNode *e;
    int ncount = 0;  //统计输出顶点个数,用于判断是否有环
    int nIndex ; // y用于保存度为0的坐标
    stack<int> staNode;
    for (int i = 0;i < pList->numVertess; i++)
    {
        if (pList->vertexList[i].in == 0)
        {
            staNode.push(i);    //将入度为0的顶点入栈
        }
    }
    while(!staNode.empty())
    {
        nIndex = staNode.top();  // 获取入度为0的顶点
        staNode.pop();     //出栈
        printf("%c ->",pList->vertexList[nIndex].nNodeData);  //打印顶点
        ncount ++;
        //对此顶点的处理:下一个顶点入度减1,如果为0还要进栈
        for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next)
        {
            //对此顶点弧表遍历
            int temp = e->nNodevex;
            //将temp号顶点邻接点的入度减1,若为0,则入栈,以便于下次循环输出
            if (!(--pList->vertexList[temp].in))
            {
                staNode.push(temp);
            }
        }
    }
    if (ncount < pList->numVertess)
    {
        printf(" 图有内环");
    }

}
拓扑程序

将例图解释中的图输入到程序中,其中0对应a,1对应b,等输入其中,进行拓扑排序输出。拓扑排序输出类似广度遍历。

首先将入度为0的顶点压入堆栈;堆栈为0,1.(a,b)

判断堆栈是否为空,不为空,压出;此时为1.(b),输出。

获取顶点1的弧尾顶点,有4,3,2,入度分别减1,入度为0的入栈,4入栈(e)。

4(e)出栈,输出,继续循环。

image

原文地址:https://www.cnblogs.com/polly333/p/4778372.html