您的位置:首页 > 其它

LintCode 解题记录 17.10.14 拓扑排序

2017-10-16 11:18 423 查看
第六周,抽了周二、周四和周六三晚做了五道题,来总结一下吧。

Clone Graph

题目描述

给你一幅图,还有一大堆Balabala关于图的介绍,然后返回这幅图的“克隆”。

思路

“克隆”是什么意思呢?就是对于你遍历到的每一个节点,用其节点的label值新建一个节点,然后对于其邻接节点也是如此的操作。此处遍历采用BFS的方法,需要一个OldNode->NewNode的一个map映射来实现。

代码

UndirectedGraphNode* cloneGraph(UndirectedGraphNode* node) {
// write your code here
if (node == NULL) return NULL;
UndirectedGraphNode *copy;
copy = new UndirectedGraphNode(node->label);
//想到用这个映射题目思路就变得很清晰了
map<UndirectedGraphNode*, UndirectedGraphNode*> vis;
queue<UndirectedGraphNode*> q;

q.push(node);
vis.insert({node, copy});
while (!q.empty()) {
UndirectedGraphNode *front = q.front();
q.pop();
for (auto nextNode: front->neighbors) {
if (vis.find(nextNode) == vis.end()) {
copy = new UndirectedGraphNode(nextNode->label);
vis[nextNode] = copy;
q.push(nextNode);
}
//更新队首节点的复制节点的邻接链表的信息
vis[front]->neighbors.push_back(vis[nextNode]);
}
}
return vis[node];
}


Course Schedule

题目描述

现在有n门课标号为0~n-1,给定一个pair{A, B},表示如果要修A课必须得先修B课,现在给你一个pair的集合,问你能否修完所有的课程。

思路

有向图判断是否有环问题。该方法常用拓扑排序来解决,而拓扑排序的常规写法为BFS。当然算法导论中介绍了拓扑排序的dfs写法,当然下面的题目中会涉及到。

代码

bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
// write your code here
vector<vector<int> > graph(numCourses, vector<int>(0));
//声明一个数组,表示图中每个节点的入度,就是有几个箭头指向该节点的意思
vector<int> InDegree(numCourses, 0);
for (auto pair: prerequisites) {
graph[pair.second].push_back(pair.first);
InDegree[pair.first]++;
}

queue<int> q;
for (int i = 0; i < numCourses; i++) {
if (InDegree[i] == 0) q.push(i);
}
//拓扑排序的大致思路就是从入度为0的点开始,遍历其邻接链表,将其入度减1,相当于是删除该入度为0的节点,然后更新何其相邻的节点的入度信息,然后再次寻找下一个入度为0节点直到找不到。
while (!q.empty()) {
int curr = q.front(); q.pop();
for (auto a: graph[curr]) {
InDegree[a]--;
if (InDegree[a] == 0) q.push(a);
}
}
//最后判断所有节点的入度信息,如果有入度不为0的,就代表有环的存在。
for (int i = 0; i < numCourses; i++) {
if (InDegree[i] != 0) return false;
}
return true;
}


Course Schedule II

题目描述

跟Course Schedule一样,如果不能修完全部课程,就返回空数组,如果可以,就可以返回一个拓扑排序序列。

代码

vector<int> findOrder(int numCourses, vector<pair<int, int>> &prerequisites) {
// write your code here
vector<int> res;
vector<vector<int>> graph(numCourses, vector<int>{});
vector<int> in(numCourses, 0);
queue<int> q;

for (auto pair: prerequisites) {
graph[pair.second].push_back(pair.first);
in[pair.first]++;
}

for (int i = 0; i < numCourses; i++) {
if (in[i] == 0) q.push(i);
}

while (!q.empty()) {
int curr = q.front(); q.pop();
res.push_back(curr);
for (auto course: graph[curr]) {
in[course]--;
if (in[course] == 0) q.push(course);
}
}

for (int i = 0; i < numCourses; i++) {
if (in[i
4000
] != 0) return vector<int>{};
}

return res;
}


Route Between Two Nodes in Graph

题目描述

给定一幅有向图,和两个点,问你能否从一个点走到另一个点。

思路

显然是图的搜索问题,bfs+dfs。不过显然bfs的方法要更好一些。

代码

