分治策略Divide and Conquer

在計算機科學中,分治法是一種很重要的算法。字面上的解釋是“分而治之”,通常是遞歸算法,就是 把一個複雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併。這個技巧是很多高效算法的基礎,如排序算法(快速排序,歸併排序),傅立葉變換。

任何一個可以用計算機求解的問題所需的計算時間都與其規模有關。問題的規模越小,越容易直接求解,解題所需的計算時間也越少。例如,對於n個元素的排序問題,當n=1時,不需任何計算。n=2時,只要作一次比較即可排好序。n=3時只要作3次比較即可…而當n較大時,問題就不那麼容易處理了。要想直接解決一個規模較大的問題,有時是相當困難的。


分治策略的基本思想

分治算法是 將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。如果原問題可分割成k 個子問題,1<kn ,且這些子問題都可解並可利用這些子問題的解求出原問題的解,那麼這種分治法就是可行的。由分治法產生的子問題往往是原問題的較小模式,這就爲使用遞歸技術提供了方便。在這種情況下,反覆應用分治手段,可以使子問題與原問題類型一致而其規模卻不斷縮小,最終使子問題縮小到很容易直接求出其解。這自然導致遞歸過程的產生。分治與遞歸像一對孿生兄弟,經常同時應用在算法設計之中,並由此產生許多高效算法。

熟悉的例子

二分查找: 給定已按升序排好序的n 個元素a[0:n1] ,現要在這n 個元素中找出一特定元素x

算法 1 binarysearch(T,x)
輸入:排好序的數組T ;數x
輸出j
1. l1;rn
2. while lr do
3.      ml+r2
4.     if T[m]=x then return m
5.     else if T[m]x then rm1
6.     else lm+1
7.return 0

分析:

1、該問題的規模縮小到一定的程度就可以容易地解決;
2、該問題可以分解爲若干個規模較小的相同問題;
3、分解出的子問題的解可以合併爲原問題的解;
4、分解出的各個子問題是相互獨立的。

很顯然此問題分解出的子問題相互獨立,即在a[i] 的前面或後面查找x是獨立的子問題,因此滿足分治法的第四個適用條件。

#include <iostream>
using namespace std;

// 查找成功返回value索引,查找失敗返回-1
template <class T>
int binary_search(T array[],const T& value,int left,int right){
    while (right >= left) {
        int m = (left + right) / 2;
        if (value == array[m])
            return m;
        if (value < array[m])
            right = m - 1;
        else
            left = m + 1;
    }
    return -1;
}

int main()
{
    int array[] = {0,1,2,3,4,5,6,7,8,9};

    cout << "0 in array position: " << binary_search(array,0,0,9) << endl;
    cout << "9 in array position: " << binary_search(array,9,0,9) << endl;
    cout << "2 in array position: " << binary_search(array,2,0,9) << endl;
    cout << "6 in array position: " << binary_search(array,6,0,9) << endl;
    cout << "10 in array position: " << binary_search(array,10,0,9) << endl;

    return 0;
}

算法複雜度分析

W(n)=W(n2)+1W(1)=1

每執行一次算法的while循環, 待搜索數組的大小減少一半。因此,在最壞情況下,while循環被執行了O(logn) 次。循環體內運算需要O(1) 時間,因此整個算法在最壞情況下的計算時間複雜性爲O(logn) 。

上述算法就是用分治算法,它們的共同特點是:對於一個規模爲n的問題,若該問題可以容易地解決(比如說規模n較小)則直接解決,否則將其分解爲k個規模較小的子問題,這些子問題互相獨立且與原問題形式相同,遞歸地解這些子問題,然後將各子問題的解合併得到原問題的解

分治算法的一般性描述

對這k個子問題分別求解。如果子問題的規模仍然不夠小,則再劃分爲k個子問題,如此遞歸的進行下去,直到問題規模足夠小,很容易求出其解爲止。將求出的小規模的問題的解合併爲一個更大規模的問題的解,自底向上逐步求出原來問題的解。分治算法divide-and-conquer的僞碼描述如下:

算法2: divideandconquer(P)
1.if(|P|c) S(P) // 解決小規模的問題
2. divide P into smaller subinstances P1,P2,,Pk //分解問題
3. for i=1 to k do
4. yidivideandconquer(Pi) //遞歸的解各子問題
5. ReturnMerge(y1,y2,y3,yk1,yk) //將各子問題的解合併爲原問題的解

人們從大量實踐中發現,在用分治法設計算法時,最好使子問題的規模大致相同。即將一個問題分成大小相等的k 個子問題的處理方法是行之有效的。這種使子問題規模大致相等的做法是出自一種平衡(balancing)子問題的思想,它幾乎總是比子問題規模不等的做法要好。

一個分治法將規模爲n 的問題分成k 個規模爲nm 的子問題去解。設分解閥值c=1 ,且S(P) 解規模爲1的問題耗費1個單位時間。再設將原問題分解爲k 個子問題以及用Merge將k 個子問題的解合併爲原問題的解需用f(n) 個單位時間。用T(n) 表示該分治法解規模爲|P|=n 的問題所需的計算時間,則有:

W(n)=W(|P1|)+W(|P2|)+...+W(|Pk|)+f(n)W(c)=C

分治算法的分析技術

定義1: Fibonacci 數列
無窮數列1,1,2,3,5,8,13,21,34,55,……,稱爲Fibonacci數列。它可以遞歸地定義爲:

F(n)=1,F(n)=1,F(n)=F(n1)+F(n2),if n=0if n=1if n>1 

實現:

#include <iostream>
using namespace std;

// fibonacci implement by recursive
long fibonacci_recursive(long n)
{
    if (n <= 1 )
        return 1;

    return fibonacci_recursive(n - 1)
            + fibonacci_recursive(n - 2);
}

// fibonacci implement by loop
long fibonacci_loop(long n)
{
    if (n == 0 || n == 1)
        return 1;

    long f1 = 1;
    long f2 = 1;
    long result = 0;
    for (long i = 1; i < n ; ++ i) {
        result = f1 + f2;
        f1 = f2;
        f2 = result;
    }
    return result;
}

int main()
{
    cout << "fibonacci implement by recursive: " << endl;
    for (long i = 0; i <= 20; ++ i)
        cout << fibonacci_recursive(i) << " " ;
    cout << endl << endl;

    cout << "fibonacci implement by loop: " << endl;
    for (long i = 0; i <= 20; ++ i)
        cout << fibonacci_loop(i) << " " ;
    cout << endl;
    return 0;
}

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

  • 1) 該問題的規模縮小到一定的程度就可以容易地解決
  • 2) 該問題可以分解爲若干個規模較小的相同問題,即該問題具有最優子結構性質。
  • 3) 利用該問題分解出的子問題的解可以合併爲該問題的解;
  • 4) 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。

