算法:分治法

96 阅读3分钟

基本概念

1.把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

2.分治策略是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

适用情况

1)该问题的规模缩小到一定的程度就可以容易地解决

2)该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

3)利用该问题分解出的子问题的解可以合并为该问题的解;

4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

分治法的复杂性分析

一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:

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

通过迭代法求得方程的解:

递归方程及其解只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由n等于m的方幂时T(n)的值可以估计T(n)的增长速度。通常假定T(n)是单调上升的,从而当 mi≤n<mi+1时,T(mi)≤T(n)<T(mi+1)。

分治法例题:合并排序和快速排序

public class 分治_合并排序 {
 /**
  * 函数说明:在数组被拆分以后进行合并
  */
 static void Merge(int a[], int left, int middle, int rigth) {
  //定义左端数组大小
  int n1 = middle - left+1;
  int n2 = rigth - middle;
  
  //初始化数组,分配内存
  int bejin[] = new int[n1];
  int end[] = new int[n2];
  
  //数组赋值
  for(int i = 0; i < n1; i++)
   bejin[i] = a[left + i];
   
  for(int i = 0; i < n2; i++) 
   end[i] = a[middle+1+i];
  
  //用key做原数组索引,每调用一次函数重新给原数组赋一次值
  int i = 0, j = 0, key;
  for(key = left; key <= rigth; key++){
   
   if(n1>i&&n2>j&&i < n1 && bejin[i] <= end[j])
    a[key] = bejin[i++];
   else if(n1>i&&n2>j&&j < n2 && bejin[i] >= end[j])
    a[key] = end[j++]; 
   else if(i == n1 && j < n2)
    a[key] = end[j++];
   else if(j == n2 && i < n1)
    a[key] = bejin[i++]; 
  }
 }
 /**
  * 差分数组区间,不断分支
  */
 static void MergeSort(int a[],int left,int rigth) {
  int middle=0;
  if(left<rigth) {
   middle =(rigth+left)/2;
   MergeSort(a, left, middle);
   MergeSort(a, middle+1, rigth);
   Merge(a, left, middle, rigth);
  }
 }
 public static void main(String[] args) {
  int a[]= {85,3,52,9,7,1,5,4};
  MergeSort(a, 0,7); 
  for(int i=0;i<8;i++) {
   System.out.print(" "+a[i]);
  }
 }
}
​
public class 分治_快速排序 {
 /**
  *交换函数,i,j为数组索引
  */
 static void swap(int A[], int i, int j)
 {
     int temp = A[i];
     A[i] = A[j];
     A[j] = temp;
 }
 /**
  * 选取一个关键字(key)作为枢轴,一般取整组记录的第一个数/最后一个,这里采用选取序列最后一个数为枢轴。
  * 设置两个变量left = 0;right = N - 1;
  * 从left一直向后走,直到找到一个大于key的值,right从后至前,直至找到一个小于key的值,然后交换这两个数。
  * 重复第三步,一直往后找,直到left和right相遇,这时将key放置left的位置即可。
  * @return
  */
 static int PartSort(int[] array,int left,int right)
 {
     int key = array[right];//定义基准 
     int count=right;//保存rigth值
     while(left < right)//防止数组越界
     {
         while(left < right && array[left] <= key)
         {
             ++left;
         }
         while(left < right && array[right] >= key)
         {
             --right;
         }
         swap(array,left,right);
     }
     swap(array,right,count);
     return right;
 }
 /**
  *分治思想,递归调用
  */
 static void QuickSort(int array[],int left,int right)
 {
     if(left >= right)//表示已经完成一个组
     {
         return;
     }
     int index = PartSort(array,left,right);//枢轴的位置
     QuickSort(array,left,index - 1);
     QuickSort(array,index + 1,right);
 }
 public static void main(String[] args) {
  int a[]= {1,5,-5,54,15,67,16,23};
  QuickSort(a,0,7);
  for(int i=0;i<a.length;i++) {
   System.out.print(" "+a[i]);
  }
     System.out.print("\n");
 }
}
​

算法心得

作为分治法里很典型的一种算法,合并排序和快速排序充分展现了分治法的思想,分而治之,在此次编程使用此方法中,给我的体会是程序简单分为两部分,第一部分,不断“拆”,缩小子问题规模,达到最优子结构。然后合并,在合并过程中,应为子问题足够小,容易计算,再者不断合并子问题答案,最终求出问题解。