分治法(归并排序、快速排序)

作者:disappearedgod
文章出处:http://blog.csdn.net/disappearedgod/article/details/23599343
时间:2014-4-13




分治算法也叫分治策略,把输入分为若干个部分,递归的解每一个问题,最后将这些子问题合并成为一个全局解


由此可以得到分治策略解决的问题特点:

  • 该问题的规模缩小到一定的程度就可以容易地解决;
  • 该问题可以分解为若干个规模较小的相同问题;
  • 分解出的子问题的解可以合并为原问题的解;
  • 分解出的各个子问题是相互独立的。

分治模型在每一层递归上都有三个步骤:

  • 分解(Divide):将原问题分解成一系列子问题。
  • 解决(Conquer):递归的解各个子问题。若子问题足够小,则直接求解。
  • 合并(Combine):将子问题的结果合并成原问题的解。

经典问题

二分搜索

递归版本,效率低:

int BSearch(int a[],int x, int low, int high){
  int mid;
  if(low>high) return -1;
  if(x==a[mid]) return mid;
  if(x<a[mid]) return(BSearch(a,x,low,mid-1));
  else return(BSearch(a,x,mid+1,high));
}

非递归版本,不能找到边界(多值问题:查询值在其内多种情况)

int BSearch(int a[],int key, int n){
  int low, high, mid;
  low=0;high = n-1;
  while(low<=high){
    mid =(low+high)/2;
    if(a[mid]==key) return mid;
    else if(a[mid]<key) low=mid+1;
    else high = mid -1;  
  }
  return -1;
}

例题

变形题目:在旋转有序数组中查找元素(剑指offer)
题目:给定一个旋转的有序数组,比如{7,8,9,10,1,2,3}是{1,2,3,7,8,9,10}旋转之后得到的,在数组中查找是否存在元素key。要求时间复杂度为O(lgn)。假定数组中不存在重复元素。

int rotateBinarySearch(int a[],int aLength, int key){
}

解题:
主要思想:

  • 每次根据L和R求出M后,M左边[L, M]和右边[M+1, R]这两部分中至少一个是有序的。
  • arr[M]和X比较
  1. arr[M]==X,返回M
  2. arr[M] < arr[R],说明右侧有序,当 arr[M]<X<arr[R],则L=M+1,否则R=M-1
  3. arr[M] > arr[L],说明左侧有序,当 arr[L]<X<arr[M],则R=M-1,否则L=M+1

代码:

int rotateBinarySearch(int a[],int aLength, int key){
  int low =0;
  int high = aLength-1;
  while(low<=high){
    int mid = low + ((high-low)>>1);
	if(a[mid]==key) return mid;
	if(a[low]<=a[mid]){//低端有序
	  if(key>a[low]&&key<a[mid]) high = mid -1;
	  else low = mid +1;
	}
	else //高端有序
	{
	  if(key<a[mid]&&key<=a[high])
	    low = mid +1;
	  else
	    high = mid -1;
	}
  }
}

归并排序

MergeSort算法完全依照了上述模式,直观的操作如下:
分解:将n个元素分成各含n/2个元素的子序列;
解决:用合并排序法对两个子序列递归的排序;
合并:合并两个已排序的子序列以得到排序结果。


归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。

可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。

//将有二个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
	int i = first, j = mid + 1;
	int m = mid,   n = last;
	int k = 0;
	
	while (i <= m && j <= n)
	{
		if (a[i] <= a[j])
			temp[k++] = a[i++];
		else
			temp[k++] = a[j++];
	}
	
	while (i <= m)
		temp[k++] = a[i++];
	
	while (j <= n)
		temp[k++] = a[j++];
	
	for (i = 0; i < k; i++)
		a[first + i] = temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
	if (first < last)
	{
		int mid = (first + last) / 2;
		mergesort(a, first, mid, temp);    //左边有序
		mergesort(a, mid + 1, last, temp); //右边有序
		mergearray(a, first, mid, last, temp); //再将二个有序数列合并
	}
}