第一條特徵是絕大多數問題都可以滿足的,因爲問題的計算複雜性一般是隨着問題規模的增加而增加;
第二條特徵是應用分治法的前提它也是大多數問題可以滿足的,此特徵反映了遞歸思想的應用;
第三條特徵是關鍵,能否利用分治法完全取決於問題是否具有第三條特徵,如果具備了第一條和第二條特徵,而不具備第三條特徵,則可以考慮用貪心法或動態規劃法。
第四條特徵涉及到分治法的效率,如果各子問題是不獨立的則分治法要做許多不必要的工作,重複地解公共的子問題,此時雖然可用分治法,但一般用動態規劃法較好。


提高分治算法的途經

從所周知,直接或間接地調用自身的算法稱爲遞歸算法。用函數自身給出定義的函數稱爲遞歸函數。它優點是結構清晰,可讀性強,而且容易用數學歸納法來證明算法的正確性,因此它爲設計算法、調試程序帶來很大方便。可是遞歸算法的運行效率較低,無論是耗費的計算時間還是佔用的存儲空間都比非遞歸算法要多。

解決方法:在遞歸算法中消除遞歸調用,使其轉化爲非遞歸算法。

  • 1、採用一個用戶定義的棧來模擬系統的遞歸調用工作棧。該方法通用性強,但本質上還是遞歸,只不過人工做了本來由編譯器做的事情,優化效果不明顯。
  • 2、用遞推來實現遞歸函數。
  • 3、通過變換能將一些遞歸轉化爲尾遞歸,從而迭代求出結果。

後兩種方法在時空複雜度上均有較大改善,但其適用範圍有限。

大整數的乘法:請設計一個有效的算法,可以進行兩個n位大整數的乘法運算。設X ,Y 是兩個n位二進制數,n=2k ,求XY。

解:以每爲乘1次作爲1次基本運算,則普通乘法的時間複雜度是O(n2) .

下面考慮分治算法將X 和Y都分成相等的兩段,每段n/2 位,X的上半段(高位部分)記作A,下半段(低位部分)記作B;類似地,Y的上半段(高位部分)記作C,下半段(低位部分)記作D,那麼有:

X=A2n/2+B
Y=C2n/2+D
XY=AC2n+(AD+BC)2n2+BD

時間複雜度的遞推方程如下:
T(n)=O(1),T(n)=4T(n2)+cn,if n = 1if n > 1

W(n)=O(nlog4)=O(n2) 這個分治算法與普通乘法的時間複雜度一樣。根據代數知識,不難發現:

AD+BC=(A-B)(D-C)+AC+BD

爲了降低時間複雜度,必須減少乘法的次數
XY=AC2n+(AD+BC)2n2+BD

XY=AC2n+((AB)(DC)+AC+BD)2n2+BD

細節問題:兩個XY 的複雜度都是O(nlog3) ,但考慮到A+B ,C+D 可能得到n+1 位的結果,使問題的規模變大.於是遞推方程:
T(n)=O(1),T(n)=3T(n2)+cn,if n = 1if n > 1

解得:
W(n)=O(nlog3)=O(n1.59)

較大的改進.更快的方法?? 如果將大整數分成更多段,用更復雜的方式把它們組合起來,將有可能得到更優的算法。Strassen矩陣乘法.矩陣乘法問題

對於兩個n*n的矩陣A,B,求其乘積.傳統方法:O(n3) .A和B的乘積矩陣C中的元素C[i,j].若依此定義來計算A和B的乘積矩陣C,則每計算C的一個元素C[i][j],需要做n次乘法和n-1次加法。因此,算出矩陣C的個元素所需的計算時間爲O(n3)

算法複雜度分析

W(n)=8W(n2)+cn2W(1)=1

W(n)=O(nlog8)=O(n3) 這個分治算法與普通乘法的時間複雜度一樣.

爲了降低時間複雜度,必須減少乘法的次數。

算法複雜度分析

W(n)=7W(n2)+18(n2)2W(1)=1

由主定理得:

W(n)=O(nlog27)=O(n2.8075)
較大的改進

更快的方法??

Hopcroft和Kerr已經證明(1971),計算2個2×2矩陣的乘積,7次乘法是必要的。因此,要想進一步改進矩陣乘法的時間複雜性,就不能再基於計算2×2矩陣的7次乘法這樣的方法了。或許應當研究3×3或5×5矩陣的更好算法。

在Strassen之後又有許多算法改進了矩陣乘法的計算時間複雜性。目前最好的計算時間上界是 O(n^2.376)

是否能找到O(n^2)的算法?

僞碼如下

Strassen (N,MatrixA,MatrixB,MatrixResult)     
//splitting input Matrixes, into 4 submatrices each.
      for i <-0 to N/2
        for j <-0 to N/2
          A11[i][j] <- MatrixA[i][j]; //a矩陣塊
          A12[i][j] <- MatrixA[i][j+N/2];//b矩陣塊
          A21[i][j] <- MatrixA[i+N/2][j]; //c矩陣塊
          A22[i][j] <- MatrixA[i+N/2][j+N/2];//d矩陣塊                           
          B11[i][j] <- MatrixB[i][j]; //e 矩陣塊
          B12[i][j] <- MatrixB[i][j+N/2]; //f 矩陣塊
          B21[i][j] <- MatrixB[i+N/2][j]; //g 矩陣塊
          B22[i][j] <- MatrixB[i+N/2][j+N/2];//h矩陣塊
          //here we calculate M1..M7 matrices .
          //遞歸求M1
          HalfSize <- N/2
          AResult  <-  A11+A22
          BResult  <-  B11+B22
          Strassen( HalfSize, AResult, BResult, M1 );   //M1=(A11+A22)*(B11+B22) p5=(a+d)*(e+h)
          //遞歸求M2
          AResult  <-  A21+A22
          Strassen(HalfSize, AResult, B11, M2);          //M2=(A21+A22)B11 p3=(c+d)*e
          //遞歸求M3
          BResult  <-  B12 - B22 
          Strassen(HalfSize, A11, BResult, M3);         //M3=A11(B12-B22)  p1=a*(f-h)
          //遞歸求M4
          BResult  <-  B21 - B11
          Strassen(HalfSize, A22, BResult, M4);         //M4=A22(B21-B11)  p4=d*(g-e)
          //遞歸求M5
          AResult  <-  A11+A12
          Strassen(HalfSize, AResult, B22, M5);         //M5=(A11+A12)B22  p2=(a+b)*h
          //遞歸求M6
          AResult  <-  A21-A11
          BResult  <-  B11+B12
          Strassen( HalfSize, AResult, BResult, M6);     //M6=(A21-A11)(B11+B12) p7=(c-a)(e+f)
          //遞歸求M7
          AResult  <-  A12-A22
          BResult  <-  B21+B22
          Strassen(HalfSize, AResult, BResult, M7);      //M7=(A12-A22)(B21+B22) p6=(b-d)*(g+h)
          //計算結果子矩陣
          C11  <-  M1 + M4 - M5 + M7;
          C12  <-  M3 + M5;
          C21  <-  M2 + M4;
          C22  <-  M1 + M3 - M2 + M6;
          //at this point , we have calculated the c11..c22 matrices, and now we are going to
          //put them together and make a unit matrix which would describe our resulting Matrix.
          for i<-0 to N/2
             for j<-0 to N/2
                MatrixResult[i][j]<-C11[i][j];
                MatrixResult[i][j+N/2]<-C12[i][j];
                MatrixResult[i +N/2][j]<-C21[i][j];
                MatrixResult[i +N/2][j+N/2]<-C22[i][j];

