当前位置: 首页 > article >正文

详解八大排序(三)------(快速排序)

文章目录

  • 前言
  • 1. 递归版本(QuickSort)
    • 1.1 hoare版本
      • 1.1.1 核心思路
      • 1.1.2 实现代码
    • 1.2 挖坑法
      • 1.2.1 核心思路
      • 1.2.2 实现代码
    • 1.3 lomuto版本
      • 1.3.1 核心思路
      • 1.3.2 实现代码
  • 2. 非递归版本(QuickSortNonR)
    • 2.1 核心思路
    • 2.2 实现代码
  • 3.完整代码

前言

快速排序的本质是找到数组中的中间值,然后从中间值对半二分数组,再从二分之后的两个数组里面找到新的中间值,再二分。直到数组全部二分完成,无法二分之后,再返回数组。
那么,问题来了。我们应该如何找到这个中间值呢?
下面我会介绍三个找到中间值的方法。

1. 递归版本(QuickSort)

1.1 hoare版本

1.1.1 核心思路

hoare版本的核心思路就是直接定义第一个数为基准值,然后把数组排序成基准值位于中间。
再二分数组,将两个数组的第一个数定义为各自的基准值,将数组排序,让基准值位于中间。
如此重复,直到新的数组无法再找基准值。
此时,递归结束,函数之间返回。

在这里插入图片描述

以上述数组为例。我们将4定义为中间值,那么从左边和右边开始比较,也就是2和5。
先从右边开始比较,右边放置的数均要大于基准值。因为5大于4,所以从倒数第二个数开始比较,一直比较到1。
右边比较完成,再从左边开始比较,因为左边的数均要小于基准值。因为2小于4,所以再比较1,一直到8。
此时得到:

在这里插入图片描述

此时再将基准值与right交换,得到:

在这里插入图片描述

此时数组的基准值4,位于基准值左边的数均小于基准值,位于基准值右边的数均大于基准值。满足二分要求。我们就可以将数组一分为二。得到:

在这里插入图片描述

此时的1和8是各自数组的基准值。以新的基准值重复上面的操作,得到:

在这里插入图片描述

可以知道,左边数组基准值的右边均大于1,右边数组基准值的左边等于5,右边均大于5。然后继续二分之一,寻找基准值,并且交换后,得到:

在这里插入图片描述

重复上述步骤,我们最终可以得到:

在这里插入图片描述

此时递归结束,将这些数组全部返回到原数组,得到:

在这里插入图片描述

1.1.2 实现代码

// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* arr, int left, int right)
{
	int keyi = left;//以数组的第一个数为基准值
	++left;//从第二个数开始比较
	while (left <= right)//当left大于等于right时,说明所有的数比较完毕
	{
		while (left <= right && arr[right] > arr[keyi])//基准值右边的数大于大于基准值
		{
			right--;
		}
		while (left <= right && arr[left] < arr[keyi])//基准值左边的数小于基准值
		{
			left++;
		}
		if (left <= right)//进入此判断条件说明,left遇到了大于等于基准值的数,right均遇到了小于等于基准值的数
		{
			Swap(&arr[left++], &arr[right--]);//交换left和right的值,同时++,判断下一个数
		}
	}
	//退出循环,说明此时right指针的左边均小于基准值,右边均大于基准值。
	Swap(&arr[keyi], &arr[right]);//让基准值与right交换
	return right;//此时right指针指向基准值
}


//快速排序---需要借助其他方法找到中间值
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)//如果left大于right,说明所有的数比较完,可以直接返回
	{
		return;
	}
	int keyi = PartSort1(arr, left, right);//优先找到中间值
	QuickSort(arr, left, keyi - 1);//二分之后再找左边的中间值,再二分,直到二分到只剩下一个数据
	QuickSort(arr, keyi + 1, right);//左边的二分完成之后,再二分右边的数值
}

1.2 挖坑法

1.2.1 核心思路

挖坑法的核心思路是将第一个数定义为坑,坑的左边均小于坑,坑的右边均大于坑。

在这里插入图片描述

以上述数组为例。我们可以将4定义为坑,4定义为left,5定义为right,同时定义一个key = 4。得到:

在这里插入图片描述

我们先从right开始比较,大于key的,我们让right减少,right停止减少之后,再把right位置的值赋值给hole位置,同时让hole指向right位置。得到:

在这里插入图片描述

再从left开始比较,比key小的位置,让left增加,再把left位置的值赋值给hole位置,同时让hole指向left位置。得到:

在这里插入图片描述

此时由于left大于right。循环退出,再把key的值赋值给hole位置。得到:

在这里插入图片描述

此时数组的基准值4,位于基准值左边的数均小于基准值,位于基准值右边的数均大于基准值。满足二分要求。我们就可以将数组一分为二。得到:

在这里插入图片描述

我们再按照上述步骤重复定义第一个数组的hole为1,第二个数组的hole为8。可以得到

在这里插入图片描述

在这里插入图片描述

以第二个数组为例,我们重复上面的步骤。得到:

在这里插入图片描述

之后的步骤与上面的一致。最终得到:

在这里插入图片描述

1.2.2 实现代码

// 快速排序挖坑法
int PartSort2(int* arr, int left, int right)
{
	int hole = left;//把第一个数定义为坑
	int key = arr[hole];//把坑的数值记录在key里面

	while (left < right)
	{
		while (left < right && arr[right] > key)
		{
			right--;//从右边起,找到第一个比hole要小的数
		}

		arr[hole] = arr[right];
		hole = right;//把hole放在right位置

		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = key;
	return hole;
}

//快速排序---需要借助其他方法找到中间值
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)//如果left大于right,说明所有的数比较完,可以直接返回
	{
		return;
	}
	int keyi = PartSort2(arr, left, right);//优先找到中间值
	QuickSort(arr, left, keyi - 1);//二分之后再找左边的中间值,再二分,直到二分到只剩下一个数据
	QuickSort(arr, keyi + 1, right);//左边的二分完成之后,再二分右边的数值
}

1.3 lomuto版本

1.3.1 核心思路

lomuto的核心思路是快慢指针的运用。

在这里插入图片描述

以上述数组为例,可以定义两个指针,prev指向4,cur指向2,再记录下第一个数的值,keyi = 4。得到:

在这里插入图片描述

在比较时,我们将cur指向的数与prev指向的数进行对比,当cur指向的值小于prev指向的值并且cur不位于prev的下一位时,我们可以将cur的值与prev交换。全部交换完成后,prev指向的位置就是中间值的位置,让prev的值与keyi交换。得到:

在这里插入图片描述

之后再将数组一分为二,得到:

在这里插入图片描述

再重复上面的步骤,得到:

在这里插入图片描述

1.3.2 实现代码

// 快速排序前后指针法
int PartSort3(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[prev], &arr[keyi]);
	return prev;
}

//快速排序---需要借助其他方法找到中间值
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)//如果left大于right,说明所有的数比较完,可以直接返回
	{
		return;
	}
	int keyi = PartSort3(arr, left, right);//优先找到中间值
	QuickSort(arr, left, keyi - 1);//二分之后再找左边的中间值,再二分,直到二分到只剩下一个数据
	QuickSort(arr, keyi + 1, right);//左边的二分完成之后,再二分右边的数值
}

2. 非递归版本(QuickSortNonR)

2.1 核心思路

非递归版本的核心思路与lomuto版本的思路非常像。只不过我们需要借助栈(Stack)这一数据结构来帮助我们排序好基准值。

在这里插入图片描述

由于栈这一数据结构只能从栈顶一端进,一端出,那么可以给待排序数组的底部划分序号。得到:

在这里插入图片描述

将数组的最后一个位置和第一个位置的下标放入栈中,得到:

在这里插入图片描述

再取出这两个数,作为需要比较的数组的区间,并且将begin定义为0,prev定义为0,cur定义为0 + 1,end定义为7。得到:

在这里插入图片描述

然后根据lomuto版本的比较法,依次比较,得到:

在这里插入图片描述

第一个基准值已经找好了,接着找第二个基准值,把 [begin,keyi - 1][keyi + 1,end] 放入栈中。得到:

在这里插入图片描述

再取出两个数,作为下一次比较的数组的区间。将这一区间比较好之后,再取出两个数。直到取出的区间均小于1。就可以返回数组。得到:

在这里插入图片描述

2.2 实现代码

// 快速排序 非递归实现
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		//取栈顶元素---取两次
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);
		//[begin,end]---找基准值

		int prev = begin;
		int cur = begin + 1;
		int keyi = begin;

		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
			{
				Swap(&arr[cur], &arr[prev]);
			}
			cur++;
		}
		Swap(&arr[keyi], &arr[prev]);

		keyi = prev;
		//根据基准值划分左右区间
		//左区间:[begin,keyi-1]
		//右区间:[keyi+1,end]

		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}

	STDestroy(&st);
}

3.完整代码

