您的位置:首页 > 其它

动态规划_最大子序列和问题以及最大子序列问题

2013-11-04 12:33 435 查看
看书看到用分治法解最大子序列和的问题。看懂了思路后自己试着写了一下。所谓的分治法就是通过不断将求解范围划分成更小的的求解问题,知道问题范围不再可分为止。这里用到了递归。那么我不禁要想,递归和分治有什么区别,也不去Google了。递归的使用通常是当前问题是基于上一个更小范围的问题求解,这种关联关系是连续的。分治则是由"分"和"治"两部分组成的,这中间当然也是有递归的思想在的。这么说吧,求N阶乘问题就是一个纯粹的递归问题。而求最大子序列和问题则是一个分治问题。

下面是一段包含最大子序列和问题以及最大子序列问题两个问题的代码。其中最大子序列问题并没有用分治法实现,只是使用了人类的观察能力,总结问题,然后用程序语言表现出来而已,算法也没有通用性,无法给其他算法问题提供参考思路。

package com.wly.algorithmbase.dailyproblem;

import java.util.ArrayList;

/**
 * 最大子序列和问题以及最大子序列问题
 * @author wly
 *
 */
public class MaxSubSequence {
	
	static ArrayList<Result> resultList;

	public static void main(String[] args) {
	
		int[] array = {-1,-2,4,3,-5,9,7,-10,2,1,-200,12,6};
		
		//测试最大子序列和问题
		System.out.println("--测试最大子序列和问题--");
		System.out.println("result:" + findMaxSubSequenceValue(array, 0, array.length-1));
		
		
		//测试最大子序列问题
		System.out.println("--测试最大子序列问题--");
		findMaxSubSequence(array);
		for(Result result:resultList) {
			System.out.println("S:" + result.getStart() + "|E:" + result.getEnd() + "|V:" + result.getValue());
			
		}
	}

	/**
	 * 使用分治法求解最大子序列和问题,注意这里仅仅是求最大子序列的和,并非求最大子序列(包含开始索引位置和结束索引位置)
	 * @param array
	 * @param start
	 * @param end
	 * @return
	 */
	public static int findMaxSubSequenceValue(int[] array,int start,int end) {
		
		//递归的基值条件
		if(start == end) {
			if(array[start] < 0) {
				return 0;
			} else {
				return array[start];
			}
		}
		
		 // 最大子序列有三种位置可能,一、在start到mid之中。二、在mid到end之中。三、横跨左右两个分区,但必须包含mid元素,这种情况的求解仅需线性时间,无需进一步迭代
		int mid = (start + end) / 2;
		//求解左分区的最大子序列
		int maxLeft = findMaxSubSequenceValue(array, start, mid);
		//求解右分区的最大子序列
		int maxRight = findMaxSubSequenceValue(array, mid+1, end);
		//求解横跨左右分区,但必须包含mid的最大子序列
		int sum_right = 0;
		int sum_left = 0;
		int temp = 0;
		
		for(int i=mid;i>start;i--) { //向mid左侧遍历,得到左侧连续的最大和值
			temp += array[i];
			if(temp > sum_left) {
				sum_left = temp;
			}
		}
		
		temp = 0;
		for(int i=mid+1;i<end;i++) { //向mid右侧遍历,得到右侧连续的最大和值
			temp += array[i];
			if(temp > sum_right) {
				sum_right = temp;
			}
		}
		
		int maxContainMid = sum_left + sum_right;
		
		return max(maxLeft,maxRight,maxContainMid);
	}
	
	
	
	/**
	 * 得到三个数的最大值
	 * @param a
	 * @param b
	 * @param c
	 * @return
	 */
	private static int max(int a,int b,int c) {
		int temp = a>b?a:b;
		return temp>c?temp:c;
	}
	
	/**
	 * 求解最大子序列,没有用递归求解,只是通过观察实现的,并不是一个通用的算法
	 * 有一个问题,就是可能存在多个最大子序列,,是啊,麻烦
	 * @return 最大子序列
	 */
	public static ArrayList<Result> findMaxSubSequence(int[] array) {
		resultList = new ArrayList<Result>();
		
		int currSum = 0; //当前遍历得到的和值
		int tempBigest = 0; //当前保存的临时最大值
		
		int currStart = -1; //当前遍历的最大子序列的开始坐标
		int currEnd = -1; //当前遍历的最大子序列的终止坐标
		
		for(int i=0;i<array.length;i++) {
			if((currSum + array[i]) <= 0) { //因为小于零,对后面的序列叠加没有作用,所以舍弃累加结果
				currSum = 0;
				currStart = -1;
				currEnd = -1;
			} else { //大于零,对后面的序列叠加仍然有作用
				currSum += array[i];
				if(currSum > tempBigest) { //大于当前子序列的最大值,更新对应的索引位置
					tempBigest = currSum;
					if(currStart == -1) {
						currStart = i;
						currEnd = currStart;
					} else {
						currEnd = i;
					}
					updateResult(new Result(tempBigest, currStart, currEnd));
				} else if (currSum == tempBigest){ //针对可能有多个最大子序列的情况
					tempBigest = currSum;
					if(currStart == -1) {
						currStart = i;
						currEnd = currStart;
					} else {
						currEnd = i;
					}
					//增加结果列表
					updateResult(new Result(tempBigest, currStart, currEnd));
				}else if(currSum < tempBigest) { //不要忘了这种情况哈~~
					if(currStart == -1) {
						currStart = i;
						currEnd = currStart;
					}
				}
			}
		}
		
		return resultList;
	}
	
	/**
	 * 更新结果列表
	 * @param result
	 */
	private static void updateResult(Result result) {
		if(resultList.size() == 0) {
			resultList.add(result);
		} else {
			if(result.getValue() > resultList.get(0).getValue()) {
				resultList.clear();
				resultList.add(result);
			} else if(result.getValue() == resultList.get(0).getValue()) {
				resultList.add(result);
			} else {
				//DO NOTHING
			}
		}
	}
	
	
	/**
	 * 代表结果对象,即一个子序列的描述
	 * @author wly
	 *
	 */
	static class Result {
		int value;
		int start;
		int end;
		
		public Result(int value, int start, int end) {
			super();
			this.value = value;
			this.start = start;
			this.end = end;
		}
		
		public int getValue() {
			return value;
		}
		public void setValue(int value) {
			this.value = value;
		}
		public int getStart() {
			return start;
		}
		public void setStart(int start) {
			this.start = start;
		}
		public int getEnd() {
			return end;
		}
		public void setEnd(int end) {
			this.end = end;
		}
		
	}
}

输出结果

--测试最大子序列和问题--
result:18
--测试最大子序列问题--
S:2|E:6|V:18
S:11|E:12|V:18

再扯一下最大子序列问题,实现思路:从做左到右遍历数据,将读到的元素值累加,如果得到的累加值为负,则将其舍弃并。直到得到一个大于零的累加值(即第一个非负数),保存其状态,然后检测该累加值,不断的更新保存最大值。关键在于舍弃和值为负数的序列(因为它对后面要遍历的序列已经没有作用了)

O啦~~~,要是每天都有时间能写一个简单的问题,动动脑筋确是极好的

转载请保留出处:/article/2121217.html

谢谢!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: