首先我们考虑如何表示一张图,一张图由顶点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的个数。