分治算法使用实例

参考:

https://www.cnblogs.com/xsyfl/p/6921687.html

简单介绍

    分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

        这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

  如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

分治算法可以求解的一些经典问题

  • 大整数乘法

  • 二分搜索

  • 棋盘覆盖

  • 合并排序

  • 快速排序

  • 线性时间选择

  • 最接近点对问题

  • 循环赛日程表

  • 汉诺塔

分治法能解决的问题具备的特征

  1) 该问题的规模缩小到一定的程度就可以容易地解决
  2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
  3) 利用 该问题分解出的子问题的解可以合并为该问题的解
  4) 该问题所分解出的 各个子问题是相互独立的 ,即子问题之间不包含公共的子子问题
第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率 ,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但 一般用动态规划法较好

分治法在每层递归上的步骤

  1. 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题

  2. 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

  3. 合并:将各个子问题的解合并为原问题的解。

分治算法的设计模式:

    假设:

  1. |P|表示问题P的规模

  2. n0表示分解阈值;即当问题P的规模不超过n0时,问题P不需要继续分解

  3. ADHOC(Pk)表示分治法的基本子算法,用于直接解小规模的问题P;即当Pk的规模不超过n0时,直接用ADHOC(Pk)求解

  4. MERGER(y1, y2, ......, yk)合并子算法,用于将P的子问题P1,P2,......,Pk的相应的解y1,y2,......,yk合并为P的解。

DivideAndConquer(P){
    //归上来的起始条件
    if |P| <= n0 :
        then return ADHOC(P);

    //将P分解为较小的子问题P1, P2, ... , Pk
    //递下去
    for i <- 1 to k :
        do yi <- DivideAndConquer(Pi);

    T <- MERGE(y1, y2, ... , yk);
    return T;
}

分治法的复杂性分析

    提前告知:假设

  • 问题规模为n
  • 分解为k个子问题
  • 第i个子问题的规模为:n/(m^i),i ∈ [0, 1, ... , k]
  • 分解阈值n0为1
  • ADHOC( ) 解 规模为1的问题 需要耗费 1个单位时间
  • 将原问题分解为k个子问题 以及 用merge将k个子问题的解合并为原问题的解 需要f(n)个单位时间。
  • T(n)表示该分治法解规模为|P|=n的问题所需的计算时间

    则一般情况下的计算时间为:

    T(n) = f(n) + k*T(n / m)

    这里假设:n = m^k,即可以分解到子问题的规模1,或者称:只给出n等于m的方幂时 递归方程T(n)的值

    另外,如果T(n)曲线是光滑的(可求导),那么根据自变量n和因变量T(n)就可以估计T(n)的增长速度。

    一般情况下,假设T(n)单调递增,从而当mi <= n < mi+1时,T(mi) <= T(n) < T(mi+1)。

最经典问题问题之一:汉诺塔

    在汉诺塔游戏中,有三个分别命名为A、B、C得塔座,几个大小各不相同,从小到大一次编号得圆盘,每个原盘中间有一个小孔。最初,所有得圆盘都在A塔座上,其中最大得圆盘在最下面,然后是第二大,以此类推.

 

    游戏的目的是将所有的圆盘从塔座A移动到塔座B,塔座C用来当作临时圆盘,游戏的规则如下:

  • 在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

    问题分析:

        肯定是先把圆盘4放到柱子B,才能把圆盘3放到柱子B,才能把圆盘2放到柱子B,以此类推。

        即,我们不能直接移动圆盘4,但是圆盘4最终将从塔座A移动到塔座B。按照游戏规则,在移动圆盘4之前的情况一定如下图:

 

    所以,完成游戏的思路为:

    先将①前三个圆盘从A移动到C,然后②圆盘4从A移动到B,最后③前三个圆盘从C移动到BC为辅助

                ①前三个圆盘从A移动到C:a. 前两个从A移动到B,b. 圆盘3从A移动到C,c. 前两个圆盘从B移动到CB为辅助

                            a. 前两个从A移动到B:第一个从A移动到C,圆盘2从A移动到B,第一个从C移动到BC为辅助

                            c. 前两个从B移动到C:第一个从B移动到A,圆盘2从B移动到C,第一个从A移动到CA为辅助

                ③前三个圆盘从C移动到B:前两个从C移动到A,第三个从C移动到B,前两个从A移动到B。A为辅助

    注意:高亮的部分为分解的过程(递下去的过程),未高亮的部分为每层递归应该做的事情,归上来的起始条件为分解为一个圆盘的时候。

    最终我们只需要处理一个圆盘从一个塔座移动到另一个塔座的问题。

/**
* @author wkn
* @create 2021-11-29 14:59
*/
public class DivideAndConquer {
    public static int count = 1;

    public static void main(String[] args) {
        moved(4, "第一根柱子", "第二根柱子", "第三根柱子");
    }

    /**
     * @param i 第几个圆盘
     * @param a 圆盘所在位置
     * @param b 圆盘要移动到的位置
     * @param c 辅助位置
     */
    public static void moved(int i, String a, String b, String c){
        if(i == 1){
            disPlay(i, a, b);
        }else{
            moved(i - 1, a, c, b);
            disPlay(i, a, b);
            moved(i - 1, c, b, a);
        }
    }

    public static void disPlay(int i, String col1, String col2){
        System.out.println("第" + count + "步: 将第" + i + "个圆盘从" + col1 + "移动到" + col2);
        count++;
    }
}

前奏:合并两个升序链表

来源:
迭代法:使用哨兵结点
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null || l2 == null) {
            return l1 == null ? l2 : l1;
        }
        
        ListNode hair = new ListNode();
        hair.next = l1;
        ListNode p1 = l1;
        ListNode p1Pre = hair;
        ListNode p2 = l2;
        while (p1 != null && p2 != null) {
            if(p2.val < p1.val) {
                ListNode temp = p2.next;
                p1Pre.next = p2;
                p2.next = p1;
                p2 = temp;
                p1Pre = p1Pre.next;
            }else {
                p1Pre = p1Pre.next;
                p1 = p1.next;
            }
        }
        
        if(p2 != null) {
            p1Pre.next = p2;
        }
        
        return hair.next;
    }
}

困难题:合并K个升序链表

来源:合并k个升序链表

分治法:先分组再合并

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
     return merge(lists, 0, lists.length - 1);
    }
    
    public ListNode merge(ListNode[] lists, int left, int  right) {
     if(left > right) {
          return null;
     }
     
     if(left == right) {
          return lists[left];
     }
     
     int mid = (left + right) >> 1;//位运算,除以2
     
     ListNode leftListNode = merge(lists, left, mid);
     ListNode rightListNode = merge(lists, mid + 1,  right);
     
     return mergeListNode(leftListNode, rightListNode);
    }
    
    public ListNode mergeListNode(ListNode l1, ListNode  l2) {
     if(l1 == null || l2 == null) {
          return l1 == null ? l2 : l1;
     }
     
     ListNode hair = new ListNode();
     hair.next = l1;
     ListNode p1 = l1;
     ListNode p1Pre = hair;
     ListNode p2 = l2;
     while (p1 != null && p2 != null) {
              if(p2.val < p1.val) {
                   ListNode temp = p2.next;
                   
                   p1Pre.next = p2;
                   p2.next = p1;
                   
                   p2 = temp;
                   p1Pre = p1Pre.next;
              }else {
                   p1Pre = p1Pre.next;
                   p1 = p1.next;
              }
          }
     
     if(p2 != null) {
          p1Pre.next = p2;
     }
     
     return hair.next;
    }
}

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值