java算法----动态规划(1)


1.机器人行走问题

2.两人博弈问题

 

看上面这道题目,首先先用暴力递归的方法来写,暴力递归就是把机器人从m位置开始,经过k步的所有方法都列举出来,然后找出最终位置在p的点,数一数一共有多少这样的路径就是我们最终想要的答案。然后我们就开始自然尝试,会发现机器人在1位置的时候,其下一步只能去2号位置,当机器人到达n位置的时候,其下一步就只能去n-1这个位置,当机器人在其他位置的时候,可以往左走也可以往右走(自然尝试)。由这些想法之后我们就可以写出暴力递归的方法了。

	public static void main(String[] args) {
		// TODO Auto-generated method stub]
		System.out.println("===========暴力递归========");
		System.out.println("方法数为:"+MaxMethod(5,3,3,4));
		System.out.println("============动态规划===========");
		System.out.println("方法数为:"+Cmain(5,3,3,4));
	}
	public static int MaxMethod(int n,int k,int begin,int end) {
		if(k==0)
			return begin==end?1:0;
			if(begin==1)
				return MaxMethod(n,k-1,begin+1,end);
			else if(begin==n)
				return MaxMethod(n,k-1,begin-1,end);
			else
				return MaxMethod(n,k-1,begin+1,end)+MaxMethod(n,k-1,begin-1,end);
	}
	//根据暴力递归来改成动态规划
	public static int Cmain(int n,int k,int begin,int end) {
		if(n<1||k<0||begin<=0||begin>n|end<=0||end>n)
			return -1;
		int dp[][]=new int[n+1][k+1];
		for(int i=0;i<=n;i++) {
			for(int j=0;j<=k;j++)
				dp[i][j]=-1;
		}
		return MaxMethod1(n,k,begin,end,dp);
	}
	public static int MaxMethod1(int n,int k,int begin,int end,int dp[][]) {
		if(dp[begin][k]!=-1)
			return dp[begin][k];
		//之前没有算过
		int ans=0;
		if(k==0)
			ans=begin==end?1:0;
		else if(begin==1)
			ans=MaxMethod1(n,k-1,2,end,dp);
		else if(begin==n)
			ans=MaxMethod1(n,k-1,begin-1,end,dp);
		else
			ans=MaxMethod1(n,k-1,begin+1,end,dp)+MaxMethod1(n,k-1,begin-1,end,dp);
		dp[begin][k]=ans;
		return ans;
	}

 那么怎样由暴力递归改成动态规划呢?

首先我们要明白暴力递归和动态规划他们两的的区别在哪,暴力递归的过程就是把所有可能存在的方法都枚举一遍,在枚举的过程中即使有些信息我们已经得知了,暴力递归还会再次计算。由于这个原因,暴力递归的时间复杂度就比较高,而动态规划就是把已经算过的数据直接放到数组中,等下次在用这个数据的时候不在计算,直接取值即可。

这个就是递归的过程,我们可以看到,有些过程会重复进行。

怎样由暴力递归改成动态规划呢?

只需要将dp数组中的每个元素设成一个不会用到的值(如-1),在递归的时候判断相应格子上的数是不是-1,如果不是,证明其已经计算过了,直接返回使用就行了。

如何判断dp表的大小?

就看一直在变的量的大小。比如这一题,k和M(begin)一直在变,那么定义一个int dp[][]=new int[n+1][k+1];大的表就行。

两个人纸牌博弈问题

这也是一道比较经典的dp题型了。

 我们需要考虑的是先手和后手,因为两个人都绝顶聪明,所以两个人在选择纸牌的时候会考虑对方的得分情况,所以我们分别对先后和后手进行考虑。先手和后手都可以拿第一张牌,也可以拿最后一张牌

其不能为贪心策略(那张牌大选那一张),因为还要考虑接下来对手拿的牌。我们递归的策略就是先手拿第一张牌加上后手拿一张牌(后手拿的最优)剩下牌的最优和先手拿最后一张牌加上后手拿一张牌剩下牌的最优,算出这两种情况中最大的那种情况就是先手的最好得分。后手就是拿第一张牌或者最后一张牌,因为先手已经把最优的情况选择了,所以后手就只能选择这两种情况得分最小的那种情况。

