目录

基本思想

图解

代码

分割数组的代码

1.hoare版本

2.挖坑法

3.前后指针法

快排代码

1.递归

2.循环

复杂度与稳定性

时间复杂度

空间复杂度

稳定性

完整代码

注意


基本思想

任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

图解

假设有下列一组数据,此时规定每次分割选定当前序列最右侧的数值为基准值,那么第一次的基准值为3,分割为两个子序列后,在分别选定子序列最右侧的数据为基准值,再重复上述过程,就完成了排序。

代码

分割数组的代码

分析上述过程,需要一个将数组进行分割的函数,我们只需要想办法对此函数进行合理的多次调用,就可以完成排序。那么就先给出分割数组的这个函数,有三种方式。
接下来的排序都是排升序,且代码中的right代表数组元素个数

1.hoare版本

原理:选定数组最右侧的值为基准值。然后从数组起始位置开始往后,找到一个比基准值大的值,在从数组最后的位置开始往前,找到一个比基准值小的值,交换这两个数值在数组中的位置。继续重复上述过程,直到遍历完成整个数组,此时一次分割就完成了。代码如下:
int PartSort1(int* arr,int left,int right){//hoare版本int end=right-1;//拿到当前数组最后一个元素的下标while(left<end){while(left<end&&arr[left]<=arr[right-1]){//从左往右找到一个大于基准值的值left++;}while(left<end&&arr[end]>=arr[right-1]){//从右往左找到一个小于基准值的值end--;}Swap(&arr[left],&arr[end]);//交换找到的两个元素}Swap(&arr[left],&arr[right-1]);//别忘了最后把基准值放在对应的位置return left;
}

2.挖坑法

原理:给定两个指针begin和end分别指向数组的第一个元素和最后一个元素,此时记录下数组最后一个元素的值充当基准值,然后从数组第一个元素开始往后找到一个比基准值大的元素,用这个元素填充arr[end],end--后,在从后往前找到一个比基准值小的元素,用这个元素来填充arr[begin],begin++后。重复上述过程,直到begin和end相遇,此时将一开始记录下来的值填充到begin和end相遇的位置即可。代码:

int PartSort2(int* arr,int left,int right){int end=right-1;//数组最后一个元素的下标int data=arr[end];//记录的基准值while(left<end){while(left<end&&arr[left]<=data){//从前往后找比基准值大的值left++;}if(left<end){//填充坑位arr[end]=arr[left];end--;}while(left<end&&arr[end]>=data){//从后往前找比基准值小的值end--;}if(left<end){//填充坑位arr[left]=arr[end];left++;}}arr[left]=data;//最后一次坑位填充return left;
}

3.前后指针法

原理:前后指针法的原理不易理解,先直接看代码:

int PartSort3(int* arr,int left,int right){int cur=left;int prev=cur-1;while(cur<right){if(arr[cur]<=arr[right-1]&&++prev!=cur){Swap(&arr[prev],&arr[cur]);}cur++;}return prev;
}

根据上述代码,依然选定数组最后的元素为基准值,会发现在函数执行过程中arr[left]和arr[cur]中间夹的元素都是比基准值大的元素,代码非常巧妙,原理可以意会。

快排代码

1.递归

分割数组的代码进行一次调用后,会按照比基准值小或者大将元素分别排列在基准值的左右,并返回基准值的下标。那么我们可以利用递归,来完成整个快速排序,代码如下:

