算法分析与设计_3_分治

第三章 分治法

1. 分治法概述

1.1 分治法的设计思想

  • 分治法:对于一个规模为n的问题:若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解
  • 分治法所能解决的问题的特征
    • 该问题的规模缩小到一定的程度就可以容易地解决
    • 该问题可以分解为若干个规模较小的相同问题
    • 利用该问题分解出的子问题的解可以合并为该问题的解
    • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题

1.2 分治法的求解过程

  • 分治法通常采用递归算法设计技术,在每一层递归上都有3个步骤:

    • 分解:将原问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题
    • 求解子问题:若子问题规模较小而容易被解决则直接求解,否则递归地求解各个子问题
    • 合并:将各个子问题的解合并为原问题的解
  • 分治法的一般的算法设计框架

    divide-and-conquer(P){
         
        if(|P| ≤ n0) return adhoc(P);
        // 将P分解为较小的子问题 P1,P2,…,Pk;
        for(i=1;i<=k;i++){
         
            //循环处理k次
            yi=divide-and-conquer(Pi);	//递归解决Pi
          return merge(y1,y2,…,yk);	//合并子问题
        }
    }
    
  • 人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同,即,将一个问题分成大小相等的k个子问题的处理方法是行之有效的;当k=1时称为减治法

  • 许多问题可以取 k=2,称为二分法,如下图示,这种使子问题规模大致相等的做法是出自一种平衡子问题的思想,它几乎总是比子问题规模不等的做法要好

    image-20240602203956768

2. 求解排序问题

2.1 快速排序

  • 基本思想:在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放入最终位置后,整个数据序列被基准分割成两个子序列,所有小于基准的元素放置在前子序列中,所有大于基准的元素放置在后子序列中,并把基准排在这两个子序列的中间,这个过程称作划分。然后对两个子序列分别重复上述过程,直至每个子序列内只有一个记录或空为止

  • 分治策略

    • 分解:将原序列a[s…t]分解成两个子序列a[s…i-1]和a[i+1…t],其中i为划分的基准位置
    • 求解子问题:若子序列的长度为0或为1,则它是有序的,直接返回;否则递归地求解各个子问题
    • 合并:由于整个序列存放在数组中a中,排序过程是就地进行的,合并步骤不需要执行任何操作
  • 示例:对于{2,5,1,7,10,6,9,4,3,8}序列,其快速排序过程如下

image-20240602204927201

  • 快速排序算法代码:

    // 划分算法
    int Partition(int a[], int s, int t){
         
        int i=s, j=t;
        int tmp=a[s];						// 用序列的第1个记录作为基准
        while(i != j){
         						// 从序列两端交替向中间扫描,直至i=j为止     
    		while (j>i && a[j]>=tmp) j--;   // 从右向左扫描,找第1个关键字小于tmp的a[j]
            a[i] = a[j];					// 将a[j]前移到a[i]的位置
            while (i<j && a[i]<=tmp) i++;   //从左向右扫描,找第1个关键字大于tmp的a[i]
            a[j] = a[i];					//将a[i]后移到a[j]的位置
      }
      a[i]=tmp;
      return i;
    }
    //对a[s..t]元素序列进行递增排序
    void QuickSort(int a[], int s, int t){
         
    	if(s < t){
          							//序列内至少存在2个元素的情况
    		int i=Partition(a, s, t);
            QuickSort(a, s, i-1);			//对左子序列递归排序
            QuickSort(a, i+1, t);			//对右子序列递归排序
        }
    }
    
  • 算法分析

    快速排序的时间主要耗费在划分操作上,对长度为n的区间进行划分,共需n-1次关键字的比较,时间复杂度为O(n)

    对n个记录进行快速排序的过程构成一棵递归树,在这样的递归树中,每一层至多对n个记录进行划分,所花时间为O(n)

    当初始排序数据正序或反序时,此时的递归树高度为n,快速排序呈现最坏情况,即最坏情况下的时间复杂度为O(n2);当初始排序数据随机分布,使每次分成的两个子区间中的记录个数大致相等,此时的递归树高度为log2n,快速排序呈现最好情况,即最好情况下的时间复杂度为O(nlog2n);快速排序算法的平均时间复杂度也是O(nlog2n)

2.2 归并排序

基本思想

首先将a[0…n-1]看成是n个长度为1的有序表,将相邻的k(k≥2)个有序子表成对归并,得到n/k个长度为k的有序子表;然后再将这些有序子表继续归并,得到n/k2个长度为k2的有序子表,如此反复进行下去,最后得到一个长度为n的有序表

若k=2,即归并在相邻的两个有序子表中进行的,称为二路归并排序;若k>2,即归并操作在相邻的多个有序子表中进行,则叫多路归并排序

2.2.1 自底向上的二路归并排序算法
  • 示例:对于{2,5,1,7,10,6,9,4,3,8}序列

    image-20240602210339088

  • 分支策略:循环log2n次,length依次取1、2、…、log2n。每次执行以下步骤:

    • 分解:将原序列分解成length长度的若干子序列
    • 求解子问题:将相邻的两个子序列调用Merge算法合并成一个有序子序列
    • 合并:由于整个序列存放在数组中a中,排序过程是就地进行的,合并步骤不需要执行任何操作
  • 算法代码:

    // a[low..mid]和a[mid+1..high]→a[low..high]
    void Merge(int a[], int low, int mid, int high){
         
        int *tmpa;
        int i=low, j=mid+1, k=0;
        tmpa=(int *)malloc((high-low+1)*sizeof(int));
        while (i<=mid && j<=high){
         
    		if (a[i]<=a[j]){
         							// 将第1子表中的元素放入tmpa中
    			tmpa[k]=a[i];
                i++;
                k++;
            }else{
         										// 将第2子表中的元素放入tmpa中
    			tmpa[k]=a[j];
                j++;
                k++;
            }
        }
    	while (i<=mid){
         									// 将第1子表余下部分复制到tmpa
    		tmpa[k]=a[i];
            i++;
            k++;
        }
    	while (j<=high){
         								// 将第2子表余下部分复制到tmpa
    		tmpa[k]=a[j];
            j++;
            k++;
        }
        for (k=0, i=low; i<=high; k++, i++) a[i]=tmpa[k];// 将tmpa复制回a中
        free(tmpa);										// 释放tmpa所占内存空间
    }
    // 一趟二路归并排序
    void MergePass(int a[], int length, int n){
         
    	int i;
        for (i=0; i+2*length-1<n; i=i+2*length){
         		//归并length长的两相邻子表
            Merge(a,i,i+length-1,i+2*length-1);
        }
        if (i+length-1<n){
         								//余下两个子表,后者长度小于length
            Merge(a, i, i+length-1, n-1);				  //归并这两个子表
        }
    }
    //二路归并算法
    void MergeSort(int a[], int n){
         	
    	int length;
        for (length=1; length<n; length=2*length) MergePass(a, length, n
  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值