首页 > 其他分享 >利用DSF深度优先搜索来解容器倒水问题

利用DSF深度优先搜索来解容器倒水问题

时间:2023-06-14 11:05:12浏览次数:54  
标签:倒水 容器 Container 10 来解 water containers DSF empty


在一些面试算法或智力题中,时不时会遇到容器倒水的问题,例如,有三个容器,分别是10升,7升,4升,7升和4升的容器装满了水,10升容器是空的,如果将容器a中的水倒入容器b时,必须使得a中的水全部倒完,或者b被倒满,问有没有一种倒水序列,使得7升容器或4升容器中只有2升的水。


这个问题怎么会跟图论的深度优先搜索联系起来呢。如果我们把三个容器的水量和容量状态当作一个点,例如初始时刻[10(empty:10, water:0), 7(empty:0, water:7), 4(empty:0, water: 4)] 对应一个点A,进行一次倒水操作,例如将7升容器的水倒入10升容器中后,容器容量状态为[10(empty:3, water:7), 7(empty:7, water:0), 4(empty:0, water:4)] 则对应一个点B,这两个点之间就有一条有向边 A->B. 因此该问题的处理方法就是查找从初始点A开始,进行深度优先搜索,看是否有一条路径,使得从点A, 可达到点C,点C代表中7升容器或4升容器装有两升的水。


在DSF遍历中,需要处理一个问题,那就是遍历中访问一个已经被访问的节点,即遇到环或回路的时候,要停止继续搜索。对应于该问题,也存在对应的回路情形,例如将7升的水倒入10升的容器,接着又将10升容器中的水倒回7升容器,这就形成一个回路,如果程序中不处理这种情况,那就回出现死循环。


接下来我们看看代码设计,首先用一个类来表示容器:

public class Container {
	public int water;
	public int empty;
	
	public Container() {
		water = 0;
		empty = 0;
		
	}
	
	public Container(Container otherContainer) {
		water = otherContainer.water;
		empty = otherContainer.empty;
	}
	
	@Override
	public boolean equals(Object otherContainer) {
		if (this.water == ((Container)otherContainer).water && this.empty == ((Container)otherContainer).empty) {
			return true;
		}
		
		return false;
	}
}

water 表示容器中有多少水,empty 是容器的容量,Container 的包含3个元素的数组可以用来表示一个图节点, 代码如下:

public class Vertice {

	private Container[] containers = new Container[3];
	public Vertice(Container[] containers) {
		for (int i = 0; i < 3; i++) {
			this.containers[i] = new Container(containers[i]);
		}
	}
	
	@Override
	public boolean equals(Object obj ) {
		if (!(obj instanceof Vertice)) {
			return false;
		}
		
		Vertice otherContainers = (Vertice)obj;
		if (containers.length != otherContainers.getContainers().length) {
			return false;
		}
		else {
			for (int i = 0; i < containers.length; i++) {
				if (!containers[i].equals(otherContainers.getContainers()[i])) {
					return false;
				}
			}
		}
			
		return true;
	}
	
	public Container[] getContainers() {
		return containers;
	}
}



接下来,我们再看看程序的逻辑代码:所以的主逻辑代码都通过类 DSFPollWater 来实现,先给出代码,然后我再逐步分析:


<pre name="code" class="java">import java.util.Stack;


public class DSFPollWater {
	private Container[] containers = new Container[3];
	private Stack<Vertice> containerStack = new Stack<Vertice>();
    private boolean stackPrinted = false;
	
	public DSFPollWater() {
		initContainers();
	}
	
	private void initContainers() {
		
		for (int i = 0; i < 3; i++) {
			containers[i] = new Container();
		}
		
		containers[0].empty = 10;
		containers[0].water = 0;
		
		containers[1].empty = 0;
		containers[1].water = 7;
		
		containers[2].empty = 0;
		containers[2].water = 4;
	}
	
	
	public void dsfPollingWater() {
	
		if (containerStateExisted()) {
			return;
		}
	
		for (int i = 0; i < containers.length; i++) {
			for (int j = 0; j < containers.length; j++) {
				if (j != i) {
					saveContainerState();	
					boolean bPollResult = pollWater(containers[i], containers[j]);
					if (isSuccessed() && stackPrinted == false) {
						System.out.println("--ok--");
						printStack();
						return;
					}
					
					if (bPollResult) {
					    dsfPollingWater(); 
					}
					
					restoreContainerState();
				}
			}		
		}	
	}
	