void QuickSort(int *arr,int left,int right){if(right-left<=1){//元素个数小于一个,不用分割return;}int div=PartSort3(arr,left,right);//分割当前数组QuickSort(arr,left,div);//处理当前数组的左半部分,不包括下标为div的元素QuickSort(arr,div+1,right);//处理当前数组的右半部分,不包括下标为div的元素
}

上述代码相当于一次将一个元素放到对应的位置,经过不断递归,将所有的元素放到想对应的位置。此时发现,快排的代码非常像二叉树的前序遍历。

2.循环

递归的代码虽然简单,但是如果元素的个数很多,那么很有可能因为递归深度过深,而将栈上的空间用光,导致栈溢出。此时可以在递归的代码上改进,递归到一定深度后,采用其他的排序方式,这算是一种优化。但是可不可以从一开始就不递归呢?答案是肯定的,此时递归转循环,我们需要借助栈(一种数据结构,和上述的存储空间的栈不同)这种结构。在C语言中,栈没有在库函数中提供,需要我们自己写,假设栈已经存在,那么非递归的代码如下:

void QuickSortNonR(int* arr,int left,int right){Stack s;//定义栈StackInit(&s);//初始化栈StackPush(&s,left);//数组最左侧下标入栈StackPush(&s,right);//right代表元素个数,入栈while (!StackEmpty(&s)){//栈不空,一直执行下述操作right = StackTop(&s);//获取栈顶元素StackPop(&s);//栈顶元素出栈left = StackTop(&s);StackPop(&s);if (right-left>1){//元素个数大于1,在分割int div = PartSort3(arr, left, right);StackPush(&s, left);StackPush(&s, div);StackPush(&s, div + 1);StackPush(&s, right);}}
}

在非递归的代码中,利用了栈这种结构将数组的元素下标保存起来,实现了与递归相同的功能,但是大大节省了空间。入栈时先左后右,但是出栈是相反的过程,注意栈的特性。具体实现过程阅读代码后就可以深刻理解了。

复杂度与稳定性

时间复杂度

排序的过程如下图,每次排好一个元素:

整个递归的深度为logN,每一层进行分割时的复杂度为O(N),所以时间复杂度就是O(NlogN)。非递归方式和递归时大致一样,时间复杂度也为:O(NlogN)

空间复杂度

递归时深度为logN,所以空间复杂度为:O(logN)。

稳定性

元素有跨区间交换,不稳定。

完整代码

#include <stdio.h>
#include <windows.h>
#include <assert.h>
#include <malloc.h>
#define CAP_TY 2
typedef int DataType;
typedef struct Stack{//栈DataType *array;int top;int capicity;
}Stack;
void StackInit(Stack *ps){//初始化栈assert(ps);ps->array = (DataType *)malloc(sizeof(DataType)*CAP_TY);if (ps->array == NULL){perror("malloc");exit(0);}ps->top = 0;ps->capicity = CAP_TY;
}
int StackEmpty(Stack *ps){//判空return ps->top == 0;
}
void StackDestroy(Stack *ps){//销毁栈assert(ps);if (ps->array != NULL){free(ps->array);ps->array = NULL;ps->top = 0;ps->capicity = 0;}
}
void Checkcapicity(Stack *ps){//判断栈满并且扩容if (ps->top == ps->capicity){ps->array = (DataType *)realloc(ps->array, sizeof(DataType)*(ps->capicity + 2));if (ps->array == NULL){perror("relloc");exit(0);}ps->capicity = ps->capicity + 2;}
}
void StackPush(Stack *ps, DataType data){//入栈Checkcapicity(ps);ps->array[ps->top] = data;ps->top++;
}
void StackPop(Stack *ps){//出栈assert(ps);if (StackEmpty(ps)){return;}ps->top--;
}
DataType StackTop(Stack* ps){//获取栈顶元素assert(!StackEmpty(ps));return ps->array[ps->top - 1];
}void Swap(int* x,int*y){int temp=*x;*x=*y;*y=temp;
}
int PartSort1(int* arr,int left,int right){//hoare版本int end=right-1;//拿到当前数组最后一个元素的下标while(left<end){while(left<end&&arr[left]<=arr[right-1]){//从左往右找到一个大于基准值的值left++;}while(left<end&&arr[end]>=arr[right-1]){//从右往左找到一个小于基准值的值end--;}Swap(&arr[left],&arr[end]);//交换找到的两个元素}Swap(&arr[left],&arr[right-1]);//别忘了最后把基准值放在对应的位置return left;
}
int PartSort2(int* arr,int left,int right){int end=right-1;//数组最后一个元素的下标int data=arr[end];//记录的基准值while(left<end){while(left<end&&arr[left]<=data){//从前往后找比基准值大的值left++;}if(left<end){//填充坑位arr[end]=arr[left];end--;}while(left<end&&arr[end]>=data){//从后往前找比基准值小的值end--;}if(left<end){//填充坑位arr[left]=arr[end];left++;}}arr[left]=data;//最后一次坑位填充return left;
}
int PartSort3(int* arr,int left,int right){int cur=left;int prev=cur-1;while(cur<right){if(arr[cur]<=arr[right-1]&&++prev!=cur){Swap(&arr[prev],&arr[cur]);}cur++;}return prev;
}
void QuickSort(int *arr,int left,int right){//快排递归if(right-left<=1){//元素个数小于一个,不用分割return;}int div=PartSort3(arr,left,right);//分割当前数组QuickSort(arr,left,div);//处理当前数组的左半部分,不包括下标为div的元素QuickSort(arr,div+1,right);//处理当前数组的右半部分,不包括下标为div的元素
}
void QuickSortNonR(int* arr,int left,int right){Stack s;//定义栈StackInit(&s);//初始化栈StackPush(&s,left);StackPush(&s,right);while (!StackEmpty(&s)){//栈不空,一直执行下述操作right = StackTop(&s);StackPop(&s);left = StackTop(&s);StackPop(&s);if (right-left>1){//元素个数大于1,在分割int div = PartSort3(arr, left, right);StackPush(&s, left);StackPush(&s, div);StackPush(&s, div + 1);StackPush(&s, right);}}
}
void Print(int* arr,int size){int i=0;for(;i<size;i++){printf("%d ",arr[i]);}printf("\n");
}
int main(){int arr[]={2,7,8,5,4,1,3,9,0,6};QuickSortNonR(arr,0,sizeof(arr)/sizeof(arr[0]));Print(arr,sizeof(arr)/sizeof(arr[0]));system("pause");return 0;
}

注意

在以上代码中,总是选取数组最右侧的数据作为基准值,假如这组数据是降序的,要排升序,此时依照上述方法进行排序,每次的基准值都是当前数组中的最小值,这时递归的深度为n-1,那么时间复杂度将退化为O(N^2),空间复杂度退化为O(N)。

快速排序(代码+详细分析)相关推荐

  1. Blueprint代码详细分析-Android10.0编译系统(七)

    摘要:Blueprint解析Android.bp到ninja的代码流程时如何走的? 阅读本文大约需要花费18分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Andro ...

  2. seq2seq翻译任务代码详细分析

    文章目录 题目 代码 总结 题目 ''' Description: seq2seq代码详细分析 Autor: 365JHWZGo Date: 2021-12-16 19:59:38 LastEdito ...

  3. linux加密模块,Linux加解密支持模块代码详细分析之演示验证方案1实验代码及结果...

    原标题:Linux加解密支持模块代码详细分析之演示验证方案1实验代码及结果 3.1.5.实验代码 #include #include #include #include #include #inclu ...

  4. [细读经典]Megatron论文和代码详细分析(1)

    [细读经典]Megatron论文和代码详细分析(1) 导航: 迷途小书僮:[细读经典]Megatron论文和代码详细分析(2)102 赞同 · 41 评论文章正在上传-重新上传取消 前言 作为一款支持 ...

  5. 4.imx6 IPU代码详细分析

    4.0 ipu_soc,ipu_channel_t ,ipu_channel_params_t结构体详解 1.ipu_soc结构体: struct ipu_soc { unsigned int id; ...

  6. uboot代码详细分析.pdf

    目录 u-boot-1.1.6 之cpu/arm920t/start.s 分析 ............................................................ ...

  7. 区块链实现代码详细分析(Python)

    代码 import hashlib import json import requests from textwrap import dedent from time import time from ...

  8. Android RIL代码详细分析

    RIL代码分析 代码位于:android/hardware/ril 1 rild.c中的main()函数提供了rild的入口 首先,通过main函数的传参,cmdline,内核选项等方式获取rild. ...

  9. R语言曲面拟合代码详细分析(1)

    #代码参考<R语言数据可视化之美-专业图表绘制指南>例4.2.1,主要代码链接参见 #https://github.com/EasyChart/Beautiful-Visualizatio ...

最新文章

  1. 【转载】究竟啥才是互联网架构“高可用”
  2. java:不同数据类型的转换规则
  3. linux软链接的创建、删除和更新
  4. 牛津英语3a_空中课堂 | 牛津英语3A学霸笔记 (全) 附3B
  5. Red5 9的安装配置以及AS3连接red5简单示例 .
  6. asp.net mvc4 mysql_ASP.NET MVC4 with MySQL: Configuration Error (MySql.Web.v20)
  7. Windows API一日一练(17)DialogBox和DialogBoxParam函数
  8. Python+Opencv身份证号码区域提取及识别
  9. JavaScript--对象类型详解
  10. 类似组卷网实现快速组卷功能,实现试题,试卷,课件快速录入、搜索、分类查询,支持mathtype和latex2word。
  11. OkHttp简单封装
  12. 嵌入式行业技术思维导图
  13. 用java处理speex编码/解码
  14. 凸包问题--旋转卡壳
  15. DOS中del和rd的区别
  16. SRE重案调查组 第二集 | 挖掘应用处理变慢的“真相”
  17. wince 默认输入法_wince下中文输入法
  18. 智力题解题报告 No.3 计算24点
  19. win10系统U盘安装详细经验附系统软件
  20. 微信小程序 组件无法使用全局样式

热门文章

  1. 22.10.23补卡 CF-1754A
  2. 使用selenium爬取腾讯动漫
  3. 在Windows10中打开服务窗口的5种方法
  4. Latex公式编辑的常见命令
  5. 2022-2028全球与中国环氧模塑料市场现状及未来发展趋势
  6. Gerrit Replication
  7. 数据分析岗笔试卷——目录索引
  8. matlab 求高阶累积量,高阶累积量matlab源码
  9. 机器人会喊疼?新型电子皮肤能感知疼痛,或用于皮肤移植
  10. java 释放static_JAVA中的static关键字作用与用法