4.1 题目描述
牛童骑在牛背上过河,共有甲、乙、丙、丁四头牛,甲牛过河要2分钟,乙牛过河要3分钟,丙牛过河要8分钟,丁牛过河要5分钟,每次只能赶两头牛过河。问:要把四头牛都赶到河对岸去,最少要多少分钟。(提示:牛童回来赶牛过河也得骑在牛背上)。
4.2 程序使用说明
- 输入牛的头数n;
2. 输入每头牛的耗时spend。4.3 分析和设计
- 思路
当只有一头牛时,直接赶过去。当有两头及以上未过河时,一次就需要赶两头牛,如果还有牛未赶过来就需要把一头牛赶回去。以此类推,只要有牛未过河就需要继续赶,在这里运用深度遍历的方法,当所有牛都过河后就等到一个可行解,由于赶牛时有不同的选取方式,这就产生了多个可行解,需要遍历所有解才能找到最优解。
2. 伪代码CrossRiver(left[0...n], right[0...m])
// 输入:未过河的牛left,已过河的牛right
// 输出:过河最少时间
minTime <- sum(left)
if n = 1
// 只有一头牛
Return left[0]
for i <- 1 to n do
for j <- i+1 to n do
// 选两头牛过河
nowTime <- 0
// 不能在原位上修改
newLeft <- left
NewLeft.remove(j)
newLeft.remove(i)
newRight <- right
newRight.add(left[i])
newRight.add(left[j])
nowTime <- nowTime+max(left[i], left[j])
if newLeft.length > 0
// 左边还有牛未过河,牧童要回到右边
for k <- 1 to newRight.length-1 do
tempLeft <- newLeft
tempLeft.add(newRight[k])
tempRight <- newRight
tempRight.remove(k)
tempTime <- nowTime+newRight[k]
// 当前时间加上把剩余的牛驱赶过去的时间就是总时间
tempTime <- tempTime + CrossRiver(tempLeft, tempRight)
if tempTime < minTime
minTime <- tempTime
else if nowTime < minTime
minTime <- nowTime
return minTime
- 时间复杂度
当有n头牛时,从左边任选两头牛最多有n(n-1)/2种方案,即(n-1)+(n-2)+…+1,而从右边选一头牛回来最多有n-1种方案,而整个过程需要n-1个来回,所以时间复杂度为{[n(n-1)/2]*(n-1)}n-1∈O(n3n)4.4 测试用例
| | | | | | | —- | —- | —- | —- | —- | | 1 | n = 4
spend = [2, 3, 8, 5] | 19 | 19 | 通过 | | 2 | n = 5
spend = [3, 5, 8, 7, 6] | 35 | 35 | 通过 | | 3 | N = 2
Spend = [2, 3] | 3 | 3 | 通过 |
4.5 源代码(含注释)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class CrossRiver {
int[] spend = new int[]{2, 3, 8, 5};
int minTime = Integer.MAX_VALUE;
public static void main(String[] args) {
System.out.println("请输入牛头数:");
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println("请输入"+n+"头牛耗时:");
int[] spend = new int[n];
for (int i = 0; i < n; i++){
spend[i] = scanner.nextInt();
}
CrossRiver crossRiver = new CrossRiver(spend);
crossRiver.crossing();
System.out.println("贪心算法结果:"+ crossRiver.minTime);
crossRiver.minTime = Integer.MAX_VALUE;
List<Integer> list = new ArrayList<>();
for (int i = 0; i < crossRiver.spend.length; i++) {
list.add(crossRiver.spend[i]);
}
crossRiver.minTime = crossRiver.crossing(list, new ArrayList<>());
System.out.println("遍历算法结果:"+crossRiver.minTime);
}
public CrossRiver(int[] spend) {
this.spend = spend;
}
public CrossRiver() {
}
/**
* 在1、2、3头牛时,驱赶方式是固定的
* 在剩余牛大于等于4时,运用贪心算法的思想,借助最快的把最慢的先赶过去,为此需要先对耗时排序
* 有两种方案能较快驱赶最慢的两头牛:
* 1. 最快的(即所用时间spend[0])和次快的过河,然后把最快的赶回来,再次慢的和最慢的过河,然后把次快的赶回来.
* 即所需时间为:spend[1]+spend[0]+spend[n-1]+spend[1] = spend[0]+2*spend[1]+spend[n-1]
* 2. 最快的和最慢的过河,然后把最快的赶回来,再最快的和次慢的过河,然后把最快的赶回来.
* 即所需时间为:spend[n-1]+spend[0]+spend[n-2]+spend[0] = 2*spend[0]+spend[n-2]+spend[n-1]
*/
public void crossing() {
// 对花费时间进行排序
Arrays.sort(this.spend);
int left = this.spend.length;
int sum = 0;
while (left > 0) {
if (left == 1) {
sum += spend[0];
break;
} else if (left == 2) {
sum += spend[1];
break;
} else if (left == 3) {
// 先把最慢的赶过去,然后用最快的回来,再把剩余的赶过去
sum += spend[2] + spend[0] + spend[1];
break;
} else {
sum += Math.min(spend[0] + 2 * spend[1] + spend[left - 1], 2 * spend[0] + spend[left - 2] + spend[left - 1]);
left -= 2;
}
}
minTime = sum;
}
/**
* 深度遍历,先从左边选两个赶到右边,再从右边选一个赶到左边,然后再递归调用,直到左边没有牛
* @param left 左边牛状态
* @param right 右边牛状态
*/
public int crossing(List<Integer> left, List<Integer> right) {
int minTime = Integer.MAX_VALUE;
if (left.size() == 1){
return left.get(0);
}
for (int i = 0; i < left.size(); i++) {
for (int j = i + 1; j < left.size(); j++) {
int nowTime = 0;
// 两两组合从左到右,选择两个移动到右边
List<Integer> newLeft = new ArrayList<>(left);
newLeft.remove(j);
newLeft.remove(i);
List<Integer> newRight = new ArrayList<>(right);
newRight.add(left.get(i));
newRight.add(left.get(j));
nowTime += Math.max(left.get(i), left.get(j));
if (newLeft.size() > 0) {
// 左边有剩余,需要牛童回到左边
for (int k = 0; k < newRight.size(); k++) {
List<Integer> tempLeft = new ArrayList<>(newLeft);
tempLeft.add(newRight.get(k));
List<Integer> tempRight = new ArrayList<>(newRight);
tempRight.remove(k);
int tempNowTime = nowTime+newRight.get(k);
// 在当前状态继续驱赶剩余的牛
tempNowTime+=crossing(tempLeft, tempRight);
if (tempNowTime < minTime){
minTime = tempNowTime;
}
}
} else if (nowTime < minTime) {
minTime = nowTime;
}
}
}
return minTime;
}
}