完成測試代碼
Strassen.h

#ifndef STRASSEN_HH
#define STRASSEN_HH
template<typename T>
class Strassen_class{
public:
      void ADD(T** MatrixA, T** MatrixB, T** MatrixResult, int MatrixSize );
      void SUB(T** MatrixA, T** MatrixB, T** MatrixResult, int MatrixSize );
      void MUL( T** MatrixA, T** MatrixB, T** MatrixResult, int MatrixSize );//樸素算法實現
      void FillMatrix( T** MatrixA, T** MatrixB, int length);//A,B矩陣賦值
      void PrintMatrix(T **MatrixA,int MatrixSize);//打印矩陣
      void Strassen(int N, T **MatrixA, T **MatrixB, T **MatrixC);//Strassen算法實現
};
template<typename T>
void Strassen_class<T>::ADD(T** MatrixA, T** MatrixB, T** MatrixResult, int MatrixSize )
{
    for ( int i = 0; i < MatrixSize; i++)
    {
        for ( int j = 0; j < MatrixSize; j++)
        {
            MatrixResult[i][j] =  MatrixA[i][j] + MatrixB[i][j];
        }
    }
}
template<typename T>
void Strassen_class<T>::SUB(T** MatrixA, T** MatrixB, T** MatrixResult, int MatrixSize )
{
    for ( int i = 0; i < MatrixSize; i++)
    {
        for ( int j = 0; j < MatrixSize; j++)
        {
            MatrixResult[i][j] =  MatrixA[i][j] - MatrixB[i][j];
        }
    }
}
template<typename T>
void Strassen_class<T>::MUL( T** MatrixA, T** MatrixB, T** MatrixResult, int MatrixSize )
{
    for (int i=0;i<MatrixSize ;i++)
    {
        for (int j=0;j<MatrixSize ;j++)
        {
            MatrixResult[i][j]=0;
            for (int k=0;k<MatrixSize ;k++)
            {
                MatrixResult[i][j]=MatrixResult[i][j]+MatrixA[i][k]*MatrixB[k][j];
            }
        }
    }
}

