快速排序
参考:https://www.bilibili.com/video/BV17s41197Yy?spm_id_from=333.999.0.0
一,partition 函数
1,算法名称
我不知道这种算法叫什么,但我愿称之为 —— 区间移动
2,算法功能
解释
① 从过程看:将某一区间的数,以区间最右边的数为支点,将其余的数根据与支点的大小比较放在支点的左右两边。
如果是升序序列,则将小于支点的数放在支点左边,将大于支点的数放在支点的右边。
如果是降序序列,则将大于支点的数放在支点左边,将小于支点的数放在支点的右边。
② 从结果上看:找到支点在对该区间元素排序后应该存在的位置。
注意点:
① 支点可以选择区间内的任意一个数。
② 支点的左右两边的数允许是乱序的。
3,算法思想
将一个空的移动区间从全局区间进行从左到右的移动,如果遍历到的数属于这个支点右边的数,则将这个数放入区间中,否则移动到区间前面。这样遍历一遍全局区间后,就能完成上述函数功能。
4,步骤
① 定
苏菲:支点的右边区间
用 i 指向苏菲的最后一个元素的位置, j 指向苏菲的第一个元素的位置,所以苏菲是全闭区间,表示为【 j,i 】
苏菲的右边为苏菲的前面,苏菲的左边为苏菲的后面。
哈尔:全局区间
支点:取哈尔中的最右边的元素
当苏菲遍历支点的时候,此时支点不满足苏菲的条件,被替换到苏菲后面,正好处于支点左右两个区间的交界处,天衣无缝。
② 初始化
将 i 初始化为 -1 ,j 初始化为 0,此时苏菲内并无元素。
③ 循环遍历
苏菲通过 i++ 向前移动,遍历哈尔的每一个元素,i 的取值在遍历过程从 -1 移动到 哈尔的最后一个元素的位置。
如果 i 遍历到的元素属于苏菲,则用 i++ 将其包括在苏菲里面。
如果 i 遍历到的元素不属于苏菲,则 将这个元素和苏菲的第一个元素交换位置,再 j++。
注意点:
当 i 遍历到的元素属于苏菲时,所进行的 i++,其实和苏菲通过 i 遍历哈尔的 i++ 是同一个步骤,所以此时可以不做任何处理。
当 i 遍历到的元素不属于苏菲时,所进行的 交换元素,是不稳定的移动,易得。
当 i 遍历到的元素不属于苏菲时,之所以要 j++,是因为交换元素后,j 指向的元素不属于苏菲。其中,j++ 和 i++ 从所达到的效果从宏观起来看,就像是苏菲在向前移动时,将遇到的障碍物搬到自己后面,然后再往前走。
④ 遍历结果
左边的区间为【0,j-1】, j 为支点,苏菲为【j+1,n-1】。
二,quick_sort 函数
利用递归的搜索功能
边搜索边使用 partition。
每次利用 partition 找到某一个元素排序后的位置后,就对剩余位置的元素进行细分搜索。
在不断缩小搜索范围的过程中,就能找到所有元素排序后的位置。
三,快排代码
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define N 100 int a[N]; int partition(int L, int R) { int i = L - 1, j = L; while (i + 1 <= R) { i++; if (a[i] <= a[R]) { int t = a[i]; a[i] = a[j]; a[j++] = t; } } return j - 1; } void quickSort(int L, int R) { if (L >= R) return; int m = partition(L, R); quickSort(L, m - 1); quickSort(m + 1, R); } int main(void) { int n; while (scanf("%d", &n) != EOF) { for (int i = 0; i < n; i++) scanf("%d", &a[i]); quickSort(0, n - 1); for (int i = 0; i < n; i++) printf("%d ", a[i]); puts(""); } return 0; }
四,算法分析
① 算法的稳定性
见一的④步骤,所以快速排序是一种不稳定的算法。
② 算法的时间复杂度
对于长度为 n 的数组,由于其递归搜索分成左右两边,所以递归树的深度为 log2(n),而每次搜索时使用的 partition 函数的时间复杂度为 O(n),所以快速排序的时间复杂度为 O(n*log2(n))
五,快排与归并的比较
① 总体思想
快排:利用递归的搜索分治,在搜索时使用 partition
归并:利用递归的搜索分治,在回溯时使用 merge
② 功能函数
partition:将数组根据大小划分在支点两边。
merge:将左右两边的数组归并到一起。
六,选择快排
1,例题
https://leetcode-cn.com/problems/kth-largest-element-in-an-array/
2,思路
要求解序列中的第 k 大的数,即只需找到某个元素降序排序后的位置刚好是 k-1 的。所以,我们可以简化快速排序,即在搜索的时候限定条件,只搜索包含 k -1 位置的区间。
3,代码
#include<vector> using namespace std; class Solution { public: int partition(int L, int R, vector<int>& a) { int p = a[R]; int i = L - 1, j = L; while (i + 1 <= R) { i++; if (a[i] >= p) { int t = a[i]; a[i] = a[j]; a[j] = t; j++; } } return j - 1; } void qs(int L, int R, int k, vector<int>& a) { if (L >= R) return; int m = partition(L, R, a); if (k - 1 == m) // 快排的有选择的进行排序 return; if (m > k - 1) qs(L, m - 1, k, a); else qs(m + 1, R, k, a); } int findKthLargest(vector<int>& nums, int k) { qs(0, nums.size() - 1, k, nums); return nums[k - 1]; } };
七,非递归的实现
1,概述
因为快排的递归实现的函数尾部存在两个递归调用,所以无法单纯的用循环实现快排的非递归,需要借用栈来存储两个递归调用
2,代码
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #include<stack> using namespace std; #define N 110 int a[N]; struct Node{ int l, r; }; int partition(int l, int r) { int i = l - 1, j = l; while (i + 1 <= r) { i++; if (a[i] <= a[r]) { int t = a[i]; a[i] = a[j]; a[j++] = t; } } return j - 1; } void qortSort(int l, int r) { // 初始化 stack<Node>s; s.push(Node{ l, r }); // 栈中存在元素则循环 while (s.size()) { Node vertex = s.top(); s.pop(); if (vertex.l >= vertex.r) // 判断边界 continue; int m = partition(vertex.l, vertex.r); Node n1 = { vertex.l, m - 1 }; Node n2 = { m + 1, vertex.r }; s.push(n2); s.push(n1); } } int main(void) { int n; while (scanf("%d", &n) != EOF) { for (int i = 0; i < n; i++) scanf("%d", &a[i]); qortSort(0, n - 1); for (int i = 0; i < n; i++) printf("%d ", a[i]); puts(""); } return 0; }
========== ========= ======== ======= ====== ===== ==== === == =
菩萨蛮 韦庄 唐
翠屏金屈曲,醉入花丛宿。此度见花枝,白头誓不归。