1 题目
题目:拓扑排序(Topological Sorting)
描述:给定一个有向图,图节点的拓扑排序定义如下:对于图中的每一条有向边 A -> B,在拓扑排序中A一定在B之前;拓扑排序中的第一个节点可以是图中的任何一个没有其他节点指向它的节点。针对给定的有向图找到任意一种拓扑排序的顺序。
lintcode题号——127,难度——medium
样例1:
输入:graph = {0,1,2,3#1,4#2,4,5#3,4,5#4#5}
输出:[0, 1, 2, 3, 4, 5]
解释:
样例中的序列表示的图如下所示
拓扑排序可以为
[0, 1, 2, 3, 4, 5]
[0, 2, 3, 1, 5, 4]
...
您只需要返回给定图的任何一种拓扑顺序。
2 解决方案
2.1 思路
拓扑排序是在有逻辑依赖关系的有向无环图中寻找逻辑顺序的算法,并不是一种传统的排序算法,简单来说将节点看成事件、有向边看成依赖的话,就是找到互相依赖的各个事件的可执行先后顺序。
拓扑排序的过程,首先计算各节点的入度(被指向的次数),从任意入读为0的节点为起点开始,从图中移除该节点,其所有邻居入度-1,再寻找入读为0的节点,不断重复,直到遍历完整个图,节点被移除的顺序即为拓扑排序。
拓扑排序的结果可能并不唯一,有多个解。
2.2 时间复杂度
该题的宽度优先搜索,不仅处理节点还需要处理边的关系,算法的时间复杂度为O(n+m),其中n为节点数,m为边数。
因为在图中的m的数量级最高是n的平方,所以也可以认为是O(m)。
2.3 空间复杂度
该题使用了queue队列数据结构保存节点,算法的空间复杂度为O(n)。
3 源码
细节:
- 计算各点入度;
- 取入度0的所有点压入队列作为起点进行BFS宽度优先搜索;
- 从队列弹出节点的时候,其所有指向的邻居入度需-1;
- BFS不断寻找入度为0的点压入队列;
- 弹出顺序即为拓扑排序。
C++版本:
/**
* Definition for Directed graph.
* struct DirectedGraphNode {
* int label;
* vector<DirectedGraphNode *> neighbors;
* DirectedGraphNode(int x) : label(x) {};
* };
*/
/**
* @param graph: A list of Directed graph node
* @return: Any topological order for the given graph.
*/
vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*>& graph) {
// write your code here
if (graph.empty())
{
return vector<DirectedGraphNode*>();
}
// 计算所有节点入度
map<DirectedGraphNode *, int> mapIndegree = inDegree(graph);
// 入度为0的节点作为起点
vector<DirectedGraphNode *> startNodes = getStartNode(mapIndegree);
// 进行宽度优先搜索
vector<DirectedGraphNode *> orders = bfs(startNodes, mapIndegree);
if (orders.size() == graph.size())
{
return orders;
}
return vector<DirectedGraphNode*>();
}
map<DirectedGraphNode *, int> inDegree(vector<DirectedGraphNode *> & graph)
{
map<DirectedGraphNode *, int> result;
for (auto node : graph)
{
result.insert(make_pair(node, 0));
}
for (auto node : graph)
{
for (auto neighbor : node->neighbors)
{
result.at(neighbor)++;
}
}
return result;
}
vector<DirectedGraphNode *> getStartNode(map<DirectedGraphNode *, int> & nodeIndegree)
{
vector<DirectedGraphNode *> result;
for (auto n : nodeIndegree)
{
if (n.second == 0)
{
result.push_back(n.first);
}
}
return result;
}
vector<DirectedGraphNode*> bfs(vector<DirectedGraphNode *> roots, map<DirectedGraphNode *, int> & mapIndegree)
{
vector<DirectedGraphNode*> result;
queue<DirectedGraphNode *> nodeQueue;
for (auto n : roots)
{
nodeQueue.push(n);
result.push_back(n); // queue和vector一起操作
}
// BFS弹出顺序即为排序顺序,但vector最好与queue一起操作,模板代码推荐一起插入数据
while (!nodeQueue.empty())
{
DirectedGraphNode * cur = nodeQueue.front();
nodeQueue.pop();
for (auto n : cur->neighbors)
{
if (--mapIndegree.at(n) == 0)
{
nodeQueue.push(n);
result.push_back(n); // queue和vector一起操作
}
}
}
return result;
}
简洁版本:
vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*>& graph) {
// write your code here
if (graph.empty())
{
return vector<DirectedGraphNode *>();
}
// 计算结点的入度,不记录没有入度的节点
map<DirectedGraphNode *, int> mapIndegree;
for (auto n : graph)
{
for (auto neighbor : n->neighbors)
{
if (mapIndegree.find(neighbor) == mapIndegree.end())
{
mapIndegree.insert(make_pair(neighbor, 1));
}
else
{
mapIndegree.at(neighbor)++;
}
}
}
// 没有入度的点直接放入队列
queue<DirectedGraphNode *> nodeQueue;
vector<DirectedGraphNode *> result;
for (auto n : graph)
{
if (mapIndegree.find(n) == mapIndegree.end())
{
nodeQueue.push(n);
}
}
// BFS弹出顺序即为排序顺序
while (!nodeQueue.empty())
{
DirectedGraphNode * cur = nodeQueue.front();
nodeQueue.pop();
result.push_back(cur);
for (auto neighbor : cur->neighbors)
{
mapIndegree.at(neighbor)--;
if (mapIndegree.at(neighbor) == 0)
{
nodeQueue.push(neighbor);
}
}
}
if (result.size() == graph.size())
{
return result;
}
return vector<DirectedGraphNode *>();
}