算法导论 学习笔记 第七章 快速排序

2023-11-20

快排最坏时间复杂度为Θ(n²),但它的平均性能很好,通常是实际排序应用中最好的选择,它的期望时间复杂度为Θ(nlgn),且Θ(nlgn)中隐含的常数因子非常小,且它还能进行原址排序。

快排也使用了分治思想:
1.分解:数组被划分为两个子数组,使得一个子数组中的每个元素都小于A[q],而另一个子数组中的每个元素都大于A[q]。
2.解决:通过递归调用快排,对两个子数组进行排序。
3.合并:子数组都是原址排序,不需要合并操作。

快排伪代码:

QUICKSORT(A, p, r):
if p < r
	q = PARTITION(A, p, r)
	QUICKSORT(A, p, q - 1)
	QUICKSORT(A, q + 1, r)

PARTITION过程:

PARTITION(A, p, r):
x = A[r]
i = p - 1
for j = p to r - 1
	if A[j] <= x
		i = i + 1
		exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

以下是PARTITION图解,它选择x=A[r]作为主元,并围绕它来划分子数组:
在这里插入图片描述
PARTITION的时间复杂度为O(n)。

当数组中的值都相同时,PARTITION返回r,可以使算法在数组中所有值都相同时,返回一个中间的值。

快排的运行时间依赖于划分是否平衡,而平衡与否依赖于用于划分的元素。

当划分产生的两个子问题分别包含n-1个元素和0个元素时,快排的最坏情况发生,此时时间复杂度为Θ(n²)。

快排的最好情况是每一层递归都平衡划分子数组,即PARTITION得到的两个子问题的规模都不大于n/2(一个⌊n/2⌋,一个⌈n/2⌉-1),此时时间复杂度为Θ(nlgn)。

快排的平均运行时间更接近其最好情况,即使每次划分子数组总是产生9:1的划分:
在这里插入图片描述
在这里插入图片描述
虽然递归每一层都产生9:1的划分,直观上看起来非常不平衡,但运行时间还是O(nlgn)。事实上,任何一种常数比例的划分都会产生Θ(lgn)的递归树,其中每一层的代价都是O(n)。

一个好的和坏的划分交替出现的序列和每次都是完美划分的序列快排时的时间复杂度相同,只是前者情况下,O符号中隐含的常数因子大一些:
在这里插入图片描述
对几乎有序的序列排序时,插入排序性能往往要优于快排。

我们可以通过在算法中引入随机性,使得算法对于所有输入都能获得较好的期望性能。我们可以采用随机抽样的方法选出主元:

RANDOMIZED-PARTITION(A, p, r):
i = RANDOM(p, r)
exchange A[r] with A[i]
return PARTITION(A, p, r)

使用随机方法选主元的快排代码:

#include <iostream>
#include <vector>
#include <random>
#include <time.h>
using namespace std;

size_t partition(vector<int> &ivec, size_t start, size_t end) {
	uniform_int_distribution<size_t> u(start, end);
	default_random_engine e(time(0));
	size_t rand = u(e);

	swap(ivec[end], ivec[rand]);

	size_t firstBigIndex = start;
	for (size_t i = start; i < end; ++i) {
		if (ivec[i] < ivec[end]) {
			swap(ivec[i], ivec[firstBigIndex]);
			++firstBigIndex;
		}
	}

	swap(ivec[firstBigIndex], ivec[end]);

	return firstBigIndex;
}

void quickSort(vector<int> &ivec, size_t start, size_t end) {
	size_t mid = partition(ivec, start, end);
	if (start < mid) {
		quickSort(ivec, start, mid - 1);
	}
	if (end > mid) {
		quickSort(ivec, mid + 1, end);
	}
}

int main() {
	vector<int> ivec = { 4,5,7,3,2,1,9,6 };
	quickSort(ivec, 0, ivec.size() - 1);
	
	for (int i : ivec) {
		cout << i;
	}
	cout << endl;
}

