现在的位置: 首页 > 综合 > 正文

关于数组中最小两数差的学习总结

2014年01月16日 ⁄ 综合 ⁄ 共 2913字 ⁄ 字号 评论关闭

 

zwdnet同学在下面的贴子提到了如下问题:

“求助:求一个整数数组的最小子序列之和结果出错

是《算法设计与分析基础》一书的一道习题,原题是要求一个整数数组中大小相差最小的两个元素之差(只要求差值),题目给出了穷举的方法,并要求改进。我在网上搜了,有人给出了答案,但是没有代码(见http://fayaa.com/tiku/view/116/),我按其方法用c++试了试,答案和用穷举法做的不一致,而且相差很大。麻烦各位帮忙看看。谢谢。”

http://topic.csdn.net/u/20091107/11/dc280e49-43b1-4a6d-800c-49a799ab5ea1.html?7444

 

我思索了一下,觉得这个问题与我先前看过的一些问题有关联,很值得研究,所以就把它整理出来,写在我的博客里了。(这个问题并没有完全解决)

 

首先要弄懂“最大子段和”这个问题,这个问题在刘汝佳的上课讲义上有,我就直接COPY过来了,如下:

 

题目:给一串整数a[1n],求出它和最大的子序列,即找出1<=i<=j<=n,使a[i]+a[i+1]++a[j]最大。

 

介绍四个算法并分析时间复杂度

枚举:O(n3)

优化的枚举:O(n2)

分治:O(nlogn)

贪心:O(n)

 

问题的解答其实只与算法四“贪心算法”有关,但是为了保持叙述的完整性,把算法一至自算法三都写出来了。

 

算法一(穷举):

思想:枚举所有的ij,计算a[i]++a[j],选择最大的

程序如下:

max := a[1];

for i:=1 to n do

  for j:=i to n do begin

    sum := 0;

    for k:=i to j do

      sum := sum + a[k];

    if sum > max then max := sum;

  end;

时间复杂度为O(n3),显而易见。

 

算法二(优化的穷举):

思路

枚举ij后,能否快速算出a[i]++a[j]呢?

能,预处理!

s[i] = a[1] + a[2] + + a[i], 规定s[0] = 0

则可以在O(n)的时间内计算出s[1], , s[n]

s[0] := 0;

for i:=1 to n do

  s[i] := s[i–1] + a[i];

有了s[i],怎么快速求a[i]++a[j]呢?

a[i]++a[j]

= (a[1] + + a[j]) (a[1] + + a[i-1])

=s[j] s[i-1]

s[i]经过预处理以后可以直接读出!

程序如下:

max := a[1];

for i:=1 to n do

  for j:=i to n do

    if s[j] – s[i-1] > max then

      max := s[j] – s[i-1];

时间复杂度:预处理+主程序=O(n+n2)=O(n2)

 

算法三(分治):

用一种完全不同的思路

最大子序列的位置有三种可能

1.完全处于序列的左半,即j<=n/2

2.完全处于序列的右半,即i>=n/2

3.跨越序列中间,即i<n/2<j

用递归的思路解决!

max(i, j)为序列a[ij]的最大子序列

mid = (i + j)/2,即区间a[ij]的中点

最大的第一种序列为max(i, mid)

最大的第二种序列为max(mid+1, j)

问题:最大的第三种序列为???

既然跨越中点,把序列a[ij]划分为两部分

a[imid]:最大序列用扫描法在n/2时间内找到

一共只有mid-1=n/2种可能的序列,一一比较即可

a[mid+1j]:同理

一共花费时间为j-i+1

分治算法时间复杂度:O(nlogn)

 

算法四(贪心):

(加红色部分是解决问题的关键)

算法二的实质是求出i<=j,s[j]-s[i-1]最大

对于给定的j,能否直接找到在j之前的最小s值呢?

从小到大扫描j

j=1时,只有一个合法的i,即i=1, s[1]=0

如果s[j]变大,则最小的s值和上次一样

如果s[j]再创新低,应该让s[j]作为今后的最优s

min_s := 0;

For j :=1 to n do begin

  if s[j] < min_s then min_s := s[j];

  if s[j] – min_s > max then

    max := s[j] – min_s;

End;

时间复杂度很明显:O(n)

举个例子来看:

a[1..9] : 5, 6, -7, -8, 4, 9, 8,-6, 7;

s[1..9]: 5,11,4,-4,0,9,17,11,18

上述标志为红色的代码运行过程如下:

min_s=0;

循环9次,两个变量的变化情况:

mis_s: 0,0,0,-4,-4,-4,-4,-4,-4

max: 5,11,11,11,11,13,21,21,22

所以最大连续子段和为22。事实上224+9+8-6+7.

 

 

好了,介绍完了连续最大子段和之后,就可以用它来解决我们前面所提出相差最小的两个元素之差这个题了。

b[1..n]为所给的原始数值,

构造如下a[1..n-1],使得a[i]=b[i]-b[i+1]; 于是,原始数据中

b[i]-b[j]=(b[i]-b[i+1])+ (b[i+1]-b[i+2])+…+(b[j-1]-b[j])=a[i]+a[i+1]+…+a[j];

问题的答案就出来了,b[1..n]的差问题,就转化为a[1..n-1]的连续子区间求和问题。

亦即:b[1..n]两个元素之差,等于a[1..n-1]中连续区间的和。解最小差值的问题,变成了解连续区间的最小和的问题,解法与上述算法四(贪心算法)类似。

 

下面举例求解该问题:

b[1..6]:5,38,90,65,33,75

显而易见,差值最小的是38-335;过程如下:

a[1..5]:-33,-52,25,32,-42

s[1..5]:-33,-85,60,92,50

执行前述贪心过程,注意现在是求最小和,而不是最大和了:

max_s=0

循环5次,过程中的变化:

max_s:0,0,60,92,92

min:  -33,-85,-85,-85,-85

结果不正确!这是因为负数的存在,所以-85这个结果是由5-95-85得来的,上述求解过程确实求得了两数的最小差,最小差为-85,且一定是前一个数减后一个数。这与原题是不相符合的,所以应该对算法进行调整,使得输出是差的绝对最小。

改进如下:

和前面一样,b[1..n]得到a[1..n-1]s[1..n-1];

b[1..6]:5,38,90,65,33,75

a[1..5]:-33,-52,25,32,-42

s[1..5]:-33,-85,60,92,50

我们把原先“求a[1..n-1]中连续区间的最小子段和”问题,改为“求a[1..n-1]中连续区间的子段和的绝对值最小”

分两种情况,如果是负数,那么应该求最大的负数;如果是正数,那么应该求最小的正数。所以,除了要记录max_s之外,还要记录min_s;原先的min,应方该改为min_positivemax_minus;伪代码如下:

????真的很不好意思了,分析到这一步,发现会有很多问题。感觉不能用最大子段和这种类似的方法。因为无法用记录最小和min_s和最大和max_s这样的方法去得到绝对值。

如果问题作如下的修正,是可以做出来的:

“请在线性时间,线性空间的条件下求一个未排序数组的

抱歉!评论已关闭.