其代码为:

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner srr=new Scanner(System.in);
		//System.out.println("请你输入一串数字,中间用空格分开,");
		int a[]= {1,23,34,54,65};
		//先手做多得分
		int p1=Fist_Play(a,0,4);
		int p2=Secound_Play(a,0,4);
		System.out.println("先手的最高得分"+p1);
		System.out.println("后手的最高得分"+p2);
		System.out.print("获胜者为:");
		if(p1>p2)
			System.out.println("先手");
		else if(p2>p1)
			System.out.println("后手");
		else
			System.out.println("平局");
		//动态规划
		System.out.println("---------动态规划--------");
		System.out.println(Cmain(a));
	}
	//先手的拿牌情况
	public static int Fist_Play(int a[],int left,int right) {
		if(right==left)
			return a[left];
		else {
			int p1=a[left]+Secound_Play(a,left+1,right);
			int p2=a[right]+Secound_Play(a,left,right-1);
			return Math.max(p1, p2);
		}
	}
	public static int Secound_Play(int a[],int left,int right) {
		if(right==left)
			return 0;//因为剩余的那一张牌已经被先手拿走了,此时数组内没有数字
		else{
			int p1=Fist_Play(a,left+1,right);
			int p2=Fist_Play(a,left,right-1);
			return Math.min(p1, p2);
		}
	}
	/*
	 * 由暴力递归改成动态规划需要注意三点
	 * 1.创建一个合适大小的dp表
	 * 2.找到这个dp表的初始值(看暴力递归的base,case)
	 * 3.填表的顺序(根据暴力递归递归的过程)
	 */
	public static int Cmain(int a[]) {
		int number=a.length;
		int fmap[][]=new int[number][number];
		int gmap[][]=new int[number][number];
		for(int i=0;i<number;i++) {
			for(int j=0;j<number;j++) {
				fmap[i][j]=-1;
				gmap[i][j]=-1;
			}
		}
		int p1=Fist_Play1(a,0,number-1,fmap,gmap);
		int p2=Secound_Play1(a,0,number-1,fmap,gmap);
		if(p1>p2)
			System.out.println("先手");
		else if(p2>p1)
			System.out.println("后手");
		else
			System.out.println("平局");
		return Math.max(p1, p2);
	}
	public static int Fist_Play1(int a[],int left,int right,int dp1[][],int dp2[][]) {
		/*
		 * 根据left和right判断出这个dp表的大小为[a.length][a.length]
		 * if(right==left)
		 *	return a[left];
		 *根据这一句话来判断这个表初始值都哪些
		 */
		int k=0;
		for(int i=0;i<a.length;i++)
			dp1[i][i]=a[left];
		if(dp1[left][right]!=-1)
			return dp1[left][right];
		else {
			/*
			 * int p1=a[left]+Secound_Play(a,left+1,right);
			 *int p2=a[right]+Secound_Play(a,left,right-1);
			 * return Math.max(p1, p2);
			 *由这一段可以得出dp表内的关系
			 */
			int p1=a[left]+Secound_Play1(a,left+1,right,dp1,dp2);
			int p2=a[right]+Secound_Play1(a,left,right-1,dp1,dp2);
			k=Math.max(p1, p2);
			dp1[left][right]=k;
			return dp1[left][right];
		}
	}
	public static int Secound_Play1(int a[],int left,int right,int dp1[][],int dp2[][]) {
		//和先手同理
		for(int i=0;i<a.length;i++)
			dp2[i][i]=0;
		if(dp2[left][right]!=-1)
			return dp2[left][right];
		else {
			int p1=Fist_Play1(a,left+1,right,dp1,dp2);
			int p2=Fist_Play1(a,left,right-1,dp1,dp2);
			dp2[left][right]=Math.min(p1, p2);
			return dp2[left][right];
		}
		
	}

根据第一题的提示,生成一个dp数组,将算过的数放到表中,当要使用表中数据的时候直接调用就行了。填表的时候这个题也比较独特,两张表相互依赖,先手表的对角线 全为第一个纸牌的值,后手的对角线全为0.然后再根据递归,找到依赖将表填完就行了。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小魏苦练算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值