[Day15 分治算法 | 青训营笔记]

37 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第15天

分治算法

基本思想

将一个难以直接解决的大问题,分解为一些规模较小的相同子问题,各子问题相互独立;递归的解决各子问题,将子问题的解并成原问题的解。

分治法的解题步骤

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

2.治理------步骤2-1:求解各个子问题(递归)

步骤2-2:各个子问题的解的合并

二分查找

#include<stdio.h>
int BinarySearch(int s[],int x,int low,int high){
	if(low>high) return -1;
	int middle = (low+high);//分解
	if(x == s[middle]) return middle;
	else if(x>s[middle]){
		return BinarySearch(s,x,middle+1,high);
	}
	else{
		return BinarySearch(s,x,low,middle-1);
	}	
}
int main(){
	int a[10]= {0,1,2,3,4,5,6,7,8,9};
	int b; 
	b = BinarySearch(a,3,a[0],a[9]);
	printf("%d",b);
	return 0;
}

结果为:

合并排序

算法思想

合并排序是采用分治策略对n个元素进行排序的算法,是分治法的典型应用和完美体现。它是一种平衡的、简单的二分分治策略,其计算过程分为三步:

1.分解:将排序元素分成大小大致相同的两个子序列。

2.求解子问题:用合并排序法分别对两个子序列递归的进行排序。

3.合并:将排好序的有序子序列进行合并,得到符合要求的有序序列。

算法描述

快速排序

快速排序的求解过程

1.分-------选定一个元素作为基准元素,小于基准元素的放左边,大于基准元素的放右边

2.治-------递归求解子问题

快速排序的算法描述

首先根据基准元素进行排序,然后再左边右边分别排序。

快速排序算法的实现
def partition(arr,p,q):
    #lo、hi分别表示指向首个元素和倒数第 2 个元素的索引
    lo = p
    hi = q-1
    #pivot 表示选中的中间值
    pivot = arr[q]
    while True:
        #lo从左往右遍历,直至找到一个不小于 pivot 的元素
        while arr[lo] < pivot:
            lo = lo + 1
        #hi从右往左遍历,直至找到一个不大于 pivot 的元素
        while hi > 0 and arr[hi] > pivot:
            hi = hi - 1
        #如果 lo≥hi,退出循环
        if lo >= hi:
            break
        else:
            #交换 arr[lo] 和 arr[hi] 的值
            arr[lo],arr[hi] = arr[hi],arr[lo]
            #lo 和 hi 都向前移动一个位置,准备继续遍历
            lo = lo + 1
            hi = hi - 1
    #交换 arr[lo] 和 arr[q] 的值
    arr[lo],arr[q] = arr[q],arr[lo]
    #返回中间值所在序列中的位置
    return lo

def quick_sort(arr,p,q):
    #如果待排序序列不存在,或者仅包含 1 个元素,则不再进行分割
    if q - p <= 0:
        return
    #调用 partition() 函数,分割 [p,q] 区域
    par = partition(arr,p,q)
    #以 [p,par-1]作为新的待排序序列,继续分割
    quick_sort(arr,p,par-1)
    #以[par+1,q]作为新的待排序序列,继续分割
    quick_sort(arr,par+1,q)
  
arr=[35,33,42,10,14,19,27,44,26,31]
#对于 arr 列表中所有元素进行快速排序
quick_sort(arr,0,9)
print(arr)

汉诺塔问题

汉诺塔问题源自印度一个古老的传说,印度教的“创造之神”梵天创造世界时做了 3 根金刚石柱,其中的一根柱子上按照从小到大的顺序摞着 64 个黄金圆盘。梵天命令一个叫婆罗门的门徒将所有的圆盘移动到另一个柱子上,移动过程中必须遵守以下规则:

  • 每次只能移动柱子最顶端的一个圆盘;

  • 每个柱子上,小圆盘永远要位于大圆盘之上;

图 1 给您展示了包含 3 个圆盘的汉诺塔问题:

图 1 汉诺塔问题

一根柱子上摞着 3 个不同大小的圆盘,那么在不违反规则的前提下,如何将它们移动到另一个柱子上呢?图 2 给大家提供了一种实现方案:


图 2 汉诺塔问题的解决方案

汉诺塔问题中,3 个圆盘至少需要移动 7 次,移动 n 的圆盘至少需要操作 2n-1 次。

在汉诺塔问题中,当圆盘个数不大于 3 时,多数人都可以轻松想到移动方案,随着圆盘数量的增多,汉诺塔问题会越来越难。也就是说,圆盘的个数直接决定了汉诺塔问题的难度,解决这样的问题可以尝试用分治算法,将移动多个圆盘的问题分解成多个移动少量圆盘的小问题,这些小问题很容易解决,从而可以找到整个问题的解决方案。

汉诺塔问题的代码实现
#include <stdio.h>
void hanoi(int num, char sou, char tar,char aux) {
    //统计移动次数
    static int i = 1;
    //如果圆盘数量仅有 1 个,则直接从起始柱移动到目标柱
    if (num == 1) {
        printf("第%d次:从 %c 移动至 %c\n", i, sou, tar);
        i++;
    }
    else {
        //递归调用 hanoi() 函数,将 num-1 个圆盘从起始柱移动到辅助柱上
        hanoi(num - 1, sou, aux, tar);
        //将起始柱上剩余的最后一个大圆盘移动到目标柱上
        printf("第%d次:从 %c 移动至 %c\n", i, sou, tar);
        i++;
        //递归调用 hanoi() 函数,将辅助柱上的 num-1 圆盘移动到目标柱上
        hanoi(num - 1, aux, tar, sou);
    }
}

int main()
{
    //以移动 3 个圆盘为例,起始柱、目标柱、辅助柱分别用 A、B、C 表示
    hanoi(3, 'A', 'B', 'C');
    return 0;
}