【數據結構】通俗易懂的快速排序遞歸及非遞歸(Hoare版本、挖坑法、前後指針)C++完整代碼

快速排序是Hoare於1962年提出的一種二叉樹結構的交換排序方法。

其基本思想爲:任取待排序元素序列中 的某元素作爲基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小於基準值,右子序列中所有元素均大於基準值,然後最左右子序列重複該過程,直到所有元素都排列在相應位置上爲止。

如何尋找基準值?

一般情況下會選擇排序序列的最後一個元素作爲基準值,但是如果待排序列的最後一個元素是最值或者接近於最值的情況下,會使得排序後的左右序列元素個數相差過大,降低了效率,所以我們一般採用三數取中法:找出序列的開始節點、結束節點以及序列的中的節點取中間值作爲基準值。

將區間按照基準值劃分爲左右兩半部分的常見方式有:

hoare版本 :

1、首先設定基準值

假設我們找到了基準值是最後一個值,此時我們設定其位置爲 pivot ,同時設定兩個指針 left 、right 分別指向待排序列的左右兩端;

2、然後開始移動 left(++) 和 right(--)

當 left 所指向的數字比 pivot 所指向的數字大時,停止移動

當 right 所指向的數字比 pivot 所指向的數字小時,停止移動

當 left 和 right 都停止移動後,交換兩個指針所指向的數字;

 3、繼續移動,當 left 和 right 想遇時停止移動,這裏我們假設 left 先移動,那麼兩者的可以相遇的情況一定是 左右都已經排好了,並且此時 left 和 right 所指向的數字一定比 pvoit 所指向的數字大!!!

此時只需要將,left 和 right 所指向的數組和 pvoit 所指向的數字進行交換即可;

4、此時以及成功將待排序列分爲左右兩部分 (以 6 爲中心),之後只需要對左右序列進行以上同的工作就可以了;

用遞歸實現是非常簡單的,主要是要完成一次排序後,不斷對左右生於的子序列進行遞歸即可。

那麼,非遞歸又怎麼辦?其實也很簡單,利用棧(stack)的結構,來模擬進行遞歸就可以了,函數的遞歸調用本來也就是在函數調用棧中進行的~

代碼(C++)如下所示:

template <class T> 
void Swap(T* a, T* b)
{
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}
int partition(int* data, int left, int right)
{	
	int pivot = right;
	while (left < right) {
		while (left < right && data[left] <= data[pivot])
			++left;
		while (left < right && data[right] >= data[pivot])
			--right;
		Swap(&data[left], &data[right]);
	}
	Swap(&data[left], &data[pivot]);
	return pivot;
}

//遞歸
void quickSortRecusive(int* data, int left, int right){
	if (data == NULL || left < 0 || right <= 0 || left > right)
		return;
	int pivot = partition(data,left,right);
	if (pivot > left)
		quickSortRecusive(data, left, pivot - 1);
	if (pivot < right)
		quickSortRecusive(data, pivot + 1, right);
	
}

//非遞歸實現
void quickSortNorecusive(int* data, int left, int right)
{
	if (data == NULL || left < 0 || right <= 0 || left > right)
		return;
	stack<int> s;
	int l, r;
	s.push(right);
	s.push(left);
	while (!s.empty()) {
		l = s.top();
		s.pop();
		r = s.top();
		s.pop();
		if (l < r) {
			int povit = partition(data, l, r);
			if (povit > l) {
				s.push(povit - 1);
				s.push(l);
			}
			if (r > povit) {
				s.push(r);
				s.push(povit + 1);
			}
		}
	}

}

挖坑法  :

挖坑法顧名思義,我們需要把選擇的基準值的位置固定下來,就像於是挖了一個坑。

1、首先需要定一個指針 index 指向選定的基準值而且記住這個值 pivot,並且需要定義兩個指針 left 和 right 分別指向 代拍序列的首段和尾端;

