分治算法

算法思想


分治算法把一个问题实例分解成若干个小型而独立的实例,从而可以在并行计算机上执行;那些小型而独立的实例可以在并行计算机的不同处理器上完成。

分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。

分治算法的特征


分治法所能解决的问题一般具有以下几个特征:

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

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

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

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

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;

第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;

第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一、二条特征,而不具备第三条特征,则可以考虑用贪心算法或动态规划算法。

第四条特征涉及到分治法的效率,如果各子问题是不独立,则分治法要做很多不必要的工作,重复地解公共的子问题,此时一般使用动态规划算法。

解题步骤


分治算法在每层递归中包含三个步骤:

1)分解原问题为若干子问题,这些子问题是原问题的规模较小的实例; 
2)解决这些子问题,递归地求解各个子问题。然而,若子问题的规模足够小,则直接求解;
3)合并这些子问题的解为原问题的解。

应用


二分查找

问题描述:

给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。

C++非递归实现:

template <typename T>
int binarySearch(T *a, const T& x, int length)
{
    int left = 0;
    int right = length - 1;
    while (left <= right)
    {
        int mid = (left + right) / 2;
        if (x == a[mid])
            return mid;
        else if (x < a[mid])
            right = mid - 1;
        else
            left = mid + 1;
    }
    //未找到与x相等的元素
    return -1;
}

C++递归实现:

template <typename T>
int binarySearch(T *a, const T& x, int left, int right)
{
    if (left > right)
        return -1;

    int mid = (left + right) / 2;
    if (x == a[mid])
        return mid;
    else if (x < a[mid])
        binarySearch(a, x, left, mid - 1);
    else
        binarySearch(a, x, mid + 1, right);
}

归并排序(merge sort)

问题描述:

把n个元素按照非递减顺序排列。

分析:

可以用分治策略来设计算法,这种算法的结构为:若n为1,则算法终止;否则,将序列划分为k个子序列(k是不小于2的整数)。先对每一个子序列排序,然后将有序子序列归并为一个序列。(归并排序时k=2)

假如有10个元素,关键字分别为[10,6,7,2,5,8,3,1,4,9]。
则归并排序过程如下:

[10] [6] [7] [2] [5] [8] [3] [1] [4] [9]

[6 10] [2 7] [5 8] [1 3] [4 9]

[2 6 7 10] [1 3 5 8] [4 9]

[1 2 3 5 6 7 8 10] [4 9]

[1 2 3 4 5 6 7 8 9 10]

C++实现:

template <typename  T>
void mergeSort(T a[], int n)
{// Sort a[0 : n - 1] using the merge sort method.
   T *b = new T [n];
   int segmentSize = 1;
   while (segmentSize < n)
   {
      mergePass(a, b, n, segmentSize); // merge from a to b
      segmentSize += segmentSize;
      mergePass(b, a, n, segmentSize); // merge from b to a
      segmentSize += segmentSize;
   }
}

template <typename T>
void mergePass(T x[], T y[], int n, int segmentSize)
{// Merge adjacent segments from x to y.
   int i = 0;   // start of the next segment
   while (i <= n - 2 * segmentSize)
   {// merge two adjacent segments from x to y
      merge(x, y, i, i + segmentSize - 1, i + 2 * segmentSize - 1);
      i = i + 2 * segmentSize;
   }

   // fewer than 2 full segments remain
   if (i + segmentSize < n)
      // 2 segments remain
      merge(x, y, i, i + segmentSize - 1, n - 1);
   else
      // 1 segment remains, copy to y
      for (int j = i; j < n; j++)
         y[j] = x[j];
}

template <typename T>
void merge(T c[], T d[], int startOfFirst, int endOfFirst,
                         int endOfSecond)
{// Merge two adjacent segments from c to d.
   int first = startOfFirst,       // cursor for first segment
       second = endOfFirst + 1,    // cursor for second
       result = startOfFirst;      // cursor for result

   // merge until one segment is done
   while ((first <= endOfFirst) && (second <= endOfSecond))
      if (c[first] <= c[second])
         d[result++] = c[first++];
      else
         d[result++] = c[second++];

   // take care of leftovers
   if (first > endOfFirst)
      for (int q = second; q <= endOfSecond; q++)
          d[result++] = c[q];
   else
      for (int q = first; q <= endOfFirst; q++)
          d[result++] = c[q];
}

