【数据结构】快速排序详解

这篇具有很好参考价值的文章主要介绍了【数据结构】快速排序详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、基本介绍

二、快排的实现

1. 调试环境

2.快排的单趟排序

(1)Hoare版本

(2)挖坑法

(3)前后指针法

2.递归过程

三、快排的优化

1. 优化取key方式,防止栈溢出

2. 小区间优化

四、快排的非递归方式


前言:

        排序算法是日常使用最频繁的一个算法,生活中也很常见什么排队呀按照高矮次序呀,分数按照一个从高到低的排序等等,但是如果是要设计出来面对基数很大又要很快的排序方法这就是需要很大难度了,先给大家看看排序的种类有哪些,和其对应的时间空间复杂度。

【数据结构】快速排序详解

         今天我要分享的便是当今使用最为广泛的快速排序算法。快速排序是由英国计算机专家Tony Hoare大佬在他26岁时发布的算法,快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。接下来,我将介绍快排的主要内容。

一、基本介绍

        快速排序的基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下: 

  • 首先设定一个分界值Key,通过该分界值将数组分成左右两部分。 
  • 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。  
  • 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。 
  • 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

上述为快速排序递归实现的主框架,简而言之就是快排分为两步:

(1)通过找Key将数组分成一大一小两部分

(2)递归

我们不难发现这与二叉树前序遍历规则非常像,所以在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。因此,快排也可以说是基于二叉树的排序算法。


二、快排的实现

1. 调试环境

为了方便测试写出的快排的正确性以及效率,我们首先写出下面两段用于测试的代码。

其中PrintArray是数组打印函数

TestQuickSort函数用于测试代码的正确性

TestOP函数用于测试快排的效率

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void TestQuickSort()
{
	int a[] = { 9,8,7,6,5,4,3,2,1,0 };
	QuickSort(a, 0, 9);
	PrintArray(a, 10);
}
void TestOP()
{
	srand(time(0));
	const int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}

	int begin1 = clock();
	QuickSort(a1, 0, N - 1);
	int end1 = clock();
	printf("QuickSort:%d\n", end1 - begin1);
}

现在我们有了测试函数之后,可以进行快排的具体实现过程了。


2.快排的单趟排序

按照上文中所说的步骤,我们首先进行第一步,找key将数组分成一大一小的两部分,也就是快排的单趟排序。快排发展到现在,单趟排序一共有三种主流的方式,下面我将逐一解释这三种方式的具体做法。

(1)Hoare版本

第一种做法是Hoare大佬最初设计快排时提出并采用的方法。首先我们定义leftright两个指针分别指向数组的头和尾,然后将数组中第一个数据定义为key

【数据结构】快速排序详解

         我在这里按照升序演示,因为我们需要保证数组左边的数据比key小,右边的数据比key大,所以我们让right指针先出发找小,left指针找大,找到后两指针指向的数据交换。

【数据结构】快速排序详解

 然后让left和right继续前进,分别找大找小交换后,直到两指针相遇。

【数据结构】快速排序详解

 两指针相遇后,将相遇位置的数据与key交换,此时快排的第一步单趟排序结束。

【数据结构】快速排序详解

注意: 你可能会对最后一步有疑问,为什么直接将key与相遇位置交换,相遇位置有没有可能比key大,直接交换是不是会出错?

答案是不会,因为我们让左边第一个数做key,right先走

  • R停下来,L撞到R,相遇位置比key小
  • L停下来,R撞到L,相遇位置比key小

因此,如果选右边第一个数做key的话,就需要让left先走了。

 这便是Hoare版本的具体过程,下面请看具体代码。

(返回值是为了后面要提及的递归)

//快排的单趟排序(hoare版本)
int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	int meeti = left;
	Swap(&a[keyi], &a[meeti]);
	return meeti;
}

(2)挖坑法

【数据结构】快速排序详解 

 【数据结构】快速排序详解

 文章来源地址https://www.toymoban.com/news/detail-484306.html

【数据结构】快速排序详解

 【数据结构】快速排序详解

 下面是具体的代码。

//单趟排序--挖坑法
int PartSort2(int* a, int left, int right)
{
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

(3)前后指针法

 【数据结构】快速排序详解

下面是具体代码。

//前后指针法
int PartSort3(int* a, int left, int right)
{
	int key = a[left];
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < key && ++prev < cur)
			Swap(&a[prev], &a[cur]);
		cur++;
	}
	Swap(&a[left], &a[prev]);
	return prev;
}

2.递归过程

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

	int keyi = PartSort3(a, begin, end);

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
	
}

        只要写完快排的单趟排序,递归过程就非常简单了。上面的单趟排序中返回值便是排序好后key的位置,也就是说快排的单趟排序可以确实一个元素的正确位置,并且将数组分成了比key小和比key大的两个部分。所以直接递归【begin,key-1】 和 【key+1,end】就可以了。


三、快排的优化

1. 优化取key方式,防止栈溢出

        经过上面的讲解我们已经有了一个快排的初步代码,现在我们可以使用开头给出的测试函数检查一下快排的正确性,以及快排的效率怎么样。

【数据结构】快速排序详解

        首先,我们写的快排正确性没有问题,然后我给出了测试有一百万个随机数的数组,排序用了 183ms,速度还是比较快的。

让我们来计算一下快排的时间复杂度,不难得出快排的时间复杂度为O(NlogN)

【数据结构】快速排序详解

 但是,让我把测试函数改一下的话,则会出现一些状况。

void TestOP()
{
	srand(time(0));
	const int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}
	QuickSort(a1, 0, N - 1);
	int begin1 = clock();
	QuickSort(a1, 0, N - 1);
	int end1 = clock();
	printf("QuickSort:%d\n", end1 - begin1);
}

