分治算法

算法思想


分治算法把一個問題實例分解成若干個小型而獨立的實例,從而可以在並行計算機上執行;那些小型而獨立的實例可以在並行計算機的不同處理器上完成。

分治法的思想:將原問題分解爲幾個規模較小但類似於原問題的子問題,遞歸地求解這些子問題,然後再合併這些子問題的解來建立原問題的解。

分治算法的特徵


分治法所能解決的問題一般具有以下幾個特徵:

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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章