zoukankan      html  css  js  c++  java
  • 分治法(一)

    泛泛而谈晦涩难懂的概念非常的枯燥,接下来利用排序这样一个问题引入今天的主题


    给定7个数字的序列:6,202,100,301,38,8,1

    要求对其按照升序进行排序。

    首先使用冒泡的方法进行比较排序(每次红色字体的两个数字进行比较,较大者置后)

    1               6,202,100,301,38,8,1

    2               6,100,202,301,38,8,1

    3               6,100,202,301,38,8,1

    4               6,100,202,38,301,8,1

    5               6,100,202,38,8,301,1

    6               6,100,202,38,8,1,301

    进行六次的比较和互换将最大的数字301放在了应该在的位置。

    。。。

    如此进行(7-1)*(7-1)=36次才能将整个序列排序完成。由此验证了冒泡排序o(n*n)的时间复杂度。

    接下来使用著名的归并排序方法。归并排序通常用递归实现,将待排序区间一分为二,对左右两部分分别进行排序,最后将两个排好序的部分进行合并。如此层层分割,直至区间长度为2,排序轻而易举地实现,再层层合并。

    对上述序列进行归并排序如下

    初始序列     :6,202,100,301,38,8,1

    先将序列进行分割

    6,202,100,301,|38,8,1

    6,202,|100,301,|38,8,|1

    第一次归并:

    (6,202),(100,301),(8,38),1     比较次数3次

    第二次归并:

    (6,100,202, 301),(1,8,38)比较次数4次

    第三次归并:

    (1,6,8,38,100,201,301)比较次数4次

    综上,共比较11次。在处理过程中对原序列进行分割求解这样看似多此一举的行为带来的是整体时间复杂度的优化,有句俗语“磨刀不误砍柴工”。在此问题中,将整个问题分割,形成的两个子问题需要完成的工作和原问题性质是一样的,方法也是类似,用同样的方法处理子问题,最后将问题的解合并,这就是分治思想。


    分治法

    分:将一个大规模的问题化为若干规模较小的子问题。

    治:求出每个子问题的解并将这些解合并为原问题的解。

    通常情况下,分解成的若干个子问题与原问题具有相同的结构和性质,因此可以递归的求解。

    有人会好奇,分治法的意义在哪里,同样是解决问题,多此一举的将问题拆分,而后又合并,看似无形中增加了工作量。实际不然,采用分治法的有效保证是“当问题的规模足够小的时候,可以非常轻松的求出问题的答案”,这样将复杂的大问题转换为无数个简单的小问题的合并,在用的巧妙的场合,这种方法的效果是非常显著的。

    总结一下分治法的步骤就是:分割--求解--合并,分割虽然技术含量不是很高,但是合并可是一项技术活。不妨看看下面这个例子,对分治法加深一下印象。

    请看例题:

    题目简述:给定一个数字序列,其中的数字可正可负可为零,要求出一个子序列满足该序列和最大。

    题目要求简洁明了,数据范围和输入输出格式也都明确给出。不过别忘了题目中有一个提示“如果存在多个最大和序列,那么给出第一个即可”。此处的第一个指的是始末位置下标较小的一个。

    乍一看这道题,初学者可能没有办法将它和分治法联系在一起,最可能想到的就是暴力又直接的方法:从首元素到末元素挨个假设作为该“最大和子序列”的初始元素,枚举该子序列长度为1,2,3,。。。并计算序列元素和,不断更新最大值,直至求出结果。这时候,你一定心里暗暗发牢骚,为什么求的是连续子序列和,如果是不连续序列和,直接将所有非负数相加不就大功告成了。我只能告诉你“想得美”。

    虽然在题目上不能走捷径,但是在解法上是可以优化的,那现在就勉为其难的将本题往分治法上贴呗。

    分治法第一步要将区间分割。先假设将区间分为左右两部分,这时候,就有两种情况,一种是:要求的目标子序列处在左半部分的区间中,另一种情况:要求的目标子序列处在右半部分的区间中。我好像是发现了小秘密,似乎和分治法沾边了,如果目标子序列在左半部分区间中,那么将左半部分区间再一分为二。。。这么分下去的话结果就出来了——那就是,整个区间被分成了一个一个的小碎片,哪里来的目标子序列?情况似乎有些不妙,不过这才是第一步,接着往下。

    分治法的第二步是将分割的小碎片进行求解,求解每个小碎片区间里的最大和子序列,很简单,分割结束时每个元素自成一区间,本身的值就是区间里子序列的最大和。经过几轮合并,每个区间包含的元素大于一个,这时候才谈得上真正的求解,需要求解的就是在这些区间内部的最大和序列,经过前面的分析可知,任一待求序列就是构成该区间的两个子区间包含的最大和序列之一。

    分治法的第三步是合并,这是整个方法的核心,合并处理的就是两个子区间合并起来究竟该选哪个子区间内的最大和序列作为合并后区间的最大和序列。至此,问题基本上已经解决完了。不知你是否能记起第一步的时候我提出的问题:整个序列已经被切割成小碎片了。整个求解的过程只是在子区间中选择序列和较大的子序列,如何能将这些分散的序列连接起来?其次,将区间一分为二,最大和序列也有可能正好被切断从而分布在两个子区间中。因此在将两个子区间合并的过程中,除了要考虑两个子区间中的两个最大和序列,还应该对被切断这种情况进行特殊处理,即应该在切口两侧进行左右延伸,求得切口处的最大和序列,在左、右、中三者中选择较大的一个,这样就真正做到把分割开的序列重新连接为整体的合并处理。

    主要思路如上,本题的测试样例为:

    本题核心的代码实现如下:


    想看本题完整解答过程的请打开网页http://paste.ubuntu.com/25118866/。

  • 相关阅读:
    商户网站使用第三方支付的大致原理和实现
    ASP.NET MVC中检测浏览器版本并提示下载更新
    如何选择使用IEnumerable, ICollection, IList
    IEnumerable和IQueryable的区别以及背后的ExpressionTree表达式树
    IEnumerable是集合,IEnumerator是集合的迭代器
    ASP.NET MVC中使用Session来保持表单的状态
    在ASP.NET MVC中实现Select多选
    总结ASP.NET MVC视图页使用jQuery传递异步数据的几种方式
    在ASP.NET MVC4中使用Quartz.NET执行定时任务
    委托, 泛型委托,Func<T>和Action<T>
  • 原文地址:https://www.cnblogs.com/detrol/p/7533604.html
Copyright © 2011-2022 走看看