当输入数据几乎有序时,插入排序速度很快,可以利用它提高快排的速度,当对一个长度小于k的子数组调用快排时,让它不做任何排序就返回,当上层快排调用返回后,对整个数组运行插入排序完成排序过程,这一算法的时间复杂度为O(nk+nlg(n/k)),理论上,k的取值为:
在这里插入图片描述
这是不可能的,如果加上常数因子:
在这里插入图片描述
实践中,需要根据实验测试k的取值。

可将PARTITION方法中选主元的过程改为从数组中随机选3个元素,选择中间大小的数字作为主元所在下标。

Hoare设计的划分算法:

HOARE-PARTITION(A, p, r):
x = A[p]
i = p - 1
j = r + 1
while TRUE
	repeat 
		j = j - 1
	until A[j] <= x
	repeat 
		i = i + 1
	until A[i] >= x
	if i < j
		exchange A[i] with A[j]
	else 
		return j

以上代码中的repeat-until相当于do-while,因此,循环内容无论如何都会至少执行一次。

使用以上过程的快排代码:

#include <iostream>
#include <vector>
using namespace std;

int partition(vector<int> &ivec, int start, int end) {
	int sign = ivec[start];
	int l = start - 1;
	int r = end + 1;
	
	while (true) {
		do {
			--r;
		} while (ivec[r] > sign);

		do {
			++l;
		} while (ivec[l] < sign);

		if (l < r) {
			swap(ivec[l], ivec[r]);
		} else {    // 返回前,整个数组分为两部分,start~j子数组中的元素全部小于等于j+1~end子数组中的元素
			return r;
		}
	}
}

void quickSort(vector<int> &ivec, int start, int end) {
	if (start >= end) {
		return;
	}

	int mid = partition(ivec, start, end);
	quickSort(ivec, start, mid);
	quickSort(ivec, mid + 1, end);
}

int main() {
	vector<int> ivec = { 8,6,9,5,3,2,0,1,4,7,6,9,2,3 };
	quickSort(ivec, 0, ivec.size() - 1);

	for (int i : ivec) {
		cout << i;
	}
	cout << endl;
}

运行它:
在这里插入图片描述

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

算法导论 学习笔记 第七章 快速排序 的相关文章

