分治法

发布时间 2023-04-11 20:30:21作者: 林月映清泉

分治法的基本思想

分治法的基本思想是将一个规模为\(n\)的问题分解为\(k\)个规模较小的子问题,这些子问题互相独立且与原问题相同。递归的解这些子问题,然后将各子问题的解合并得到原问题的解。
    1.分治法划分问题的方法是递归划分,且子问题的规模最好是相等的,即将一个问题分为大小相等的\(k\)个子问题,多出情况下可以取\(k=2\)
    2.对子问题进行递归求解,在问题规模最够小的时候可以使用停止递归
    3.合并子问题得到原问题解,合并的过程是整个算法的精髓(@>_<@)

一般算法设计模式

\(|P|\)表示问题\(P\)的规模,\(n_0\)表示一个阈值,当\(|P|\)小于等于\(n_0\)时,问题可以很容易求出,不必再分解;当\(|P|\)大于\(n_0\)时,问题分解为\(k\)个子问题,分别求出之后再将其合并。所以可得出分治法的一般算法设计模式如下:

divide-and-conquer(p) {
  if (|p| <= n0)
    adhoc(P);
  divide P into smaller subinstances P1,P2,···,Pk;
  for (i=1; i <= k; i++)
    yi = divide-and-conquer(Pi);
  return merge(y1, y2, ···, yk);
}

下面分析上述算法时间复杂性,设原问题规模\(|P|=n\),子问题规模相等且为\(n/m\)adhoc解规模为\(n_0\)的问题耗费1单位时间;将原问题分解为\(k\)个子问题,并将它们的解合并为原问题的解耗费\(f(n)\)单位时间,则

\[ T(n)=\left\{ \begin{aligned} O(1)&&n=n_0\\ kT(n/m)+f(n)&&n>n_0\\ \end{aligned} \right. \]

分治典例

1.二分搜索算法

二分搜索算法的基本思想是,将\(n\)个元素分成个数大致相同的两半,取\(a[n/2]与x\)做比较。如果\(x=a[n/2]\),则找到\(x\),算法终止;如果\(x<a[n/2]\),则只在数组\(a\)的左半部继续搜索\(x\);如果\(x>a[n/2]\),则只在数组\(a\)的右半部继续搜索\(x\)。具体算法如下:

template<class Type>
int BinarySearch(Type a[], const Type& x, int n) {
  //找到x时返回其在数组中的下标,否则返回-1
  int left = 0; int right = n-1;
  while (left <= right){
    int middle = (left+right)/2;
    if (x == a[middle])
      return middle;
    if (x > a[middle])
      left = middle + 1;
    else
      right = middle - 1;
  }
  return-1;
}

在该算法中,原问题被分为\(k=2\)个规模相同且与原问题相同的子问题。在最坏的情况下,while循环被执行了\(O(long_2^n)\)次,循环内运行所需时间为\(O(1)\),所以该算法在最坏情况下的时间复杂性为\(O(long_2^n)\)

2.快速排序

快速排序算法的基本思想是将待排序数组\(a[p:r]\),分为\(a[p:q-1]\)\(a[q]\)\(a[q+1:r]\)三个部分。其中,\(a[p:q-1]\)中任意元素小于等于\(a[q]\)\(a[q+1:r]\)中任意元素大于\(a[q]\)。通过递归调用快速排序算法,分别对\(a[p:q-1]\)\(a[q+1:r]\)进行排序。最后将结果合并,如果排序就地进行,则算法结束之后\(a[p:r]\)就已排好序。具体算法如下:

template<class Type>
void QuickSort (Type a[], int p, int r){
  if (p < r){
    int  q = Partition(a, p, r);//以a[q]为基准排序
    QuickSort(a, p, q-1);
    QuickSort(a, q+1, r);
  }
}

Partition()如下:

template<class Type>
int Partition (Type a[], int p, int r){
  int i = p, j = r+1;
  Type x = a[p];
  while (true){
    while (a[++i] < x && i < r)  ;
    while (a[--j] > x)  ;
    if (i >= j)
      break;
    Swap(a[i], a[j]);
  }
  a[p] = a[j];
  a[j] = x;
  return j;//返回划分节点
}

该算法将原问题分为了\(k=2\)个与原问题相同的解,但是问题规模和所选定的基准有关。对于\(n\)个输入,Partition算法的计算时间为\(O(n)\)。在最坏情况下,划分的两个数组一个有\(1\)个元素,另一个有\(n-1\)个元素,则

\[ T(n)=\left\{ \begin{aligned} O(1)&&n\leq1\\ T(n-1)+O(n)&&n>1\\ \end{aligned} \right. \]

解此递归方程可得\(T(n)=O(n^2)\)
在最好情况下,每次划分都使得两边的元素数量相等,此时有

\[ T(n)=\left\{ \begin{aligned} O(1)&&n\leq1\\ 2T(n/2)+O(n)&&n>1\\ \end{aligned} \right. \]

解此递归方程可得\(T(n)=O(nlog_2^n)\)
可以从数组中随机抽取一个元素作为基准,这样可以期望划分是较对称的。