快速排序的递归解法(C++实现)

快速的排序在这里讨论两种常规解法和一种考虑数组中有相同值情况时的简便解法。
首先来看常规解法一:
参考快速排序—(面试碰到过好几次)
如下图所示,假设要给由4个元素7,13,5,9组成的数组进行排序。最开始的基准数据为数组第一个元素7。首先用一个临时变量tmp来存储基准数据,然后分别从数组的两端扫描整个数组,设两个指示标志:L指向左边的起始位置,R指向右边的末尾位置。
在这里插入图片描述
首先,从末尾位置开始,如果扫描到的值大于基准数据就让R减一,即往前移动一个位置。
赋值之前
如果发现有元素比该基准数据的值小,比如下一个数5比7小,就将R位置的值赋给L位置,结果如下:
赋值之后
赋值结束之后,转为从前往后扫描,如果扫描到的值小于等于基准数据,就让L加1,即往后移动一个位置。如果扫描到的值大于基准数据,将L位置的元素赋值给R位置的元素,结果如下:
在这里插入图片描述
接着转为从后往前扫描,发现L=R,结束循环。
在这里插入图片描述
此时L或R的下标就是基准数据7。
在这里插入图片描述
上述演示的是只有四个元素数组的排序过程,如果有更多元素的话,过程如下:

  1. 先从队尾开始扫描且当L<R时,如果a[R] > tmp,则R–,但如果a[R] < tmp,则将a[R]的值赋给a[L],即a[R]= a[L],同时要转换数组扫描的方式,即需要从队首开始向队尾扫描了。
  2. 先从队首开始扫描且当L<R时,如果a[L] < tmp,则L++,但如果a[L] > tmp,则将a[L]的值赋给a[R],即a[L]= a[R],同时要转换数组扫描的方式,即需要从队尾开始向队首扫描了。
  3. 不断重复1和2,知道L=R,L或R的位置就是基准数据在数组中的位置。

代码C++实现如下:

