快速排序—C语言实现

news2024/9/23 13:17:50

目录

前言

快速排序

实现逻辑

1. hoare版本​编辑

2. 挖坑法

3. 前后指针版本

快速排序优化

1. 三数取中法选key

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

快速排序非递归(用栈实现)

快速排序的特性总结

 全部代码

前言

        🥰在学数据结构的第一节课就知道了数据结构课程是要管理并且学会操作数据,当然操作数据首先想到的就是数据的排序,排过顺序的数据的使用价值才够大。前面我们学习了顺序表也学习了链表等等,这些就是储存数据的方法,下面我们来看一看传说中的快速排序的特点与效率怎么样。😍

快速排序

        快速排序,又称划分交换排序(partition-exchange sort) 

中文名快速排序算法外文名quick sort
别    名快速排序提出者C. A. R. Hoare
提出时间1960年时间复杂度O ( n*\log_{2}^{}\textrm{n} )
空间复杂度O ( \log _{2}^{}\textrm{n} )

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

实现逻辑

        快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

① 从数列中挑出一个元素,称为 “基准”(pivot),
② 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
③ 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

        递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
    if(right - left <= 1)
        return;

    // 按照基准值对array数组的 [left, right)区间中的元素进行划分
    int div = partion(array, left, right);
    // 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
    // 递归排[left, div)
    QuickSort(array, left, div);
    // 递归排[div+1, right)
    QuickSort(array, div+1, right);
}

        上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。将区间按照基准值划分为左右两半部分的常见方式有:

1. hoare版本
【数据结构】快速排序递归实现 _三种方法详解+优化_快速排序 递归_JoyCheung-的博客-CSDN博客

代码 

int PartSort1(int* a, int begin, int end)
{

	int left = begin, right = end;
	int keyi = left;
	while (left < right)
	{
		//右边先走,找比a[keyi]小的
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//左边先走,找比a[keyi]大的
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		//交换左右边
		Swap(&a[left], &a[right]);
	}
	//交换keyi与交点的值
	Swap(&a[left], &a[keyi]);
	keyi = left;
}

 

 2. 挖坑法

Java集合与数据结构——七大排序算法的实现_RAIN 7的博客-CSDN博客

代码

// 挖坑法
int PartSort2(int* a, int begin, int end)
{
	int key = a[begin];
	int piti = begin;
	while (begin < end)
	{
		// 右边找小,填到左边的坑里面去。这个位置形成新的坑
		while (begin < end && a[end] >= key)
		{
			--end;
		}

		a[piti] = a[end];
		piti = end;

		// 左边找大,填到右边的坑里面去。这个位置形成新的坑
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}

		a[piti] = a[begin];
		piti = begin;
	}

	a[piti] = key;
	return piti;
}

 

 3. 前后指针版本

代码

int PartSort3(int* a, int begin, int end)
{
	int keyi = begin;
	int cur = begin + 1;
	int prev = begin;

	// 加入三数取中的优化
	int midi = GetMidIndex(a, begin, end);
	Swap(&a[keyi], &a[midi]);

	while (cur <= end)
	{
		if (a[cur] < a[keyi])
		{
			prev++;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[begin], &a[prev]);
	keyi = prev;
	return keyi;
}

 

快速排序优化


1. 三数取中法选key

        当我们知道这组无序数列的首和尾后,我们便可以求出这个无需数列的中间位置的数,我们只需要在首,中,尾这三个数据中,选择一个排在中间的数据作为基准值,进行快速排序,即可进一步提高快速排序的效率。那么为什么要取中间呢?我们可以假设待排序的数列是一组高度有序的数列,显然首极大可能是最小值,尾极大可能是最大值,此时如果我们选取一个排在中间的值,哪怕是在最坏的情况下,begin和end只需要走到中间位置,那么这个中间值的位置也就确定下来,而不需要begin或end指针要把整个数列遍历一边,从而大大提高快速排序的效率。即取数组最左端最右端以及数组中间三个数的中间数为分区点,减少采用左右端点碰到极端顺序的出现的最坏情况( 当选取左右端点,碰到数据有序,从大到小或是从小到大的情况 ,算法时间复杂度就会变成最坏时间复杂度)。

int GetMidIndex(int* a, int begin, int end)  // 三数取中,优化算法,避免发生最坏的情况
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return end;
		}
		else
		{
			return begin;
		}
	}
	else // (a[begin] >= a[mid])
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}

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

        序列长度达到一定大小时,使用插入排序当快排达到一定深度后,划分的区间很小时,再使用快排的效率不高。当待排序列的长度达到一定数值后,可以使用插入排序。由《数据结构与算法分析》(Mark Allen Weiness所著)可知,当待排序列长度为5~20之间,此时使用插入排序能避免一些有害的退化情形。