随机推荐

  • Ubuntu - OpenSSH安装或升级

    1 准备安装包 openssl 1 0 2o tar gz wget https www openssl org source openssl 1 0 2o tar gz https www openssl org source opens
  • 插入排序超详解释,一看就懂

    目录 一 插入排序的相关概念 1 基本思想 2 基本操作 有序插入 二 插入排序的种类 三 直接插入排序 1 直接插入排序的过程 顺序查找法查找插入位置 2 使用 哨兵 直接插入排序 四 直接插入排序算法描述 五 折半插入排序 1 查找插入
  • linux 操作mysql 命令_linux下mysql操作命令大全

    Linux下掌握基本的mysql操作命令大全能够为你学习linux中mysql提供更好的帮助 下面由学习啦小编为大家整理了linux下mysql操作命令大全的相关知识 希望对大家有帮助 linux的mysql操作命令大全详解 linux的m
  • uniapp.request遇到的坑

    uniapp request遇到的坑 发起post请求的时候data接收不到参数 解决 发起请求中添加 uni request header content type application x www form urlencoded
  • 索引优化之Explain 及慢查询日志

    索引 本质是数据结构 简单理解为 排好序的快速查找数据结构 以索引文件的形式存储在磁盘中 目的 提高数据查询的效率 优化查询性能 就像书的目录一样 优势 提高检索效率 降低IO成本 排好序的表 降低CPU的消耗劣势 索引实际也是一张表 该表
  • Java 实现MySQL数据库插入1000万条数据

    Java 实现MySQL数据库插入1000万条数据 针对某些特殊测试 需要添加大量数据 且这些测试具有一定的规律性 可以按照以下的sql脚本循环添加 可以是1000条 也可以是一百万条 Java实现 准备表一张 CREATE TABLE t
  • 日志清理脚本

    需求背景 解决某些中间件或者应用日志无法自动清理的情况 比如 Nacos 的 access 日志清理 临时目录文件清理等 简介 Filename clear logs sh Revision 0 0 3 Date 2020 06 05 Au
  • 探究Xcode New Build System对于构建速度的提升

    在Xcode9发布的时候 Apple在Build System上提供了新版本的构建系统 New Build System 具体可见WWDC2017 不过令人失望的是 该新特性的讲解很简短 短到只在一页PPT上露脸 在这短短的时间里 苹果讲述
  • Python 生成器如何设置和使用

    Python 的生成器其实可以理解为一种比较复杂的迭代器 关于迭代器 可以参考 Python 迭代器的设置和使用方法 一 代码举例 def gen x y txt I love x yield txt txt 1 You love y yi
  • 作用域和内存问题

    文章目录 一 基本类型和引用类型的值 基本类型和引用类型的区别 1 动态的属性 2 复制变量值 3 传递参数 4 监测类型 二 执行环境及作用域 1 延长作用域链 2 没有块级作用域 一 基本类型和引用类型的值 变量可能包括两种不同的数据类
  • 【Docker】之安装 Redis

    一 下载 Redis 镜像 下载最新版 Redis 镜像 默认版本为 latest docker pull redis 更多版本镜像 1 访问 Docker 官网 https hub docker com 在镜像搜索栏中输入 Redist
  • flea-auth使用之用户子模块介绍

    用户子模块 本篇主要介绍笔者 授权模块 flea auth 下的用户子模块 1 总览 表名 中文描述 flea account 账户 flea account attr 账户扩展属性 flea user 用户 flea user attr
  • libevent的消息传递和回调注册函数

    参考原帖地址 https www cnblogs com secondtonone1 p 5554075 html 1 evconnlistener new bind函数 1 evconnlistener new bind 完成socket
  • JDK8下载安装

    参考 JDK8下载安装教程 涵涵想养猫的博客 CSDN博客 jdk8下载安装 下载地址 https www oracle com java technologies javase javase jdk8 downloads html 根据需
  • python 中 os._exit(), sys.exit()

    1 os exit 不抛异常 后面的代码就不执行了 不执行相关清理工作 直接退出 Python 解释器一般来说用在子线程中退出 2 sys exit 引发一个 SystemExit 异常 没有捕获这个异常 会直接退出 捕获这个异常可以做一些
  • 软考:中级软件设计师:程序语言基础:表达式,标准分类,法律法规,程序语言特点,函数传值传址

    软考 中级软件设计师 程序语言基础 表达式 提示 系列被面试官问的问题 我自己当时不会 所以下来自己复盘一下 认真学习和总结 以应对未来更多的可能性 关于互联网大厂的笔试面试 都是需要细心准备的 1 自己的科研经历 科研内容 学习的相关领域
  • 禁止ios浏览器页面上下滚动 (橡皮筋效果)弹性滚动 微信的下拉回弹

    发现之前阻止页面滚动的代码e preventDefault代码失效了 于是自己折腾了一番 找到了解决办法 一 前言 浏览器在移动端有一个默认触摸滚动的效果 让我们感触最深的莫过于微信浏览器里面 下拉时自带橡皮筋的效果 然而在开发的时候我们经
  • 软件测试日常分享

    以下是测试主管 测试经理 质量保证经理的面试问题和答案 供新人和有经验的求职者获得他们梦想的工作 1 测试经理的职责是什么 QA经理的角色包括 从启动到结束管理项目 测试计划 获得客户对交付成果的认可 向客户端批准中间交付物和补丁发布 提交
  • 动手学深度学习——softmax回归(原理解释+代码详解)

    目录 1 softmax回归 1 1 分类问题 1 2 网络架构 1 3 全连接层的参数开销 1 4 softmax运算 1 5 小批量样本的矢量化 1 6 损失函数 1 6 1 对数似然 1 6 2 softmax及其导数 1 6 3 交
  • 算法导论 学习笔记 第七章 快速排序

    快排最坏时间复杂度为 n 但它的平均性能很好 通常是实际排序应用中最好的选择 它的期望时间复杂度为 nlgn 且 nlgn 中隐含的常数因子非常小 且它还能进行原址排序 快排也使用了分治思想 1 分解 数组被划分为两个子数组 使得一个子数组