经典排序之快速排序

2023-11-08

一、概述

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值, 按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。快速排序的关键点在于如何按照基准值划分区间。快速排序递归实现的框架(本文以升序排序为基础):
 
// 按照升序对a数组中[left, right)区间中的元素进行排序
void QuickSort(int* a, int left, int right) {
	if (left+1>=right) {
		return;
	}
    // 按照基准值对a数组的 [left, right)区间中的元素进行划分
	int div = partion(a, left, right);
    //以div为边界递归左右区间
	QuickSort(a, left, div - 1);
	QuickSort(a, div+1, right);
}

 

 

二、按基准值划分区间

 1.hoare版本

74ee7452d4e74d599ae4c16c4a60ef2a.gif        

 

         首先选取一个key值,一般选取最左或最右。当选最左为key值时需要R先移动,当R所在位置的数值小于key值时停下,L移动找到比key值大的位置,然后交换L和R的数值,重复以上操作直到L和R相遇。一趟排序完成后key值被放在了正确的位置即左子序列均小于key值,右子序列均大于key值。这里值得注意的是当key值选取最左端时需要R先移动,选取最右端时需要L先移动。这是因为相遇时的数值与后移动的一方有关,如果key值选左而L先移动那么就会将比key值大的数字交换到最左边,这不符合规则。hoare版本划分区间代码:

int Partion(int* a, int left, int right) {
	int key = left;
	while (left < right) {
		while (left < right && a[right] >= a[key]) {
			right--;
		}
		while (left < right && a[left] <= a[key]) {
			left++;
		}
		swap(&a[left], &a[right]);
	}
	swap(&a[key], &a[left]);

	return left;
}

2.挖坑法

        93cd6bb281484f7097675990dab91ea3.gif

        挖坑法是hore法的一种变形 ,先将第一个数据存放在临时变量key中,形成一个坑位。R移动找到比key值小的就停下与坑位交换,L再移动找到比key值大的与坑位交换。相遇时将key值填入坑位。挖坑法划分区间代码:

int partion(int* a, int left, int right) {
	int key = a[left];
	int pivot = left;
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		a[pivot] = a[right];
		pivot = right;
		while (left < right && a[left] <= key) {
			left++;
		}
		a[pivot] = a[left];
		pivot = left;
	}
	a[pivot] = key;
	return pivot;
}

3.前后指针法

        f67123fc8ea34abca11392728ac954d6.gif

         初始时pre指向序列起始位置,cur指向其后。cur移动找到比key值小的时停下,交换cur的值和pre后一位置的值。cur继续移动重复操作直到越界。

int partion(int* a, int left, int right) {
	int pre = left;
	int key = left;
	int cur = pre + 1;
	while (cur <= right) {
		if (a[cur] < a[key] && ++pre != cur) {
			swap(&a[pre], &a[cur]);
		}
		cur++;
	}
	swap(&a[key], &a[pre]);
	return pre;
}

 三、快速排序非递归实现

        递归版本中每个创建的栈帧都保存了当前区间的left和right值,我们也可以利用栈这种数据结构存入待处理的区间,每次取出区间后按key值划分区间,并将形成的两个新区间压入栈中,当划分的区间内没有值时停止压栈,重复操作直到栈空。

//写的栈的代码就不列出
void QuickSortNonR(int* a, int left, int right) {
	ST st;//创建栈
	StackInit(&st);//初始化栈
    //将左右区间值压入栈中
	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st)) {
        //取出栈中存放的左右区间值
		int left=StackTop(&st);
		StackPop(&st);
		int right=StackTop(&st);
		StackPop(&st);
        //划分区间,前面已讲
		int div = partion(a, left, right);
        //区间内有值继续压入栈中
		if (div + 1 < right) {
			StackPush(&st, right);
			StackPush(&st, div+1);
		}
		if (left + 1 < div) {
			StackPush(&st, div-1);
			StackPush(&st, left);
		}
	}	
	StackDestroy(&st);//销毁栈
}

四、优化