void Qsort(int* a, int begin, int end)
{
	if (begin >= end) return;

	if (end - begin > 10)// 优化二:在递归到剩余数据量小于一定值的时候跳出递归,进行小数据量的插入排序
	{
		int keyi = PartSort3(a, begin, end);
		// [begin, keyi-1] keyi [keyi+1, end]
		Qsort(a, begin, keyi - 1);
		Qsort(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

快速排序非递归(用栈实现)

void QsortStack(int* a, int begin, int end)
{
	ST st;
	STInit(&st);
	STPush(&st, end);
	STPush(&st, begin);
	while (!STEmpty(&st))
	{
		int left = STTop(&st);
		STPop(&st);
		int right = STTop(&st);
		STPop(&st);
		int keyi = PartSort3(a, left, right);
		// [left, keyi-1] keyi[keyi+1, right]

		if (keyi + 1 < right)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}



	STDestroy(&st);

}

快速排序的特性总结

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定

 全部代码

//快速排序
int PartSort1(int* a, int begin, int end)
{

	int left = begin, right = end;
	int keyi = left;
	while (left < right)
	{
		//右边先走,找比a[keyi]小的
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//左边先走,找比a[keyi]大的
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		//交换左右边
		Swap(&a[left], &a[right]);
	}
	//交换keyi与交点的值
	Swap(&a[left], &a[keyi]);
	keyi = left;
}
// 挖坑法
int PartSort2(int* a, int begin, int end)
{
	int key = a[begin];
	int piti = begin;
	while (begin < end)
	{
		// 右边找小,填到左边的坑里面去。这个位置形成新的坑
		while (begin < end && a[end] >= key)
		{
			--end;
		}

		a[piti] = a[end];
		piti = end;

		// 左边找大,填到右边的坑里面去。这个位置形成新的坑
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}

		a[piti] = a[begin];
		piti = begin;
	}

	a[piti] = key;
	return piti;
}
int GetMidIndex(int* a, int begin, int end)  // 三数取中,优化算法,避免发生最坏的情况
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return end;
		}
		else
		{
			return begin;
		}
	}
	else // (a[begin] >= a[mid])
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}
int PartSort3(int* a, int begin, int end)
{
	int keyi = begin;
	int cur = begin + 1;
	int prev = begin;

	// 加入三数取中的优化
	int midi = GetMidIndex(a, begin, end);
	Swap(&a[keyi], &a[midi]);

	while (cur <= end)
	{
		if (a[cur] < a[keyi])
		{
			prev++;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[begin], &a[prev]);
	keyi = prev;
	return keyi;
}
void Qsort(int* a, int begin, int end)
{
	if (begin >= end) return;

	if (end - begin > 10)// 优化二:在递归到剩余数据量小于一定值的时候跳出递归,进行小数据量的插入排序
	{
		int keyi = PartSort3(a, begin, end);
		// [begin, keyi-1] keyi [keyi+1, end]
		Qsort(a, begin, keyi - 1);
		Qsort(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

void QsortStack(int* a, int begin, int end)
{
	ST st;
	STInit(&st);
	STPush(&st, end);
	STPush(&st, begin);
	while (!STEmpty(&st))
	{
		int left = STTop(&st);
		STPop(&st);
		int right = STTop(&st);
		STPop(&st);
		int keyi = PartSort3(a, left, right);
		// [left, keyi-1] keyi[keyi+1, right]

		if (keyi + 1 < right)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}



	STDestroy(&st);

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/739507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea-spring boot开发

安装maven与配置配置maven安装插件 已经装好了idea与jdk 安装maven与配置 下载地址: https://maven.apache.org/download.cgi 下载合适的版本 配置maven 打开设置: 直接搜索 :maven 配置变量: 此电脑->属性->高级系统设置->环境变量 新建系统变量 MAVEN_HOME&#xff…

Web安全——渗透测试基础知识下

渗透测试基础 Web安全一、VMware虚拟机学习使用1、虚拟机简单介绍2、网络模式2.1 桥接网络&#xff08;Bridged Networking&#xff09;2.2 NAT模式2.3 Host-Only模式 3、通俗理解 二、Kali的2021安装与配置1、简单介绍2、Kali的版本3、配置3.1 安装虚拟机open-vm-tools-deskto…

基于matlab从ROI和蒙版在图像中创建标记(附源码)

一、前言 此示例演示如何从一组 ROI 创建标记的阻止映像。 在此示例中&#xff0c;您使用两种方法来获取和显示标记的数据。一种方法使用多边形ROI对象来存储肿瘤和正常组织区域边界的坐标。该函数将ROI坐标转换为标记的块图像。第二种方法使用掩码来指示图像的二进制分割为组…

能不能推荐个 vue 后台管理系统模板?

前言 下面是我整理的vue2和vue3的一些后台管理系统模板&#xff0c;希望对你有帮助~ Vue2 1、iview-admin Star: 16.4k 基于 iview组件库开发的一款后台管理系统框架&#xff0c;提供了一系列的强大组件和基础模板&#xff0c;方便开发人员快速搭建一套功能丰富、界面美观、…

web入门案例-部门篇

开发流程 完成对应部门管理和员工管理的需求 准备工作 注意&#xff1a;service还要写接口实体类&#xff0c;mapper只写接口即可&#xff0c;controller是实体类 对应的三个注解 RestController&#xff08;方法返回值作为响应值&#xff09; Mapper(控制反转IOC&#xff0c…

漏洞深度分析 | CVE-2023-36053-Django 表达式拒绝服务

​ 项目介绍 Django 是一个高级 Python Web 框架&#xff0c;鼓励快速开发和简洁、务实的设计。它由经验丰富的开发人员构建&#xff0c;解决了 Web 开发的大部分麻烦&#xff0c;因此您可以专注于编写应用程序&#xff0c;而无需重新发明轮子。它是免费且开源的。 项目地址…

CodeTop整理-树篇

目录 103. 二叉树的锯齿形层次遍历 236. 二叉树的最近公共祖先 124. 二叉树中的最大路径和 102. 二叉树的层序遍历 94. 二叉树的中序遍历 110. 平衡二叉树 572. 另一个树的子树 96. 不同的二叉搜索树 543. 二叉树的直径 297. 二叉树的序列化与反序列化 199. 二叉树的…

eNSP-VRRP虚拟路由器冗余技术

VRRP-虚拟路由器冗余技术 文章目录 VRRP-虚拟路由器冗余技术一、拓扑结构二、基本配置三、测试验证四、知识点详解1.VRRP路由器2.报文格式3.工作过程 一、拓扑结构 二、基本配置 R1: #配置ip <Huawei>sys [Huawei]sys r1 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip a…

快速排序算法!

快速排序 什么是快速排序&#xff08;quickSort&#xff09;&#xff1f; 主要分成两部分实现&#xff0c;分区、递归操作。 分区 从数组中任意选择一个 “基准”&#xff0c;所有比基准小的元素放在基准前面&#xff0c;比基准大的元素放在基本后面。 递归 递归地对基准…

Todo-List案例版本四

全局事件总线 使用步骤 1.定义全局事件总线 new Vue({...beforeCreated(){Vue.prototype.$busthis //安装全局事件总线&#xff0c;$bus就是当前应用的vm}... }) 2.使用事件总线 a.接收数据&#xff1a;A组件想接收数据&#xff0c;则在A组件中给$bus绑定自定义事件&…

Outlook---撤回(或替换)已发出的邮件

0 Preface/Foreword 发送邮件时&#xff0c;发现邮件发错了&#xff0c;或者忘了添加附件&#xff0c;那么就需要用到撤回或者替换功能。 1 撤回/替换邮件方法 步骤如下&#xff1a; 第一步&#xff1a;双击打开邮件&#xff1b; 第二步&#xff1a;找到撤回按键

自锁电路分析与应用

原理图分享 今天工作中遇到一个设计很妙的电路&#xff0c;请教了一下硬件的工程师。 大家自己直接看图可以分享出这个电路的作用吗&#xff0c;可以在评论区告诉我哦&#xff01; 自锁电路 如上图就是一个自锁电路&#xff0c;和下面的电路一样&#xff1a; 电路现象描述&a…

23.RTC实时时钟

1.STM32 RTC介绍&#xff1a; &#xff08;1&#xff09;RTC简介&#xff1a; STM32的实时时钟(RTC)是一个独立的定时器。STM32的RTC模块拥有一组连续计数的计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和…

虚幻5-could not find root physics body 布料系统问题解决方法

不做不知道自己身体好&#xff0c;又碰到问题了&#xff1a; could not find root physics body 1.据说是Skeleton 和SkeletaMesh傻傻分不清楚 &#xff08;但就是排查后&#xff0c;就不是这个问题&#xff09; 2.重新创造一个Physic Asset吧 Creating a New Physics Asse…

【C语言初阶(13)】三子棋游戏(优化:多子棋实现)

文章目录 一、游戏的实现思路二、游戏的实现步骤1. 菜单函数2. 设置棋盘3. 初始化棋盘4. 打印棋盘5. 玩家下棋6. 电脑下棋7. 多子棋判断输赢8. 判断棋盘是否已满 三、模块化代码实现1. test.c2. game.h3. game.c 四、结果演示 由于模块化编程的需要&#xff0c;我们需要把整个游…

解决Bridge材质导入到Blender为白色的问题

文章目录 前言一、复现问题二、解决方案总结 前言 一、复现问题 在Bridge上看到一块不错的草皮, 导入成功后是白色材质: 二、解决方案 以前用这个方法导入过模型, 那时候还没启用汉化, 也没什么材质问题. 这次操作之前刚启用了汉化, 我猜是汉化导致: 取消勾选’新建数据’, 重…

【Java】-初识java

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 文章目录 前言…

Linux 基础之 TOP 命令详解

文章目录 一、前言二、Top说明 一、前言 TOP 命令是 Linux 常用的性能分析工具&#xff0c;能够实时显示系统中各个进程资源占用状况&#xff0c;类似于 Windows 的任务管理器。 二、Top说明 当使用一个工具时&#xff0c;对此最快的了解方式就是查看说明&#xff0c;那就是…

6.任务调度:保存和还原现场,时间片轮转

实现任务调度&#xff0c;模拟时间片 1.任务调度 在进行上下文切换时&#xff0c;需要保存和切换以下内容&#xff1a; 寄存器&#xff1a;包括通用寄存器&#xff08;如 EAX、EBX、ECX等&#xff09;和特殊寄存器&#xff08;如程序计数器 PC、堆栈指针 SP、基址指针 BP等&a…

LeetCode | C++ 动态规划——完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

目录 完全背包518. 零钱兑换 II377. 组合总和 Ⅳ参考 完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包…