初學算法-快速排序與線性時間選擇(Deterministic Selection)的C++實現

    快速排序算法其實只作了兩件事:尋找分割點(pivot)和交換數據。html

    所謂尋找分割點,既找到一個預計會在中間位置附近的點,固然儘可能越接近中點越好。
ios

    所謂交換數據,就是把比這個分割點小的數據,最終都放在分割點左邊,比它大的都放在右邊。算法

    

    設要排序的數組是A[left]……A[right],首先任意選取一個數據(通常算法:使用隨機數選取一個區間內的數。 文藝算法:取A[left]、A[right]和A[rand()]的中值。 二筆算法:選用數組的第一個數)做爲關鍵數據,而後將全部比它小的數都放到它前面,全部比它大的數都放到它後面,這個過程稱爲一趟快速排序。值得注意的是,快速排序不是一種穩定的排序算法,也就是說,多個相同的值的相對位置也許會在算法結束時產生變更。數組

快速排序的具體算法是:ui

1)設置兩個變量i、j,排序開始的時候:i=left,j=left+1;spa

2)取關鍵數據和A[left]交換,賦值給key,即key=A[left];code

3)從j開始向後搜索,即由前開始向後搜索(j++),找到第一個小於key的A[j],將A[++i]和A[j]互換。orm

4)重複第3步,直到 j>right,此時循環結束htm

5)此時令 int q=i;在A[left]...A[q-1]和A[q+1]...A[right]上重複1-4過程直到遞歸結束。
排序

    

    這樣,咱們就能夠將它兌現爲代碼:

/**
 * The Quick Sort Algorithm by C++
 * Average Time Cost: nlogn
 * Author: Zheng Chen / Arc001
 * Copyright 2015 Xi'an University of Posts & Telecommunications
 */
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <fstream>
using namespace std;
long long ans = 0;

void swap(int &a, int &b)
{
    int c = a;
    a = b;
    b = c;
}

int partition(int A[],int l,int r)
{
    int t = rand()%(r-l);
	int x = A[l+t];
	int i = l;
	int j = l+1;

	swap(A[l+t],A[l]);

	for(;j<=r;j++){
		if(A[j]<=x){
			++i;
			swap(A[i],A[j]);
		}
	}

	swap(A[i],A[l]);
	return i;
}


void Quick_Sort(int A[],int l,int r)
{
    if(l<r){
        int q = partition(A,l,r);
        Quick_Sort(A,l,q-1);
        Quick_Sort(A,q+1,r);
    }
}

int main()
{
    /*int A[] = {7,6,5,4,3,2,1};
    Quick_Sort(A,0,6);
    for(int i=0;i<7;i++)
    	cout<<A[i]<<' ';
    */
    fstream in;
    in.open("QuickSort.txt");
    int *A = new int[10000];
    int i = 0;
    for(i=0;i<10000;i++)
        in>>A[i];
    Quick_Sort(A,0,9999);
    for(i=0;i<10;i++)
        cout<<A[i]<<' ';
    return 0;
}


    如今咱們來看一個問題:如何找出數組A中的第 k 小的元素? (1<=k<=n)

    在筆者看來,至少有如下三種方法:

     好比咱們能夠分爲兩種狀況:k>n/2時,問題化爲找到第 n-k 大的元素。咱們能夠構造一個長度爲 n-k 的數組,而後維持這個數組的單調性。這樣每個元素均可以進來數組「打擂」,找到合適的位置。到了最後咱們取數組的首元素或者末元素(具體要看聰明的你選用遞增仍是遞減),就是答案了。

    當k<=n/2時,也是一樣的道理。

    很容易看到,這種算法的時間複雜度在O(n^2),實在沒法使人滿意。

    可是,Can we do better?

    是的,咱們能夠經過維持一個堆來加速,因爲堆的優秀的特性,咱們能夠把時間複雜度下降到O(nlogn)

    咱們還能夠先將這些元素排序,再取出A[k-1]便可,時間複雜度也是O(nlogn)。

    仍是那個問題,Can we do better?

    能夠。

    還記得快速排序的算法嗎?咱們進行一次partition操做後,咱們的分割點(pivot)元素必定「歸位」了。咱們再比較分割點元素和待查找元素的大小,就能夠捨去左邊或右邊部分,只看剩下那部分。能夠證實,這種算法的平均時間複雜度爲Θ(n)。

    咱們能夠很容易的將其兌現爲代碼:

/**
 * Find out the n th smallest number in an array.
 * Coursera : Algorithms: Design and Analysis, Part 1 by Tim Roughgarden
 * Average Time Cost : θ(n)
 * Worst Time Cost: O(n^2) when every time the smallest number was chosen.
 *
 * Author: Zheng Chen / Arclabs001
 * Copyright 2015 Xi'an University of Posts & Telecommunications
 */
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;

void swap(int &a, int &b)
{
    int c = a;
    a = b;
    b = c;
}

int partition(int A[],int l,int r)
{
    int t = rand()%(r-l);
    int x = A[l+t];
    int i = l;
    int j = l+1;

    swap(A[l+t],A[l]);

    for(;j<=r;j++){
	if(A[j]<=x){
	    ++i;
	    swap(A[i],A[j]);
	}
    }

    swap(A[i],A[l]);
    return i;
}

