排序基本概念
定义
假设含有n个记录的序列为{r1,r2,…,rn},其相应的关键字分别为{k1,k2,…,kn},需确定1,2,…,n的一种排列p1,p2,…pn,使其相应的关键字满足kp1<=kp2<=…<=kpn非递减(或非递增)关系,即使得序列成为一个按关键字有序的序列{rp1,rp2,…rpn},这样的操作就称为排序。
相关概念
- 稳定
如果a原本在b前面,而a=b,排序之后a仍然在b的前面。 - 不稳定
如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。 - 时间复杂度
对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。 - 空间复杂度
是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
排序算法
1、冒泡排序
冒泡排序(Bubble Sort),顾名思义,就是指越小的元素会经由相邻两两交换慢慢“浮”到数列的顶端
/// 冒泡排序
/// @param a 数组
/// @param n 数组元素个数
void bubbleSort(int a[],int n){
int i,j,temp,flag;
i = j = temp = 0;
flag = 1;
//要进行n-1次排序
for (i = 0; i < n-1 && flag; i++) {
flag = 0;
for (j = n-1; j > i; j--) {
//把数组元素两两交换,小的浮到最上面(数组的前面地址)
if (a[j-1] > a[j]) {
temp = a[j-1];
a[j-1] = a[j];
a[j] = temp;
flag = 1;
}
}
}
}
- 复杂度:
- 时间复杂度
- 平均:O(n^2)
- 最好:O(n)
- 最坏:O(n^2)
- 空间复杂度 O(1)
- 时间复杂度
- 稳定:
- 稳定
2.选择排序
选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想就是,每一趟 n-i+1(i=1,2,...,n-1) 个记录中选取关键字最小的记录作为有序序列的第 i 个记录。
算法步骤:
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;
- 在剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾;
- 重复步骤 2,直到所有元素排序完毕。
/// 选择排序
/// @param a 数组
/// @param n 数组元素个数
void selectSort(int a[],int n){
int i,j,temp,min;
for (i = 0; i < n-1; i++) {
min = i;
for (j = i + 1; j < n; j++) {
if (a[j] < a[min]) {
min = j;
}
}
if (min != i) {
temp = a[min];
a[min] = a[i];
a[i] = temp;
}
}
}
- 复杂度:
- 时间复杂度
- 平均:O(n^2)
- 最好:O(n^2)
- 最坏:O(n^2)
- 空间复杂度 O(1)
- 时间复杂度
- 稳定:
- 稳定
3.插入排序
直接插入排序算法(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加1的有序表。
/// 直接插入排序
/// @param a 数组
/// @param n 数组元素个数
void InsertSort(int a[],int n){
int i,j,temp;
//这里i从1开始,因为我们需要j指向它的前一个元素
for (i = 1; i < n; i++) {
//后面的元素大于前面的元素,不符合已排序的条件了
if (a[i] < a[i-1]) {
//将i位置的数据放入缓存区
temp = a[i];
for ( j = i-1; j >= 0 && a[j] > temp; j--) {
/*
从i的前一个元素开始,发现有比缓存区数据大的,就往后挪一步
因为我们是从前面开始排的,所以前面一定是排好序的,
这里就是挪位找到temp应该插入位置的过程
*/
a[j+1] = a[j];
}
//找准了位置,将缓存区位置的数据插入
a[j+1] = temp;
}
}
}
- 复杂度:
- 时间复杂度
- 平均:O(n^2)
- 最好:O(n)
- 最坏:O(n^2)
- 空间复杂度 O(1)
- 时间复杂度
- 稳定:
- 稳定
4.希尔排序
希尔排序
- 先将整个待排序列分割成若干个字序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
- 子序列的构成不是简单地“逐段分割”,将相隔某个增量的记录组成一个子序列,让增量逐趟缩短,直到增量为 1 为止
/// 希尔排序
/// @param a 数组
/// @param n 数组元素个数
void shellSort(int a[],int n){
int i,j,step,temp;
//step表示分组步长,每次除以2
step = n >> 1;
while (step > 0) {
for (i = step; i < n; i += step) {
if (a[i] < a[i-step]) {
temp = a[i];
for (j = i - step; j >= 0 && a[j] > temp; j -= step) {
a[j+step] = a[j];
}
a[j+step] = temp;
}
}
step >>= 1;
}
}
** 复杂度:
- 时间复杂度
- 平均:O(n log n) ~ O(n^2)
- 最好:O(n^1.3)
- 最坏:O(n^2)
- 空间复杂度 O(1)
- 稳定:
- 不稳定
5.堆排序
① 完全二叉树
堆是利用完全二叉树的概念来构建的。我们回顾下完全二叉树的概念:对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点位置完全相同,则这棵二叉树称为完全二叉树。如下图所示
完全二叉树有如下特性:
- 叶子结点只能出现在最下两层
- 最下层的叶子一定集中在左部连续位置
- 倒数第二层,如有叶子结点,一定都在右部连续位置
- 如果结点度为1,则该结点只有左孩子
- 同样结点树的二叉树,完全二叉树的深度最小
② 堆
通过如上的完全二叉树的索引我们可以发现,对于任何一个根节点的索引i,它的左孩子索引为2i,右孩子为2i+1
我们将数组a[]
构建为完全二叉树的形式,让它满足:
- a[i] >= a[2i] 并且 a[i] >= a[2i+1],这个我们称之为大顶堆
- a[i] <= a[2i] 并且 a[i] <= a[2i+1],这个我们称之为小顶堆
- 其中,1<=i<=n/2
-
其中,下标i的结点与2i结点和2i+1结点是双亲和子女关系
③堆排序算法
堆排序算法(Heap Sort)就是利用堆进行排序的算法,它的基本思想是:
- 将待排序的序列构造成一个大顶堆(小顶堆)
- 此时,整个序列的最大值就是堆顶的根结点。将它移走(就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值)
- 然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的最大值。
- 如此反复执行,便能得到一个有序序列。
④堆排序代码实现
- 如果我们要从小到大排序,那么我们就构建一个大顶堆,从大到小则构建一个小顶堆
- 我们对于一个数组构建堆的过程为从下往上,从右往左,假设n为数组的个数,那么从n/2开始构建,一直到1
- 堆排序我们的下标从1开始,所以对于数组,下标为0位置的数据是无意义的,因为它不参与排序,所以我们传入的数组个数也是实际的数组个数减1的值
void swap(int a[],int i,int j);
void buildHeap(int a[],int n);
void heapify(int a[],int i,int n);
/// 堆排序
/// @param a 数组
/// @param n 数组元素个数
void heapSort(int a[],int n){
//将数组构建为大顶堆
buildHeap(a, n);
//构建成大顶堆后,下标为1的元素就是最大值
for (int i = n; i > 1; i--) {
//将最大值放大最后一个位置去
swap(a, 1, i);
/*
去除掉最后一个后,重新把前面的构建为大顶堆
因为之前是大顶堆,只会在下标为1的位置出现不符合
所以我们直接从1开始
*/
heapify(a, 1, i-1);
}
}
/// 将一个数组构建为大顶堆
/// @param a 数组
/// @param n 数组元素个数
void buildHeap(int a[],int n){
for (int i = n/2; i > 0; i--) {
heapify(a, i, n);
}
}
/// 从i结点开始构建大顶堆(从下往上构建)
/// @param a 数组
/// @param i 开始的索引
/// @param n 需要构建的个数
void heapify(int a[],int i,int n){
int j,temp;
//取到这个根节点的值
temp = a[i];
for (j = 2*i; j <= n; j *= 2) {
/*
索引j和j+1如果都在n的范围内,也就是判断是否左右子树都有
j是i结点的左子树,j+1是i结点的右子树,
这一步我们是找出左子树还是右子树的值更大
如果右子树的值更大,那么将j定位到右子树根节点上
*/
if (j < n && a[j+1] > a[j]) {
j++;
}
//如果这个结点的值比i结点的值小,那么是符合了大顶堆的,直接退出
if (a[j] < temp) {
break;
}
//将这个点的值给放入i位置,让它符合大顶堆
a[i] = a[j];
//接下来就是根据j这个根节点来看它是否符合大顶堆了
//所以将它赋值给i
i = j;
}
//经过所有遍历后,将最早的根节点值放入新的位置
a[i] = temp;
}
/// 交换数组下标为i和j的两个元素
/// @param a 数组名
/// @param i 下标
/// @param j 下标
void swap(int a[],int i,int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
我们发现,让数组a从下标1才开始排序是极其不合理的行为,所以,我们可以利用内存操作,新建一个数组b,让它的内存大小是n+1个int的大小,然后将a的数据从b+1的位置拷贝进b去,在排序后,将b数组的数据从b+1位置拷贝进a中,这时就实现了跟其他排序一样的排序表征了。
void heapSortNew(int a[],int n){
int *b = (int *)malloc(sizeof(int) * (n+1));
memset(b, 0, n+1);
memcpy(b+1, a, sizeof(int) * n);
buildHeap(b, n);
for (int i = n; i > 0; i--) {
swap(b, 1, i);
heapify(b, 1, i-1);
}
memset(a, 0, n);
memcpy(a, b+1, sizeof(int) * n);
free(b);
}
- 复杂度:
- 时间复杂度
- 平均:O(n log n)
- 最好:O(n log n)
- 最坏:O(n log n)
- 空间复杂度 O(1)
- 时间复杂度
- 稳定:
- 不稳定
6.归并排序
定义
归并排序(Merge Sort)就是利用归并的思想实现的排序算法。它的原理是假设初始序列有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列;再两两归并,...,如此重复,直到得到一个长度为n的有序序列为止,这种排序方法称为二路归并排序。
归并法其实就是使用分治法将数组两两分解,一直到只剩一个元素,然后再两两合并组合成有序数组。
两两数组比较合并为一个数组时,我们可以使用插入排序的办法,将右边数组一个个按照顺序插入到左边数组中,这样可以极大的优化排序。
/*
归并排序
*/
void merging(int *list_left,int list_left_size,int *list_right,int list_right_size);
/// 归并排序
/// @param a 数组
/// @param n 数组元素个数
void mergeSort(int a[],int n){
if (n <= 1) {
return;
}
//将数组等分为两段
int *list_left = a;
int list_left_size = n/2;
int *list_right = a + n/2;
int list_right_size = n - list_left_size;
//分治法,不断递归,直到分为一个元素的数组,一个肯定是有序的
mergeSort(list_left, list_left_size);
mergeSort(list_right, list_right_size);
merging(list_left, list_left_size, list_right, list_right_size);
}
/// 两个有序数组合并为一个有序数组 两个数组的元素一个个的相互比较来实现。
/// @param list_left 左数组
/// @param list_left_size 左边数组大小
/// @param list_right 右数组
/// @param list_right_size 右数组大小
//void merging(int *list_left,int list_left_size,int *list_right,int list_right_size){
// int i,j,k,m;
// i = j = k = m = 0;
// //临时数组,用于存放比较后的数据
// int temp[list_left_size+list_right_size];
// //两个数组的元素相互比较,直到左右两边有一个数组的元素被取完
// while (i < list_left_size && j < list_right_size) {
// if (list_left[i] < list_right[j]) {
// temp[k++] = list_left[i++];
// } else {
// temp[k++] = list_right[j++];
// }
// }
// //如果左边还有剩,加到临时数组后面
// while (i < list_left_size) {
// temp[k++] = list_left[i++];
// }
// //如果是右边还有剩,加到临时数组后面
// while (j < list_right_size) {
// temp[k++] = list_right[j++];
// }
// //再把临时数组的数据加到原来那边
// for (m = 0; m < (list_left_size + list_right_size); m++) {
// list_left[m] = temp[m];
// }
//}
/// 两个有序数组合并为一个有序数组 使用插入排序来实现,将list_right的数据插入左边(优化))
/// @param list_left 左数组
/// @param list_left_size 左边数组大小
/// @param list_right 右数组
/// @param list_right_size 右数组大小
void merging(int *list_left,int list_left_size,int *list_right,int list_right_size){
//我们可以看做右边数组的每个元素需要在左边数组中找位置插入
int i,j,leftMax,temp;//i表示右边数组的下标,j表示左边数组的下标
//使用插入排序 优化排序
for (i = 0; i < list_right_size; i++) {
//leftMax是左边数组的最后一个值,也就是最大的值
leftMax = list_left[list_left_size-1];
if (list_right[i] < leftMax) {
temp = list_right[i];
for (j = list_left_size - 1; j >= 0 && list_left[j] > temp; j--) {
list_left[j+1] = list_left[j];
}
list_left[j+1] = temp;
list_left_size++;
} else {
//由于右边数组是有序的,所以这时候后面的都是已经有序了
//而且由于数组是顺序存储结构,right是紧跟在left后面的
//所以这时候数组a已经保证是n个连续区块是有序的
//直接返回即可
break;
}
}
}
- 复杂度:
- 时间复杂度
- 平均:O(n log n)
- 最好:O(n log n)
- 最坏:O(n log n)
- 空间复杂度 O(n)
- 时间复杂度
- 稳定:
- 稳定
7.快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
解析:
- 1.在数组中找一个值,作为分隔点,初始的时候将分隔点都是设置为low位置的值
- 2.将小于分隔点的值扔在分割点的左边,将大于分割点的值扔在分割点的右边
- 3.最后将分割点的值赋值给中间位置,然后继续对左右两边执行1和2步骤,知道分无可分
快速排序是对于大数组最好的排序方法,它是冒泡排序的升级版,代码实现如下:
void qSort(int a[],int low,int high);
int partition(int a[],int low,int high);
/// 快速排序
/// @param a 数组
/// @param n 数组元素个数
void quickSort(int a[],int n){
qSort(a,0,n-1);
}
void qSort(int a[],int low,int high){
//point表示一个分割点的下标
int point;
if (low < high) {
//获取分割点,并将比分隔点元素小的放在分隔点左边,比分隔点大的放到分隔点右边
point = partition(a,low,high);
//递归排序分隔点左边
qSort(a, low, point-1);
//递归排序分隔点右边
qSort(a, point+1, high);
}
}
/// 将分隔点移动到中间,小于分隔点的元素放到分隔点左边,大于分隔点的元素放到分隔点右边
/// @param a 数组
/// @param low 低位
/// @param high 高位
int partition(int a[],int low,int high){
//分隔点元素设置取低位的元素值
int point = a[low];
//这个循环最终将结束在low=high的时候
while (low < high) {
//从右边开始,循环找到比分割点小的元素位置
while (low < high && a[high] >= point) {
high--;//往左移动,那么high右边就是已经比分隔点大的
}
//在右边找到了比分隔点小的,交换位置,也就是放到分隔点的左边
swap(a, low, high);
//从左边开始,循环找到比分隔点大的元素位置
while (low < high && a[low] <= point) {
low++;//往右移动,那么low左边的就是已经比分隔点小的
}
//在左边找到了比分隔点大的元素,交换位置,也就是放到了分隔点的右边
swap(a, low, high);
}
return low;
}
(1)、快速排序的优化:三数取中法
算法实现中我们可以发现,我们取分隔点一直都是取的a[low]
,而a[low]
的值我们并不知道它是多大的,加入它是最大值或者最小值,那么我们就会多执行一次的排序,那么我们将它优化一下,取low、high、middle的值的中间值作为分隔点,这种优化方法叫做三数取中法。
/*
快排优化:三数取中法
*/
int partition(int a[],int low,int high){
//分隔点元素使用三数取中法来获取
//也就是在最前面、中间、最后面,三个数中取中间的值
//中间位置
int mid = low + (high-low)/2;
//让前面的数是小的,后面的数是大的
if (a[low] > a[high]) {
swap(a, low, high);
}
//中间位置比最后的数值大,交换
if (a[mid] > a[high]) {
swap(a, mid, high);
}
//前面两个if保证了low和mid位置的元素都比high小
//这时候,我们应该确保让low位置的元素为low和mid位置中较大的
//也就是low位置元素要保证是中位数
if (a[low] < a[mid]) {
swap(a, low, mid);
}
int point = a[low];
//这个循环最终将结束在low=high的时候
while (low < high) {
//从右边开始,循环找到比分割点小的元素位置
while (low < high && a[high] >= point) {
high--;//往左移动,那么high右边就是已经比分隔点大的
}
//在右边找到了比分隔点小的,交换位置,也就是放到分隔点的左边
swap(a, low, high);
//从左边开始,循环找到比分隔点大的元素位置
while (low < high && a[low] <= point) {
low++;//往右移动,那么low左边的就是已经比分隔点小的
}
//在左边找到了比分隔点大的元素,交换位置,也就是放到了分隔点的右边
swap(a, low, high);
}
return low;
}
(2)、快速排序的优化: 优化掉不必要的交换
在将元素左右移动的时候,我们使用的是swap(a, low, high);
,实际上,因为我们已经存储了point作为分割值,根本用不着进行交换,在循环中只用赋值即可,循环结束,再把中间位置的值设置为point即可
/*
优化掉不必要的交换
*/
int partition(int a[],int low,int high){
//分隔点元素使用三数取中法来获取
//也就是在最前面、中间、最后面,三个数中取中间的值
//中间位置
int mid = low + (high-low)/2;
//让前面的数是小的,后面的数是大的
if (a[low] > a[high]) {
swap(a, low, high);
}
//中间位置比最后的数值大,交换
if (a[mid] > a[high]) {
swap(a, mid, high);
}
//前面两个if保证了low和mid位置的元素都比high小
//这时候,我们应该确保让low位置的元素为low和mid位置中较大的
//也就是low位置元素要保证是中位数
if (a[low] < a[mid]) {
swap(a, low, mid);
}
int point = a[low];
//这个循环最终将结束在low=high的时候
while (low < high) {
//从右边开始,循环找到比分割点小的元素位置
while (low < high && a[high] >= point) {
high--;//往左移动,那么high右边就是已经比分隔点大的
}
/*
在右边找到了比分隔点小的,那么直接将它赋值到低位上
在之前中,我们是进行交换,但是这个分隔点的值我们已经有point来存储了
所以我们只用将值赋过去即可,根本用不着交换
*/
a[low] = a[high];
//从左边开始,循环找到比分隔点大的元素位置
while (low < high && a[low] <= point) {
low++;//往右移动,那么low左边的就是已经比分隔点小的
}
/*
在左边找到了比分隔点大的元素,那么直接将它放到高位上
在之前中,我们是进行交换,但是这个分隔点的值我们已经有point来存储了
所以我们只用将值赋过去即可,根本用不着交换
*/
a[high] = a[low];
}
//最后将中央的值赋值为分隔点的值即可
a[low] = point;
return low;
}
(3)、快速排序的优化:优化小数组时的排序方案
快速排序适合大数组时候的排序方式,而小数组的时候,直接插入排序是最好的排序方法
所以,我们设置一个数组长度值,大于这个值,我们使用快排,小于这个值,我们使用插入排序
/*
优化小数组时的排序方案
快速排序适合大数组时候的排序方式,而小数组的时候,直接插入排序是最好的排序方法
所以,我们设置一个数组长度值,大于这个值,我们使用快排,小于这个值,我们使用插入排序
*/
#define MAX_LENGTH_INSERT_SORT 7
void qSort(int a[],int low,int high){
//point表示一个分割点的下标
int point;
//如果大于7,使用快排
if ((high-low) > MAX_LENGTH_INSERT_SORT) {
//获取分割点,并将比分隔点元素小的放在分隔点左边,比分隔点大的放到分隔点右边
point = partition(a,low,high);
//递归排序分隔点左边
qSort(a, low, point-1);
//递归排序分隔点右边
qSort(a, point+1, high);
} else {
//小于等于7,使用直接插入排序
insertSort(a+low, high-low+1);
}
}
(4)、快速排序的优化:优化递归操作,改为尾递归的形式
什么是尾递归呢?如果一个函数中递归形式的调用出现在函数的末尾,我们称这个递归函数是尾递归的。
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活跃记录而不是在栈中取创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时,栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了。通过覆盖当前栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的执行效率会变得更高。
因此,只要有可能我们就需要将递归函数写成尾递归的形式。
#define MAX_LENGTH_INSERT_SORT 7
/*
优化递归操作
*/
void qSort(int a[],int low,int high){
//point表示一个分割点的下标
int point;
//如果大于7,使用快排
if ((high-low) > MAX_LENGTH_INSERT_SORT) {
while (low < high) {
//获取分割点,并将比分隔点元素小的放在分隔点左边,比分隔点大的放到分隔点右边
point = partition(a,low,high);
//构建成尾递归
if (point - low < high - point) {
qSort(a, low, point-1);
low = point + 1;
} else {
qSort(a, point+1, high);
high = point - 1;
}
}
} else {
//小于等于7,使用直接插入排序
InsertSort(a+low, high-low+1);
}
}
(5)、全部优化后的完整算法
/*
快速排序
*/
void qSort(int a[],int low,int high);
int partition(int a[],int low,int high);
/// 快速排序
/// @param a 数组
/// @param n 数组元素个数
void quickSort(int a[],int n){
qSort(a,0,n-1);
}
/*
优化掉不必要的交换
*/
int partition(int a[],int low,int high){
//分隔点元素使用三数取中法来获取
//也就是在最前面、中间、最后面,三个数中取中间的值
//中间位置
int mid = low + (high-low)/2;
//让前面的数是小的,后面的数是大的
if (a[low] > a[high]) {
swap(a, low, high);
}
//中间位置比最后的数值大,交换
if (a[mid] > a[high]) {
swap(a, mid, high);
}
//前面两个if保证了low和mid位置的元素都比high小
//这时候,我们应该确保让low位置的元素为low和mid位置中较大的
//也就是low位置元素要保证是中位数
if (a[low] < a[mid]) {
swap(a, low, mid);
}
int point = a[low];
//这个循环最终将结束在low=high的时候
while (low < high) {
//从右边开始,循环找到比分割点小的元素位置
while (low < high && a[high] >= point) {
high--;//往左移动,那么high右边就是已经比分隔点大的
}
/*
在右边找到了比分隔点小的,那么直接将它赋值到低位上
在之前中,我们是进行交换,但是这个分隔点的值我们已经有point来存储了
所以我们只用将值赋过去即可,根本用不着交换
*/
a[low] = a[high];
//从左边开始,循环找到比分隔点大的元素位置
while (low < high && a[low] <= point) {
low++;//往右移动,那么low左边的就是已经比分隔点小的
}
/*
在左边找到了比分隔点大的元素,那么直接将它放到高位上
在之前中,我们是进行交换,但是这个分隔点的值我们已经有point来存储了
所以我们只用将值赋过去即可,根本用不着交换
*/
a[high] = a[low];
}
//最后将中央的值赋值为分隔点的值即可
a[low] = point;
return low;
}
#define MAX_LENGTH_INSERT_SORT 7
/*
优化递归操作
*/
void qSort(int a[],int low,int high){
//point表示一个分割点的下标
int point;
//如果大于7,使用快排
if ((high-low) > MAX_LENGTH_INSERT_SORT) {
while (low < high) {
//获取分割点,并将比分隔点元素小的放在分隔点左边,比分隔点大的放到分隔点右边
point = partition(a,low,high);
//构建成尾递归
if (point - low < high - point) {
qSort(a, low, point-1);
low = point + 1;
} else {
qSort(a, point+1, high);
high = point - 1;
}
}
} else {
//小于等于7,使用直接插入排序
InsertSort(a+low, high-low+1);
}
}
快速排序的复杂度与稳定性
- 复杂度:
- 时间复杂度
- 平均:O(n log n)
- 最好:O(n log n)
- 最坏:O(n^2)
- 空间复杂度 O(log n) ~ O(n)
- 时间复杂度
- 稳定:
- 不稳定
三、总结
从上面可以看出,排序算法可以分为两大类
- 比较类排序
通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。 -
非比较类排序
不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
各个排序算法的复杂度如下