我们让快排去给一个有序的数组排序,按理说应该速度更快,结果却栈溢出了。

【数据结构】快速排序详解

 


        刚才我们计算时间复杂度的时候是按照较为理想的情况,如果一个数组有序或者接近有序,我们在使用刚才写出的代码进行排序时,每个key选择的都是左边第一个,那么递归深度就会很大,接近N,此时就是出现栈溢出的情况。

【数据结构】快速排序详解

 所以,为了避免这种情况,我们就需要优化选key的方式,在这里,我给出三种方法:

  • 随机选一个位置做key
  • 针对有序,选正中间做key
  • 三数取中:选出数组的begin end mid 三个位置然后取中间值做key。

其中,第三种方式最常用,实现过程也比较简单,下面我直接给出代码。 

//三数取中选key
int getMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[right] < a[left])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return mid;
		}
		else
			return right;
	}
	else  //a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return left;
		}
		else
			return right;
	}
}

2. 小区间优化

【数据结构】快速排序详解

我们在递归的过程中,小区间占用的函数栈帧最多,此时数组的长度较小,使用快排的递归反而会导致效率下降,因此我们可以优化一下小区间使用其他排序方式排序。

        我们进行如下优化,当递归区间长度小于8时,我们使用插入排序,这样可以进一步提高快排的效率。

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

	// 小区间优化
	if (end - begin < 8)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort1(a, begin, end);

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

四、快排的非递归方式

【数据结构】快速排序详解

 

void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);

	while ( !StackEmpty(&st) )
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);
		int mid = PartSort3(a, left, right);

		if (mid + 1 < right)
		{
			StackPush(&st, mid+1);
			StackPush(&st, right);
		}

		if (mid - 1 > left)
		{
			StackPush(&st, left);
			StackPush(&st, mid-1);
		}

	}
	StackDestroy(&st);
}

到了这里,关于【数据结构】快速排序详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】快速排序,归并排序

    1.hoare版本 根据动图的演示,整理的思路如下, 1.定义left,right,key。key默认是左边第一个元素,像两个指针,左边找比key大的,右边找比k小的,找到的话,交换二者,往返这个过程,当left与right相遇时,交换key和此时相遇的值. 单趟下来,6出现在正确的位置。 1.为什么大循环

    2024年01月20日
    浏览(30)
  • 【数据结构】排序(2)—冒泡排序 & 快速排序

                                      目录 一. 冒泡排序 基本思想 代码实现 时间和空间复杂度 稳定性 二. 快速排序 基本思想 代码实现 hoare法 挖坑法 前后指针法 时间和空间复杂度 稳定性            冒泡排序是一种交换排序。两两比较数组元素,如果是逆序(即排列顺序

    2024年02月08日
    浏览(37)
  • 【数据结构--八大排序】之快速排序

    💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃 个人主页 :阿然成长日记 👈点击可跳转 📆 个人专栏: 🔹数据结构与算法🔹C语言进阶 🚩 不能则学,不知则问,耻于问人,决无长进 🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍 前言: 前面,我花费

    2024年02月08日
    浏览(31)
  • 数据结构——排序算法之快速排序

        个人主页: 日刷百题 系列专栏 : 〖C/C++小游戏〗 〖Linux〗 〖数据结构〗   〖C语言〗 🌎 欢迎各位 → 点赞 👍+ 收藏 ⭐️+ 留言 📝  ​ ​ 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。 基本思想: 任取待排序元素序列中 的某元素作为基准值,按照

    2024年01月21日
    浏览(38)
  • 数据结构--快速排序

    快速排序是通过二叉树的思想,先设定一个值,通过比较,比它大的放在它的右边,比它小的放在它的左边;这样相当于在二叉树中,小的放在左子树,大的放在右子树,设定的值就是根;再通过递归的思想,将它们继续按这种方式进行排序,排到最后就排好了;这就是快速

    2024年02月08日
    浏览(30)
  • 数据结构——lesson11排序之快速排序

    hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥 个人主页:大耳朵土土垚的博客 💥 所属专栏:数据结构学习笔记 、排序算法合集 💥对于数据结构顺序表、链表、堆以及排序有疑问的都可以在上面数据结构专栏和排序合集专栏进行

    2024年04月16日
    浏览(55)
  • 【数据结构与算法】:选择排序与快速排序

    🔥 个人主页 : Quitecoder 🔥 专栏 :数据结构与算法 我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:腾讯云 欢迎来到排序的第二个部分:选择排序与快速排序! 选择排序是一种简单直观的比较排序算法。该算法的基本思想是在每一轮中选出当前未排序部分的最

    2024年03月17日
    浏览(40)
  • 【数据结构】第十三站:排序(中)快速排序

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

    2024年02月01日
    浏览(33)
  • 【数据结构初阶】八大排序(二)——快速排序&&冒泡排序

    大家好我是沐曦希💕 书接【数据结构初阶】八大排序(一)——希尔排序堆排序直接插入排序直接选择排序 基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是: 将键值较大的记录向序列的尾部移动,键值较小

    2024年02月21日
    浏览(40)
  • 数据结构——快速排序的介绍

    快速排序是霍尔(Hoare)于1962年提出的一种二叉树结构的交换排序方法。快速排序是一种常用的排序算法,其基本思想是通过选择一个元素作为\\\"基准值\\\",将待排序序列分割成两个子序列,其中一个子序列的元素都小于等于基准值,另一个子序列的所有素都大于基准值。然后对这

    2024年02月11日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包