1.三数取中法选key值

        理想情况下快速排序的时间复杂度为O(n*logn),但是如果待排序的序列是有序的,每次划分区间时选择左值为key值,划分后左子区间都是是不存在的,此时时间复杂度变成了O(n^2).

a515ade2462d4f96ba99ebdd3625eea6.png

 解决方法:

(1).随机选择key值,不推荐

(2).三数取中,选择left、right、left+(right-left)/2,这三个位置的中间大小的数值做key值。

2.递归到小的子区间时,考虑使用插入排序

        与二叉树结构类似,当递归到最后几层时,大量的小区间调用了函数,代价较大。于是就有了小区间优化的概念。当区间的序列个数小于一定数时(这里取十),采用插入排序。

 快速排序优化版本完整代码:

int FindMiddle(int* a, int left, int right) {
	int middle = left+(right-left)/2;
	if (a[right] > a[middle]) {
		if (a[middle] > a[left]) {
			return middle;
		}
		else {
			return left;
		}
	}
	else {
		if (a[right] > a[left]) {
			return right;
		}
		else {
			return left;
		}
	}
}
//前后指针法划分区间
int partion(int* a, int left, int right) {
	int mid = FindMiddle(a,left,right);//取中
    swap(&a[left],&a[mid);//交换mid位置的值和left的值
    int pre = left;
    int key=left;
	int cur = pre + 1;
	while (cur <= right) {
		if (a[cur] < a[key] && ++pre != cur) {
			swap(&a[pre], &a[cur]);
		}
		cur++;
	}
	swap(&a[key], &a[pre]);
	return pre;
}

void QuickSort(int* a, int left, int right) {
	if (left+1>=right) {
		return;
	}
    //小区间优化,区间内的个数小于10时采用插入排序
    if(right-left<10){
        InsertSort(a,right-left+1);
    }else{
	int div = partion(a, left, right);
	QuickSort(a, left, div - 1);
	QuickSort(a, div+1, right);
    }    
}

         特殊情况:当待排序序列为同一个值或者大量相同数值时,优化也不能解决此类问题,此时不建议采用快速排序。

        

          

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

经典排序之快速排序 的相关文章

随机推荐

  • centos7安装配置supervisor保姆教程

    介绍 Supervisor是一个进程管理工具 是由python语言编写 基于linux操作系统的一款服务器管理工具 用以监控服务器的运行 发现问题能立即自动预警及自动重启等功能 是一个客户 服务器系统 服务器端称为supervisord 管
  • chisel相比verilog优势之一:复用特性

    0 绪论 世界由于人这个最大的无厘头变量 还是比技术本身复杂难懂很多 各种技术的兴起与发展总是有其背后的理由的 这篇文章是这个系列的第三篇文章 主要来说明Chisel比Verilog在某些方面具有优势的理由 换句话说 为什么要用Chisel
  • S32DS IDE使用Tips--参考汽车电子expert成长之路

    目录 一 S32DS for Arm PA PEMicro系列调试器包括以下接口类型 1 如何创建在MCU应用工程中添加SDK 2 如何使用SDK的demo工程 3 如何查看SDK外设组件 Component 的帮助文档 4 S32DS 使
  • 网络--TCP/IP

    TCP IP 是供已连接因特网的计算机进行通信的通信协议 在 TCP IP 内部 TCP IP不是一个协议 而是一个协议族的统称 包含一系列用于处理数据通信的协议 TCP 传输控制协议 应用程序之间通信 UDP 用户数据包协议 应用程序之间
  • [软件工程]毕业设计选题软件

    1 分析文档 1 1软件功能概述 本系统由3个功能模块组成 分别是学生功能模块 教师功能模块 教务员功能模块 附加一个独立的高级查询模块 学生功能 l 学生可以在任何能够连接Internet的计算机登录到毕业设计选题系统中 l 学生可以在选
  • HBase分布式架构处理大数据量(高并发和实时处理)

    先来了解下Hadoop的简单原理 一 HDFS主要是用于做什么的 HDFS Hadoop Distributed File System 分布式文件管理系统 是Hadoop项目的核心子项目 是分布式计算中数据存储管理的基础 是基于流数据模式
  • (董付国)Python 学习笔记---Python面向对象程序设计(3)

    6 4 2 案例精选 例6 1 自定义数组 在MyArray py文件中 定义了一个数组类 重写了一部分特殊方法以支持数组之间 数组与整数之间的四则运算以及内积 大小比较 成员测试和元素访问等运算符 class MyArray 在内部封装
  • js-用onclick写的手动轮播图

  • C++调用外部应用程序的方法的整理总结

    一 三个SDK函数 WinExec ShellExecute CreateProcess可以实现调用其他程序的要求 其中以WinExec最为简单 ShellExecute比WinExec灵活一些 CreateProcess最为复杂 WinE
  • NestedScrollView使用和理解

    https www jianshu com p fda06c916d6b 一 前言 NestedScrollView 即 支持嵌套滑动的 ScrollView 因此 我们可以简单的把 NestedScrollView 类比为 ScrollV
  • MyBatis动态SQL 多条件查询(if、where、trim标签)

    一 动态SQL概述 以前在使用JDBC操作数据时 如果查询条件特别多 将条件串联成SQL字符串是一件痛苦的事情 通常的解决方法是写很多的if else条件语句对字符串进行拼接 并确保不能忘了空格或在字段的最后省略逗号 MyBatis使用动态
  • Ubuntu18.04 (腾讯云服务器)安装MySQL 5.7,更改MySQL的root密码、并使其可远程登录的一种配置方式

    一 安装MySQL 首先需要在Ubuntu中安装MySQL 5 7相关命令如下 sudo apt get install mysql server 5 7 二 修改MySQL的root密码 1 登录 sudo mysql 上面这一步也可以使
  • 量子通信(QKD)与BB84协议

    量子密钥分发 QKD Quantum key Distribution QKD是量子信息的一个重要分支 也称为 量子保密通信 一个量子通信的课程推荐给大家 论述的全面 详细 https ke qq com course 382160 一个系
  • c#文件下载示例的4种方法

    原文链接 http www jb51 net article 57068 htm C 实现HTTP下载文件的方法
  • 电脑可以聊微信但是无法上网页的解决方法

    电脑可以聊微信但是无法上网页 ping不通百度的IP地址 一般是电脑的DNS出现错误 解决方案如下 打开360安全卫士 点击功能大全中的断网急救箱 进行扫描 之后进行修复 问题即可解决
  • clickhouse重启报错以及远程无法连接

    1 启动报Processing configuration file etc clickhouse server config xml 文件没有写入权限 先添加写入权限 sudo chmod 600 etc clickhouse serve
  • JVM性能优化之Tomcat服务器参数配置优化

    前言 tomcat 服务器在JavaEE项目中使用率非常高 所以在生产环境对tomcat的优化也变得非常重要了 对于tomcat的优化 主要是从2个方面入手 一是tomcat本身的配置 另一个是tomcat所运行的Jvm虚拟机的调优 优化传
  • Windows XP环境下IPSec 隧道的配置

    前言 这是这学期防火墙课程的一个实验 觉得挺有意义 所以记录在博客里 一 实验目的 本实验主要验证IP通信在建立IPSec隧道前后的变化 为了简化实验过程 这里只对ICMP进行加密 但在配置的过程中即可发现 其他IP协议要进行同样的加密也是
  • 【轩说AI】生成模型(1)——自编码器AE+变分自编码器VAE

    文章目录 生成模型 从概率分布的角度去理解 生成 一张图片 生成宝可梦 生成系列图片 自动编码器Auto Encoder AE的模型及其存在的问题 AE中的高斯混合模型 AE的训练情况 举例理解从AE到VAE 变分自动编码器Variatio
  • 经典排序之快速排序

    一 概述 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法 其基本思想为 任取待排序元素序列中的某元素作为基准值 按照该排序码将待排序集合分割成两子序列 左子序列中所有元素均小于基准值 右子序列中所有元素均大于基准值 然后