【排序算法】—— 快速排序

news2024/11/23 3:54:43

        快速排序的原理是交换排序,其中qsort函数用的排序原理就是快速排序,它是一种效率较高的不稳定函数,时间复杂度为O(N*longN),接下来就来学习一下快速排序。

一、快速排序思路

1.整体思路

以升序排序为例:

        (1)、首先随便找一个数(一般取首元素)作为排序对象(记为key),先将这个数排到准确的位置,即把小于等于key的全部放在key左边,大于key的全部放在key右边。这是排一趟需要做的事。

        (2)、排完一趟后被排的那个数key此时它在的位置是准确的,接下来只需要用同样的方法分别对它的左数组和右数组排序,这就有点类似于二叉树的前序遍历

如下:

void QuickSort(int* arr, int left, int right)
{
	if (left >= right)//如果左区间>=右区间就不用再排,直接返回。
		return;
	int key=_Sort3(arr, left, right);//该函数用来排单趟
	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

        至于排单趟的算法一般有三种分别是:霍尔法,挖坑法,前后指针法。在下面会细讲。 

2.单趟排序

2.1.霍尔法

        把第一个元素当排序对象key(首元素的下标),用L储存左下标,R储存右下标。先让R从右往左走找到比key小的元素时停下,然后让L从左往右走找到比key大的元素停下。然后把位于L位置和位于R位置的的值进行交换,最后重复上面的操作直到L>=R为止。再把key的位置与L位置的值交换。至此一趟排序就结束了。

代码示例:

int _Sort1(int* arr, int begin, int end)//霍尔法
{
	int key = begin;
	while (begin < end)
	{
		while (begin < end && arr[end] > arr[key])
			end--;
		while (begin < end && arr[begin] <= arr[key])
			begin++;
		Swap(&arr[begin], &arr[end]);
	}
	Swap(&arr[key], &arr[begin]);
	key = begin;
	return key;
}

2.1.挖坑法

          把首元素当做排序对象赋值给key,然后把第一个下标L当做坑位,让R从右边出发找到比key小的数放在坑位,然后R的位置变成坑位,让L从左往右找到比key大的数放在坑位,L位置变成坑位,再次让R走,循环往复直到L与R相遇。最后把key放在坑位,至此一趟排序结束。

 代码示例:

int _Sort2(int* arr, int begin, int end)
{
	int key = arr[begin];
	int kw = begin;//坑位下标
	while (begin < end)
	{
		while (begin < end && arr[begin] < key)
			begin++;
		arr[kw] = arr[begin];
		kw = begin;
		while (begin < end && arr[end] >= key)
			end--;
		arr[kw] = arr[end];
		kw = end;
	}
	arr[kw] = key;
	return kw;
}

2.3.前后指针法

         把第一个元素当排序对象key(首元素的下标),prev指向首元素下标,cur指向首元素下一个元素下标。

        如果cur指向的元素小于key的话prev向右走,并且如果prev不等于cur,那么把prev与cur互换。最后cur都需要无条件向右走一步。

代码示例:

int _Sort3(int* arr, int begin, int end)//前后指针法
{
	int perv = begin, cur = begin + 1;
	while (cur <= end)
	{
		if (arr[cur] < arr[begin] && ++perv != cur)
			Swap(&arr[perv], &arr[cur]);
		cur++;
	}
	Swap(&arr[perv], &arr[begin]);
	return perv;
}

二、提供效率的方法

1.三数取中(key值的选取)

        快速排序的"三数取中"(Median of Three)是一种优化技术,它相当于选排序对象key。其具体做法如下:

        (1).选择三个元素:从待排序数组中选择三个关键元素,通常是第一个元素、中间元素和最后一个元素。
        (2).比较并选择中位数:将这三个元素进行比较,选择值不是最大也不是最小的数。比如,如果三个元素分别是arr[left]、arr[mid]、arr[right],则中位数是介于这三个值之间的那个数。
        (3).交换为第一个元素:将选定的中位数元素与数组的第一个元素交换位置,以便在后续的快速排序中使用。

        通过使用"三数取中"的方法,可以有效地减少快速排序中最坏情况的发生概率,因为选取的主元更有可能接近数组的中值,而不是极端值。这样可以更均匀地划分数组,减少排序的平均时间复杂度,提高算法的整体性能。

2.小区间优化

        小区间优化指的是在快速排序算法中针对较小规模的子数组(或子问题)采用其他排序方法,而不是继续使用快速排序本身。这种优化技术的目的是避免在小规模问题上快速排序的递归调用带来的额外开销,提高算法的整体效率。
        具体来说,当快速排序的递归过程划分的子数组规模减小到一定程度时,可以选择使用插入排序或其他适合小规模数组的排序方法来处理。这样可以避免递归深度过深,减少递归调用和分区操作的开销,从而提高整体排序算法的性能。
具体的优化策略包括:

        (1).设定阈值:在实现快速排序时设定一个阈值,当子数组的大小小于这个阈值时,转而使用插入排序或者直接选择排序等方法进行排序。常见的阈值一般设定在5到10之间,具体取决于具体实现和排序数据的特性。
        (2).切换到其他排序算法:在快速排序的递归过程中,当子数组大小小于设定的阈值时,停止快速排序的递归,转而使用其他排序算法完成剩余的排序工作。

        小区间优化技术能有效降低快速排序在小规模数据上的不必要开销,提高整体排序算法的效率和性能。

三、非递归实现

        把递归该为非递归毋庸置疑首先考虑使用栈结构,这里我们需要抓住重点,考虑需要用栈结构储存什么数据,先从函数参数上看左右区间的参数是一直在变化的,那么只需要把区间储存到栈中,每次从栈中就可以取到需要排序的区间,对这个区间进行一趟排序后再分为两个小区间存入栈中,重复以上操作直到栈为空。

四、源码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include"Stack.h"
#define size 100
void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
void InsertSort(int* arr, int sz)//插入排序(小区间优化)
{
	for (int i = 0; i < sz - 1; i++)
	{
		int j = i;
		int tmp = arr[j + 1];
		while (j >= 0 && tmp < arr[j])
		{
			arr[j + 1] = arr[j--];
		}
		arr[j + 1] = tmp;
	}
}
int Sk(int* arr, int left, int right)//三数取中
{
	int v = (left + right) / 2;
	if (arr[left] > arr[right])
	{
		if (arr[right] > arr[v])
			return right;
		else
		{
			if (arr[left] < arr[v])
				return left;
			else
				return v;
		}
	}
	else
	{
		if (arr[left] > arr[v])
			return left;
		else
		{
			if (arr[right] > arr[v])
				return right;
			else
				return v;
		}
	}
}
int _Sort1(int* arr, int begin, int end)//霍尔法
{
	int key = begin;
	while (begin < end)
	{
		while (begin < end && arr[end] > arr[key])
			end--;
		while (begin < end && arr[begin] <= arr[key])
			begin++;
		Swap(&arr[begin], &arr[end]);
	}
	Swap(&arr[key], &arr[begin]);
	key = begin;
	return key;
}
int _Sort2(int* arr, int begin, int end)//挖坑法
{
	int val = arr[begin];
	int kw = begin;//坑位
	while (begin < end)
	{
		while (begin < end && arr[begin] < val)
			begin++;
		arr[kw] = arr[begin];
		kw = begin;
		while (begin < end && arr[end] >= val)
			end--;
		arr[kw] = arr[end];
		kw = end;
	}
	arr[kw] = val;
	return kw;
}
int _Sort3(int* arr, int begin, int end)//前后指针法
{
	int perv = begin, cur = begin + 1;
	while (cur <= end)
	{
		if (arr[cur] < arr[begin] && ++perv != cur)
			Swap(&arr[perv], &arr[cur]);
		cur++;
	}
	Swap(&arr[perv], &arr[begin]);
	return perv;
}
void QuickSort(int* arr, int left, int right)//快速排序(递归实现)
{
	if (left >= right)
		return;
	if (right - left <= 10)
	{
		InsertSort(arr + left, right - left + 1);
		return;
	}
	int key=_Sort1(arr, left, right);
	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}
void QuickSortNoR(int* arr, int left, int right)//快速排序(非递归)
{
	Stack st;
	StackInit(&st);//关于栈结构的函数实现在这里并未展示,已在Stack.h中声明
	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);
		int perv = begin, cur = begin + 1;
		int end = StackTop(&st);
		StackPop(&st);
		if (begin >= end)
			continue;
		while (cur <= end)
		{
			if (arr[cur] < arr[begin] && ++perv != cur)
				Swap(&arr[cur], &arr[perv]);
			cur++;
		}
		Swap(&arr[begin], &arr[perv]);
		StackPush(&st, end), StackPush(&st, perv + 1);
		StackPush(&st, perv - 1), StackPush(&st, begin);
	}
	StackDestroy(&st);
}
int main()
{
	int arr[size] = { 0 };
	srand((unsigned int)time(NULL));//随机数种子
	for (int i = 0; i < size; i++)
	{
		arr[i] = rand() % 1000 + 1;//生成随机数赋值给arr[i]
	}
	QuickSort(arr, 0, size - 1);
	for (int i = 0; i < size; i++)
	{
		printf("%-3d ", arr[i]);
		if ((i + 1) % 20 == 0)
			printf("\n");
	}
	return 0;
}

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

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

