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会全军覆灭。一个怪兽(节点)自成一个“强连通分量”,所求结果即图中入度为零的强连通分量的个数。

思路:

  1. 先把参数字符矩阵G转化成两张图:graph和reverseGraph。
  2. 调用KosarajuSCC函数计算出所有入度为0的强连通分量,存储在sccs中。
  3. 统计入度为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);} 

步骤:

  1. 在图reverseGraph(Gr)上,调用dfs,得到其逆后序。(注意如果用vector存储,则序列最后一个应当属于GR的源SCC,即图Gr的后序,此时需要把该序列反转一下才是真的逆后序,按dfs的post值从大到小排序,第一个元素属于GR的源SCC;如果用栈逐个压入,那么正好栈顶就是在GR的源SCC中,直接弹出就可以。GR的源SCC就是graph的汇SCC)。
  2. 在图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;// 构建图的关系矩阵和逆矩阵GRfor (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的源SCCreverse(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;
}

算法期中1007. 怪兽训练 (找出有向图中所有的强连通分量的Kosaraju算法)相关推荐

  1. 程序设计思维 C - 班长竞选 (强连通分量、kosaraju算法)

    题目 大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了 ...

  2. python判断数组中是否有重复元素_python经典面试算法题4.1:如何找出数组中唯一的重复元素...

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. [百度面试题] 难度系数:⭐⭐⭐ 考察频率:⭐⭐⭐⭐ 题目描述 ...

  3. Tree Operations 打印出有向图中的环

    题目: You are given a binary tree with unique integer values on each node. However, the child pointers ...

  4. 数据结构第5章例题 若矩阵Am×n中存在某个元素aij满足:aij是第i行中的最小值且是第j列中的最大值,则称该元素为矩阵A的一个鞍点。试编写一个算法,找出A中的所有鞍点。

    [例5.1] 若矩阵Am×n中存在某个元素aij满足:aij是第i行中的最小值且是第j列中的最大值,则称该元素为矩阵A的一个鞍点.试编写一个算法,找出A中的所有鞍点. 算法如下: void saddl ...

  5. C++找出数组中的第一个非重复整数的算法(附完整源码)

    C++找出数组中的第一个非重复整数的算法 C++找出数组中的第一个非重复整数的算法完整源码(定义,实现,main函数测试) C++找出数组中的第一个非重复整数的算法完整源码(定义,实现,main函数测 ...

  6. 找出数组中第i小元素(时间复杂度Θ(n)--最坏情况为线性的选择算法

    找出数组中第i小元素 期望时间复杂度:Θ(n) 最坏情况的时间复杂度Θ(n^2) int randomized_select(int *array,int start,int end,int inde ...

  7. 【408计算机考研】|【2018统考真题-41】| 给定一个含 n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数

    目录 一.题目 二.解答 三.测试数据 一.题目   给定一个含 n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算 法,找出数组中未出现的最小正整数.例如,数组{-5, 3, 2, 3}中未 ...

  8. 算法Day8|字符串专题二 剑指 Offer 58 - II. 左旋转字符串,28. 找出字符串中第一个匹配项的下标,459. 重复的子字符串

    剑指 Offer 58 - II. 左旋转字符串 解题思路: 反转区间为前n的子串 反转区间为n到末尾的子串 反转整个字符串 class Solution {public String reverse ...

  9. 给定一个含n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。

    笔者初涉<算法设计与分析>这门专业课,在做一些算法设计题的过程中遇到一些小感悟,特此记录和大家分享. 下面直接给出算法题目: 给定一个含n(n≥1)个整数的数组,请设计一个在时间上尽可能高 ...

最新文章

  1. 几张图看懂列式存储(转)
  2. 构建弹性架构组件—ELB和ASG
  3. 备战2022秋季“金三银四”跳槽必备:软件测试面试题,贡献给需要的小伙伴,最后有惊喜哦
  4. volatile 的内存语义
  5. word2vec原理(二):基于Hierarchical Softmax的模型
  6. How Browser Works
  7. 苹果电脑无法用普通域用户加入域,用域管理员却可以,怎么破!?
  8. win10安装中国蚁剑
  9. ubuntu下安装jdk、tomcat、mysql
  10. Windows系统下搭建Git本地代码库
  11. (图文)HBASE的知识点以及工作原理的详细解释--架构
  12. 数据结构---堆的相关操作
  13. java中常用的缓存流程、缓存分类、缓存问题
  14. win10 安装dig工具与使用dig命令
  15. 扬州市 工程师职称计算机考试,扬州市建筑专业工程师专业技术资格条件
  16. 《分布式任务调度平台XXL-JOB》
  17. 勾股定理的毕达哥拉斯证明
  18. R语言基础—学习笔记 lecture01
  19. 【思特奇杯·云上蓝桥-算法集训营】第1周----真题汇总+思路分析
  20. 快速实现抖音的分享登录(android)

热门文章

  1. 如何结交大佬 -- 舔狗是要技术的
  2. 什么是Adobe After Effects? AE代表什么?
  3. oracle 分布式事务处理等待锁,关于“ORA-02049: 超时: 分布式事务处理等待锁”的处理过程...
  4. 一起撸个朋友圈吧(step2) 数据结构(JSON结构)【下】篇
  5. ISTA FISTA
  6. 截取数组对应的长度值(形成一个新的数组)
  7. postgressql数据库集群搭建
  8. 几种非易失性存储器的比较
  9. 解压后java文字乱码_怎么解决java解压zip包出现乱码
  10. 转:心理学中的巴纳姆效应