bool MergeSort(int a[], int n)
{
	int *p = new int[n];
	if (p == NULL)
		return false;
	mergesort(a, 0, n - 1, p);
	delete[] p;
	return true;
}

《分治法(归并排序、快速排序)》

快速排序

像归并排序一样,快速排序也是基于分支模式的。对一个典型子数组A[p…r]排序的分治过程的三个步骤:

  • 分解:数组A[p..r]被划分成两个(可能空)子数组A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每个元素都小于等于A(q),而且,小于等于A[q+1..r]中的元素。下标q也在这个划分过程中进行运算。
  • 解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]排序
  • 合并:因为两个子数组是就地排序的,将他们合并不需要操作:整个数组A[p..r]已排序。

QuickSort(A,p,r)
  if p < r
     then q <- Partition(A,p,r)
              QuickSort(A,p,q-1)
              QuickSort(A,q+1,r)



请参看:十二之再续:快速排序算法之所有版本的c/c++实现

求数组最大最小值

问题:在n个元素中找出最大值和最小值。

void  MaxMin(Typea[],int n,Type&max,Type &min){}

简单的查找算法:

 void  MaxMin(Type a[],int n,Type &max,Type &min)
{
    max=min =a[0];
    for(int i=1;i<n;i++)
            {  if(a[i]>max) max=a[i];
               if(a[i]<min)  min=a[i];
            }
} 

评价:
算法的时间复杂度体现在比较的次数上,当a[n]中的元素是多项式,矢量及非常大的数时或字符串,元素的比较代价就比其他操作代价高的很。

用分治法处理这个问题。

  • 当n=2时,可以通过一次比较就能查出最大值和最小值。
  • 当n>2时,可以将a[n]分为(a[0]…a[n/2])和(a[n/2+1]…a[n])两个子问题,递归的调用分治法求解,最后在合并最大最小值。
void MaxMin(int i,int j,Type &max,Type &min)
{   
       if(i==j) max=min=a[i];//分解的最小问题
       else if(i==j-1){//分解的另一种的最小问题
                      if(a[i]<a[j]){  max=a[j];min=a[i];}
                      else{max=a[i];min=a[j];}
                     }
      else {
           int mid=(i+j)/2;
           Type max1,min1;
          //分解成子问题
           MaxMin(i,mid,max,min);
           MaxMin(mid+1,j,max1,min1);
           // 合并子问题的解     
           if(max<max1) max=max1;
           if(min<min1) min=min1;
           }
}


大整数乘法
最近点对

 非负数开根号sqrt

思路:

二分逼近,注意eps误差定义:当底数为小于1时,右界为1

const double eps = le-6;
double my_sqrt(const double a){
  double left = 0; right = a<1? 1:a;
  while(left<right){
    double mid = (left + right)/2;
	double temp = mid * mid ;
	if(temp<a) left = mid + eps;
	else if (temp > a ) right = mid - eps;
	else return mid;
  }
  reutrn left;
}

三分查找

三分查找技术适用于答案在某一个区间内,这个区间的特定点是,以答案为分点的联测区间都单调的增加或者减小:
三分查找的目的是找到最值
三分法所面向的搜索序列的要求是:序列为一个凸性函数通俗来讲,就是该序列必须有一个最大值(或最小值),在最大值(最小值)的左侧序列,必须满足不严格单调递增(递减),右侧序列必须满足不严格单调递减(递增)。

在区间为【l,r】中,取得x1=l+(r-l)/3 x2=l-(r-l)/3 作为分点,看谁更接近标准答案,并依此开更新左右区间,继续进行二分,知道得到逼近答案。

分治法其他经典问题

大整数乘法

高位*高位 + 低位*低位 + 近位*高位 (8次乘法 变成 7次乘法(加法不计)) orXXX矩阵

矩阵乘法

棋盘覆盖题目

最近点对

平面分两部分,最近点内,最近点间

    原文作者:递归与分治算法
    原文地址: https://blog.csdn.net/disappearedgod/article/details/23599343
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