快速排序算法 -- 深入研究

一 .  快排性能的关键点分析

快排性能的关键点分析 : 决定快排性能的关键点是每次单趟排序后 , key 对数组的分割 , 如果每次选key 基本二分居中,那么快排的递归树就是颗均匀的满二叉树,性能最佳。但是实际中虽然不可能每次都是二分居中,但是性能也还是可控的。但是如果出现每次选到的最小值/最大值,划分为0个和N-1个子问题时,时间复杂度会退化,时间复杂度为O(N^2) , 数组有序的时候会出现这样的问题 , 可以使用 三数取中 或者 随机选key 解决这个问题 , 但是还是有一些情景没有解决(有大量重复的数据) 

//数组中有多个与key相等的值
int a[] = {6,1,7,6,6,6,4,9}
int a[] = {3,2,3,3,3,3,3,2}

//数组全部是相同的值
int a[] = {2,2,2,2,2,2,2,2}
以下是《算法导论》书籍中给出的hoare和lomuto给出的快排的单趟排序的伪代码:

二 . 三路划分基本思路:

 当面对有大量跟key相同的值时 , 三路划分的核心思想有点类似hoare的左右指针 和 lumuto 的前后指针的结合 。 核心思想是把数组中的数据分成三段

【比key小的值】 【与key相等的值】 【比key大的值】

所以,称之为三路划分法 !

1 . key 默认取 left 位置的值

2 . left 指向区间最左边 , right 指向区间最右边 , cur指向left + 1 的位置

3 . cur 遇到比key小的值后 , left 与 cur交换 , left++ , cur--

4 . cur 遇到比key大的值后 , right 与 cur交换 ,right--

5 . cur遇到跟key相等的值后 , cur++

6 . 直到 cur > right 结束

 

三 . hoare和lomuto和三路划分单趟排序代码分析:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
// hoare
// [left, right]
int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	++left;
	while (left <= right)//left和right相遇的位置的值⽐基准值要⼤
	{
		//right找到⽐基准值⼩或等
		while (left <= right && a[right] > a[keyi])
		{
			right--;
		}
		//left找到⽐基准值⼤或等
		while (left <= right && a[left] < a[keyi])
		{
			left++;
		}
		//right left
		if (left <= right)
		{
			Swap(&a[left++], &a[right--]);
		}

	}
	 //right keyi交换
		 Swap(&a[keyi], &a[right]);
	
		 return right;	
}
// 前后指针
int PartSort2(int* a, int left, int right)
{
	 int prev = left;
	 int cur = left + 1;
	 int keyi = left;
	 while (cur <= right)
		 {
		 if (a[cur] < a[keyi] && ++prev != cur)
			 {
			 Swap(&a[prev], &a[cur]);
			 }
		
			 ++cur;
		 }
	
		 Swap(&a[prev], &a[keyi]);
	 keyi = prev;
	 return keyi;
	 }

 typedef struct
{
	 int leftKeyi;
	 int rightKeyi;
}KeyWayIndex;

// 三路划分
 KeyWayIndex PartSort3Way(int* a, int left, int right)
{
	 int key = a[left];
	
		 // left和right指向就是跟key相等的区间
		// [开始, left-1][left, right][right+1, 结束]
		 int cur = left + 1;
	while (cur <= right)
		 {
		 // 1、cur遇到⽐key⼩,⼩的换到左边,同时把key换到中间位置
		// 2、cur遇到⽐key⼤,⼤的换到右边
		if (a[cur] < key)
			{
			Swap(&a[cur], &a[left]);
			 ++cur;
			++left;
			 }
		else if (a[cur] > key)
			{
			Swap(&a[cur], &a[right]);
			 --right;
			}
		 else
		{
			 ++cur;
		 }
		
	}
	
	 KeyWayIndex kwi;
	 kwi.leftKeyi = left;
	 kwi.rightKeyi = right;
	 return kwi;
 }

