【贪心法反例】最小代价数组合并

题目如下:

有n个分别排好序的整数数组A0 ,A1 ,… ,An-1 , 其中Ai含有Xi个整数,i = 0,1,…,n-1. 已知这些数组顺序存放在一个圆环上,现在要将这些数组合并成一个排好序的大数组,且每次只能把两个在圆环上处于相邻位置的数组合并。定义合并的代价为两个数组中的元素个数之和。问如何选择这n-1次合并次序以使得合并时总的代价达到最少?有人如下设计贪心法:计算所有相邻两个数组的元素数之和,从中选择元素之和最小的两个数组进行合并。这种贪心法是否能够对所有的实例得到最优解?举出反例或证明你的结果。


解:告诉你这个方法肯定不对啦。。。所以任务就是找出一个反例来,偷懒的办法就是用笔算,自己大脑构造一个,可惜并不是那么容易构造的。所以,果断把正确算法实现了,产生随机数对拍啊。。。


一种正确的解法是用动态规划,定义dp[i, j]表示从i到j的元素合并的最小代价,闭区间,注意i > j是有可能的,表示i, i + 1... n, 1, ... j这个子数组。

于是状态转移方程为:dp[i, j] = min(dp[i, k] + dp[k + 1, j] + sum(i..j)), for k in i...j


下面是java实现代码:

package edu.pku.course;

import java.util.ArrayList;

public class ArrayTest {
	
	private static int[] arr;
	
	private static int getSum(int i, int j) {
		int res = 0;
		for (int k = i; k % arr.length != j; ++k) {
			res += arr[k % arr.length];
		}
		return res + arr[j];
	}
	
	public static int correctAlgo() {
		int len = arr.length;
		int[][] dp = new int[len][len];		
		for (int i = 0; i < len; ++i) dp[i][i] = 0;
		int res = Integer.MAX_VALUE;
		for (int l = 2; l <= len; ++l) {
			for (int i = 0; i < len; ++i) {
				int r = (i + l - 1) % len;
				dp[i][r] = Integer.MAX_VALUE;
				for (int k = i; k % len != r; ++k) {					
					dp[i][r] = Math.min(dp[i][r], dp[i][k % len] + dp[(k + 1) % len][r] + getSum(i, r));
				}
				if (l == len) res = Math.min(res, dp[i][(i - 1 + len) % len]);
			}			
		}
		return res;
	}
	
	public static int wrongAlgo() {
		ArrayList<Integer> list = new ArrayList<Integer>();
		for (int num : arr) list.add(num);
		int res = 0;
		while (list.size() > 1) {
			int pos = -1;
			int mn = Integer.MAX_VALUE;
			for (int i = 0; i < list.size(); ++i) {
				int tmp = list.get(i) + list.get((i + 1) % list.size());
				if (tmp < mn) {
					mn = tmp;
					pos = i;
				}
			}
			int tmp = list.get(pos) + list.get((pos + 1) % list.size());
			res += tmp;
			if (pos == list.size() - 1) {
				list.remove(pos);
				list.remove(0);
				list.add(tmp);
			}
			else {
				list.remove(pos);
				list.remove(pos);
				list.add(pos, tmp);
			}
		}
		return res;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int loop = 10000;
		while (loop-- != 0) {
			int size = 5;
			arr = new int[size];
			for (int i = 0; i < arr.length; ++i) {
				arr[i] = (int)(Math.random() * 100) + 1;
//				System.out.print(arr[i] + " ");
			}
//			System.out.println();			
			int ans1 = correctAlgo();
			int ans2 = wrongAlgo();
			if (ans1 != ans2) {
				for (int num : arr) System.out.print(num + " ");
				System.out.println();
				System.out.println(ans1 + " " + ans2);
//				return;
			}
//			System.out.println("correct algorithm: " + correctAlgo());
//			System.out.println("wrong algorithm: " + wrongAlgo());			
		}
		System.out.println("finished");
	}

}
运行程序,于是很愉快的发现我们找出来了很多反例:

数组:43 12 8 10 11 
最优:166 

贪心:172

数组:37 38 31 55 95 
最优:578 

贪心:581


总结:对于不明显的反例,不要总是用大脑死磕,人脑虽然聪明,但是运算速度毕竟赶不上CPU。。。


done

你可能感兴趣的:(【贪心法反例】最小代价数组合并)