本周记录两道图论的题目,第一道是关于BFS的题目,310, Minimum Height Trees ;第二道是关于DFS的题目,332,Reconstruct Itinerary。
一、Minimum Height Trees
1. 题目描述
For a undirected graph with tree characteristics, we can choose any node as the root. The result graph is then a rooted tree. Among all possible rooted trees, those with minimum height are called minimum height trees (MHTs). Given such a graph, write a function to find all the MHTs and return a list of their root labels.
Format
The graph contains n
nodes which are labeled from 0
to n - 1
. You will be given the number n
and a list of undirected edges
(each edge is a pair of labels).
You can assume that no duplicate edges will appear in edges
. Since all edges are undirected, [0, 1]
is the same as [1, 0]
and thus will not appear together in edges
.
Example 1:
Given n = 4
, edges = [[1, 0], [1, 2], [1, 3]]
0
|
1
/ \
2 3
return [1]
Example 2:
Given n = 6
, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
0 1 2
\ | /
3
|
4
|
5
return [3, 4]
题目的大概意思,是给出n个点分别标记为0-n-1, 同时给出节点的无向边集合构成图。在一个图中,任何节点都可以作为根,然后将其他节点连接成树,找出这些树种最小高度的树MHT,返回其根节点的标号。
2. 思路
一开始采用的方法:先判断n的个数,如果是1个或者2个,那么直接输出即可。如果多于2个,那么,对那些有超过两个邻居的节点进行DFS,求出高度,然后进行比较。结果是超时的。
根据题目中给的提示,一个图最多有几棵MHT呢?答案是两棵。一棵最小高度的树有两种情况:
- 一种是两边子树一样高,此时只有一棵MHT;
- 另一种是左边比右边深一层或者右边比左边深一层,此时有两棵MHT。
考虑左边比右边深两层的时候,此时必然存在可以取根节点的左子节点作为根,使得右边叶子节点往下拉一层,左边上去一层,得到两棵子树一样高的结果。因此,一个图得到最小高度树就以上所述两种情况。
采用BFS的思路,只有一个邻居的节点为叶子节点,将这一层叶子节点删除,就会有新的叶子节点出现。依次删除,当最后只剩下一个节点时,即MHT的根;当最后只剩两个节点时,要判断,如果这两个节点彼此相连,说明都可能是MHT的根。
3. 代码
vector<int> findMinHeightTrees(int n, vector<pair<int, int> >& edges) {
vector<vector<int> > vec(n);
vector<int> res;
//先判断n为1和n为2两种简单情况
if (n == 1)
res.push_back(0);
else if(n == 2){
res.push_back(0);
res.push_back(1);
}
else{
//构建邻接表
for(int i = 0; i < edges.size(); i ++)
{
vec[edges[i].first].push_back(edges[i].second);
vec[edges[i].second].push_back(edges[i].first);
}
queue<int> q;
queue<int> p;
//将所有叶子节点入队
for(int i = 0; i < vec.size(); i ++)
if(vec[i].size() == 1){
q.push(i);
}
//删除每一轮的叶子节点,当只剩下一个叶子节点时,说明它是唯一的MHT根;若剩下两个,则要判断,如果这两个点是相连的,则两个点都可以是根
while(1){
if(q.size() == 1)
break;
if(q.size() == 2 && vec[q.front()].size() == 1)
if(vec[q.front()][0] == q.back())
break;
while(!q.empty())
{
int f = q.front();
q.pop();
int temp = vec[f][0];
for(int j = 0; j < vec[temp].size(); j ++)
if(vec[temp][j] == f)
vec[temp].erase(vec[temp].begin()+j);
if(vec[temp].size() == 1){
p.push(temp);
}
}
if(!q.empty())
break;
q = p;
}
while(!q.empty()){
cout << q.front() << endl;
res.push_back(q.front());
q.pop();
}
}
return res;
}
二、Reconstruct Itinerary
1. 题目描述
Given a list of airline tickets represented by pairs of departure and arrival airports [from, to]
, reconstruct the itinerary in order. All of the tickets belong to a man who departs from JFK
. Thus, the itinerary must begin with JFK
.
Note:
- If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string. For example, the itinerary
["JFK", "LGA"]
has a smaller lexical order than["JFK", "LGB"]
. - All airports are represented by three capital letters (IATA code).
- You may assume all tickets form at least one valid itinerary.
Example 1:tickets
= [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
Return ["JFK", "MUC", "LHR", "SFO", "SJC"]
.
Example 2:tickets
= [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Return ["JFK","ATL","JFK","SFO","ATL","SFO"]
.
Another possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"]
. But it is larger in lexical order.
题目的大概意思,是给出一组字符串对,表示每一张机票出发点和到达点,对这些机票(字符串对)进行重排,使其收尾相连的有序排序,所有机票都要用上。第一个出发点必须是“JFK”。当有两个选择时,选字典序小的。例子如上所示。
2. 思路
将每一个字符串表示的地址看做一个节点,输入给出的有序对使得节点构成一个图。以“JFK”作为图的根进行遍历。当一个节点有两条路可以选择时,优先考虑字典序小的;然而,有时候选择字典序小的路,不一定能遍历完整个图,所以,需要在保证有路的情况下,选择字典序小的。对输入的有序对构建邻接链表,一般我会采用vector<vector<string> >来实现。然而,使用vector实现存在的问题,是无法使得节点有序的排列。为了使邻接点的选择有序,考虑使用set的方式,一个起点对应多个终点,这些终点按照字典序排好,在遍历时,优先取出来判断是字典序小的。
遍历采用DFS的方式,从起点JFK开始,对每一个邻接节点,访问后,在邻接表中删除该节点,不断往下遍历,直到没有邻接节点时,将节点加到结果集。返回上一层。
3.代码
map<string, multiset<string> > go; //邻接链表,set可以使得内部值有序
void dfs(vector<string> &res, string s)
{
while(go[s].size()){
string e = *go[s].begin();
go[s].erase(go[s].begin());
dfs(res,e);
}
res.push_back(s);
}
vector<string> findItinerary(vector<pair<string, string> > tickets) {
vector<string> res;
for(int i = 0; i < tickets.size(); i ++)
{
go[tickets[i].first].insert(tickets[i].second);
}
string s = "JFK";
dfs(res, s);
reverse(res.begin(), res.end());
return res;
}
三、总结
1.DFS使用递归,BFS使用队列;
2.DFS可用于找到最长的连通路径,BFS可以用于处理每一层的节点。
3.邻接链表可以用vector实现,也可以用map实现。