void TestPartSort1()
{
	 int a1[] = { 6,1,7,6,6,6,4,9 };
	 int a2[] = { 3,2,3,3,3,3,2,3 };
	 int a3[] = { 2,2,2,2,2,2,2,2 };
	
	PrintArray(a1, sizeof(a1) / sizeof(int));
	 int keyi1 = PartSort1(a1, 0, sizeof(a1) / sizeof(int) - 1);
	 PrintArray(a1, sizeof(a1) / sizeof(int));
	 printf("hoare keyi:%d\n\n", keyi1);
	
	PrintArray(a2, sizeof(a2) / sizeof(int));
	 int keyi2 = PartSort1(a2, 0, sizeof(a2) / sizeof(int) - 1);
	 PrintArray(a2, sizeof(a2) / sizeof(int));
	 printf("hoare keyi:%d\n\n", keyi2);
	 
	 PrintArray(a3, sizeof(a3) / sizeof(int));
	 int keyi3 = PartSort1(a3, 0, sizeof(a3) / sizeof(int) - 1);
	PrintArray(a3, sizeof(a3) / sizeof(int));
	  printf("hoare keyi:%d\n\n", keyi3);
}

void TestPartSort2()
{
	int a1[] = { 6,1,7,6,6,6,4,9 };
	int a2[] = { 3,2,3,3,3,3,2,3 };
	int a3[] = { 2,2,2,2,2,2,2,2 };
	
	PrintArray(a1, sizeof(a1) / sizeof(int));
	int keyi1 = PartSort2(a1, 0, sizeof(a1) / sizeof(int) - 1);
	PrintArray(a1, sizeof(a1) / sizeof(int));
	printf("前后指针 keyi:%d\n\n", keyi1);

	PrintArray(a2, sizeof(a2) / sizeof(int));
	int keyi2 = PartSort2(a2, 0, sizeof(a2) / sizeof(int) - 1);
	PrintArray(a2, sizeof(a2) / sizeof(int));
	printf("前后指针 keyi:%d\n\n", keyi2);

	PrintArray(a3, sizeof(a3) / sizeof(int));
	 int keyi3 = PartSort2(a3, 0, sizeof(a3) / sizeof(int) - 1);
	 PrintArray(a3, sizeof(a3) / sizeof(int));
	 printf("前后指针 keyi:%d\n\n", keyi3);
}

void TestPartSort3()
{
	//int a0[] = { 6,1,2,7,9,3,4,5,10,4 };
	 int a1[] = { 6,1,7,6,6,6,4,9 };
	 int a2[] = { 3,2,3,3,3,3,2,3 };
	 int a3[] = { 2,2,2,2,2,2,2,2 };

	 PrintArray(a1, sizeof(a1) / sizeof(int));
	 KeyWayIndex kwi1 = PartSort3Way(a1, 0, sizeof(a1) / sizeof(int) - 1);
	 PrintArray(a1, sizeof(a1) / sizeof(int));
	 printf("3Way keyi:%d,%d\n\n", kwi1.leftKeyi, kwi1.rightKeyi);
	
	 PrintArray(a2, sizeof(a2) / sizeof(int));
	 KeyWayIndex kwi2 = PartSort3Way(a2, 0, sizeof(a2) / sizeof(int) - 1);
	 PrintArray(a2, sizeof(a2) / sizeof(int));
	 printf("3Way keyi:%d,%d\n\n", kwi2.leftKeyi, kwi2.rightKeyi);
	
	 PrintArray(a3, sizeof(a3) / sizeof(int));
	 KeyWayIndex kwi3 = PartSort3Way(a3, 0, sizeof(a3) / sizeof(int) - 1);
	 PrintArray(a3, sizeof(a3) / sizeof(int));
	 printf("3Way keyi:%d,%d\n\n", kwi3.leftKeyi, kwi3.rightKeyi);
}

int main()
{
	TestPartSort1();
	TestPartSort2();
	TestPartSort3();

return 0;
}

 四 .三种快排单趟排序运⾏结果分析:

从下面的运行结果分析,lomuto的前后指针法,面对key有大量重复时,lomuto划分不是很理想,性能退化,hoare相对还不错,但是大量重复时,没有三路划分快。三路划分算法,把跟key相等的值都划分到了中间,可以很好的解决这里的问题。

6 1 7 6 6 6 4 9
6 1 4 6 6 6 7 9
hoare keyi:3

