算法导论之最优解算法--钢条切割问题扩展--流量充值问题


大家应该都知道算法导论里面有一个钢条切割问题,给定一个长度的钢条,按照不同方案切割卖出不同的价格,求那种切割方式能卖对多的钱。

给出的条件中包括了长度为1-9不同长度的价格,具体的代码就不贴了,搜一下就有。

我先说一下钢条切割算法的一些缺点:

缺点一:给出的切割方案必须是连续的,比如1-9,不能是1,3,5,7,9.。如果非连续的会出现切割方案里面出现一次切割4米是一个最优切割方案,但是实际上4米这个切割方案是不存在的,这就是问题所在了。

解决上面这个问题的思路就是通过自底向上的方法,算出来那些不在条件里面的切割方案,然后把它加入到条件里面去,比如切割为2米,那么就只有切割为两个1米,没有切割为2米和0米的方案,他可以通过已知的条件算出来对应的最优切割方案。

缺点二:如果我的钢条超过给出的条件范围,比如10米,那么会出现数组越界的问题,因为没有一个切割为0米和10米的切割方案,算法执行的时候直接抛出数组越界问题。

解决这个问题的方案和解决第一个问题的方案是一致的,也是把后续的求得的解决方案放入已知条件里面去,这样一直推算,就能得到最后的结果了。因为你总是能把长度为n的一个钢条拆分为长度为i 和长度为n-i的两个解决方案,而且长度为i和n-i总能保证他们是已经存在解决方案里面的。

那么问题来了。(这个问题是我实际项目中遇到的一个问题)

我现在有一个需求,需要给客户充值1G的流量,可选的条件只有100M的10块钱,200M的18块钱,500M的40块钱。求价格最低的充值方案?

一看这个需求我第一想到的是,包越大,越便宜,直接每次减去最大的包,然后依次减去小的流量包就能解决。so easy!!!

可是实际项目中会出现促销活动,有时候200M的包可能只需要15块钱,这种情况就没法搞定了。

最后我还是选择了最优算法来解决这个问题。

按照钢条切割算法的代码直接去执行,你会发现当给定的条件不是递增的时候出问题了,没有得到预期的结果。

我按照钢条切割的思路去改进原有代码,给出的条件只有100M, 200M, 500M的包,那么我就先算出100-900的所有可能情况的最优价格,作为虚拟的条件带入进去然后递归求解后面的值。

举个例子:

我知道了100M的价格是10元,

200M 的价格可以拆分为 价格1= 18,方案为一个200M;价格二=20,方案二是拆分为两个100M,然后对比两个价格,求出最低价格为18元。

300M的可以拆分为方案一:200M+100M = 28元,方案二:100M+ 100M + 100M = 30元,对比两个价格,求出最低价格为28元,并把这个已知解决方案放入输入条件中,这个时候我们的输入条件变成了,100M 10元,200M 18元,300M 28元,500M 40元。

400M的可以拆分为方案一:300M+ 100M =38元,方案二,200M+200M = 36元,方案三100M * 4, = 40元,求得最低价格为36元。并把这个已知解决方案放入输入条件中,这个时候我们的输入条件变成了,100M 10元,200M 18元,300M 28元,400M 36元, 500M 40元。

这样不断地去补充输入条件就能满足钢条解决方案对于输入条件的依赖关系,也就解决了我的问题。

但是我的解决方案还是存在问题,没法解决没有基数值为100M流量的情况下,怎么去求充值900M的情况下最低价格,这个问题我会继续去研究解决方案。


核心算法代码如下:

public class OptimumSolution {

	public static void main(String[] args){
		int[] data = {10, 18, 40};//价格数组
		int[] values = {1, 2, 5};//流量值数组,对应100M, 200M, 500M流量值
		int[] Pid = {1001, 1002, 1003};//流量产品对应的ID,最后输出的时候需要用到
		
		List
   
   
    
     list = new ArrayList
    
    
     
     ();
		for(int i = 0; i < data.length; i++){
			Product product = ProductUtils.getProductInstance();
			product.price = data[i];
			product.value = values[i];
			product.pId = Pid[i];
			list.add(product);
		}
		Collections.sort(list);
		System.out.println(print_cut_rod_solution(list, 9));
		System.out.println();
		for(Product product:list){
			System.out.println("需要充值流量值为:"+product.value+"M的流量包,最低始价格为"+product.price);
		}
	}
	
