转载请注明: http://blog.csdn.net/HEL_WOR/article/details/50446567

写JVM的垃圾回收的时候,提到了Minor GC时用到的Cheney算法。

在莫枢的回答里有一份他写的Chenny算法的实现,有兴趣的话可以直接进去看,不过他把算法放在了Gist上。Chenny算法和BFS很相似。如果先去读读《算法(第四版)》上BFS算法实现那节,就很容易看出Cheney在处理Survive From区对象Copy到Suevive To区和算法第4版上输出起始节点A到目标节点B的路径有多相似了。
不同之处:其一是Cheney算法中起始节点变成了多个(对应使用根遍历来搜索Active对象的多个根),其二是Chenny算法中对每一个遍历到的子节点的处理是将其复制到To区,并把父节点指向了复制到To区的对象。而在算法第四版上是对每一个遍历到的子节点记录其父节点地址或值以找到起始节点到目标节点的路径。

用图片来描述会好理解好多:

在画这张图的时候,我想起了消息队列,BFS算法使用到队列的作用就像是我们解决输入输出处理速度相差太大而引入的消息队列,在遍历到每一个根节点的子节点时都将这个子节点加入到队列,而每次从队列中取出的对象都做一次引用处理。队列的使用保证了先来先处理,但又不会导致先处理某个对象导致后来出现的对象丢失。

BFS算法的核心部分不是它的使用需要你提供什么条件,而是算法本身定义了一个遍历顺序,核心就是那段入队列和出队列的结构,队列的使用保证了你从顶向下的处理都是按照处理完第一层再处理下一层的顺序,不同的功能需求如处理引用关系(Cheney算法)和输出从A到B路径,都是在这个遍历顺序上实现的。

倒酒问题:

一个8L杯子装满了酒,有一个一个3L空杯子和一个5L空杯子,问怎样才能用最少的次数倒出4L的酒。(不能倒掉)

BFS算法本身就会保证第一次遇到目标节点就会结束搜索,这个功能是通过从上之下,从左至右依次遍历完成的。保证最少到达目标节点时从根节点到目标节点路径上的节点数是最少的;类似在地图上找从城市A到城市B的最少转乘路线。

倒酒问题就是三水杯问题的一个翻版。
可能对我来说最大的难度就是

  1. 如何把这3个杯子里的酒量所表示的状态和状态的改变抽象为一幅图。
  2. 状态的改变要满足那几个条件才算成功。

对第一个问题,现在3个水杯已有的状态是800,已经有一个点了,从这个点出发,每次状态的改变都会产生一个新的点xxx,那么这次状态的改变就可以作为连接800到xxx得边。现在就又回到了第二个问题:如何用函数定义一次状态的改变。

考虑两个数组,一个数组中保存的现有的状态avail[],另一个数组中保存的是每个杯子还剩余多少空间可以倒入酒need[]。
倒酒的几个限制条件:

1,倒酒放的杯子里必须还有余量。
2,被倒入酒方的杯子不能是满的。
3,因为现在酒的总量是8,而3个杯子的最大容量分别是8,3,5;要构造出某个杯子中酒量为4的情况,因为8 = 3 + 5;如果每次杯子都被装满,我们是不能得到目的值4的,因此必须打破这种情况,也就是说倒酒方的avail不能和被倒酒方的need相等。
4,每次倒完酒只会有两种情况,倒酒方杯子空或者被倒酒方杯子满。所以当avail大于need时,该如何处理;当avail小于need时,又该如何处理。
5,状态不能回退,比如不能出现800->530->800的情况。

把这5个限制条件用代码描述出来,状态转移函数也就完成了。