相关文章

PTA甲级1005:Spell It Right

错误代码&#xff1a; #include<iostream> #include<vector> #include<unordered_map> using namespace std;int main() {unordered_map<int, string> map {{0, "zero"}, {1, "one"}, {2, "two"}, {3, "three&qu…

有一个日期(Date)类的对象和一个时间(Time)类的对象,均已指定了内容,要求一次输出其中的日期和时间

可以使用友元成员函数。在本例中除了介绍有关友元成员函数的简单应用外&#xff0c;还将用到类的提前引用声明&#xff0c;请读者注意。编写程序&#xff1a; 运行结果&#xff1a; 程序分析&#xff1a; 在一般情况下&#xff0c;两个不同的类是互不相干的。display函…

实验六 图像的傅立叶变换

一&#xff0e;实验目的 1了解图像变换的意义和手段&#xff1b; 2熟悉傅立叶变换的基本性质&#xff1b; 3熟练掌握FFT变换方法及应用&#xff1b; 4通过实验了解二维频谱的分布特点&#xff1b; 5通过本实验掌握利用MATLAB编程实现数字图像的傅立叶变换。 6评价人眼对图…

股票Level-2行情是什么,应该怎么使用,从哪里获取数据

行情接入方法 level2行情websocket接入方法-CSDN博客 相比传统的股票行情&#xff0c;Level-2行情为投资者打开了更广阔的视野&#xff0c;不仅限于买一卖一的表面数据&#xff0c;而是深入到市场的核心&#xff0c;提供了十档乃至千档的行情信息&#xff08;沪市十档&#…

