快速排序


基本做法

在此我们使用递归的快速排序。既然使用了递归,自然就是要解决一些重复的子任务,然后完成最后的大任务,而大任务自然就是让整个数组整体有序,那需要重复的小任务是什么呢?

快排的一个个子任务,就是要在各自范围内的数组中取一个数当基准数(一般取范围内的最左边、最右边或中间的数),然后让其它的数小于基准数的放左边,大于基准数的放右边,这就是快速排序递归的子任务。

整体的实现则是先对【1,n】范围内的数开始任务,任务完成后,基准数已经在中间,再对【1, 基准数的位置-1】和【 基准数的位置+1,n】开始任务,也就是对基准数左边的范围和右边的范围开始任务,而开始任务其实就是进入下一个递归了。最后所有的递归完成后,也就说明所有范围内的数都已经实现了左边的数小于当前范围的基准数,而右边的数也已经全部大于当前范围的基准数,自然数组整体已经是有序的了。不太理解为什么,可以自己画一下图,每次就取最左边的数作为基准数。

代码构造

首先我们取范围内最左边的数做为基准数,然后让ij分别指向最左边和最右边的位置,再是开始循环。循环的条件需要是i < j,然后因为我们取的是最左边的数,所以我们需要从右边开始向左查找比基准数要小的数,至于为什么取最左边就要从右边开始查找,这是为了之后基准数归位时,i所指向位置的数必定是小于基准数的,这样让基准数跟i所在位置的数进行交换,不会影响任务的完成。查找到比基准数小的数之后停止循环,再开始从左边往右开始查找比基准数大的数,查到之后停止循环。然后交换两个查找到的数,即把比基准数大的数放到右边,比基准数小的数放在左边,再是开始下个查找,直到i >= j。如果期间遇到查找不到的情况,完全没有问题,因为你会发现,最终i指向的值必定小于基准数,而i下一个位置的数要么没有数,要么就是比基准数大的数,最后把基准数和i所指向的数进行交换,任务就完成了。接着就开始基准数左边和右边的子任务,任务流程是一样的。

class Solution {
public:
	Solution() {}
	~Solution() {}
	void quickSort(vector<int>& nums, int left, int right) {
		if (left >= right) {
			return;
		}
		int tmp = nums[left];
		int i = left, j = right;
		while (i < j) {
			while (i < j && nums[j] >= tmp) {
				j--;
			}
			while (i < j && nums[i] <= tmp) {
				i++;
			}
			if (i < j) {
				swap(nums[i], nums[j]);
			}
		}
		nums[left] = nums[i];
		nums[i] = tmp;

		quickSort(nums, left, i - 1);
		quickSort(nums, i + 1, right);
	}
	
	vector<int> sortArray(vector<int>& nums) {
		int len = nums.size();
		quickSort(nums, 0, len - 1);
		return nums;
	}
};

时间复杂度

平均情况 最好情况 最坏情况
O ( N l o g 2 N ) O(Nlog{_2N}) O(Nlog2N) O ( N l o g 2 N ) O(Nlog{_2N}) O(Nlog2N) O ( N 2 ) O(N^2) O(N2)

空间复杂度

辅助存储
O ( N l o g 2 N ) O(Nlog{_2N}) O(Nlog2N)

稳定性

不稳定,快速排序在排序的时候,会把相同关键字的顺序打乱,就是本来在前面出现的数可能会被放到后面。