1. 程式人生 > >三種快速排序演算法的實現(遞迴演算法、非遞迴演算法、三路劃分快速排序)

三種快速排序演算法的實現(遞迴演算法、非遞迴演算法、三路劃分快速排序)

快速排序的三個步驟:

1、分解:將陣列A[l...r]劃分成兩個(可能空)子陣列A[l...p-1]和A[p+1...r],使得A[l...p-1]中的每個元素都小於等於A(p),而且,小於等於A[p+1...r]中的元素。下標p也在這個劃分過程中計算。

2、解決:通過遞迴呼叫快速排序,對陣列A[l...p-1]和A[p+1...r]排序。

3、合併:因為兩個子陣列時就地排序,將它們的合併並不需要操作,整個陣列A[l..r]已經排序。

1.快速排序的基礎實現:

QUICKSORT(A, l, r)

if l < r

   then q = PARTION(A, l, r)

        QUICKSORT(A, l, p-1)

        QUICKSORT(A, p+1, r)

兩路PARTION演算法主要思想:

move from left to find an element that is not less

move from right to find an element that is not greater

stop if pointers have crossed

exchange

實現程式碼:

int partition(double* a, int left, int right)
{
	double x = a[right];
	int i = left-1, j = right;
	for (;;)
	{
		while(a[++i] < x) { }
		while(a[--j] > x) { if(j==left) break;}
		if(i < j) 
			swap(a[i], a[j]);
		else break;
	}
	swap(a[i],a[right]);
	return i;
}

void quickSort1(double* a, int left, int right)
{
	if (left<right)
	{
		int p = partition(a, left, right);

		quickSort1(a, left, p-1);
		quickSort1(a, p+1, right);
	}
}

2.非遞迴演算法:其實就是手動利用棧來儲存每次分塊快排的起始點,棧非空時迴圈獲取中軸入棧。

實現程式碼:

void quickSort2(double* a, int left, int right)
{
	stack<int> t;
	if(left<right)
	{
		int p = partition(a, left, right);

		if (p-1>left)
		{
			t.push(left);
			t.push(p-1);
		}
		if (p+1<right)
		{
			t.push(p+1);
			t.push(right);
		}

		while(!t.empty())
		{
			int r = t.top();
			t.pop();
			int l = t.top();
			t.pop();

			p = partition(a, l, r);

			if (p-1>l)
			{
				t.push(l);
				t.push(p-1);
			}
			if (p+1<r)
			{
				t.push(p+1);
				t.push(r);
			}

		}
	}
}

3.三路劃分快速排序演算法:


實現程式碼:

void quickSort3Way(double a[], int left, int right)
{
	if(left < right)
	{
		double x = a[right];
		int i = left-1, j = right, p = left-1, q = right;
		for (;;)
		{
			while (a[++i] < x) {}
			while (a[--j] > x) {if(j==left) break;}
			if(i < j)
			{
				swap(a[i], a[j]);
				if (a[i] == x) {p++; swap(a[p], a[i]);}
				if (a[j] == x) {q--; swap(a[q], a[j]);}
			}
			else break;
		}
		swap(a[i], a[right]); j = i-1; i=i+1;
		for (int k=left; k<=p; k++, j--) swap(a[k], a[j]);
		for (int k=right-1; k>=q; k--, i++) swap(a[i], a[k]);

		quickSort3Way(a, left, j);
		quickSort3Way(a, i, right);
	}
}

4.測試程式碼:

#include <iostream>
#include <stack>
#include <ctime>
using namespace std;

// 產生(a,b)範圍內的num個隨機數
double* CreateRand(double a, double b, int num)
{
	double *c;
	c = new double[num];
	srand((unsigned int)time(NULL));
	for (int i=0; i<num; i++)
		c[i] = (b-a)*(double)rand()/RAND_MAX + a;
	return c;
}

// 兩路劃分,獲取中軸,軸左邊數小於軸,軸右邊數大於軸
double partition(double* a, int left, int right)
{
	...
}

// 1.遞迴快速排序,利用兩路劃分
void quickSort1(double* a, int left, int right)
{
	...
}

// 2.非遞迴快速排序,手動利用棧來儲存每次分塊快排的起始點,棧非空時迴圈獲取中軸入棧
void quickSort2(double* a, int left, int right)
{
	...
}

// 3.利用三路劃分實現遞迴快速排序
void quickSort3Way(double a[], int left, int right)
{
	...
}

void main()
{
	double *a, *b, *c;
	int k=10000000;
	time_t start,end;

	a = CreateRand(0,1,k);
	b = CreateRand(0,1,k);
	c = CreateRand(0,1,k);

	start = clock();
	quickSort1(a,0,k-1);
	end = clock();
	cout<<"1.recursive "<<1.0*(end-start)/CLOCKS_PER_SEC<<" seconds"<<endl;

	start = clock();
	quickSort2(b,0,k-1);
	end = clock();
	cout<<"2.non-recursive "<<1.0*(end-start)/CLOCKS_PER_SEC<<" seconds"<<endl;

	start = clock();
	quickSort3Way(c,0,k-1);
	end = clock();
	cout<<"3.3 way "<<1.0*(end-start)/CLOCKS_PER_SEC<<" seconds"<<endl;

	cout<<endl;
	system("pause");
}

result:

1.recursive 1.951 seconds

2.non-recursive 2.224 seconds

3.3 way 1.677 seconds

結果可以看出非遞迴演算法由於需要手動進行演算法過程中的變數儲存,執行效率低於遞迴演算法;3路劃分演算法利用少量多餘的交換減少了快排的複雜度,執行效率高於傳統2路快排演算法。