关于MCU-Cortex M7的存储结构(flash与SRAM)

MCU并没有DDR&#xff0c;所以他把代码存储在flash上&#xff0c;临时变量和栈运行在SRAM上。之所以这么做是因为MCU的CPU频率很低&#xff0c;一般低于500MHZ&#xff0c;flash的读取速度能够满足CPU的取指需求&#xff0c;但flash 的写入速度很慢&#xff0c;所以引入了SRAM …

【数据结构与算法】快速排序挖坑法

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​

go语言day11 错误 defer(),panic(),recover()

错误&#xff1a; 创建错误 1&#xff09;fmt包下提供的方法 fmt.Errorf(" 格式化字符串信息 " &#xff0c; 空接口类型对象 ) 2&#xff09;errors包下提供的方法 errors.New(" 字符串信息 ") 创建自定义错误 需要实现error接口&#xff0c;而error接口…

【植物大战僵尸杂交版】获取+存档插件

文章目录 一、还记得《植物大战僵尸》吗&#xff1f;二、在哪下载&#xff0c;怎么安装&#xff1f;三、杂交版如何进行存档功能概述 一、还记得《植物大战僵尸》吗&#xff1f; 最近&#xff0c;一款曾经在15年前风靡一时的经典游戏《植物大战僵尸》似乎迎来了它的"文艺复…

C++初学者指南-5.标准库(第一部分)--迭代器