int Quick_Sort(int A[],int l,int r, int target)
{
    if(target > r)
        return -1;
    if(l<r){
        int q = partition(A,l,r);
        if(q==target)
        	return A[q];
        else if(q>target)
        	return Quick_Sort(A,l,q-1,target);
        else
        	return Quick_Sort(A,q+1,r,target);
    }
    else return A[l];
}
int main()
{
    int A[] = {3,4,6,1,5,9,2,8,7};
    int n;
    for(int i=0;i<9;i++)
        cout<<A[i]<<' ';
    cout<<endl;
    cin>>n;

    cout<<"The "<<n<<"th largest number in the array is: "<<Quick_Sort(A,0,8,n-1);
    return 0;
}

    

    進階篇:

    可是,這種實現也有缺點:可能咱們就是點背,每次選的元素,不是待排序元素的最小值、就是最大值。這樣咱們每次只能排除一個元素,而每次操做的代價都是O(n),所以算法的最壞時間複雜度可能達到O(n^2)。

    仍是那個問題,Can we do better?

    Yes.

    咱們若是能讓分割點在在數組中至少爲 2n/3 大,且不大於 n/3 個元素(就是排序後的位置應該在中間的1/3),這樣每次咱們至少能夠排除 n/3 個元素。T(n) <= T(2n/3) + O(n),解得 T(n) = O(n)。

    那麼,咱們如何作到這點呢?

    這裏有一個解法:

        1.咱們準備 upper(n/5) 個長度爲5的數組,把A中全部元素「裝進去」。

        2.使用低級排序把它們分別排序,因爲每次只有5個元素,所以低級排序更快。而後取每組第三個元素(中間元素)放到另外一個數組Sub中。

        3.對長度爲n/5的Sub數組,調用主算法查找第n/10大的數。

    

    能夠證實,儘管可能看起來有些複雜,可是每次確實只須要O(n)的時間代價便可查找到適合的分割點、並至少能捨棄 n/3 個必定不符合條件的元素,達到咱們對時間複雜度的需求。

    固然不能忘了代碼實現:

/**
 * Deterministic Selection Algorithm in C++
 * Below is the pseudo-code
 * Coursera : Algorithms: Design and Analysis, Part 1 by Tim Roughgarden
 * Time cost: O(n)
 * Author: Zheng Chen / Arclabs001
 * Copyright 2015 Xi'an University of Posts & Telecommunications
 */
#include <iostream>

using namespace std;

int Quick_Sort(int A[],int l,int r, int target);

void swap(int &a, int &b)
{
    int c = a;
    a = b;
    b = c;
}

void insersion_sort(int A[] , int len)
{
	for(int j = 1; j<len; j++){
		int i = j -1;
		int key = A[j];
		while(i>0 && A[i] > key){
			A[i+1] = A[i];
			i--;
		}
		A[i+1] = key;
	}
}

int ChoosePivot(int A[], int l, int r)
{
	int size = r-l+1;
	int sub_num = size/5 ;
	int i, j, k=0;
	int tmp[sub_num][5];
	int Sub[sub_num];

	for(i=0; i<sub_num; i++){
		for(j=0; i<5; j++)
			tmp[i][j] = A[k++];
		Sub[i] = Quick_Sort(tmp[i], 0, 4, 2);
	}

	return Quick_Sort(Sub , 0, sub_num-1, sub_num/2);
}

int partition(int A[],int l,int r,int M)
{
	int x = A[M];
	int i = l;
	int j = l+1;

	swap(A[M],A[l]);

	for(;j<=r;j++){
		if(A[j]<=x){
			++i;
			swap(A[i],A[j]);
		}
	}

	swap(A[i],A[l]);
	return i;
}

int Quick_Sort(int A[],int l,int r, int target)
{
    if(target > r)
        return -1;
    if(l<r){
    	if( r-l+1 <10){
			int tmp[r-l+1];
			for(int i=l;i<r-l+1;i++)
				tmp[i] = A[i];
			insersion_sort( tmp , r-l+1 );
			return tmp[target];
		}

		int M = ChoosePivot(A,l,r);

        int q = partition(A,l,r,M);

        if(q==target)
        	return A[q];
        else if(q>target)
        	return Quick_Sort(A,l,q-1,target);
        else
        	return Quick_Sort(A,q+1,r,target);
    }
    else return A[l];
}

int main()
{
    int A[] = {3,4,6,1,5,9,2,8,7};
    int n;
    for(int i=0;i<9;i++)
        cout<<A[i]<<' ';
    cout<<endl;
    cin>>n;

    cout<<"The "<<n<<"th largest number in the array is: "<<Quick_Sort(A,0,8,n-1);
    return 0;
}
/**
 *  select(L,k)
    {
    if (L has 10 or fewer elements)
    {
        sort L
        return the element in the kth position
    }

    partition L into subsets S[i] of five elements each
        (there will be n/5 subsets total).

    for (i = 1 to n/5) do
        x[i] = select(S[i],3)

    M = select({x[i]}, n/10)

    partition L into L1<M, L2=M, L3>M
    if (k <= length(L1))
        return select(L1,k)
    else if (k > length(L1)+length(L2))
        return select(L3,k-length(L1)-length(L2))
    else return M
    }
 */


     本文參考資料:

         [1].Coursera : Algorithms: Design and Analysis, Part 1 by Tim Roughgarden   (斯坦福大學算法課)

         [2].《Introduction to Algorithms》 by  Thomas H. Cormen,Charles E. Leiserson,Ronald L. Rivest,Clifford Stein. Chapter 7, Quick Sort and Chapter 9, Medians and Order Statics.

        [3].ICS 161 : Design and Analysis of Algorithms. Lecture notes for Jan 30th, 1996.  http://www.ics.uci.edu/~eppstein/161/960130.html


        若是您以爲本文對您有些許幫助,歡迎收藏和轉發本文!

相關文章
相關標籤/搜索