package BFS;
import java.util.*;public class StateTransfer {/// <summary>/// 每个杯子的现有酒量/// </summary>private static int[] avail;/// <summary>/// 每个杯子的现有酒量/// </summary>private static int[] availTemp;/// <summary>/// 每个杯子的还可装入的酒量/// </summary>private static int[] need;/// <summary>/// 每个杯子的还可装入的酒量/// </summary>private static int[] needTemp;/// <summary>/// 每个杯子的还可装入的酒量/// </summary>private static int[] capacityArr;/// <summary>/// 已存在的状态存记录/// 用数组长度不好限定。。所有只能用链表了/// </summary>private static List<Integer> stateRecord = new ArrayList<Integer>();/// <summary>/// 扩展长度构造函数/// </summary>/// <param name="length">杯子个数</param>public StateTransfer(int length) {avail = new int[length];need = new int[length];capacityArr = new int[length];availTemp = new int[length];needTemp = new int[length];}/// <summary>/// 默认构造函数/// </summary>public StateTransfer() {avail = new int[3];need = new int[3];capacityArr = new int[3];availTemp = new int[3];needTemp = new int[3];}/// <summary>/// 状态转移函数/// </summary>/// <param name="input">每个杯子的现有酒量</param>/// <param name="capacity">每个杯子的最大容量</param>/// <returns>所有子节点</returns>public List<Integer> StateTransferMethod(int input, int capacity) {List<Integer> child = new ArrayList<Integer>();//// 此次转移完成后的状态int state = 0;int inputLength = 0;int calcLen = input;while(calcLen != 0){inputLength++;calcLen /= 10;}//// 先填充数据for (int i = 0; i < inputLength; i++) {// 以8561 千位/1000  个位%10  百位/100再%10 十位/10再%10// 取最高位   if(i == 0){avail[i] = (int)(input / Math.pow((double)10, (double)(inputLength - 1)));capacityArr[i] = (int)(capacity / Math.pow((double)10, (double)(inputLength - 1)));need[i] = capacityArr[i] - avail[i];availTemp[i] = avail[i];needTemp[i] = need[i];}// 取个位if(i == inputLength - 1){avail[i] = input % 10;capacityArr[i] = capacity % 10;need[i] = capacity % 10 - input % 10;availTemp[i] = avail[i];needTemp[i] = need[i];}// 0-inputLength之间avail[i] = (int)(input / Math.pow((double)10, (double)(inputLength - 1 - i))) % 10;capacityArr[i] = (int)(capacity / Math.pow((double)10, (double)(inputLength - 1 - i))) % 10;need[i] = capacityArr[i] - avail[i];availTemp[i] = avail[i];needTemp[i] = need[i];}//// 再构造状态转移条件for (int i = 0; i < inputLength; i++) {//// 如果杯子已满 已经不能再向其倒酒了 跳过if (need[i] == 0) {continue;}//// 到达这一步表示杯子没满 其它可以向其倒酒以改变状态for (int j = 0; j < inputLength; j++) {if (j == i) {continue;}//// 其他杯子里已经没酒了 不可能倒出酒来 跳过if (avail[j] == 0) {continue;}//// 如果要倒出非杯子capacity的酒量 就必须打破达到容量值的倒法if (need[i] == avail[j]) {continue;}//// 走到这一步 则可以让其他杯子向其倒酒了//// 不能让酒满出来if (need[i] < avail[j]){//// 倒酒结束后要么倒酒方杯子空 要么被倒酒方杯子满avail[i] = capacityArr[i];//// 更新倒酒方余量avail[j] = avail[j] - need[i];//// 更新倒酒方所需need[j] = need[j] + need[i];}else {avail[i] = avail[i] + avail[j];need[i] = need[i] - avail[j];avail[j] = 0;need[j] = capacityArr[j];}state += avail[2] * 100 + avail[1] * 10 + avail[0] * 1;//// 满足状态状态转移条件  但不能是已到达过的状态//// 不能让状态回退 比如800-305-800if (stateRecord.contains(new Integer(state))){state = 0;for (int k = 0; k < inputLength; k++){avail[k] = availTemp[k];need[k] = needTemp[k];}continue;}else{stateRecord.add(new Integer(state));child.add(new Integer(state));}state = 0;for (int k = 0; k < inputLength; k++){avail[k] = availTemp[k];need[k] = needTemp[k];}}}return child;}
}

用Integer来描述状态有一个问题,就是你无法输入或者输出062,只能是62。可能需要用String来描述状态了,再用charAt(int index)来取出某个酒杯的当前酒量,改动太大,所以就这样吧。

