首先我们考虑如何表示一张图,一张图由顶点V及边E构成,图的一般表示方法是邻接矩阵,但邻接矩阵通常无法表示大数据,因为无意义数据的0占据了空间,所以我们一般采取邻接表的方法来表示一张图。

以下是关于图的一些基本概念:
①一个点的入度就是以该点为终点的边的个数,一个点的出度就是以该点为起点的边的个数。
②圈是指在图中没有一条路径(path)的起点和终点是相同的。
③简单图是指没有多重边。

拓扑排序适用于简单有向无圈图,该算法将遍历输出图中的结点,且输出的是顺序必是沿着方向输出(举个例子,就像你要先修这门课才能修那门课的意思)。
它的思路非常简单,每当找到一个入度为0的点,就删去该点并减少与它邻接的点的入度,直到每一个点的入度都为0,如果遍历所有点后还有点的入度不为零,则该图中有圈。

下面我们将以codevs中2833这道题这道题解释拓扑排序:
首先我们定义一张表,并根据给出的M个信息构建一张图。

typedef struct end {
    int end;
    struct end *next;
}end;

typedef end start;

typedef struct {
    start *container;
    int N;
}graph;

graph *Initialize(int N){
    graph *result = (graph *)malloc(sizeof(graph));
    result->container = (start *)malloc(sizeof(start) * (N + 1));
    int i;
    for(i = 1;i <= N;i++){
        result->container[i].next = NULL;
    }
    return result;
}

这里我采用了一个一维的结点指针数组去储存边的起点(即start, 确保每一个点都有可能作为边的起点),每个数组的指针指向一个链表,链表中每个结点存储的是边的终点,以此构造一张邻接表。(数组从1开始故分配N + 1个空间)

接下来我们给出main函数及解题思路:
①InDegree数组代表每一个点的入度,应初始化为0.
②找出数组中入度为0的点,删除该点并减少与它邻接的点的入度(所以必须要以边的起点作为邻接表的行,不然在找它邻接点上会很麻烦)
③当所有点都执行过以上操作后,一个没有圈的图所得到的入度数组应该所有点都为0,若有圈则不为零,于是我们只要统计不为0的个数就好了。

int main(void){
    int N, M;
    scanf("%d%d", &N, &M);
    int *InDegree = (int *)malloc(sizeof(int) * (N + 1));
    int i;
    for(i = 1;i <= N;i++){
        InDegree[i] = 0;
    }
    graph *result = Initialize(N);
    int begin, next;
    for(i = 0;i < M;i++){
        scanf("%d%d", &begin, &next);
        InDegree[next]++;
        Insert(result, begin, next);
    }

    int index;
    start *current;
    for(i = 1;i <= N;i++){
        index = FindZero(InDegree, N);
        if(index != 0){
            current = &result->container[index];
            current = current->next;
            while(current){
                next = current->end;
                InDegree[next]--;
                current = current->next;
            }
            InDegree[index] = -1;
            //-1标志着该点已经查找过
        }
        else{
            break;
        }
    }
    int enter = 0;
    for(i = 1;i <= N;i++){
        if(InDegree[i] > 0){
            enter++;
        }
    }
    if(enter == 0){
        printf("o(∩_∩)o");
    }
    else{
        printf("T_T\n%d", enter);
    }
    return 0;
}

给出Insert和FindZero函数:

void Insert(graph *result, int begin, int next){
    start *headNode = &result->container[begin];
    end *newNode = (end *)malloc(sizeof(end));
    newNode->end = next;
    newNode->next = NULL;
    end *lastNode = headNode;
    if(headNode->next == NULL){
        result->container[begin].next = newNode;
    }
    else{
        while(lastNode->next){
            lastNode = lastNode->next;
        }
        lastNode->next = newNode;
    }
}

int FindZero(int *InDegree, int N){
    int i;
    for(i = 1;i <= N;i++){
        if(InDegree[i] == 0){
            break;
        }
    }
    return i == N + 1 ? 0 : i;
}

其实对于以上的解题方法中,更好的方法是构建一个队列,并把所有入度为0的结点入队,当对图中的一个结点执行删除操作后,检查它周围邻接的点是否有入度为0的结点, 若有则入队,直到队列为空再检测InDegree数组中不为0的个数。