3 2 3 3 3 3 2 3
3 2 3 2 3 3 3 3
hoare keyi:4

2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2
hoare keyi:3

6 1 7 6 6 6 4 9
4 1 6 6 6 6 7 9
前后指针 keyi:2

3 2 3 3 3 3 2 3
2 2 3 3 3 3 3 3
前后指针 keyi:2

2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2
前后指针 keyi:0

6 1 7 6 6 6 4 9
1 4 6 6 6 6 9 7
3Way keyi:2,5

3 2 3 3 3 3 2 3
2 2 3 3 3 3 3 3
3Way keyi:2,7

2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2
3Way keyi:0,7

五 . 基于三路划分解决算法题

912. 排序数组 - 力扣(LeetCode) 

这个OJ,当我们用快排的时候,lomuto的方法,过不了这个题目,hoare版本可以过这个题目。堆排序和归并和希尔是可以过的,其 他几个O(N^2)也不过了,因为这个题的测试用例中不仅仅有数据很多的大数组,也有⼀些特殊数据的数组,如大量重复数据的数组。堆排序和归并和希尔不是很受数据样本的分布和形态的影响,但是快排会,因为快排要选key,每次key都当趟分割都很偏,就会出现效率退化问题。
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
 void Swap(int* x,int* y)
 {
    int tmp = *x;
    *x = *y;
    *y = tmp;
 }
 void QuickSort(int* arr,int left,int right)
 {
    if(left >= right)
    {
        return ;
    }
    int begin = left;
    int end = right;

    //随机选key
    int randi = left + (rand()%(right-left +1));
    Swap(&arr[left],&arr[randi]);

    int key = arr[left];
    int cur = left+1;
    while(cur <=right)
    {
        if(arr[cur] < key)
        {
            Swap(&arr[cur],&arr[left]);
            left++;
            cur++;
        }
        else if(arr[cur] > key)
        {
            Swap(&arr[cur],&arr[right]);
            right--;
        }
        else
        {
            cur++;
        }
    } 
    QuickSort(arr,begin,left-1);
    QuickSort(arr,right+1,end);

 }
int* sortArray(int* nums, int numsSize, int* returnSize) {
    srand(time(0));
    QuickSort(nums,0,numsSize-1);
    *returnSize = numsSize;
    return nums;
}

六 . 内省排序

introsort 是 introspective sort采用了缩写,他的名字其实表达了他的实现思路,他的思路就是进行自我侦测和反省,快排递归深度太深(sgi stl中使用的是深度为2倍排序元素数量的对数值)那就说明在这种数据序列下,选key出现了问题,性能在快速退化,那么就不要再进行快排分割递归了, 改换为堆排序进行排序。
void HeapSort(int* a, int n)
{
	// 建堆 -- 向下调整建堆 -- O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	// ⾃⼰先实现 -- O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		--end;
	}
}
void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		int end = i - 1;
		int tmp = a[i];
		// 将tmp插⼊到[0,end]区间中,保持有序
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{
	if (left >= right)
		return;

	// 数组⻓度⼩于16的⼩数组,换为插⼊排序,简单递归次数
	if (right - left + 1 < 16)
	{
		InsertSort(a + left, right - left + 1);
		return;
	}
	// 当深度超过2*logN时改⽤堆排序
	if (depth > defaultDepth)
	{
		HeapSort(a + left, right - left + 1);
		return;
	}
	depth++;
	int begin = left;
	int end = right;
// 随机选key
	int randi = left + (rand() % (right - left + 1));
	Swap(&a[left], &a[randi]);
	int prev = left;
	int cur = prev + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	// [begin, keyi-1] keyi [keyi+1, end]
	IntroSort(a, begin, keyi - 1, depth, defaultDepth);
	IntroSort(a, keyi + 1, end, depth, defaultDepth);
}
void QuickSort(int* a, int left, int right)
{
	int depth = 0;
	int logn = 0;
	int N = right - left + 1;
	for (int i = 1; i < N; i *= 2)
	{
		logn++;
	}


	// introspective sort -- ⾃省排序
	IntroSort(a, left, right, depth, logn * 2);
}