C初学者指南-5.标准库(第一部分)–迭代器 Iterators 文章目录 C初学者指南-5.标准库(第一部分)--迭代器 Iterators1.默认正向迭代器2.反向迭代器3.基于迭代器的循环4.示例&#xff1a;交换相邻的一对元素5.迭代器范围6.迭代器范围中的元素数量7. 总结&#xff1a;迭代器 指向某…

Vue笔记11-Composition API的优势

Options API存在的问题 使用传统Options API中&#xff0c;新增或者修改一个需求&#xff0c;就需要分别在data&#xff0c;methods&#xff0c;computed里修改&#xff0c;而这些选项分布在代码的各个地方&#xff0c;中间还穿插着其他Optional API&#xff0c;如果代码量上来…

国产化新标杆:TiDB 助力广发银行新一代总账系统投产上线

随着全球金融市场的快速发展和数字化转型的深入推进&#xff0c;金融科技已成为推动银行业创新的核心力量。特别是在当前复杂多变的经济环境下&#xff0c;银行业务的高效运作和风险管理能力显得尤为重要。总账系统作为银行会计信息系统的核心&#xff0c;承载着记录、处理和汇…

运维锅总详解系统启动流程

本文详细介绍Linux及Windows系统启动流程&#xff0c;并分析了它们启动流程的异同以及造成这种异同的原因。希望本文对您理解系统的基本启动流程有所帮助&#xff01; 一、Linux系统启动流程 Linux 系统的启动流程可以分为几个主要阶段&#xff0c;从电源开启到用户登录。每个…

FPGA-UDP实验

1. 以太网简介 水晶头的规格就是RJ45千兆网一般指的就是UDP千兆网PHY芯片是用来协商用的 协商匹配最低的通信速率 1.1. OSI模型 7层 ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin…

【LeetCode】有效的数独

目录 一、题目二、解法 一、题目 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&…

使用OpenCV与PySide(PyQt)的视觉检测小项目练习

OpenCV 提供了丰富的图像处理和计算机视觉功能&#xff0c;可以实现各种复杂的图像处理任务&#xff0c;如目标检测、人脸识别、图像分割等。 PyQt(或PySide)是一个创建GUI应用程序的工具包&#xff0c;它是Python编程语言和Qt库的成功融合。Qt库是最强大的GUI库之一。Qt的快速…

昇思MindSpore 25天学习打卡营|day18

DCGAN生成漫画头像 在下面的教程中&#xff0c;我们将通过示例代码说明DCGAN网络如何设置网络、优化器、如何计算损失函数以及如何初始化模型权重。在本教程中&#xff0c;使用的动漫头像数据集共有70,171张动漫头像图片&#xff0c;图片大小均为96*96。 GAN基础原理 这部分原…

06.C2W1.Auto-correct

往期文章请点这里 目录 OverviewAutocorrectWhat is autocorrect?How it works Building the modelMinimum edit distanceMinimum edit distance algorithmMinimum edit distance Part 2Minimum edit distance Part 3 往期文章请点 这里 Overview 本周学习目标&#xff1a;…

C++入门7——string类详解

目录 1.什么是string类&#xff1f; 2.string类对象的常见构造 2.1 string(); 2.2 string (const char* s); 2.3 string (const string& str); 2.4 string (const string& str, size_t pos, size_t len npos); 2.5 string (const char* s, size_t n); 2.7 验证…

Educational Codeforces Round 167 (Rated for Div. 2)(A~C)题解

A. Catch the Coin 解题思路: 最终&#x1d465;一定会相等&#xff0c;我们考虑直接到下面接住他。 #include<bits/stdc.h> using namespace std; typedef long long ll; #define N 1000005 ll dp[N], w[N], v[N], h[N]; ll dis[1005][1005]; ll a, b, c, n, m, t; ll…

PCIe驱动开发(2)— 第一个简单驱动编写和测试

PCIe驱动开发&#xff08;2&#xff09;— 第一个简单驱动编写和测试 一、前言 教程参考&#xff1a;02_实战部分_PCIE设备测试 教程参考&#xff1a;03_PCIe设备驱动源码解析 二、驱动编写 新建hello_pcie.c文件 touch hello_pcie.c然后编写内容如下所示&#xff1a; #i…