算法复杂度:归并排序的平均和最好最坏情况下的复杂度均为O(nlogn)


排序排序(quick sort)

问题描述:

把n个元素按照非递减顺序排列。

分析:

用分治算法可以实现另一种完全不同的排序算法——快速排序。

//对a[0:n-1]快排 
从a[0:n-1]中选择一个元素作为支点,组成中间段
把剩余元素分为左段left和有段right。是左段的元素关键字都不大于支点关键字,有段的元素关键字都不小于支点关键字
对左段递归快排
对右段递归快排
最终结果按左段、中间段和右段排列

假如有10个元素,关键字分别为[10,6,7,2,5,8,3,1,4,9]。
则快速排序过程如下:
选择首元素为支点,从1位置往右遍历,寻找不小于支点的元素;从n-1位置往左遍历,寻找不大于支点的元素。支点保证向左遍历不越界,n-1位置为数组最大值保证向右遍历不越界。

绿色为支点,黄色为不小于支点元素,红色为不大于支点的元素
这里写图片描述

1)找到不小于支点的元素为7,不大于支点的元素为4,交换两个元素,继续寻找;
2)找到不小于支点的元素为8,不大于支点的元素为1,交换两个元素,继续寻找;
3)找到不小于支点的元素为8,不大于支点的元素为3,但8的位置比3靠前,交换支点和不大于支点的元素3;数组分成两段,[3 4 2 5 1]和[6 8 7 9 10];
4)分别对两段重复上述过程。

C++实现:

template <typename T>
void quickSort(T a[], int n)
{// Sort a[0 : n - 1] using the quick sort method.
   if (n <= 1) return;
   // move largest element to right end
   int max = indexOfMax(a,n);
   swap(a[n - 1], a[max]);
   quickSort(a, 0, n - 2);
}

template <typename T>
void quickSort(T a[], int leftEnd, int rightEnd)
{// Sort a[leftEnd:rightEnd], a[rightEnd+1] >= a[leftEnd:rightEnd].
   if (leftEnd >= rightEnd) return;

   int leftCursor = leftEnd,        // left-to-right cursor
       rightCursor = rightEnd + 1;  // right-to-left cursor
   T pivot = a[leftEnd];

   // swap elements >= pivot on left side
   // with elements <= pivot on right side
   while (true)
   {
      // find >= element on left side
      while (a[++leftCursor] < pivot);

      // find <= element on right side
      while (a[--rightCursor] > pivot);

      if (leftCursor >= rightCursor) 
          break;  // swap pair not found
      swap(a[leftCursor], a[rightCursor]);
   }

   // place pivot
   a[leftEnd] = a[rightCursor];
   a[rightCursor] = pivot;

   quickSort(a, leftEnd, rightCursor - 1);   // sort left segment
   quickSort(a, rightCursor + 1, rightEnd);  // sort right segment
}


template<typename T>
int indexOfMax(T a[], int n)
{// Locate the largest element in a[0:n-1].
   if (n <= 0)
      throw illegalParameterValue("n must be > 0");

   int indexOfMax = 0;
   for (int i = 1; i < n; i++)
     if (a[indexOfMax] < a[i])
        indexOfMax = i;
   return indexOfMax;
}

算法复杂度:快速排序的平均复杂度为O(nlogn),最坏情况下为n2


值得一提的是:

STL的sort()算法,数据量大时采用Quick Sort,分段递归排序,一旦分段后的数据量小于logn的某个常数倍时,为避免Quick Sort的递归调用带来过大的额外负荷,就改用Insertion Sort。如果递归层次过深,还会改用Heap Sort。

STL的stable_sort()算法,数据量大时使用Merge Sort,数据量小于logn的某个常数倍时,使用Insertion Sort。

发布了30 篇原创文章 · 获赞 22 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章