//Bfs Version,没用这个给的graph条件,可能这个条件是用来剪枝的(判断是否访问过)。
bool hasRoute(vector<DirectedGraphNode*> graph, DirectedGraphNode* s, DirectedGraphNode* t) {
// write your code here
queue<DirectedGraphNode*> q;
q.push(s);
while (!q.empty()) {
DirectedGraphNode *curr = q.front(); q.pop();
if (curr->label == t->label) return true;
for (auto node: curr->neighbors) {
q.push(node);
}
}
return false;
}

//Dfs Version
bool hasRoute(vector<DirectedGraphNode*> graph, DirectedGraphNode* s, DirectedGraphNode* t) {
// write your code here
map<DirectedGraphNode*, int> vis;
for (auto node: graph) vis[node] = 0;
return dfs(s, t, vis);
}

bool dfs(DirectedGraphNode* s, DirectedGraphNode* t, map<DirectedGraphNode*, int> &vis) {
if (s->label == t->label) return true;
if (vis[s] == 1) return false;
vis[s] = 1;
for (auto node: s->neighbors) {
if (dfs(node, t, vis))
return true;
}
return false;
}


Topological Sorting

题目描述

给定有向图,返回拓扑排序序列。要求Bfs和Dfs两种实现。

思路1

Bfs实现。拓扑排序算法的常规思路:

1.从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它。

2.从网中删去该顶点,并且删去从该顶点发出的全部有向边。

3.重复上述两步,直到剩余的网中不再存在没有前驱的顶点为止。

因此对任一有向图进行拓扑排序有两种结果,一种是图中全部顶点都包含在拓扑序列中,这说明该图中不存在有向回路,另一种是图中部分顶点未被包含在拓扑序列中,这说明该图中存在有向回路。所以可以采用拓扑排序判断一个有向图中是否存在回路。

代码1

vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*>& graph) {
// write your code here
vector<DirectedGraphNode*> res;
queue<DirectedGraphNode*> q;
map<DirectedGraphNode*, int> in;
//Initialization
for (auto node: graph) in[node] = 0;
for (auto node: graph) {
for (auto next: node->neighbors) {
in[next]++;
}
}

for (auto pair: in) {
if (pair.second == 0) q.push(pair.first);
}

while (!q.empty()) {
DirectedGraphNode *curr = q.front(); q.pop();
res.push_back(curr);

for (auto next: curr->neighbors) {
in[next]--;
if (in[next] == 0) q.push(next);
}
}

return res;
}


思路2

看了《算法导论》关于拓扑排序的一讲,是用Dfs实现的。顿时就感觉”还有这种操作??”。

拓扑排序的Dfs实现:(摘抄自算法导论)

TOPOLOGICAL-SORT(G)

1 call DFS(G) to compute finishing times v. for each vertex v

2 as each vertex is finished, insert in onto the front of a linked list.

3 return the linked list of vertices

意思就是说在dfs回溯的时候将当前结果插入到链表中(头插法),如果是按顺序插入到数组中那么最后得到的就是拓扑排序的逆序。

我们可以在O(V+E)的时间内完成拓扑排序,因为深度优先搜索算法的运行时间为O(V+E),将节点插入到链表最前端所需要的时间为O(1),一共只有|V|个节点需要插入。

代码2

vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*>& graph) {
// DFS version
vector<DirectedGraphNode*> res;
map<DirectedGraphNode*, int> vis;
map<DirectedGraphNode*, int> in;
for (auto node: graph) {
vis[node] = 0;
in[node] = 0;
}
for (auto node: graph) {
for (next: node->neighbors) {
in[next]++;
}
}
for (auto pair: in) {
if (pair.second == 0) {
dfs(vis, pair.first, res);
}
}
reverse(res.begin(), res.end());
return res;
}

void dfs(map<DirectedGraphNode*, int> &vis, DirectedGraphNode *curr, vector<DirectedGraphNode*> &res) {
for (auto next: curr->neighbors) {
if (vis[next] == 1) continue;
vis[next] = 1;
dfs(vis, next, res);
}
//搜索结束之后,将该节点压入res中,最终的拓扑次序与节点的搜索完成时间恰好相反。
res.push_back(curr);
}


Tips

如果在Dfs算法中加上判断有无环路,如何判断呢?只需要令vis[curr] = -1表示正在访问,如果访问到当前元素vis已经被置为-1,那说明存在环路,返回false即可。

贴一个别人写的拓扑排序dfs的Blog:

http://blog.csdn.net/acceptedxukai/article/details/6959882
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: