【算法】最大子数组 分治法

前言

去年看的书比较多:java编程思想,深入理解Java虚拟机——JVM高级特性与最佳实践,jvm7的官方说明书,java并发编程实践。编程语言看的差不多了,又开始啃编程语言运行的环境:Linux操作系统,Linux Kernel Development, 3rd Edition。看了几章,感觉有点头脑风暴,虽然大学也学习了操作系统原理,但还是现在收获比较大,挑战也比较大,对算法和数据结构有比较高的要求。所以我在看完几章之后,毅然决然的奔向了算法。通过搜索引擎找到了:Introduction_To_Algorithms,算法的介绍。名字很亲民,但是正统的国内翻译是 算法导论 。我比较讨厌这样的翻译,人家英文明明是算法的介绍,只要有兴趣的人都可以阅读和了解,而算法导论,我的天啊,是不是研究生水平才能看懂。牢骚发完,开始记录一下。

分治法介绍

第一次看这个,感觉挺高端的,但是看看英文原版 divide-and-conquer ,分解和征服,这样就比较明确了嘛,将一个问题分解成多个子问题,解决完子问题,再解决原始问题。

使用分治法解决问题的思路

  1. 分解:将原始问题分解成若干个子问题,这些子问题是相同问题小的实例。
  2. 征服:通过递归征服子问题。如果子问题已经足够的小,那就直截了当的解决这个子问题。
  3. 合并:将子问题的解决方案合并成原始问题的解决方案。

最大子数组问题

现在有这么一个数组,里面都是整数型(包含负数),求最大子数组(连续几个相加最大)。这个问题猛地一看,有点蒙,来看一下使用分治法解决这个问题的思路:

假设我们要在数组A[low,high]中找到最大子数组。分治法给我们的建议是将这个数组尽可能的分解成两个大小相同的数组。也就是说,我们找到数组A的中间点,定义为 mid,然后我们征服 这两个子数组:A[low,mid],A[mid+1,high]。我们解释一下任何一个连续的A[low,high]的子数组A[i,j]必须存在一下这几个区域:

  • 全部在子数组A[low,mid],即 low<=i<=j<=mid
  • 全部在子数组A[mid+1,high],即 mid
  • 跨越中间点,即 low<=i<=mid

因此,数组A的最大子数组必须在以上的三个位置之一。实时上,A[low,high]的最大子数组一定是 这三种情况下最大子数组的和最大值。我们可以递归的找到A[low..mid]和A[mid+1..high]子数组,因为这些问题是寻找最大子数组问题小的实例。剩下的问题就是找到跨越中间点的最大子数组,最后,比较这三种情况和的最大值,最大的就是我们要找的最终答案。

跨越中间点的最大子数组

我们可以很快的找到跨越中间点最大子数组,时间复杂度时O(n)。这个问题不能通过递归的方式来解决,因为它增加了限制:最大子数组必须跨越中心点。所以它不是比原始问题小的实例。任何一个跨越中间点的子数组都是由这两个子数组A[i..mid]A[mid+1..j]组成 ,其中 low<=i<=mid,mid

static Integer[] findMaxCrossingSubArr(Integer[] arr,Integer low,Integer mid,Integer high) {
		int left_sum=Integer.MIN_VALUE;
		int sum=0;
		int maxLeft=mid;
		for(int i=mid;i>=low;i--) {
			sum=sum+arr[i];
			if(sum>left_sum) {
				left_sum=sum;
				maxLeft=i;
			}
		}
		
		int right_sum=Integer.MIN_VALUE;
		sum=0;
		int maxRight=mid+1;
		for (int j=mid+1;j<=high;j++) {
			sum=sum+arr[j];
			if (sum>right_sum) {
				right_sum=sum;
				maxRight=j;
			}
		}
		Integer[] res= {maxLeft,maxRight,left_sum+right_sum};
		return res;
	}

然后使用贴出分治法的主方法

static Integer[] findMaxSubArr(Integer[] arr,Integer low,Integer high) {
		if (low==high) {
			Integer[] _arr= {low,high,arr[low]};
			return _arr;
		}
		Integer mid=(low + high)>>1;
		Integer left[]=new Integer[3];
		Integer right[]=new Integer[3];
		Integer crossing[]=new Integer[3];
		left=findMaxSubArr(arr, low, mid);
		right=findMaxSubArr(arr, mid+1, high);
		crossing=findMaxCrossingSubArr(arr, low, mid, high);
		if (left[2]>=right[2]&&left[2]>=crossing[2]) {
			return left;
		}
		if (right[2]>=left[2]&&right[2]>=crossing[2]) {
			return right;
		}
		return crossing;
	}

后记

看算法导论的过程真的很苦逼,对数学的要求也比较高,一步一个脚印的走下去把...

 

转载于:https://my.oschina.net/huaxiaoqiang/blog/3012558

你可能感兴趣的:(【算法】最大子数组 分治法)