int* sortArray(int* nums, int numsSize, int* returnSize) {
	srand(time(0));
	QuickSort(nums, 0, numsSize - 1);

	*returnSize = numsSize;
	return nums;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ORA-65198 PDB clone 时 不能新加datafile 以及hang的一个原因

create pluggable database XX from SS keystore identified by "YYY" parallel 32 service_name_convert( _srv, _srv); 20TB 4小时 update /* rule */ undo$ set name:2,file#:3,block#:4,status$:5,user#:6,undosqn:7,xactsqn:8,scnbas:9,scnwrp:10,inst#:11,…

Android--java实现手机亮度控制

文章目录 1、开发需求2、运行环境3、主要文件4、布局文件信息5、手机界面控制代码6、debug 1、开发需求 需求&#xff1a;开发一个Android apk实现手机亮度控制 2、运行环境 Android studio最新版本 3、主要文件 app\src\main\AndroidManifest.xml app\src\main\res\layou…

HarmonyOS NEXT 实战之元服务:静态案例效果--- 日出日落

背景&#xff1a; 前几篇学习了元服务&#xff0c;后面几期就让我们开发简单的元服务吧&#xff0c;里面丰富的内容大家自己加&#xff0c;本期案例 仅供参考 先上本期效果图 &#xff0c;里面图片自行替换 效果图1完整代码案例如下&#xff1a; import { authentication } …

一起学Git【番外篇:如何在Git中新建文件】

在介绍Git之前&#xff0c;我们需要了解一下如何在Git里添加、编辑和删除文件。 首先&#xff0c;需要使用文件编辑器进行文件的创建&#xff0c;常见的文件编辑器有以下几种&#xff1a; Vim&#xff1a;一种基于命令行的编辑器&#xff0c;功能强大&#xff0c;适合开发者和…

叉车作业如何确认安全距离——UWB测距防撞系统的应用

叉车在工业环境中运行&#xff0c;常常需要在狭窄的空间内完成货物的搬运和堆垛&#xff0c;这对操作员的技术水平和安全意识提出了极高的要求。传统的叉车作业依赖操作员的经验和视觉判断来确认安全距离&#xff0c;然而这种方式往往存在误差&#xff0c;特别是在视线受阻或光…

hi168大数据离线项目环境搭建

hi168大数据离线项目环境搭建 ## **1. 服务器准备**##### 1.1 创建集群应用节点 集群服务器使用“我的应用“中的Ubuntu22.04集群模版创建三个节点应用&#xff0c;并且进入“我的应用”中去修改一下节点名称&#xff08;node1对应master&#xff0c;node2对应hadoop1&#xf…

分布式专题(10)之ShardingSphere分库分表实战指南

一、ShardingSphere产品介绍 Apache ShardingSphere 是一款分布式的数据库生态系统&#xff0c; 可以将任意数据库转换为分布式数据库&#xff0c;并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。Apache ShardingSphere 设计哲学为 Database Plus&#xff0c;旨在…

大模型-Ollama使用相关的笔记

大模型-Ollama使用相关的笔记 解决Ollama外网访问问题&#xff08;配置ollama跨域访问&#xff09;Postman请求样例 解决Ollama外网访问问题&#xff08;配置ollama跨域访问&#xff09; 安装Ollama完毕后&#xff0c; /etc/systemd/system/ollama.service进行如下修改&#…

Python:模拟(包含例题:饮料换购 图像模糊 螺旋矩阵)

模拟题&#xff1a;直接按照题目含义模拟即可&#xff0c;一般不涉及算法 注意&#xff1a; 1.读懂题&#xff1a;理清楚题目流程 2.代码和步骤一一对应&#xff1a;变量名&#xff0c;函数名&#xff0c;函数功能 3.提取重复的部分&#xff0c;写成对应的函数&#xff08;…

【数据库初阶】数据库基础知识

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; 数据库初阶 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们&#xff0c;大家好&#xff01;在这篇文章中&#xff0c;我们将深入浅出地为大家讲解 数据库…

汽车IVI中控开发入门及进阶(四十):FDK AAC音频编解码软件库

概述: FDK AAC是一个用于编码和解码高级音频编码格式音频的开源软件库,由Fraunhofer IIS开发,并作为Android的一部分包含在内。它支持多种音频对象类型,包括MPEG-2和MPEG-4 AAC LC、HE-AAC、HE-AACv2以及AAC-LD和AAC-ELD,用于实时通信。编码库支持高达96 kHz的采样率和多…

Python爬虫:速卖通aliexpress商品详情获取指南

在数字化时代&#xff0c;数据已成为企业竞争的关键资源。对于电商行业而言&#xff0c;获取竞争对手的商品信息是洞察市场动态、优化自身产品策略的重要手段。速卖通&#xff08;AliExpress&#xff09;作为全球知名的跨境电商平台&#xff0c;其商品信息的获取自然成为了许多…

要查询 `user` 表中 `we_chat_open_id` 列不为空的用户数量

要查询 user 表中 we_chat_open_id 列不为空的用户数量&#xff0c;你可以使用以下 SQL 查询语句&#xff1a; SELECT COUNT(*) FROM user WHERE we_chat_open_id IS NOT NULL AND we_chat_open_id ! ;解释&#xff1a; SELECT COUNT(*): 表示要计算符合条件的行数。FROM us…

学习思考:一日三问(学习篇)之匹配VLAN

学习思考&#xff1a;一日三问&#xff08;学习篇&#xff09;之匹配VLAN 一、学了什么&#xff08;是什么&#xff09;1.1 理解LAN与"V"的LAN1.2 理解"V"的LAN怎么还原成LAN1.3 理解二层交换机眼中的"V"的LAN 二、为何会产生需求&#xff08;为…

mac中idea菜单工具栏没有git图标了

1.右击菜单工具栏 2.选中VCS&#xff0c;点击添加 3.搜索你要的工具&#xff0c;选中点击确定就添加了 4.回到上面一个界面&#xff0c;选中你要放到工具栏的工具&#xff0c;点击应用就好了 5.修改图标&#xff0c;快捷键或者右击选中编辑图标 6.选择你要的图标就好了

深度学习中batch_size

Batch size调整和epoch/iteration的关系 训练数据集总共有1000个样本。若batch_size10&#xff0c;那么训练完全体样本集需要100次迭代&#xff0c;1次epoch。 训练样本10000条&#xff0c;batchsize设置为20&#xff0c;将所有的训练样本在同一个模型中训练5遍&#xff0c;则…

Vue使用Tinymce 编辑器

目录 一、下载并重新组织tinymce结构二、使用三、遇到的坑 一、下载并重新组织tinymce结构 下载 npm install tinymce^7 or yarn add tinymce^7重构目录 在node_moudles里找到tinymce文件夹&#xff0c;把里面文件拷贝一份放到public下&#xff0c;如下&#xff1a; -- pub…

【每日学点鸿蒙知识】蓝牙Key、页面元素层级工具、私仓搭建、锁屏显示横幅、app安装到真机

1、HarmonyOS 蓝牙key模块&#xff1f; 蓝牙key模块setCharacteristicChangeNotification后无法在BLECharacteristicChange订阅事件中监听到特征值变化 步骤&#xff1a; 调用setCharacteristicChangeNotification接口&#xff0c;使characteristic的notify属性为true调用wri…

如何记录日常笔记

如何使用Obsidian来记录日常的笔记吗&#xff1f;比如会议记录、读书笔记。 我认为&#xff0c;首先需要做好的就是建立一个单独的分类&#xff0c;比如设置会议记录的文件夹、读书笔记的文件夹&#xff0c;这样各个笔记相互不干扰。 而做日常记录&#xff0c;和我们进行卡片…

`we_chat_union_id IS NOT NULL` 和 `we_chat_union_id != ‘‘` 这两个条件之间的区别

文章目录 1、什么是空字符串&#xff1f;2、两个引号之间加上空格 好的&#xff0c;我们来详细解释一下 we_chat_union_id IS NOT NULL 和 we_chat_union_id ! 这两个条件之间的区别&#xff0c;以及它们在 SQL 查询中的作用&#xff1a; 1. we_chat_union_id IS NOT NULL 含…