/*
*c++使用二維數組,申請動態內存方法
*申請
*int **A;
*A = new int *[desired_array_row];
*for ( int i = 0; i < desired_array_row; i++)
*     A[i] = new int [desired_column_size];
*釋放
*for ( int i = 0; i < your_array_row; i++)
*    delete [] A[i];
*delete[] A;
*/
template<typename T>
void Strassen_class<T>::Strassen(int N, T **MatrixA, T **MatrixB, T **MatrixC)
{

    int HalfSize = N/2;
    int newSize = N/2;

    if ( N <= 64 )    //分治門檻,小於這個值時不再進行遞歸計算,而是採用常規矩陣計算方法
    {
        MUL(MatrixA,MatrixB,MatrixC,N);
    }
    else
    {
        T** A11;
        T** A12;
        T** A21;
        T** A22;

        T** B11;
        T** B12;
        T** B21;
        T** B22;

        T** C11;
        T** C12;
        T** C21;
        T** C22;

        T** M1;
        T** M2;
        T** M3;
        T** M4;
        T** M5;
        T** M6;
        T** M7;
        T** AResult;
        T** BResult;

        //making a 1 diminsional pointer based array.
        A11 = new T *[newSize];
        A12 = new T *[newSize];
        A21 = new T *[newSize];
        A22 = new T *[newSize];

        B11 = new T *[newSize];
        B12 = new T *[newSize];
        B21 = new T *[newSize];
        B22 = new T *[newSize];

        C11 = new T *[newSize];
        C12 = new T *[newSize];
        C21 = new T *[newSize];
        C22 = new T *[newSize];

        M1 = new T *[newSize];
        M2 = new T *[newSize];
        M3 = new T *[newSize];
        M4 = new T *[newSize];
        M5 = new T *[newSize];
        M6 = new T *[newSize];
        M7 = new T *[newSize];

        AResult = new T *[newSize];
        BResult = new T *[newSize];

        int newLength = newSize;

        //making that 1 diminsional pointer based array , a 2D pointer based array
        for ( int i = 0; i < newSize; i++)
        {
            A11[i] = new T[newLength];
            A12[i] = new T[newLength];
            A21[i] = new T[newLength];
            A22[i] = new T[newLength];

            B11[i] = new T[newLength];
            B12[i] = new T[newLength];
            B21[i] = new T[newLength];
            B22[i] = new T[newLength];

            C11[i] = new T[newLength];
            C12[i] = new T[newLength];
            C21[i] = new T[newLength];
            C22[i] = new T[newLength];

            M1[i] = new T[newLength];
            M2[i] = new T[newLength];
            M3[i] = new T[newLength];
            M4[i] = new T[newLength];
            M5[i] = new T[newLength];
            M6[i] = new T[newLength];
            M7[i] = new T[newLength];

            AResult[i] = new T[newLength];
            BResult[i] = new T[newLength];


        }
        //splitting input Matrixes, into 4 submatrices each.
        for (int i = 0; i < N / 2; i++)
        {
            for (int j = 0; j < N / 2; j++)
            {
                A11[i][j] = MatrixA[i][j];
                A12[i][j] = MatrixA[i][j + N / 2];
                A21[i][j] = MatrixA[i + N / 2][j];
                A22[i][j] = MatrixA[i + N / 2][j + N / 2];

                B11[i][j] = MatrixB[i][j];
                B12[i][j] = MatrixB[i][j + N / 2];
                B21[i][j] = MatrixB[i + N / 2][j];
                B22[i][j] = MatrixB[i + N / 2][j + N / 2];

            }
        }

        //here we calculate M1..M7 matrices .
        //M1[][]
        ADD( A11,A22,AResult, HalfSize);
        ADD( B11,B22,BResult, HalfSize);            //p5=(a+d)*(e+h)
        Strassen( HalfSize, AResult, BResult, M1 ); //now that we need to multiply 
                                                    //this , we use the strassen itself.
        //M2[][]
        ADD( A21,A22,AResult, HalfSize);            //M2=(A21+A22)B11,p3=(c+d)*e
        Strassen(HalfSize, AResult, B11, M2);       //Mul(AResult,B11,M2);

        //M3[][]
        SUB( B12,B22,BResult, HalfSize);            //M3=A11(B12-B22),p1=a*(f-h)
        Strassen(HalfSize, A11, BResult, M3);       //Mul(A11,BResult,M3);

        //M4[][]
        SUB( B21, B11, BResult, HalfSize);          //M4=A22(B21-B11),p4=d*(g-e)
        Strassen(HalfSize, A22, BResult, M4);       //Mul(A22,BResult,M4);

        //M5[][]
        ADD( A11, A12, AResult, HalfSize);          //M5=(A11+A12)B22 ,p2=(a+b)*h
        Strassen(HalfSize, AResult, B22, M5);       //Mul(AResult,B22,M5);


        //M6[][]
        SUB( A21, A11, AResult, HalfSize);
        ADD( B11, B12, BResult, HalfSize);            //M6=(A21-A11)(B11+B12),p7=(c-a)(e+f)
        Strassen( HalfSize, AResult, BResult, M6);    //Mul(AResult,BResult,M6);

        //M7[][]
        SUB(A12, A22, AResult, HalfSize);
        ADD(B21, B22, BResult, HalfSize);             //M7=(A12-A22)(B21+B22),p6=(b-d)*(g+h)
        Strassen(HalfSize, AResult, BResult, M7);     //Mul(AResult,BResult,M7);

        //C11 = M1 + M4 - M5 + M7;
        ADD( M1, M4, AResult, HalfSize);
        SUB( M7, M5, BResult, HalfSize);
        ADD( AResult, BResult, C11, HalfSize);

        //C12 = M3 + M5;
        ADD( M3, M5, C12, HalfSize);

        //C21 = M2 + M4;
        ADD( M2, M4, C21, HalfSize);

        //C22 = M1 + M3 - M2 + M6;
        ADD( M1, M3, AResult, HalfSize);
        SUB( M6, M2, BResult, HalfSize);
        ADD( AResult, BResult, C22, HalfSize);

        //at this point , we have calculated the c11..c22 matrices, and now we are going to
        //put them together and make a unit matrix which would describe our resulting Matrix.
        //組合小矩陣到一個大矩陣
        for (int i = 0; i < N/2 ; i++)
        {
            for (int j = 0 ; j < N/2 ; j++)
            {
                MatrixC[i][j] = C11[i][j];
                MatrixC[i][j + N / 2] = C12[i][j];
                MatrixC[i + N / 2][j] = C21[i][j];
                MatrixC[i + N / 2][j + N / 2] = C22[i][j];
            }
        }

        // 釋放矩陣內存空間
        for (int i = 0; i < newLength; i++)
        {
            delete[] A11[i];delete[] A12[i];delete[] A21[i];
            delete[] A22[i];

            delete[] B11[i];delete[] B12[i];delete[] B21[i];
            delete[] B22[i];
            delete[] C11[i];delete[] C12[i];delete[] C21[i];
            delete[] C22[i];
            delete[] M1[i];delete[] M2[i];delete[] M3[i];delete[] M4[i];
            delete[] M5[i];delete[] M6[i];delete[] M7[i];
            delete[] AResult[i];delete[] BResult[i] ;
        }
        delete[] A11;delete[] A12;delete[] A21;delete[] A22;
        delete[] B11;delete[] B12;delete[] B21;delete[] B22;
        delete[] C11;delete[] C12;delete[] C21;delete[] C22;
        delete[] M1;delete[] M2;delete[] M3;delete[] M4;delete[] M5;
        delete[] M6;delete[] M7;
        delete[] AResult;
        delete[] BResult ;

    }//end of else

}

template<typename T>
void Strassen_class<T>::FillMatrix( T** MatrixA, T** MatrixB, int length)
{
    for(int row = 0; row<length; row++)
    {
        for(int column = 0; column<length; column++)
        {

            MatrixB[row][column] = (MatrixA[row][column] = rand() %5);
            //matrix2[row][column] = rand() % 2;//ba hazfe in khat 50% afzayeshe soorat khahim dasht
        }

    }
}
template<typename T>
void Strassen_class<T>::PrintMatrix(T **MatrixA,int MatrixSize)
{
    cout<<endl;
    for(int row = 0; row<MatrixSize; row++)
    {
        for(int column = 0; column<MatrixSize; column++)
        {


            cout<<MatrixA[row][column]<<"\t";
            if ((column+1)%((MatrixSize)) == 0)
                cout<<endl;
        }

    }
    cout<<endl;
}
#endif //Strassen.h

Strassen.cpp

#include <iostream>
#include <ctime>
#include <Windows.h>
using namespace std;
#include "Strassen.h"