	private void printStack() {
		
		for (int i = 0; i < containerStack.size(); i++) {
			Vertice consArr =  containerStack.get(i);
			Container[] cons = consArr.getContainers();
			System.out.print("(");
			for (int j = 0; j < cons.length; j++) {
				System.out.print(cons[j].empty + cons[j].water + "[empty:" + cons[j].empty + ", water:" + cons[j].water + "]" );
				if (j < cons.length - 1) {
					System.out.print(",");
				}
			}
			System.out.print(")");
			
			if (i < containerStack.size() - 1) {
				System.out.print("->");
			}
			
			System.out.print("\n");
		}
		
		stackPrinted = true;
	}
	
	private void saveContainerState() {
		Container[] backupContainers = new Container[]{containers[0], containers[1], containers[2]};
		Vertice containerArr = new Vertice(backupContainers);
		containerStack.push(containerArr);
	}
	
	private void restoreContainerState() {
		if (containerStack.empty() == false) {
			containers = containerStack.pop().getContainers();
		}
	}
	
	public boolean isSuccessed() {
		for (int i = 1; i < containers.length; i++) {
			if (containers[i].water == 2) {
<p >                                saveContainerState();</p>				return true;
			}
		}
		
		return false;
	}
	
	private boolean pollWater(Container from, Container to) {
		if (to.empty == 0) {
			return false;
		}
		
		int volumn = from.water >= to.empty ? to.empty : from.water;
	
		from.water -= volumn;
		from.empty += volumn;
		to.water += volumn;
		to.empty -= volumn;
		
	
		return true;
	}
	
	private boolean containerStateExisted() {
		Vertice containerArr = new Vertice(containers);
		if (containerStack.contains(containerArr)) {
			return true;
		}
		
		return false;
	}
	
	public static void main(String[] args) {
		DSFPollWater dsfPollWater = new DSFPollWater();
		dsfPollWater.dsfPollingWater();
	}
}


首先,通过initContainers 来初始化三个容器的状态, dsfPollingWater 是进行深度优先便利的主函数,在函数开始,调用containerStateExisted 来判断当前容器状态是否已经存在,也相当于遍历时是否遇到了已经遍历过的点,举个例子, 初始状态是:[10(empty:10, water:0), 7(empty:0, water:7), 4(empty:0, water: 4)],接下来,将7升容器中的水全部倒入10升容器,那么状态转换为[10(empty:3, water:7), 7(empty:7, water:0), 4(empty:0, water:4)],如果接下来做的是将10升容器中的水倒入7升容器,那么初始状态又出现了,这就意味着我们进入了环回路,如果不处理这种情况的话,程序会陷入死循环,containerStateExisted 函数就是用于判断这种情况,如果出现回路,递归要立马返回,以便开始新的搜索。接下来的两个for 循环是为了模拟容器倒水情形,三个容器,倒入的方法有:容器1倒入容器2,容器1倒入容器3,容器2 倒入容器1.....,总共有9种倒法,两个for循环就是执行这9种倒水情形。


进入循环,先执行saveContainerState(), 由于执行倒水动作后,容器的存水状况会发生改变,因此在执行倒水前,会先将容器的当前状态保存起来,对应于图论,当我们进行深度优先遍历时,如果遇到了环路,或者当前路径已经遍历结束,就必须要从当前节点返回其父节点,saveContainerState() 把当前的容器状态存入栈中,以便恢复后进行新的遍历。