	public static String print_cut_rod_solution(List
     
     
      
       list, int n){
		Product[] solution = new Product[n+1];
		int[] price = new int[n+1];
		for (int i = 0; i < solution.length; i++){
			Product product = ProductUtils.getProductInstance();
			product.price = 0;
			product.value = 0;
			product.pId = 0;
			solution[i] = product;
        }
		for (int i = 0; i < price.length; i++){
			price[i] = 0;
        }
		
		cut_rod(list, n, solution, price);
		StringBuffer buff = new StringBuffer();
		buff.append("对于流量值为"+n+"的流量充值最优价格为"+price[n]+";最优拆分方案为:");
		//System.out.print("对于流量值为"+n+"的流量充值最优价格为"+price[n]+";最优拆分方案为:");
		while(n > 1){
			buff.append("产品ID为[" +solution[n].pId + "],流量值为[" + solution[n].value + "00M ];");
			//System.out.print("产品ID为[" +solution[n].pId + "],流量值为[" + solution[n].value + "M ];");
			n -= solution[n].value;
		}
		return buff.toString();
	}
	public static void cut_rod(List
      
      
       
        list, int n, Product[] solution, int[] price){
		
		for(int i = 1; i <= n; i++){
			int p = Integer.MAX_VALUE;
			for(int j = 1; j <= i; j++){
				if(j == list.get((j-1) % list.size()).value){
					if(p > list.get(j-1).price + price[i-j]){
						p = list.get(j-1).price + price[i-j];
						solution[i].value = j;
						solution[i].pId = list.get(j-1).pId;
					}
				}
			}
			price[i] = p;
			 boolean flag = true;
	            for(Product product:list){
	    			if(product.value == i){
	    				flag = false;
	    				if(product.price < p){
	    					product.price = p;
	    				}
	    			}
	    		}
	            if(flag){
	            	Product product = ProductUtils.getProductInstance();
	    			product.price = p;
	    			product.value = i;
	    			list.add(product);
	    			Collections.sort(list);
	            }
		}
	}
}
      
      
     
     
    
    
   
   


下面是我的产品类

public class ProductUtils {

	public static Product getProductInstance(){
		return new ProductUtils().new Product();
	}
	public class Product implements Comparable
   
   
    
    {
		int value;//对应产品流量值
		int price;//对应产品价格
		int pId;//产品ID
		@Override
		public int compareTo(Product o) {
			// TODO Auto-generated method stub
			if(this.value > o.value){
				return 1;
			}
			if(this.value < o.value){
				return -1;
			}
			return 0;
		}
	}
}
   
   


下面是钢条切割问题的解决思路,我的解决思路也是参考的这个解决方案,进行自己的补充和修改,来满足我的需求。

给定一段长度为n英寸的钢条和一个价格表 pi (i=1,2, …,n),求切割钢条的方案,使得销售收益rn最大。注意,如果长度为n英寸的钢条价格pn足够大,最优解可能就是完全不需要切割。

若钢条的长度为i,则钢条的价格为Pi,如何对给定长度的钢条进行切割能得到最大收益?

长度i 1 2 3 4 5 6 7 8 9 10

价格Pi 1 5 8 9 10 17 17 20 14 30

 

i = 1时,钢条不可切割,r[1]= 1;

i = 2时,钢条可分割为1+ 1,其价格为2。若不分割(0 + 2),价格为5。即r[2] = 5;

i = 3时,钢条可分割为0+ 3,1 + 2。r[3] = 8;

同理可得:

r[4] = 10(2+ 2);

r[5] = 13(2+ 3);

r[6] = 17(0+ 6);

r[7] = 18(1+ 6或4+ 3=> 2 + 2 + 3);

.......

我们可以发现,长度为7时,将其切割为长度4与长度3的钢条,并对两个钢条分别求最优解:长度4的最优解为r[4] = 10(2 + 2),长度3的最优解为r[3] = 8,即可得r[7] =r[4]+ r[3] =>原问题的最优解等于子问题的最优解之和的最大值

我们将钢条左边切割下长度为 i 的一段,只对右边剩下的长度为 n-i 的一段继续进行切割(递归求解),对左边的一段不再进行切割。即问题分解的方式为:将长度为n 的钢条分解为左边开始一段,以及剩余部分继续分解的结果。这样,不做任何切割的方案就可以描述为:第一段的长度为n ,收益为 pn,剩余部分长度为0,对应的收益为r0=0。于是公式的简化版本:

因此,在计算r[i]时,所求值即为r[0] +r[i],r[1]+ r[i- 1],r[2]+ r[i- 2],... ,r[i- 1] +r[1] 之间的最大值,而在动态规划中,r[0]——r[i -1]的值在计算r[i]之前已经保存好了,进行少量的运算便能取得最优结果。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值