int main()
{
    Strassen_class<int> stra;//定義Strassen_class類對象
    int MatrixSize = 0;

    int** MatrixA;    //存放矩陣A
    int** MatrixB;    //存放矩陣B
    int** MatrixC;    //存放結果矩陣

    clock_t startTime_For_Normal_Multipilication ;
    clock_t endTime_For_Normal_Multipilication ;

    clock_t startTime_For_Strassen ;
    clock_t endTime_For_Strassen ;
    srand(time(0));

    cout<<"\n請輸入矩陣大小(必須是2的冪指數值(例如:32,64,512,..): ";
    cin>>MatrixSize;

    int N = MatrixSize;//for readiblity.

    //申請內存
    MatrixA = new int *[MatrixSize];
    MatrixB = new int *[MatrixSize];
    MatrixC = new int *[MatrixSize];

    for (int i = 0; i < MatrixSize; i++)
    {
        MatrixA[i] = new int [MatrixSize];
        MatrixB[i] = new int [MatrixSize];
        MatrixC[i] = new int [MatrixSize];
    }

    stra.FillMatrix(MatrixA,MatrixB,MatrixSize);  //矩陣賦值

  //*******************conventional multiplication test
        cout<<"樸素矩陣算法開始時鐘:  "<< (startTime_For_Normal_Multipilication = clock());

        stra.MUL(MatrixA,MatrixB,MatrixC,MatrixSize);//樸素矩陣相乘算法 T(n) = O(n^3)

        cout<<"\n樸素矩陣算法結束時鐘: "<< (endTime_For_Normal_Multipilication = clock());

        cout<<"\n矩陣運算結果... \n";
        stra.PrintMatrix(MatrixC,MatrixSize);

  //*******************Strassen multiplication test
        cout<<"\nStrassen算法開始時鐘: "<< (startTime_For_Strassen = clock());
        stra.Strassen( N, MatrixA, MatrixB, MatrixC ); //strassen矩陣相乘算法
        cout<<"\nStrassen算法結束時鐘: "<<(endTime_For_Strassen = clock());


    cout<<"\n矩陣運算結果... \n";
    stra.PrintMatrix(MatrixC,MatrixSize);

    cout<<"矩陣大小 "<<MatrixSize;
    cout<<"\n樸素矩陣算法: ";
    cout<<(endTime_For_Normal_Multipilication-startTime_For_Normal_Multipilication);
    cout<<" Clocks.."<<(endTime_For_Normal_Multipilication- \\
    startTime_For_Normal_Multipilication)/CLOCKS_PER_SEC<<" Sec";
    cout<<"\nStrassen算法:"<<(endTime_For_Strassen-startTime_For_Strassen)<<" Clocks.."
    cout<<(endTime_For_Strassen-startTime_For_Strassen)/CLOCKS_PER_SEC<<" Sec\n";
    system("Pause");
    return 0;

}

典型實例

歸併排序

將待排序序列R[0…n-1]看成是n個長度爲1的有序序列,將相鄰的有序表成對歸併,得到n/2個長度爲2的有序表;將這些有序序列再次歸併,得到n/4個長度爲4的有序序列;如此反覆進行下去,最後得到一個長度爲n的有序序列。歸併排序其實要做兩件事:“分解”——將序列每次折半劃分。“合併”——將劃分後的序列段兩兩合併後排序。

實現

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <cstdio>
using namespace std;


template <class T>
void merge(vector<T>& arr,int start ,int middle,int end)
{
    int n1 = middle - start + 1;
    int n2 = end - middle;
    vector<T> left(n1);
    vector<T> right(n2);
    int i,j,k;

    for (i = 0;i < n1; ++ i)
        left[i] = arr[start + i];
    for (j = 0;j < n2; ++ j)
        right[j] = arr[middle + j + 1];

    i = j = 0;
    k = start;
    while (i < n1 && j < n2) {
        if (left[i] < right[j])
            arr[k ++] = left[i ++];
        else
            arr[k ++] = right[j ++];
    }
    while (i < n1)
        arr[k ++] = left[i ++ ];
    while (j < n2)
        arr[k ++] = right[j ++];
}

template <class T>
void sort(vector<T>& arr,int start,int end)
{
    // getchar();
    if (start < end)
    {
        int middle = (start + end) / 2;
        sort(arr,start,middle);
        sort(arr,middle + 1,end);
        merge(arr,start,middle,end);
    }
}

int main()
{
    const int length = 20;
    vector<int> arr(length);
    for (size_t i = 0;i < arr.size(); ++ i)
        arr[i] = i;
    random_shuffle(arr.begin(),arr.end());

    copy(arr.begin(),arr.end(),ostream_iterator<int>(cout, " "));
    cout << endl;

    sort(arr,0,length - 1);

    copy(arr.begin(),arr.end(),ostream_iterator<int>(cout, " "));
    cout << endl;

    return 0;
}

整數劃分問題

將正整數n表示成一系列正整數之和:n=n1+n2++nk ,其中n1n2nk1k1 。正整數n 的這種表示稱爲正整數n 的劃分。正整數n 的不同劃分個數稱爲正整數n 的劃分數,記作p(n)

例如:正整數6有如下11種不同的劃分,所以p(6) = 11:

6;
5+1;
4+2,4+1+1;
3+3,3+2+1,3+1+1+1;
2+2+2,2+2+1+1,2+1+1+1+1;
1+1+1+1+1+1。

前面的幾個例子中,問題本身都具有比較明顯的遞歸關係,因而容易用遞歸函數直接求解。

在本例中,如果設p(n)爲正整數n的劃分數,則難以找到遞歸關係,因此考慮增加一個自變量:在正整數n的所有不同劃分中,將最大加數n1不大於m的劃分個數記作q(n,m)。可以建立q(n,m)的如下遞歸關係。

  • (1) q(n,1)=1,n >= 1;當最大加數n1不大於1時,任何正整數n只有一種劃分形式,即n = 1 + 1 + 1 + … +1.
  • (2) q(n,m) = q(n,n),m >= n; 最大加數n1實際上不能大於n。因此,q(1,m)=1。
  • (3) q(n,n)=1 + q(n,n-1); 正整數n的劃分由n1=n的劃分和n1 ≤ n-1的劃分組成。
  • (4) q(n,m)=q(n,m-1)+q(n-m,m),n > m >1;正整數n的最大加數n1不大於m的劃分由 n1 = m的劃分和n1 ≤ m-1 的劃分組成。

前面的一個例子中,問題本身都具有比較明顯的遞歸關係,因而容易用遞歸函數直接求解。

在本例中,如果設p(n)爲正整數n的劃分數,則難以找到遞歸關係,因此考慮增加一個自變量:將最大加數n1不大於m的劃分個數記作q(n,m) 。可以建立q(n,m) 的如下遞歸關係。

q(n,m)=1,q(n,m)=q(n,m),q(n,m)=1+q(n,n1),q(n,m)=q(n,m1)+q(nm,m),if n=1,m=1if n=1,m=1if n=mif n>m>1

正整數n的劃分數p(n)=q(n,n)

實現:

#include <iostream>
using namespace std;