接下来,我们将容器i 中的水倒入容器j中(通过调用pollWater,然后判断一下,倒完水后,是否达到成功状态(isSuccessed),如果某个容器中含有2升水,那么堆栈中就保留了倒水过程中的一系列的容器状态,通过printStack 把它们打印出来就可以得知倒水过程。pollWater 如果返回true, 表明从容器i倒水到容器j 是成功的, 如果容器i中没有水,那么倒水就不会成功,pollWater 就会返回false, 如果倒水成功,我们递归调用dsfPollingWater 进行下一轮的倒水尝试,其也对应于深度优先搜索。


containerStateExisted 用于判断当前容器的状态是否已经存在过,如果存在,表明搜索进入了环路,必须返回,不然就是死循环,由于所有遍历过的容器状态都保存在堆栈中,因此只要判断堆栈中有没有于当前状态相同的节点就可以了。

运行上面的程序,可以获得以下结果:

--ok--

(10[empty:10, water:0],7[empty:0, water:7],4[empty:0, water:4])->
(10[empty:3, water:7],7[empty:7, water:0],4[empty:0, water:4])->
(10[empty:0, water:10],7[empty:7, water:0],4[empty:3, water:1])->
(10[empty:7, water:3],7[empty:0, water:7],4[empty:3, water:1])->
(10[empty:7, water:3],7[empty:3, water:4],4[empty:0, water:4])->
(10[empty:3, water:7],7[empty:3, water:4],4[empty:4, water:0])->
(10[empty:6, water:4],7[empty:0, water:7],4[empty:4, water:0])->
(10[empty:0, water:10],7[empty:6, water:1],4[empty:4, water:0])->
(10[empty:4, water:6],7[empty:6, water:1],4[empty:0, water:4])->
(10[empty:4, water:6],7[empty:2, water:5],4[empty:4, water:0])->
(10[empty:8, water:2],7[empty:2, water:5],4[empty:0, water:4])->
(10[empty:8, water:2],7[empty:0, water:7],4[empty:2, water:2])


也就是说,通过上面所标示的十二次倒水操作,我们可以从给定的初始状态实现,达到7升或4升容器装有2升水的结果,从程序运行结果看,4升容器中装有2升水。



标签:倒水,容器,Container,10,来解,water,containers,DSF,empty
From: https://blog.51cto.com/u_16160261/6476222

相关文章

  • 尝试使用硬件电路来解释CRC计算(DS1820或者DS1822的CRC计算)
       之前在培训讲解DS1822的测试时,CRC计算都是以C语言进行讲解的。今天在练习Verilog的时候,觉得也可以使用硬件电路来讲解。   DS1820的CRC计算硬件电路示意图如下:   这个是示意图,方框代表寄存器,XOR代表异或门。Verilog的硬件描述如下:1moduleD_FF2(3......
  • 攻击面管理的重要技术指标有哪些?螣龙安科受邀分享来解答
    近日,国内网络安全知名权威机构-安全牛发布了《网络安全行业全景图》(第十版)。  螣龙安科凭借先进的创新网络安全技术和高度的市场认可度,实力领跑,上榜了“攻击面管理”和“BAS”两大安全分类。  此次强势入选全景图的两大领域,不仅是业界权威机构对螣龙安科产品能力和服......
  • 通过IIS设置来解决System.BadImageFormatException错误
    工作时换了新电脑,然后运行发布后MVC程序就报错:    直接运行Code是OK。错误的原因肯定是64位系统调用了32bit的dll。尝试修改project的Targe为x86,还是无法解决问题。最后查看资料,将应用程序池修改为启用32bit就可以了。 ......
  • Sql Server 数据库事务与锁,同一事务更新又查询锁?期望大家来解惑
    我有一个People表,有三行数据:如果我们没详细了解数据库事务执行加锁的过程中,会不会有这样一个疑问:如下的这段SQL开启了事务,并且在事务中进行了更新和查询操作。BEGINTRAN updatePeoplesetName='张三'whereid=1; select*fromPeoplewhereid=1;committran我......
  • 私钥和公钥到底是谁来加密、谁来解密?
    1. 应用场景场景1(第一种用法):用于加解密,此时使用公钥加密,私钥解密。场景2(第二种用法):用于数字签名,此时使用私钥签名,公钥验签。有点混乱,不要去硬记,你只要这样想即可:-既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;-既然是......
  • 实现一个函数用来解析 URL 的 querystring
    实现如下效果consturl="https://xxxx.com?a=3&b=4&c=5&name=1+1=2";//解析后得到qs如下constqs={a:3,b:4,c:5,name:'1+1=2'};纯碎使用 javascript 完成解析函数,而不利用浏览器DOM特性API,代码如下所示,细节在注释中体现functionparse(url......
  • rsa公钥和私钥到底哪个才是用来加密,哪个用来解密?
    本文转自:91博客;原文地址:http://www.9191boke.com/138589019.html 公钥和私钥在一些银行系统、第三方支付系统SDK中经常会遇到,刚接触公钥私钥的朋友们估计很难区分两者......
  • 快来解锁小程序蓝牙开发技能
    微信小程序中很早就支持了蓝牙能力,看过不少的文档,知道大概的流程和能实现的效果,但是由于一直没有像样的实战项目导致也没有正经的开发上线过,本次缘于接到了一个外包项目,那就......
  • 03_16_JavaWeb||day19_Filter&Listener||day19_Filter&代理模式(23种设计模式之一:用来
    今日内容*Servlet,Filter,Listener被称为JavaWeb三大组件1.Filter:过滤器2.Listener:监听器1.Filter:过滤器概念:生活中的过滤器:净水器,空气净化器,土匪、web中的过滤器:当......
  • 通过写登录接口来解释action的用法
    目录通过写登录接口来解释action的用法一、路由二、表模型三、视图类通过写登录接口来解释action的用法一、路由fromdjango.contribimportadminfromdjango.urlsim......