#include <iostream>
using namespace std;
int partition1(int arr[], int L, int R) {
	int num = arr[L];
	while(L < R) {
		while(L < R && arr[R] > num) {
			R--;
		}
		arr[L] = arr[R];
		while(L < R && arr[L] <=num) {
			L++;
		}
		arr[R] = arr[L];
	}
	arr[L] = num;
	return L;
}
int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int less = partition1(arr, L, R);
		quickSort(arr, L ,less-1);
		quickSort(arr, less + 1, R);
	}
}
int main()
{
	int arr[] = {8, 4, 1, 5, 2, 3, 7, 0, 6};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 

其中如果将赋值语句改为交换,就不需要在partition的最后将基准元素赋值给arr[L],小修改之后的程序如下:

#include <iostream>
using namespace std;
void swap(int arr[], int a, int b)
{
	if(a == b) {
		return ;	
	}
	int c = arr[a];
	arr[a] = arr[b];
	arr[b] = c;
}
int partition1(int arr[], int L, int R) {
	int num = arr[L];
	while(L < R) {
		while(L < R && arr[R] > num) {
			R--;
		}
		swap(arr, L, R); 
		//arr[L] = arr[R];
		while(L < R && arr[L] <=num) {
			L++;
		}
		//arr[R] = arr[L];
		swap(arr, L, R);
	}
	//arr[L] = num;
	return L;
}
int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int less = partition1(arr, L, R);
		quickSort(arr, L ,less-1);
		quickSort(arr, less + 1, R);
	}
}
int main()
{
	int arr[] = {8, 4, 1, 5, 2, 3, 7, 0, 6};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 

下面来看常规解法二:
在这里引入小于等于区的概念,less是小于等于区的右边界。假设要给包含21、45、1、9、12、19这五个元素的数组arr进行排序。首先用一个临时变量p存储基准数据:p = arr[R]。less的初始值为L-1,因为初始状态下默认所有的数都在小于等于区右边界的右面。此时L指向数组中的第一个元素,判断该元素与基准数据之间的大小关系,如果arr[L]>p,则将L继续向右移动,不做其他操作。
在这里插入图片描述
当L指向数组中第二个元素,发现arr[L]>p,继续向下移动,不做其他操作。
在这里插入图片描述
当L指向数组中第三个元素时, 发现arr[L]<p,将arr[L]与arr[less++]进行交换,结果如下:
在这里插入图片描述
当L指向数组中第四个元素时,发现arr[L]<p,将arr[L]与arr[less++]进行交换,结果如下:
在这里插入图片描述
当L指向数组中第五个元素时,发现arr[L]<p,将arr[L]与arr[less++]进行交换,结果如下:
在这里插入图片描述
当L指向数组中第六个元素时,此时已经到达了数组的末端,arr[L]>p,这时候如果将arr[L]与arr[less++]交换之后,就可以的达到如下所示的数组:
在这里插入图片描述
此时,arr[less]就是小于等于区的有右边界,它的右边就是大于区,从而实现了把基准数大的都放在基准数的右边,把比基准数小的放在基准数的左边。以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序。
代码C++实现如下:

#include <iostream>
using namespace std;
void swap(int arr[], int a, int b)
{
	if(a == b) {
		return ;	
	}
	int c = arr[a];
	arr[a] = arr[b];
	arr[b] = c;
}
int partition1(int arr[], int L, int R) {
	int p = arr[R];
	int less = L - 1;
	for(int i = L; i <= R; i++) {
		if (arr[i] < p) {
			swap(arr, ++less, i);
		}
	}
	swap(arr, ++less, R);
	return less;
}
int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int less = partition1(arr, L, R);
		quickSort(arr, L ,less-1);
		quickSort(arr, less + 1, R);
	}
}
int main()
{
	int arr[] = {8, 4, 3, 1, 5, 2, 3,3, 7, 0, 6};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 

最后来看对数组中相同元素的特殊考虑
在划分的过程,这种算法将数组元素分为小于区、等于区和大于区。
比如0,4,5,4,3,6,4这几个数,以最后一个数4作为划分值,小于区的右边界是整个数组的前一个数,小于区刚开始是没有数的,大于区刚开始只存入最后一个数4,虽然这个数并不符合要求,但是暂且先将它放在这个区域。用less来标记小于区的右边界,more来标记大于区的左边界,示意图如下:
在这里插入图片描述
L指向数组中左边的数,arr[L]<arr[R],小于区的下一个数和当前数交换,小于区指向的是数组的前一个数,当前数是0,所以就是自己和自己交换。
在这里插入图片描述
接着遍历下一个数,此时arr[L]=arr[R],直接跳到下一个,此时arr[L]指向的元素是6,大于arr[R],将arr[L]与大于区的前一个数6进行交换,大于区前移,如下图所示:
在这里插入图片描述
交换完后,下标L继续停留在原位置,看它是大于还是小于划分值。6大于划分值,将6和3交换,数组就变成了0,4,3,4,6,5,4。大于区域再向左扩一个位置,扩完之后,这个下标停在原位置。
在这里插入图片描述
此时arr[L]的值为3,小于4,和小于区(less)下一个元素进行交换,数组就变成了0,3,4,4,6,5,4,小于区向右扩一个位置,当前数挪到下一个。
在这里插入图片描述
继续向下走,L=more,满足了while循环结束的条件。
在这里插入图片描述
此时发现大于区最右的数和最左的数是不对的,所以将arr[more]与arr[R]两个元素进行交换。
在这里插入图片描述
总结: 当前数小于划分值和当前数等于划分值时,下标都是往下跳的,唯独当前数大于划分值时,下标留在原地。下一个数是4,到达了大于区的左边界,就停下来。大于区的最右的数和最左的数位置是不对的,所以将这两个数交换。
C++程序代码实现如下:

#include <iostream>
using namespace std;
void swap(int arr[], int a, int b)
{
	if(a == b) {
		return ;	
	}
	int c = arr[a];
	arr[a] = arr[b];
	arr[b] = c;
}
int data[2] = {0};
int* partition1(int arr[], int L, int R) {
	int less = L - 1;
	int more = R;
	while(L < more) {
		if (arr[L] < arr[R]) {
			swap(arr, ++less, L++);
		} else if (arr[L] > arr[R]) {
			swap(arr, --more, L);
		} else {
			L++;
		}
	}
	swap(arr, more, R);
	data[0] = less + 1;
	data[1] = more;
	return data;
}

int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int *record = partition1(arr, L, R);
		quickSort(arr, L , record[0]-1);
		quickSort(arr, record[1] + 1, R);
	}
}
int main()
{
	int arr[] = {0, 3, 4, 4, 6, 5, 4};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章