动态规划之房屋偷盗问题(超详细C语言实现)

 问题描述:

一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响小偷偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组 nums ,请计算 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:nums = [1,2,3,1]

输出:4

解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。

 偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:nums = [2,7,9,3,1]

输出:12

解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。

 偷窃到的最高金额 = 2 + 9 + 1 = 12 。

主要思路:

本题是求最优子结构的问题,且自身存在子问题重叠(即前n号房子的最大可偷盗金额可由前n-1,n-2……号房子的最大可偷盗金额推出),根据算法设计定义,本题非常适合利用动态规划算法求解。

 决策过程及状态转移方程(最关键点)

将所有的决策对象划分为三个部分:第n号房,n-1号房,前n-2号房,

则设f(n)为前n座房子能偷盗的金额最大值,想要求出f(n)需要进行的决策如下

(1)偷第n号房子:由于相邻的房子不能偷,所以i-1号肯定不能偷,此时最优解则是是加上第n号房子的金额加上前n-2座房子的最大偷盗金额。

(2)偷第n-1号房子

由此我们就得到了解决本问题最重要的状态转移方程

 f[j]=Max(f[j-1],f[j-2]+nums[j]);

具体代码及超详细注释如下,相信聪明的你一定能看懂的

//Written by Tingho 22.11.16
#include<stdio.h>
#include<stdlib.h>
int nums[100];//用来存储房屋及对应金额 
int n =0;  //用来记录输入数据(房屋)的个数 
int f[100];//前n号房屋偷盗的金额 
int x[100];//记录号数 
int y[100];//记录金额 
 
void DisplayAll(int *nums,int n);  //声明及定义函数输入数组及元素个数,打印数组元素 
void DisplayAll(int *nums,int n){
		int x;
	for(x=1;x<=n;x++){
		printf("%d ",nums[x]);
	}
	printf("\n");
}

int Max(int a,int b);              //声明及定义函数Max,输入两个整型变量,返回较大值 
int Max(int a,int b){
	if(a>b)
	  return a;
	else 
	  return b;
}

int main(void){
	
	printf("欢迎使用,请输入房屋个数!\n");             //提示用户输入房屋个数,
	scanf("%d",&n);                                     //若个数不符合规范则提醒用户输入合法个数,直到通过 
	while(n<=0||n>100){
		printf("输入个数不符合规范,请重新输入!\n");
			scanf("%d",&n);
	}
	
	
	printf("请输入房屋金额信息!\n");                //提示用户输入房屋对应金额 
	int i;int num,add;
	int e=1;    // 
	int er[100];//创建数组“error”用以进行记录错误 
    er[0]=0;
	for(i=0;i<=n;i++){
	  if(i==0)      //为了更好地对应问题并进行理解,0号房置0,从1号房开始存储对应金额 
		nums[i]=0;       
	  else{         
	  	   scanf("%d",&num);   //从非零的一号房开始赋值 
	  	  if(0<=num&&num<=400)  //如果输入的对应金额合法,则正常赋值给数组nums 
	  	  	nums[i]=num;
	  	    
	      else{                //若数字不合法,也先按正常顺序进行赋值,但是金额不合法房子的门牌号要通过er数组收集起来,用于后续替换操作 
	       nums[i]=num;
	       er[e]=i;                                          
	       e++;
	     }
	    }
	}
	printf("当前录入的数据如下:");           //不管房子金额是否合法,打印出房子及金额信息进行确认 
	DisplayAll(nums,n);
 //如果有错误,则进行改正环节 	
	if(e>1){                             
		printf("检测到您的输入有%d处错误,请根据提示输入0~400的整数数据以进行替换!\n",e-1);//计数器e在还没有错误时就已经是1,故错误个数为e-1 
    	int l;                                       //变量l是计数器,用于循环条件及打印当前次数 , 
    	for(l=1;l<e;l++){                           //利用循环结构进行e-1次定向替换 
		printf("请输入数据以进行第%d次更正\n",l);   
		int correct;                              //变量correct用以接收改正后的金额信息
		scanf("%d",&correct);                    //输入用于替换错误的数据 
		int c=er[l];                            //c用于取出金额错误的房子门牌号 
		nums[c]=correct;                       //根据位置替换指定的错误房子金额 
		printf("第%d次修改成功!\n",l); 
	  }
	  printf("修改已经全部完成!!\n以下为修改后内容:"); 
	  DisplayAll(nums,n);
	}

	
	
	
//可知全部房子的最大偷盗金额可由前面前n个房子最大取得金额得到,即存在重叠子问题 ,通过动态规划对问题进行求解

	f[0]=nums[0];        //预处理偷盗金额的前两个解  
	f[1]=nums[1];
		int j;     //定义j作为计数器        
	for(j=2;j<=n;j++){
		f[j]=Max(f[j-1],f[j-2]+nums[j]);    //对问题具体分析后,得出问题的状态转移方程 
	}
	printf("能偷盗的最大金额为: %d\n",f[n]);
	
//对决策过程进行拆分并记录到数组中
	
	int k=n;   //通过变量k将f(k-1)代表的前k-1房子最大金额数不断拆分成对房屋选择的决策 
	int a=1;   //变量a用于计数,将决策的房屋号及金额依次存入输入x,y中 
	while(k>=1)//
	if(Max(f[k-1],f[k-2]+nums[k])==f[k-1]){//如果前k-1的最大金额没有拆分出对房屋的选择,缩小对前若干号房屋选择的范围,即k-- 
	  k--;	
	}
	else{                 //表示拆分出了对房屋偷盗选择的决策 
		x[a]=k;         //对房屋号进行记录 
		y[a]=nums[k];   //对房屋金额进行记录 
	  a++;
	  k-=2;
	}
//实现输出决策过程 
	
	int b=a-1;            //用变量b接收盗窃决策的次数 
    while(b){            //将所有通过数组x和数组y记录的策略输出 
   	if(b==a-1)          //根据决策数量补充转折词 
   	  printf("首先,");
   	else if(b==1)
   	  printf("最后,");
	else
      printf("然后,");
   	printf("偷窃%d号房屋(金额 = %d)\n",x[b],y[b]);
   	b--;
   }
//实现最后的小结部分 
   int c =a-1;
   printf("偷盗到的最高金额=");
   	while(c){
   		if(c!=1)
		   printf("%d+",y[c]);
		else{
			printf("%d=",y[c]);
	    	printf("%d",f[n]);
		}
	
		c--;
	   } 
	return 0;
} 


运行结果如下: 

 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值