2、移動指針及判斷,假設我們打算先移動 right,首先要判斷 right 所指向的值 和 index 所指向的 pivot 相比較

 如果 right 的值小於 index 的值,那就把right 所指向的值,填到 left 中,並且將 index 指向right,left 向右移動;

3、然後比較 left 和 index 所指向的值:

如果 left 的值 大於 index 的值,就將 left 的值 填入 indx 中,nidex 指向 left,right 左移;

4、繼續移動,當 left 和 right 相遇的時候,只需要將開始記錄的 Pivot 的值,填入到當前 index 和 left right 共同指向的位置即可;

代碼(C++)如下:

int partitionW(int* arr,int left,int right) {
	int pivot = arr[left];
	int index = left;
	while (left < right) {
		while (right != index) {
			if (arr[right] < pivot) {
				arr[index] = arr[right];
				index = right;
				++left;
			}
			else
				--right;
		}
		while (left != index) {
			if (arr[left] > pivot) {
				arr[index] = arr[left];
				index = left;
				--right;
			}
			else
				++left;
		}
	}
	arr[index] = pivot;
	return index;
}
//遞歸
void quickSortRecusiveW(int* data,int left,int right) {
	if (data == NULL || left < 0 || right <= 0 || left >right)
		return;
	int pos = partitionW(data, left, right);
	//左右
	quickSortRecusiveW(data, left, pos - 1);
	quickSortRecusiveW(data, pos + 1, right);
}
//非遞歸
void quickSortNorecusiveW(int* data, int left, int right) {
	if (data == NULL || left < 0 || right <= 0 || left > right)
		return;
	stack<int> s;
	int l, r;
	s.push(right);
	s.push(left);
	while (!s.empty()) {
		l = s.top();
		s.pop();
		r = s.top();
		s.pop();
		if (l < r) {
			int pos = partitionW(data, l, r);
			if (l < pos) {
				s.push(pos - 1);
				s.push(l);
			}
			if (r > pos) {
				s.push(r);
				s.push(pos + 1);
			}
		}
	}
}

前後指針版本:

1、首先我們選擇基準值設爲 piovt ,並且設定兩個指針 left 和 right 分別指向待排序列的初始位置和結尾位置。

2、首先移動 right 指針,如果 right 所指向的值小於 piovt ,則停止移動。

然後移動 left 指針,如果 left 所指向的值大於 piovt,則停止移動;

3、交換 left 的值和 right 的值;

4、繼續上面的動作,直到 left 和 right 指向同一個值,此時只需要將 pivot 的值和這個交換即可;

int partitionZ(int* arr, int left, int right) {
	int index = left;
	int pivot = arr[left];
	while (left < right) {
		while (left < right) {
			if (arr[right] < pivot)
				break;
			--right;
		}
		while (left < right) {
			if (arr[left] > pivot)
				break;
			++left;
		}
		if(left < right)
			swap(arr[left], arr[right]);
	}
	swap(arr[left], arr[index]);
	return left;
}
//遞歸
void quickSortRecusiveZ(int* arr, int left, int right) {
	if (arr == NULL || left < 0 || right <= 0 || left > right)
		return;
	int pos = partitionZ(arr, left, right);

	if(left < pos)
		quickSortRecusiveZ(arr, left, pos - 1);
	if(right > pos)
		quickSortRecusiveZ(arr, pos + 1, right);
}
//非遞歸
void quickSortNorecusiveZ(int* arr, int left, int right) {
	if (arr == NULL || left < 0 || right <= 0 || left > right)
		return;
	stack<int> s;
	s.push(right);
	s.push(left);
	int l, r;
	while (!s.empty()) {
		l = s.top();
		s.pop();
		r = s.top();
		s.pop();
		if(l < r){
			int pos = partitionZ(arr, l, r);
			if (l < pos) {
				s.push(pos - 1 );
				s.push(l);
			}
			if (r > pos) {
				s.push(r);
				s.push(l + 1);
			}
		}
	}
}

 

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