//
int __int_partition(int n,int m)
{
    if (n < 1 || m < 1)
        return 0;
    if (n == 1 || m == 1)
        return 1;
    if (n < m)
        return __int_partition(n,n);
    if (n == m)
        return __int_partition(n,m - 1) + 1;
    return __int_partition(n,m - 1) + __int_partition(n - m,m);
}
int integer_partition(int n)
{
    return __int_partition(n,n);
}

int main()
{
    for (int i = 1; i < 7; ++ i) {
        cout << "integer_patition("
             << i
             << ") = "
             << integer_partition(i)
             << endl;
    }
    return 0;
}

階乘函數

階乘函數可遞歸地定義爲:

n!=1,n!=n(n1)!,if n=0 ()if n>0 ()

邊界條件與遞歸方程是遞歸函數的二個要素,遞歸函數只有具備了這兩個要素,才能在有限次計算後得出結果。

實現:

#include <iostream>
using namespace std;

// factorial implement by recursive
long factorial_recursive(long n)
{
    if (n == 0)
        return 1;
    return n * factorial_recursive(n-1);
}

// factorial implement by loop
long factorial_loop(long n)
{
    long result = 1;
    for (int i = n; i > 0; -- i)
        result *= i;
    return result;
}

int main()
{
    for (int i = 0; i < 10; i ++ ) {
        cout << i << "!" << " = "
             << factorial_recursive(i)
             << ","
             << factorial_loop(i)
             << endl;
    }
    return 0;
}

排列問題

設計一個遞歸算法生成n 個元素{r1,r2,,rn} 的全排列。設R={r1,r2,,rn} 是要進行排列的n個元素,Ri=R{ri} 。集合X中元素的全排列記爲perm(X)(ri)perm(X) 表示在全排列perm(X)的每一個排列前加上前綴得到的排列。R的全排列可歸納定義如下:

  • 當n=1時,perm(R)=(r),其中r是集合R中唯一的元素;
  • 當n>1時,perm(R)由(r1)perm(R1),(r2)perm(R2),…,(rn)perm(Rn)構成。
#include <iostream>
#include <vector>
#include <iterator>
using namespace std;


/* 使用遞歸實現
* 遞歸產生所有前綴是list[0:k-1],且後綴是list[k,m]的全排列的所有排列.調用算法perm(list,0,n-1)則產生list[0:n-1]的全排列
*/
template <class T>
void perm_recursion(T list[],int k,int m)
{
    // 產生list[k:m]的所有排列
    if (k == m) {
        for (int i = 0; i <= m; i ++)
            cout << list[i] << " ";
        cout << endl;
    }
    else {
    // 還有多個元素,遞歸產生排列
        for (int i = k; i <= m; ++ i) {
            swap(list[k],list[i]);
            perm_recursion(list,k+1,m);
            swap(list[k],list[i]);
        }
    }
}

// 非遞歸實現(可參照STL next_permutation源碼)
template <class T>
void perm_loop(T list[],int len)
{
    int i,j;
    vector<int> v_temp(len);

    // 初始排列
    for(i = 0; i < len ; i ++)
        v_temp[i] = i;

    while (true) {
        for (i = 0; i < len; i ++ )
            cout << list[v_temp[i]] << " ";
        cout << endl;

        // 從後向前查找,看有沒有後面的數大於前面的數的情況,若有則停在後一個數的位置。
        for(i = len - 1;i > 0 && v_temp[i] < v_temp[i-1] ; i--);
        if (i == 0)
            break;
        // 從後查到i,查找大於 v_temp[i-1]的最小的數,記入j
        for(j = len - 1 ; j > i && v_temp[j] < v_temp[i-1] ; j--);
        // 交換 v_temp[i-1] 和 v_temp[j]
        swap(v_temp[i-1],v_temp[j]);

        // 倒置v_temp[i]到v_temp[n-1]
        for(i = i,j = len - 1 ; i < j;i ++,j --) {
            swap(v_temp[i],v_temp[j]);
        }
    }
}


int main()
{
    int list[] = {0,1,2};
    cout << "permutation implement by recursion: " << endl;
    perm_recursion(list,0,2);
    cout << endl;

    cout << "permutation implement by loop: " << endl;
    perm_loop(list,3);
    cout << endl;
    return 0;
}

Hanoi塔問題

設a,b,c是3個塔座。開始時,在塔座a上有一疊共n個圓盤,這些圓盤自下而上,由大到小地疊在一起。各圓盤從小到大編號爲1,2,…,n,現要求將塔座a上的這一疊圓盤移到塔座b上,並仍按同樣順序疊置。在移動圓盤時應遵守以下移動規則:

  • 規則1:每次只能移動1個圓盤;
  • 規則2:任何時刻都不允許將較大的圓盤壓在較小的圓盤之上;
  • 規則3:在滿足移動規則1和2的前提下,可將圓盤移至a,b,c中任一塔座上。

這裏寫圖片描述

#include <iostream>
using namespace std;
void __move(char t1,char t2){
    cout << t1 << " -> " << t2 << endl;
}
// 把n個圓盤,從t1塔移至t2塔通過t3塔
void hanoi(int n,char t1,char t2,char t3){
    if (n > 0) {
        hanoi(n-1,t1,t3,t2);
        __move(t1,t2);
        hanoi(n-1,t3,t2,t1);
    }
}

int main(){
    cout << "hanoi(1,'a','b','c'): " << endl;
    hanoi(1,'a','b','c');
    cout << endl;

    cout << "hanoi(1,'a','b','c'): " << endl;
    hanoi(2,'a','b','c');
    cout << endl;

    cout << "hanoi(3,'a','b','c'): " << endl;
    hanoi(3,'a','b','c');
    cout << endl;

    return 0;
}

棋盤覆蓋

在一個2k×2k 個方格組成的棋盤中,恰有一個方格與其它方格不同,稱該方格爲一特殊方格,且稱該棋盤爲一特殊棋盤。在棋盤覆蓋問題中,要用圖示的4種不同形態的L型骨牌覆蓋給定的特殊棋盤上除特殊方格以外的所有方格,且任何2個L型骨牌不得重疊覆蓋。棋盤示例(k = 2)和四種L型骨牌示例:

這裏寫圖片描述
這裏寫圖片描述

分析
當k>0時,將2k×2k 棋盤分割爲4個2(k1)×2(k1) 子棋盤所示。
特殊方格必位於4個較小子棋盤之一中,其餘3個子棋盤中無特殊方格。爲了將這3個無特殊方格的子棋盤轉化爲特殊棋盤,可以用一個L型骨牌覆蓋這3個較小棋盤的會合處,從而將原問題轉化爲4個較小規模的棋盤覆蓋問題。遞歸地使用這種分割,直至棋盤簡化爲棋盤1×1。

