快速排序(详解)

文章详细介绍了快速排序算法的实现,包括朴素版的一层循环和递归,以及针对选取key值的改进方法,如随机数取法、三路取中和前后指针法。此外,还讨论了通过限制小区间来减少递归次数以及在小规模数据时使用插入排序的优化策略。
摘要由CSDN通过智能技术生成

一.朴素版

1.一层循环

在这里插入图片描述

快排的每一层循环就是选出一个key值将其放到正确的位置(也就是放到已经完全有序时的位置)。

在这里插入图片描述

例如以上有一组数据,我们选择6为key值,这次循环我们要将6放到正确的位置。用一个left指针和一个right指针,分别指向左右两端,left指针向右走,遇到大于key值的数就停下,然后right指针向左走,遇到小于key值的数就停下。之后两指针所对应的数交换位置,直到两指针相遇,交换left所指向的数和key值,一次循环停止。就得到key数左边的数都小于等于key,右边的数都大于等于key。

在这里插入图片描述

此时一次循环结束6左边的数都小于等于6,右边的数都大于等于6。(一般令最左边的数为key值)

在这里插入图片描述

2.递归

每次循环找到一个数,那么依次递归当left>=right,也就是这次递归里已经没有元素时就说明排序完成。

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int key = left;
	while (right > left)
	{
		while (left<right&&a[right] >= a[key])
			right--;
		while (left<right&&a[left] <= a[key])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	QuickSort(a, begin,key-1);
	QuickSort(a, key + 1, end);
}

二.改进(选取key值)

缺陷

如果改数据已经是升序或者逆序的话,我们每次选最左边为key值,那么每次递归只能减少一个数(无法做到理想情况下的二分)。如果有100w个数,就容易造成栈溢出,所以需要改进。

1.随机数取法

其实快排的缺陷主要是由key值的选取造成的,那么我们可以随机选key值,就可以避免最坏的情况。

在这里插入图片描述

2.三路取中

选随机数是可行的,但有人认为太过于不确定了,所以又出炉了一个新方法,取中间(max>a[mid]>min)的坐标。

在这里插入图片描述

在这里插入图片描述

3.前后指针法

简单来说就是一个cur指针从最左边走起,pre初始位置在cur+1位置,pre向后走,如果a[pre]<a[key],就交换a[++cur]和a[pre],直到pre走到最右边。

在这里插入图片描述

在这里插入图片描述

源代码

#include<stdio.h>
#include<time.h>
#include<stdlib.h>

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[mid] > a[left])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	//int begin = left, end = right;

	//随机选key
	//int randi =left+ rand() % (right - left);//让产生的随机数在left和right之间
	//Swap(&a[randi], &a[left]);//为了避免扰乱后面的排序,我们将其调到最左边的位置

	//三数取中
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	/*int key = left;
	while (right > left)
	{
		while (left<right&&a[right] >= a[key])
			right--;
		while (left<right&&a[left] <= a[key])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	QuickSort(a, begin,key-1);
	QuickSort(a, key + 1, end);*/
	
	//前后指针法
	int cur = left, pre = cur + 1,key=left;
	while (pre<=right)
	{
		if (a[pre] < a[key])
		{
			cur++;
			Swap(&a[pre], &a[cur]);
		}
		pre++;
	}
	Swap(&a[cur], &a[key]);
	key = cur;
	QuickSort(a, left, key-1);
	QuickSort(a, key + 1, right);
}
int main()
{
	int a[20000];
	srand((unsigned int)time(NULL));
	for (int i = 0; i < 20000; i++)
	{
		a[i] = rand() % 1000;
	}
	QuickSort(a,0,19999);
	int st = 0;
	for (int i = 0; i < 19998; i++)
	{
		if (a[i] > a[i + 1])
		{
			printf("%d->%d,%d\n", a[i], a[i + 1], i);
			st = 1;
			break;
		}
	}
	if (st)
	{
		printf("out of order");
	}
	else
	{
		printf("order");
	}
}

三,小区间(减少递归)

我们知道快排是按二叉树形式排列的,而在理想情况下,最后一层递归就占总递归的二分之一。实际上当每个子树只剩下10个数时,我们就可以考虑不再递归改用插入排序,那么这样(在理想情况下)就可以去除最后三层递归,也就会减少87.5%的递归。

在这里插入图片描述

不了解插入排序的可以看看这篇博客插入排序

在这里插入图片描述

#include<stdio.h>
#include<time.h>
#include<stdlib.h>

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[mid] > a[left])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	//int begin = left, end = right;

	//随机选key
	//int randi =left+ rand() % (right - left);//让产生的随机数在left和right之间
	//Swap(&a[randi], &a[left]);//为了避免扰乱后面的排序,我们将其调到最左边的位置

	//三数取中
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	/*int key = left;
	while (right > left)
	{
		while (left<right&&a[right] >= a[key])
			right--;
		while (left<right&&a[left] <= a[key])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	QuickSort(a, begin,key-1);
	QuickSort(a, key + 1, end);*/

	//前后指针法
	if (right - left + 1 > 10)
	{
		int cur = left, pre = cur + 1, key = left;
		while (pre <= right)
		{
			if (a[pre] < a[key])
			{
				cur++;
				Swap(&a[pre], &a[cur]);
			}
			pre++;
		}
		Swap(&a[cur], &a[key]);
		key = cur;
		QuickSort(a, left, key - 1);
		QuickSort(a, key + 1, right);
	}
	else//插入排序
	{
		for (int i = 1; i < 10; i++)
		{
			int end = i - 1;
			int tmp = a[i];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + 1] = a[end];
					end--;
				}
				else
				{
					break;
				}
			}
			a[end + 1] = tmp;
		}
	}
}
int main()
{
	int a[20000];
	srand((unsigned int)time(NULL));
	for (int i = 0; i < 20000; i++)
	{
		a[i] = rand() % 1000;
	}
	QuickSort(a,0,19999);
	int st = 0;
	for (int i = 0; i < 19998; i++)
	{
		if (a[i] > a[i + 1])
		{
			printf("%d->%d,%d\n", a[i], a[i + 1], i);
			st = 1;
			break;
		}
	}
	if (st)
	{
		printf("out of order");
	}
	else
	{
		printf("order");
	}
}

四.非递归

使用栈先进后出来模拟实现深度优先遍历。如果不了解栈可以看看这篇博客

在这里插入图片描述

#include"stack.h"


#include<stdio.h>
#include<time.h>
#include<stdlib.h>

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[mid] > a[left])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}

int PartSort(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMid(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);

	int keyi = left;

	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[cur], &a[prev]);

		++cur;
	}

	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

		int keyi = PartSort(a, begin, end);
		// [begin,keyi-1] keyi [keyi+1, end]
		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}

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

	STDestroy(&st);
}
int main()
{
	/*int a[11] = {3,3,8,7,6,5,4,3,2,1,0};
	QuickSort(a, 0, 10);
	for (int i = 0; i < 10; i++) printf("%d ", a[i]);*/
	int a[200000];
	srand((unsigned int)time(NULL));
	for (int i = 0; i < 200000; i++)
	{
		a[i] = rand() % 1000;
	}
	QuickSort(a, 0, 199999);
	int st = 0;
	for (int i = 0; i < 199998; i++)
	{
		if (a[i] > a[i + 1])
		{
			printf("%d->%d,%d\n", a[i], a[i + 1], i);
			st = 1;
			break;
		}
	}
	if (st)
	{
		printf("out of order");
	}
	else
	{
		printf("order");
	}
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸蛋挞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值