#include<stdio.h>
#include<stdlib.h>
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//打印函数
void PrintArr(int* arr,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* arr, int left, int right)
{
	int keyi = left;//以数组的第一个数为基准值
	++left;//从第二个数开始比较
	while (left <= right)//当left大于等于right时,说明所有的数比较完毕
	{
		while (left <= right && arr[right] > arr[keyi])//基准值右边的数大于大于基准值
		{
			right--;
		}
		while (left <= right && arr[left] < arr[keyi])//基准值左边的数小于基准值
		{
			left++;
		}
		if (left <= right)//进入此判断条件说明,left遇到了大于等于基准值的数,right均遇到了小于等于基准值的数
		{
			Swap(&arr[left++], &arr[right--]);//交换left和right的值,同时++,判断下一个数
		}
	}
	//退出循环,说明此时right指针的左边均小于基准值,右边均大于基准值。
	Swap(&arr[keyi], &arr[right]);//让基准值与right交换
	return right;//此时right指针指向基准值
}

// 快速排序挖坑法
int PartSort2(int* arr, int left, int right)
{
	int hole = left;//把第一个数定义为坑
	int key = arr[hole];//把坑的数值记录在key里面

	while (left < right)
	{
		while (left < right && arr[right] > key)
		{
			right--;//从右边起,找到第一个比hole要小的数
		}

		arr[hole] = arr[right];
		hole = right;//把hole放在right位置

		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = key;
	return hole;
}

// 快速排序前后指针法
int PartSort3(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[prev], &arr[keyi]);
	return prev;
}

//快速排序---需要借助其他方法找到中间值
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)//如果left大于right,说明所有的数比较完,可以直接返回
	{
		return;
	}
	int keyi = PartSort3(arr, left, right);//优先找到中间值
	QuickSort(arr, left, keyi - 1);//二分之后再找左边的中间值,再二分,直到二分到只剩下一个数据
	QuickSort(arr, keyi + 1, right);//左边的二分完成之后,再二分右边的数值
}

// 快速排序 非递归实现
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		//取栈顶元素---取两次
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);
		//[begin,end]---找基准值

		int prev = begin;
		int cur = begin + 1;
		int keyi = begin;

		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
			{
				Swap(&arr[cur], &arr[prev]);
			}
			cur++;
		}
		Swap(&arr[keyi], &arr[prev]);

		keyi = prev;
		//根据基准值划分左右区间
		//左区间:[begin,keyi-1]
		//右区间:[keyi+1,end]

		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}

	STDestroy(&st);
}

int main()
{
	//int arr[] = { 5,2,7,8,1,3,9,4,6 };
	int arr[] = { 4,2,1,8,5,6,9,5 };
	//int arr[] = { 2,3,6,5 };
	int sz = sizeof(arr) / sizeof(int);
	printf("排序前:");
	PrintArr(arr, sz);
	
	//QuickSort(arr, 0, sz - 1);
	QuickSortNonR(arr, 0, sz - 1);

	printf("排序后:");
	PrintArr(arr, sz);
	return 0;
}


http://www.kler.cn/a/398554.html

相关文章:

  • 论文笔记 SuDORMRF:EFFICIENT NETWORKS FOR UNIVERSAL AUDIO SOURCE SEPARATION
  • 结构化需求分析与设计
  • 每日一博 - Java的Shallow Copy和Deep Copy
  • 023、ELK 从入门到实践
  • 联通大数据面试题及参考答案
  • redis和mongodb等对比分析
  • LLM性能优化中的一些概念扫盲
  • LabVIEW中的UDP与TCP比较
  • React Native 全栈开发实战班 - 网络与数据之网络请求基础
  • 实习冲刺练习 第二十四天
  • 《Django 5 By Example》阅读笔记:p54-p75
  • 无需制作PE系统盘,完成更换固态,数据迁移
  • Windows docker下载minio出现“Using default tag: latestError response from daemon”
  • Matlab使用深度网络设计器为迁移学习准备网络
  • Spark读MySQL数据rdd分区数受什么影响,读parquet、hdfs、hive、Doris、Kafka呢?
  • spring-gateway网关聚合swagger实现多个服务接口切换
  • OceanBase单表恢复(4.2.1.8)
  • 【SSL证书】腾讯云SSL续签备忘录
  • VScode+opencv——关于opencv多张图片拼接成一张图片的算法
  • 深入剖析Kubernetes监控体系:Prometheus、Metrics Server与Kubernetes监控体系
  • 二五、pxe自动装机
  • C# WPF .NET6程序可以直接运行?不需要装.NET运行时?
  • 【jvm】HotSpot中方法区的演进
  • 【java】值传递引用传递
  • JAVA中对象实体与对象引用有何不同?举例说明
  • Transformer学习笔记(一)