這裏寫圖片描述

算法複雜度

T(k)=O(1),T(k)=4T(k1)+O(1),if k=0 if k>0 

總共要4k13 張牌.T(n)=O(4k) 漸近意義下的最優算法.

實現

#include <iostream>
#include <vector>
#include <cmath>
#include <iterator>
using namespace std;

void __chessboard_cover(vector<vector<int> >& cheb,
                        int tx,int ty,
                        int dx,int dy,
                        int size,
                        int& tile);
/* 棋盤覆蓋主函數
* cheb: 棋盤數組  dx: 特殊方格的橫座標 dy: 特殊方格的縱座標
*/
void chessboard_cover(vector<vector<int> >& cheb,int dx,int dy)
{
    int tile = 1;
    __chessboard_cover(cheb,0,0,dx,dy,cheb.size(),tile);
}
/* 棋盤覆蓋輔助函數
* cheb: 棋盤數組 tx: 起始橫座標  ty: 起始縱座標
* dx: 特殊方格的橫座標 dy: 特殊方格的橫座標 size: 棋盤大小  tile: 骨牌編號
*/
void __chessboard_cover(vector<vector<int> >& cheb,
                        int tx,int ty,
                        int dx,int dy,
                        int size,
                        int& tile)
{
    if (size == 1)
        return ;
    int t = tile ++ ; // L骨牌號
    int s = size / 2; // 分割棋盤

    // 覆蓋左上角子棋盤
    if (dx < tx + s && dy < ty + s) {
        // 特殊方格在此子棋盤中
        __chessboard_cover(cheb,tx,ty,dx,dy,s,tile);
    }
    else {
        // 此棋盤中無特殊方格,用t號骨牌覆蓋下角方格
        cheb[tx + s - 1][ty + s - 1] = t;
        // 覆蓋其餘方格
        __chessboard_cover(cheb,tx,ty,tx + s - 1, ty + s - 1,s,tile);
    }
    // 覆蓋右上角子棋盤
    if (dx >= tx + s && dy < ty + s) {
        // 特殊方格在此棋盤中
        __chessboard_cover(cheb,tx + s,ty,dx,dy,s,tile);
    }
    else {
        // 用t號L型骨牌覆蓋左下角
        cheb[tx + s][ty + s - 1] = t;
        __chessboard_cover(cheb,tx + s,ty,tx + s,ty + s - 1,s,tile);
    }

    // 覆蓋左下角子棋盤
    if (dx < tx + s && dy >= ty + s) {
        // 特殊方格在此棋盤中
        __chessboard_cover(cheb,tx,ty + s,dx,dy,s,tile);
    }
    else {
        // 用t號L型骨牌覆蓋右上角
        cheb[tx + s - 1][ty + s] = t;
        __chessboard_cover(cheb,tx,ty + s,tx + s - 1,ty + s,s,tile);
    }

    // 覆蓋右下角子棋盤
    if (dx >= tx + s && dy >= ty + s) {
        // 特殊方格在此棋盤中
        __chessboard_cover(cheb,tx + s,ty + s,dx,dy,s,tile);
    }
    else {
        // 用t號L型骨牌覆蓋左上角
        cheb[tx + s][ty + s] = t;
        __chessboard_cover(cheb,tx + s,ty + s,tx + s,ty + s,s,tile);
    }
}
int main()
{
    int k = 2;
    int size = pow (2,k);
    vector<vector<int> > cheb(size);
    for (size_t i=  0 ;i < cheb.size(); ++i) {
        cheb[i].resize(size);
    }

    for (int i = 0; i < size; ++ i) {
        for (int j = 0;j < size; ++ j) {
            int dx = i;
            int dy = j;
            cout << "dx = " << dx << " , dy = " << dy << endl;
            cheb[dx][dy] = 0;
            chessboard_cover(cheb,dx,dy);

            for (size_t i = 0;i < cheb.size(); ++ i) {
                copy(cheb[i].begin(),cheb[i].end(),ostream_iterator<int>(cout," "));
                cout << endl;
            }
            cout << endl;
        }
    }
    return 0;
}

循環賽日程表

題目表述:

設有n=2k 個運動員要進行網球循環賽,設計一個滿足以下要求的比賽日程表:

  • (1)每個選手必須與其他n1 個選手各賽一次;
  • (2)每個選手一天只能賽一次;
  • (3)循環賽一共進行n1 天。

按分治策略,將所有的選手分爲兩半,n個選手的比賽日程表就可以通過爲n/2個選手設計的比賽日程表來決定。遞歸地用對選手進行分割,直到只剩下2個選手時,比賽日程表的制定就變得很簡單。這時只要讓這2個選手進行比賽就可以了。
這裏寫圖片描述
實現

#include <iostream>
#include <vector>
#include <cmath>
#include <iterator>
#include <iomanip>
using namespace std;
void __table(vector<vector<int> >& arr,int start,int end);
void round_match_table(vector<vector<int> >& arr)
{
    int count = arr.size();
    for (int i = 0;i < count;++ i) {
        arr[0][i] = i + 1;
    }
    __table(arr,0,count-1);
}
void __table(vector<vector<int> >& arr,int start,int end)
{
    if (end - start + 1 == 2) {
        arr[1][start] = arr[0][end];
        arr[1][end] = arr[0][start];
        return ;
    }
    int half = (end - start + 1) / 2;
    // 左上角
    __table(arr,start,start + half -1 );
    // 右上角
    __table(arr,start + half,end);
    // 左下角
    for (int i = 0;i < half; ++ i) {
        for (int j = start; j <= end; ++ j) {
            arr[i + half][j-half] = arr[i][j];
        }
    }
    // 右下角(其實左下角和右下角可以在上一個循環中實現的,
    // 但是爲了算法結構清晰,因此分爲兩個循環)
    for (int i = 0;i < half; ++ i) {
        for (int j = start; j < end; ++j) {
            arr[i + half][j + half] = arr[i][j];
        }
    }
}
int main()
{
    int k = 4;
    int size = pow(2,k);
    vector<vector<int> > arr(size);
    for (int i = 0; i < size; ++ i) {
        arr[i].resize(size);
    }

    round_match_table(arr);

    for (int i = 0;i < size; ++ i) {
        for (int j = 0;j < size; ++ j) {
            cout << std::setw(3) << arr[i][j];
        }
        cout << endl;
    }
    return 0;
}

線性時間選擇

