Description
贝爷的人生乐趣之一就是约战马会长. 他知道马会长喜欢和怪兽对决,于是他训练了N只怪兽,并对怪兽用0到N-1的整数进行编号. 贝爷训练怪兽的方式是让它们一对一互殴. 两只怪兽互殴会发生以下三种可能的结果:
1) 什么事也没发生
2) 第一只怪兽永远消失
3) 第二只怪兽永远消失
怪兽们经过了旷日持久的互殴. 贝爷不知道哪些怪兽进行了互殴也不知道它们互殴的顺序,但他确信无论经过多少次互殴,总有一些怪兽能存活下来,他将要派这些怪兽去和马会长对决. 现在他想知道至少有多少只怪兽能存活下来,你能帮他算出来吗?
请实现下面Solution类中的minLeftMonsters函数,完成上述功能.
参数G: N*N(1 <= N <= 50)字符矩阵,G[i][j]表示怪兽i和怪兽j互殴会发生的结果. 字符‘+’代表怪兽i会消失,’-’代表怪兽j会消失,数字’0’则代表什么都没发生. 输入保证G[i][i]一定是’0’,而且G[i][j]和G[j][i]一定相反(’-’和’+’互为相反,’0’和自身相反).
返回值:怪兽存活的最少数目.
class Solution {
public:
int minLeftMonsters(vector
解法一:
用递归的思路,超时了orz
class Solution {
public:
int minLeftMonsters(vector<vector<char>> G) {
int sum = G.size();
vector<int> rest;
for (int i = 0; i < sum; i++) // 最开始所有的怪兽都还存活
rest.push_back(i);
int res = findMin(G, rest, sum);
return res;
}
int findMin(vector<vector<char>> G, vector<int> rest, int restNum) {
int min = restNum;
int tmpMin = min;
for (int i = 0; i < restNum; i++) { // 罗列出两两所有情况,可能会有重复的rest结果
for (int j = i + 1; j < restNum; j++) {
vector<int> tmp = rest;
if (G[i][j] == '+') { // 这个怪兽会被杀死
tmp.erase(tmp.begin() + i);
tmpMin = findMin(G, tmp, restNum - 1);
}
else if (G[i][j] == '-') {
tmp.erase(tmp.begin() + j);
tmpMin = findMin(G, tmp, restNum - 1);
}
if (tmpMin < min) {
min = tmpMin;
}
}
}
return min;
}
};
int main() {
Solution solution;
vector<vector<char>> matrix1 = { {'0', '+', '-'}, {'-', '0', '+'}, {'+', '-', '0'} };
vector<vector<char>> matrix2 = { { '0', '0', '0' },{ '0', '0', '0' },{ '0', '0', '0' } };
int res = solution.minLeftMonsters(matrix1);
cout << res << endl;
system("pause");
return 0;
}
解法二:
抽象成一个有向图,一个怪兽抽象成一个顶点,A怪兽打死B怪兽抽象成A到B的有向边。一个强连通分量经过混战最终只会剩下一个,如果该SCC入度不为0,则该SCC会全军覆灭。一个怪兽(节点)自成一个“强连通分量”,所求结果即图中入度为零的强连通分量的个数。
思路:
- 先把参数字符矩阵G转化成两张图:graph和reverseGraph。
- 调用KosarajuSCC函数计算出所有入度为0的强连通分量,存储在sccs中。
- 统计入度为0的SCC的个数。对每一个强连通分量中的所有节点,看graph中是否有其它节点指向它,若没有,则count++。
Kosaraju算法
本题中最重要的算法就是找出有向图中所有的强连通分量的Kosaraju算法(双深搜)。就是《算法概论》3.4节(p91)的内容。
原理:
如果我们在汇点强连通部件中调用了explore的过程,则我们将恰好获得该强连通部件。在深搜中得到的post值最大的顶点一定位于一个源点强连通部件。那么,对Gr进行深搜,post最大的顶点将来自于Gr中的一个源点强连通部件,相应的也就是G中的汇点强连通部件,按照post值从大到小把各个顶点排序,那么一个SCC中的顶点就在一堆,最前面的一堆就是Gr源SCC中的所有顶点,即graph的汇SCC中所有顶点。栗子如下图所示。
在汇点强连通部件中调用了explore的过程,将恰好获得该强连通部件,因为该汇点SCC不可能去往其它节点了。那么就按照所得节点逆后序在graph执行explored遍历所有节点进行深搜。每调用一次dfs深搜,都得到一个当前图的汇点强连通分量,访问后的节点都标志visited(可以理解为删去),下一次就会访问下一个“汇点强连通分量”。如此可以找出所有的强连通分量。
附赠课本上的图以助理解:
ps: 深度优先搜索函数如下所示:
dfs在函数的最后把该点存储
/*从节点v进行深读搜索*/
void dfs(int v, vector<vector<int>> graph, vector<int>& order, vector<bool>& visited) {
visited[v] = true;
for (int i = 0; i < graph.size(); i++) {
if (!visited[i] && graph[v][i]) {
dfs(i, graph, order, visited);
}
}
order.push_back(v);
}
步骤:
- 在图reverseGraph(Gr)上,调用dfs,得到其逆后序。(注意如果用vector存储,则序列最后一个应当属于GR的源SCC,即图Gr的后序,此时需要把该序列反转一下才是真的逆后序,按dfs的post值从大到小排序,第一个元素属于GR的源SCC;如果用栈逐个压入,那么正好栈顶就是在GR的源SCC中,直接弹出就可以。GR的源SCC就是graph的汇SCC)。
- 在图graph上运行深度优先搜索,按照第一步得到的顶点post值的降序(即第一步得到的逆后序)处理每个顶点。外层对每个顶点的循环中每执行的一次完整深搜,都得到一个强连通分量。
代码:
class Solution {
public:
int minLeftMonsters(vector<vector<char>> G) {
int count = 0; // 最终的结果
int sum = G.size();
// 保存每一个强连通分量
vector<vector<int>> sccs;
KosarajuSCC(G, sccs);
// 统计入度为0的SCC的个数
for (int i = 0; i < sccs.size(); i++) {
bool flag = true;
for (int j = 0; j < sccs[i].size(); j++) {
for (int k = 0; k < sum; k++) {
if (find(sccs[i].begin(), sccs[i].end(), k) != sccs[i].end()) {
continue;
}
//G[i][j]表示怪兽i和怪兽j互殴会发生的结果,
//’-’代表怪兽i吃了怪兽j,j会消失
//也说明有向图中存在从i到j的边
if (G[k][sccs[i][j]] == '-') {
flag = false;
break;
}
}
if (flag == false) {
break;
}
}
if (flag) {
count++;
}
}
return count;
}
void KosarajuSCC(vector<vector<char>> G, vector<vector<int>>& sccs) {
int sum = G.size();
vector<bool> visited(sum, false);
vector<vector<int>> graph(sum, vector<int>(sum, 0));
vector<vector<int>> reverseGraph(sum, vector<int>(sum, 0));
vector<int> reversePostOrder;
vector<int> postOrder;
vector<int> components;
// 构建图的关系矩阵和逆矩阵GR
for (int i = 0; i < sum; i++) {
for (int j = 0; j < sum; j++) {
//G[i][j]表示怪兽i和怪兽j互殴会发生的结果,
//’-’代表怪兽i吃了怪兽j,j会消失
if (G[i][j] == '-') {
graph[i][j] = 1;
}
if (G[i][j] == '+') {
reverseGraph[i][j] = 1;
}
}
}
// 得到逆后序reversePostOrder
// 先深度搜索,得到的reversePostOrder中的序列最后一个应当属于GR的源SCC,即GR的后续
for (int i = 0; i < sum; i++) {
if (!visited[i]) {
dfs(i, reverseGraph, reversePostOrder, visited);
}
}
// 上述reversePostOrder翻转一下才是真的逆后序,按dfs的post值从大到小排序
// 第一个属于GR的源SCC
reverse(reversePostOrder.begin(), reversePostOrder.end());
// 第二次深度搜索,找出所有的强连通分量SCC
// 此处的components相当于存储每一次SCC的所有元素
visited.assign(sum, false);
for (int i = 0; i < sum; i++) {
components.clear();
if (!visited[reversePostOrder[i]]) {
dfs(reversePostOrder[i], graph, components, visited);
}
if(components.size() != 0) {
sccs.push_back(components);
}
}
}
/*从节点v进行深读搜索*/
void dfs(int v, vector<vector<int>> graph, vector<int>& order, vector<bool>& visited) {
visited[v] = true;
for (int i = 0; i < graph.size(); i++) {
if (!visited[i] && graph[v][i]) {
dfs(i, graph, order, visited);
}
}
order.push_back(v);
}
};
int main() {
Solution solution;
vector> matrix1 = { { '0', '+', '-' },{ '-', '0', '+' },{ '+', '-', '0' } };
vector> matrix2 = { { '0', '0', '0' },{ '0', '0', '0' },{ '0', '0', '0' } };
vector> matrix3 = { { '0', '-', '-', '+', '0', '0' },
{ '+', '0', '0', '-', '0', '0' },
{ '+', '0', '0', '-', '-', '0' },
{ '-', '+', '+', '0', '0', '-' },
{ '0', '0', '+', '0', '0', '-' },
{ '0', '0', '0', '+', '+', '0' } };
vector> matrix4 = {
{ '0', '-', '+', '0', '0', '0', '0' },
{ '+', '0', '-', '0', '0', '0', '0' },
{ '-', '+', '0', '0', '0', '0', '-' },
{ '0', '0', '0', '0', '-', '+', '0' },
{ '0', '0', '0', '+', '0', '-', '0' },
{ '0', '0', '0', '-', '+', '0', '-' },
{ '0', '0', '+', '0', '0', '+', '0' } };
int res = solution.minLeftMonsters(matrix3);
cout << res << endl;
system("pause");
return 0;
}