现在状态转移函数写出来了,也就是说我们要构造出的图的顶点可以产生了,并且这些产生的顶点是有效并且不重复的,那么接下来就是

1.从根节点遍历出所有可达的有效顶点。
2.将遍历出的顶点指向其父节点以产生图的边。

到这一步我们就把倒酒问题抽象成一幅图了。产生这幅图的两个结构就是BFS和状态转移函数;我们需要重点内容实现的功能比如输出目标节点路径,对引用的处理(Cheney算法),根据要实现的功能的不同,在BFS算法出队列处实现不同的代码。

package BFS;
import java.util.*;public class BreadthFirstSearch {public static Queue<Integer> scaned = new LinkedList<Integer>();public static Map<Integer, Integer> edgeToMap = new Hashtable<Integer, Integer>();public static Map<Integer, Boolean> markedMap = new Hashtable<Integer, Boolean>();private int oriState;public void BFSMethod(int oriState, int capacity) {this.oriState = oriState;scaned.offer(new Integer(oriState));while (!scaned.isEmpty()) {//// 对其每一个邻接点int temp = scaned.poll();List<Integer> child = new StateTransfer().StateTransferMethod(temp, capacity);for (int item : child) {scaned.offer(new Integer(item));//// 记录是否可达markedMap.put(item, true);//// 让子节点能找到其父节点edgeToMap.put(item, temp);}}}public boolean IsPathExist(int targe){if(markedMap.containsKey(targe)){return true;}return false;}public Stack<Integer> PathTo(int targe){if(!this.IsPathExist(targe)){System.out.println(String.format("%d不可达", targe));return null;}Stack<Integer> path = new Stack<Integer>();for(int i = targe; i != this.oriState; i = edgeToMap.get(i)){path.push(new Integer(i));}path.push(new Integer(this.oriState));return path;}
}

使用Hashtable()来表示 子节点 ->父节点。思路类似于在子节点中保存父节点的地址,需要遍历路径时子节点就可能通过这个保存的地址找到父节点。不过这里面是直接保存父节点的值。

对于遍历出的子节点和父节点的关系,如同下面这幅图:

由于目标节点在未遍历到之前时不可见的,所以从根节点开始就记录路径时行不通的,这是一个树结构,所以不能用链表或者说线性的结构来记录路径,并且父子关系只会在相邻层产生,所以只能逆序的记录父节点的地址,找到目标节点后再迭代父节点直到找到根(类似不停跳转)。对于记录父节点,只要有键值对(或者说映射功能)的数据结构都能完成这个功能,数组也可以看做是隐含的键值对。

这个实现其实还有点问题,如果要显示出所有含4的状态还需要加点代码。

最后输出结果:

从Cheney算法-广度优先搜索-倒酒问题(JAVA实现)相关推荐

  1. AI(人工智能:一种现代的方法)学习之:无信息搜索(uninformed search)算法——广度优先搜索、深度优先搜索、Uniform-cost search

    文章目录 参考 搜索算法 深度优先搜索 depth-first search 性能分析 完整性 complete 最优性 optimal 时间复杂度 空间复杂度 广度优先搜索 breadth-firs ...

  2. 算法图解第六章笔记与习题(广度优先搜索)

    算法图解第六章笔记与习题(广度优先搜索) 文章目录 算法图解第六章笔记与习题(广度优先搜索) 6.1 图(graph) 6.2 广度优先搜索 6.3.1 查找最短路径 6.3.2 队列 6.4 实现图 ...

  3. 广度优先搜索(BFS)——抓住那头牛(POJ 4001)

    本文将以(POJ 4001)抓住那头牛 为例,讲解经典算法广度优先搜索(BFS)的STL写法 在实际写算法中,怎么能不使用更快.更方便.更准确.更高效的C++ STL模板呢 相信很多人都了解过广度优先 ...

  4. 《算法笔记》学习日记——8.2 广度优先搜索(BFS)

    目录 8.2 广度优先搜索(BFS) 问题 A: Jugs 问题 B: DFS or BFS? 问题 C: [宽搜入门]8数码难题 问题 D: [宽搜入门]魔板 问题 E: [宽搜入门]巧妙取量 小结 ...

  5. 用BFS(广度优先搜索queuelist)算法解决农夫过河问题

    用BFS(广度优先搜索queue&&list)算法解决农夫过河问题 一.问题需求分析 一个农夫带着一只狼.一只羊和一棵白菜,身处河的南岸.他要把这些东西全部运到北岸.问题是他面前只有一 ...

  6. Java实现算法导论中图的广度优先搜索(BFS)和深度优先搜索(DFS)

    对算法导论中图的广度优先搜索(BFS)和深度优先搜索(DFS)用Java实现其中的伪代码算法,案例也采用算法导论中的图. import java.util.ArrayList; import java ...

  7. 广度优先搜索_计算机入门必备算法——广度优先遍历搜索

    1.  序言 又很久没有学习了,上次学到哈希表又称散列表的相关知识,这次我们学习一种新的数据结构来建立网络模型.这种数据结构被称作图.首先,我们先应该先了解一下什么是图,其次学习第一种图的算法,这种图 ...

  8. 小白的算法初识课堂(part6)--广度优先搜索

    学习笔记 学习书目:<算法图解>- Aditya Bhargava 文章目录 图简介 图是啥 广度优先搜索 寻找最短路径 队列 实现图 实现算法 运行时间 图简介 今天是五一,假如我要从家 ...

  9. 算法导论--广度优先搜索和深度优先搜索

    广度优先搜索 在给定图G=(V,E)和一个特定的源顶点s的情况下,广度优先搜索系统地探索G中的边,以期"发现"可从s 到达的所有顶点,并计算s 到所有这些可达顶点之间的距离(即最少 ...

  10. 算法十——深度优先搜索和广度优先搜索

    文章出处:极客时间<数据结构和算法之美>-作者:王争.该系列文章是本人的学习笔记. 搜索算法 算法是作用于数据结构之上的.深度优先搜索.广度优先搜索是作用于图这种数据结构之上的.图上的搜索 ...

最新文章

  1. 谷歌母公司投资成绩:4大机构各有侧重,投资2个马斯克项目
  2. 使用Entity Framework和WCF Ria Services开发SilverLight之6:查找指定字段
  3. 很经典的117句惊世良言
  4. Java实现螺旋矩阵
  5. ASP.NET Core整合Zipkin链路跟踪
  6. MySQL Cookbook 学习笔记-02
  7. 畅销书主编推出 MATLAB 春季班,限量优惠+送百G资料!
  8. 易理解的海明码的编码和校验原理【转载】
  9. java从外部得到数据_java – 如何实现Observer以从侦听器获取数据?
  10. window mysql 服务安装
  11. python3.7运行报错_Python 3.7 环境下运行 scrapy crawl 报错 def write(self, data, async=False)?...
  12. 用自定义函数联合IF函数实现“一对多”查询
  13. Ubuntu16.04 安装 VIM 代码自动补全插件 YCM
  14. 装饰者模式的应用:react高阶组件和ES6 装饰器
  15. c#学习笔记之Application.DoEvents应用
  16. TCP的SYN报文可以携带payload吗?
  17. 关于Windows的 “睡眠“ 和 “休眠“
  18. Hadoop Yarn Linux Container Executor配置
  19. 8个酷炫的GitHub技巧
  20. 4000块一晚,住进地下88米深坑,这是全国首家AI超五星酒店

热门文章

  1. UWP-Naïve Media Player 2.0
  2. Js Switch语句
  3. 解决Visio另存为(或者导出)pdf字符间距变化/不均等字母间距的问题
  4. 【Android驱动】高通msm8953背光流程
  5. GPS同步时钟(NTP时间服务器)网口物理隔离的好处
  6. 罗技产品序列号追溯条码扫描系统
  7. java 条形码校验_java 实现条形码ENA-13校验码计算方法
  8. MySQL ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  9. “The file being opened for reading does not exist“-HyperMesh
  10. matlab 怎麼卸載乾淨,matlab set gca用法