給定線性序集中n個元素和一個整數k,1kn ,要求找出這n個元素中第k小的元素。僞碼描述如下:

// 在數組a的p到r區間內找到第k小的元素
template<class Type>
Type RandomizedSelect(Type a[],int p,int r,int k){
 if (p == r)  
   return a[p]; // 如果p,r相等,第n小都是a[p]
                // 數組a[p:r]被隨機分成兩個部分,a[p:i]和a[i+1:r],
               // 使得a[p:i]中的元素都小於a[i+1:r]中的元素。
 int i = RandomizedPartition(a,p,r);
 j = i - p + 1;
 if (k <= j)
    return RandomizedSelect(a,p,i,k);
 else 
    return RandomizedSelect(a,i+1,r,k-j);

}

在最壞情況下,算法randomizedSelect 需要O(n2) 計算時間(在找最小元素的時候,總在最大元素處劃分),但可以證明,算法randomizedSelect 可以在O(n)平均時間內找出n個輸入元素中的第k小元素。

如果能在線性時間內找到一個劃分基準,使得按這個基準所劃分出的2個子數組的長度都至多爲原數組長度的ε倍(0<ε<1是某個正常數),那麼就可以在最壞情況下用O(n)時間完成選擇任務。

例如,若ε=910 ,算法遞歸調用所產生的子數組的長度至少縮短110 。所以,在最壞情況下,算法所需的計算時間T(n)滿足遞歸式T(n)T(9n10)+O(n) 。由此可得T(n)=O(n)

步驟

  • 第一步,將n個輸入元素劃分成én/5ù個組,每組5個元素,只可能有一個組不是5個元素。用任意一種排序算法,將每組中的元素排好序,並取出每組的中位數,共én/5ù個。
  • 第二步,遞歸調用select來找出這én/5ù個元素的中位數。如果én/5ù是偶數,就找它的2箇中位數中較大的一個。以這個元素作爲劃分基準。

分析
這裏寫圖片描述

僞碼描述如下:

Type Select(Type a[], int p, int r, int k)
{
if (r - p < 75) {
        // 問題的規模足夠小,用某個簡單排序算法對數組a[p:r]排序;
        return a[p + k - 1];  
}
for (int i = 0; i <= ( r - p - 4 ) / 5 ; i ++ ) {
         將a[p + 5 * i]至a[p + 5 * i + 4]的第3小元素與a[p+i]交換位置;

}
    // 找中位數的中位數,r - p - 4即上面所說的n - 5
Type x = Select(a, p, p + (r - p - 4 ) / 5, (r - p - 4) / 10);

// 數據n根據x劃分開來

int i = Partition(a,p,r,x); 
j = i - p + 1;
if (k <= j) 
  return Select(a,p,i,k);
else 
   return Select(a,i+1,r,k-j);
}

算法複雜度分析

T(n)C1,T(n)C2n+T(n5)+T(3n4),n <75n >= 75

解得時間複雜度是W(n)=O(n) 。上述算法將每一組的大小定爲5,並選取75作爲是否作遞歸調用的分界點。這2點保證了T(n) 的遞歸式中2個自變量之和n5+3n4=19n20=εn0<ε<1 。這是使W(n)=O(n) 的關鍵之處。當然,除了5和75之外,還有其他選擇。

實現

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

/* 線性時間查找
* arr: 數據存儲數組 start:開始查找點 end: 結束查找點 n: 查找第n小(n = 1,2,3,...,end-start+1)
*/
template <class T>
T linear_time_select(vector<T>& arr,int start,int end,int n)
{
    if (end - start < 75) {
        sort (arr.begin() + start,arr.begin() + end + 1);
        return arr[start + n - 1];
    }

    for (int i = 0;i < (end - start - 4) / 5; ++ i) {
        sort (arr.begin() + start + 5 * i,arr.begin() + start + 5 * i + 5);
        swap (*(arr.begin() + start + 5 * i + 2),*(arr.begin() + start + i));
    }
    // 找到中位數的中位數
    T median = linear_time_select(arr,start,start + (end-start-4)/5-1,(end-start-4)/10+1);

    // 數據 arr 根據 median 劃分開來
    int middle = __partition_by_median(arr,start,end,median);
    int distance = middle-start+1; // 中位數的位置與start的距離
    if (n <= distance)
        return linear_time_select(arr,start,middle,n);// 在前半截
    else
        return linear_time_select(arr,middle + 1,end,n - distance);// 在後半截
}

// 將arr按照值median劃分開來,並返回界限的位置
template <class T>
int __partition_by_median(vector<T> &arr,int start,int end,T median)
{
    while (true) {
        while (true) {
            if (start == end)
                return start;
            else if (arr[start] < median)
                ++ start;
            else
                break;
        }
        while (true) {
            if (start == end)
                return end;
            else if (arr[end] > median) {
                -- end;
            }
            else
                break;
        }
        swap(arr[start],arr[end]);
    }
}
int main()
{
    vector<int> arr;
    const int c = 2000;
    for (int i = 0;i < c; ++ i) {
        arr.push_back(i);
    }
    // 隨機排列
    random_shuffle(arr.begin(),arr.end());

    for (int i = 1; i < c+1; ++ i) {
        cout << "find the " << i << " element,position is "
            << linear_time_select(arr,0,c-1,i) << endl;
    }
    return 0;
}

參考資料

  1. Donald E.Knuth 著,蘇運霖 譯,《計算機程序設計藝術,第1卷基本算法》,國防工業出版社,2002年
  2. Donald E.Knuth 著,蘇運霖 譯,《計算機程序設計藝術,第2卷半數值算法》,國防工業出版社,2002年
  3. Donald E.Knuth 著,蘇運霖 譯,《計算機程序設計藝術,第3卷排序與查找》,國防工業出版社,2002年
  4. Thomas H. Cormen, Charles E.Leiserson, etc., Introduction to Algorithms(3rd edition), McGraw-Hill Book Company,2009
  5. Jon Kleinberg, Ėva Tardos, Algorithm Design, Addison Wesley, 2005.
  6. Sartaj Sahni ,《數據結構算法與應用:C++語言描述》 ,汪詩林等譯,機械工業出版社,2000.
  7. 屈婉玲,劉田,張立昂,王捍貧,算法設計與分析,清華大學出版社,2011年版,2013年重印.
  8. 張銘,趙海燕,王騰蛟,《數據結構與算法實驗教程》,高等教育出版社,2011年 1月

關於程序算法藝術與實踐更多討論與交流,敬請關注本博客和新浪微博songzi_tea.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章