快速排序 QuickSort ,T= O(nlogn),S=O(logn)

61 篇文章 2 订阅

基于C++标准容器Vector实现的十大经典排序方法


int nums[] = {11,24,5,32,50,34,54,76};

假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。

序列的一个数作为基准数。选取第一个数6作为基准数。

我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。类似下面这种排列:

3 1 2 5 4 6 9 7 10 8

1 思路1 (每次必须j先出发)

1.1 思路演示

方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。

我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即=10),指向数字。

在这里插入图片描述
首先哨兵j开始出动。(再友情提醒,每次必须是哨兵j先出发)
哨兵j一步一步地向左挪动(即j–),直到找到一个小于6的数停下来。
接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。
最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

在这里插入图片描述
在这里插入图片描述
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下:6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。

接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。
哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:
6 1 2 5 4 3 9 7 10 8
在这里插入图片描述
在这里插入图片描述
第二次交换结束,“探测”继续。

哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。

哨兵i继续向右移动,此时哨兵i和哨兵j相遇了,“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下:
3 1 2 5 4 6 9 7 10 8
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。

重复上述过程:分别对左右子区间,进行上述递归;

在这里插入图片描述

1.2 代码实现

//以基准为分界,划分左右子区间,左边的都比基准小,右边都比他大,,,递归
void static QuickSort(int arr[], int start, int end) {
	if (start > end) return;

	int i, j, base;//设置左右移动哨兵,及基准(枢轴)
	i = start, j = end;
	base = arr[start];//每一个子区间,第一个元素作为基准
	

	while (i < j) {
		while (i < j && arr[j] >= base) j--;//每次都是右边的哨兵先左移,找到比基准小的停止
		while (i < j && arr[i] <= base) i++;//然后,左边的哨兵右移,找到比基准大的停止

		if (i < j)
			swap(arr[i], arr[j]);//此时若i<j,交换
	}
	//当两个哨兵i,j相遇,将arr[start]与哨兵交换,此时基准到达最终位置点;且基准为分界,左边的都比基准小,右边都比他大
	swap(arr[start], arr[j]);
	//注意:将arr[start]与哨兵交换,,,,不是base与哨兵交换,,,虽然base与arr[start]相等,,但目的是要改变数组
	//到此,第一趟快排结束;

	//分别对左右子区间,进行上述递归
	QuickSort(arr, start, i - 1);
	QuickSort(arr, j + 1, end);
}

swap(arr[start], arr[j]);
注意:将arr[start]与哨兵交换,,,不是base与哨兵交换,,,虽然base与arr[start]相等,,但目的是要改变数组

这个地方,刚开始base与哨兵交换,,,结果第一个元素一直是6不变,,,查了好久才找到原因

测试

void print(int arr[], int n) {
	for (int i = 0; i < n; i++)
		cout << arr[i] << " ";
	cout << endl;
}
void main() {
	int nums[] = {11,24,5,32,50,34,54,76};
	int n = sizeof(nums)/sizeof(nums[0]); //数组的长度 = 数组总字节数/ 每个元素字节数

	cout << "快速排序前: " << endl;
	print(nums,n);
	QuickSort(nums, 0, n- 1);
	cout << "快速排序后: " << endl;
	print(nums, n);
}

在这里插入图片描述

数组的长度 = 数组总字节数/ 每个元素字节数

int n = sizeof(nums)/sizeof(nums[0]); //数组的长度 = 数组总字节数/ 每个元素字节数

1.3 区间划分和递归调用独立封装

递归、区间划分分开的,

本来想i = start, j = end;可以省去的,直接使用start,end变量;
但是,枢轴与首元交换时,发现首元下边变了,swap(arr[start], arr[j]);

所以,还是要令,i = start, j = end;

//以基准为分界,划分左右子区间,左边的都比基准小,右边都比他大(一趟快排)
int Partion(int arr[], int start, int end) {
	int i, j, base;//设置左右移动哨兵,及基准(枢轴)
	i = start, j = end;
	base = arr[start];//每一个子区间,第一个元素作为基准


	while (i < j) {
		while (i < j && arr[j] >= base) j--;//每次都是右边的哨兵先左移,找到比基准小的停止
		while (i < j && arr[i] <= base) i++;//然后,左边的哨兵右移,找到比基准大的停止

		if (i < j)
			swap(arr[i], arr[j]);//此时若i<j,交换
	}
	//当两个哨兵i,j相遇,将arr[start]与哨兵交换,此时基准到达最终位置点;且基准为分界,左边的都比基准小,右边都比他大
	swap(arr[start], arr[j]);
	//注意:将arr[start]与哨兵交换,,,,不是base与哨兵交换,,,虽然base与arr[start]相等,,但目的是要改变数组
	//到此,第一趟快排结束;

	return i;//返回分界元素下标;;;此时ij是相等的
}
void QuickSort(int arr[], int start, int end) {
	if (start < end)
	{
		int pivot = Partion(arr, start, end);

		//分别对左右子区间,进行上述递归
		QuickSort(arr, start, pivot - 1);
		QuickSort(arr, pivot + 1, end);
	}
	

在这里插入图片描述

2 思路2 ( 推荐使用,代码好写,不易错)(j,i到哪边哪边先走,赋值代替交换)

快速排序(Quick sort)参考

2.1 思路演示

[23,35,15,20,9,5,45,56,11,2]
待排序的数据元素第一个作为基准值元素 key=num[0];

设置双指针first指向区间左端,last指向区间右端。(即上述i,j)
在这里插入图片描述
哨兵last先开始,一步一步地向左挪动(即j–),直到找到一个小于key的数停下来。
num[first]=num[last],将这个比key小的数放到左边firt的位置;
在这里插入图片描述

在这里插入图片描述

然后,哨兵first开始,一步一步地向右挪动(即i++),直到找到一个大于key的数停下来。
num[last]=num[first],将这个比key大的数放到右边last的位置;

在这里插入图片描述
在这里插入图片描述

重复上述一二步骤

last左移,找到11比基准小,放到fist的位置;
在这里插入图片描述
first右移,找到45比基准大,放到last的位置;

在这里插入图片描述
last继续左移

fisrt、last相遇,“探测”结束。将基准数23放到相遇的位置;

在这里插入图片描述
在这里插入图片描述
第一趟排序结束,得到[2,11,15,20,9,5] 23 [56,45,35] 然后对左右子数列进行同样的操作。
2 [11,15,20,9,5] 23 [35,45] 56
2 [5,9] 11 [20,15] 23 35 45 56
2 5 9 11 15 20 23 35 45 56
完成从小到大的排序

2.2 代码实现

单趟快排

//以基准为分界,划分左右子区间,左边的都比基准小,右边都比他大(一趟快排)
int Partion(int arr[], int low, int high) {
	int base = arr[low];//每一个子区间,第一个元素作为基准
	while (low < high) {
		while (low < high && arr[high] >= base) high--;//右边的哨兵先左移,找到比基准小的停止
		arr[low] = arr[high]; //将比基准小的数放到左边low的位置;
		while (low < high && arr[low] <= base) low++;//然后,左边的哨兵右移,找到比基准大的停止
		arr[high] = arr[low]; //将比基准大的数放到右边high的位置;
	}
	//当两个哨兵i,j相遇,将枢轴(基准)元素放到哨兵位置,此时基准到达最终位置点;且基准为分界,左边的都比基准小,右边都比他大
	arr[low] = base;//将枢轴(基准)元素放到最终位置  //其实这里交换,也不影响结果,只是意思变了swap(arr[low], base);
	//到此,第一趟快排结束;

	return low;//返回分界元素下标;;; 此时low和high相等,随便返回哪个都可以
}

递归调用

void QuickSort(int arr[], int start, int end) {
	if (start < end)
	{
		int pivot = Partion(arr, start, end);

		//分别对左右子区间,进行上述递归
		QuickSort(arr, start, pivot - 1);
		QuickSort(arr, pivot + 1, end);
	}
}



测试

void print(int arr[], int n) {
	for (int i = 0; i < n; i++)
		cout << arr[i] << " ";
	cout << endl;
}
void main() {
	int nums[] = { 23,35,15,20,9,5,45,56,11,2 };
	int n = sizeof(nums)/sizeof(nums[0]); //数组的长度 = 数组总字节数/ 每个元素字节数

	cout << "快速排序前: " << endl;
	print(nums,n);
	QuickSort(nums, 0, n- 1);
	cout << "快速排序后: " << endl;
	print(nums, n);
}

在这里插入图片描述

2.3 用vector代替int arr[]

int arr[] 替换成vector&arr,其余的地方一样

int partition(vector<int>& arr, int low, int high) {
    int base = arr[low];
    while (low < high) {
        while (low < high && arr[high] >= base) high--;
        arr[low] = arr[high];
        while (low < high && arr[low] <= base)low++;
        arr[high] = arr[low];
    }
    arr[low] = base;
    return low;
}

void QuickSort(vector<int> &arr, int start, int end) {
    if (start < end) {
        int pivot = partition(arr, start, end);
		QuickSort(arr, start, pivot - 1);
		QuickSort(arr, pivot + 1, end);
    }
}

测试

void printVectorInt(vector<int>arr, int n) {
	for (int i = 0; i < n; i++)
		cout << arr[i] << " ";
	cout << endl;
}
void main() {

	//vector<int> res = { 11,24,5,32,50,34,54,76 };
	vector<int> res = { 1,4,7,8,9,6,3,2,5,10 };
	cout << "快速排序前: " << endl;
	printVectorInt(res, res.size());
	QuickSort(res, 0, res.size() - 1);
	cout << "快速排序后: " << endl;
	printVectorInt(res, res.size());
	
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

R-G-B

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值