首页 > 编程语言 >排序算法2:直接选择排序与快速排序

排序算法2:直接选择排序与快速排序

时间:2024-08-05 21:23:47浏览次数:12  
标签:arr right 基准值 int 算法 排序 快速 left

目录

1.直接选择排序

1.1直接选择排序的优化

2.快速排序

2.1基准值的置位(Hoare版)

 2.2挖坑法

2.3lomuto前后指针


前言

前面我们进入了排序算的讲解。今天我们将继续学习几种重要的排序思想,好,咱们三连上车开始今天的内容。

1.直接选择排序

在元素的集合中选出最大值(最小值),存放在序列的起始位置,直到全部的待排序位置数据排完

  1. 在元素集合中arr[i]-----arr[n-1]中选择值最大(小)数据
  2. 若他不是这组元素中的最后一个数据,则将他与这组的最后一个元素交换。
  3. 在剩余的arr[i]----arr[n-2](arr[i + 1]-----arr[n-1])的集合中,传重复上面的步骤,直到集合剩余最后一个元素! 

 思路十分的简单,我们按照这样的思想来实现一下代码:

void SelectSort1(int* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		int begin = i;
		int min = begin;
		for (int j = begin + 1; j < sz; j++)
		{
			if (arr[min] > arr[j])
			{
				min = j;
			}
		}
		Swap(&arr[min], &arr[begin]);
	}
}

 

 我们这样就实现了排序的目的,但是大家也不难看出,直接这样的排序跟冒泡排序相差无几,同样的效率低下,想要扩大他的使用场景,就要将他进行进一步的优化。

1.1直接选择排序的优化

原来的直接选择排序在排序时只是将特定范围内的最小值与该范围内的第一个元素进行交换,那如果我们在寻找最小值的同时,也来寻找最大值来与最后面的元素进行交换,这样就可以大幅提高排序的效率了。

按照这个思路我们来实现这个代码:

void SelectSort(int* arr, int sz)
{
	int begin = 0;
	int end = sz - 1;
	while (end > begin)
	{
		int min = begin;
		int max = begin;
		//找特定范围中最小的值和最大值
		for (int j = begin + 1; j <= end; j++)
		{
			if (arr[min] > arr[j])
				min = j;
			if (arr[max] < arr[j])
				max = j;
		}
		//避免max与begin都在同一位置,begin和min进行交换后,max数据变成了最小的数据
		if (max == begin)
		{
			max = min;
		}
		Swap(&arr[min], &arr[begin]);
		Swap(&arr[max], &arr[end]);
		end--;
		begin++;
	}
}
	

直接选择牌序的优化就完成了!! 

2.快速排序

 快熟排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本的思想就是:任取待排序的元素序列中的某个元素作为基准值,按照排序码将待排序的集合分割为两个子序列,左子序列中所有元素都要小于基准值,有序列中的元素都大于基准值,然后在左右子序列种重复该过程,直到左右的元素排列到相应的位置为止。

2.1基准值的置位(Hoare版)

这里我们单独创建一个函数来查找排序区域的基准值位置:

int FindKey(int* arr, int left, int right)
{
	//从左边找比基准值大的数据
	//从右边找基准值较小的数据
	int key = left;
	left++;
	while (left <= right)
	{
		while (left <= right&&arr[left] < arr[key])
		{
			left++;
		}
		while (left <= right && arr[right] > arr[key])
		{
			right--;
		}
		if (left <= right)
		Swap(&arr[right--], &arr[left++]);//在遇到一定区域内的值都相等时,需要让基准值的位置向中间移,这样才能让区域越划越小
	}
		Swap(&arr[key], &arr[right]);
	return right;
}

找到基准值后,我们根据二叉树结构的性质,很容易就能够确定使用递归的思想来实现循环划分左右子区域的操作

void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;
	//left and right----->找基准值mid
	//1.如何找基准值
	int keyi = FindKey(arr, left, right);
	//左子序列
	QuickSort(arr, left, keyi - 1);
	//右子序列
	QuickSort(arr, keyi + 1, right );
}

 所以Hoare版的快速排序我们就完成了,接下来我们来验证一下:

int FindKey(int* arr, int left, int right)
{
	//从左边找比基准值大的数据
	//从右边找基准值较小的数据
	int key = left;
	left++;
	while (left <= right)
	{
		while (left <= right&&arr[left] < arr[key])
		{
			left++;
		}
		while (left <= right && arr[right] > arr[key])
		{
			right--;
		}
		if (left <= right)
		Swap(&arr[right--], &arr[left++]);//在遇到一定区域内的值都相等时,需要让基准值的位置向中间移,这样才能让区域越划越小
	}
		Swap(&arr[key], &arr[right]);
	return right;
}
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;
	//left and right----->找基准值mid
	//1.如何找基准值
	int keyi = FindKey(arr, left, right);
	//左子序列
	QuickSort(arr, left, keyi - 1);
	//右子序列
	QuickSort(arr, keyi + 1, right );
}

 

快速排序在实际的生活工作场景中运用广泛,这得益于他二叉树的结构性质,使其拥有了与堆排序相近甚至更快的速率。 

 2.2挖坑法

思路:创建左右指针。首先从右向左找比基准值小的数据,找到后立即放入左边的坑位,当前位置变为新的“坑”, 然后从左向右找比基准值大的数据,找到后立即放入右边的坑洞中,当前位置变为新的坑,结束循环后将最开始存储的分界值放入当前的“坑”中,返回当前“坑”的下标(即分界值下标)。

 挖坑法相较于Hoare的方法要简单,我们稍加理解就可以将代码写出来。

我们来看代码:

int _QuickSort(int* a, int left, int right)
{
	int mid = a[left];
	int hole = left;
	int key = a[hole];
	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;
}

本质上这时一种新的找基准值的方法,虽然相较于Hoare版的代码挖坑法的代码会更加的冗余,但是他的思想和实现方法更易于理解。

int _QuickSort(int* a, int left, int right)
{
	int mid = a[left];
	int hole = left;
	int key = a[hole];
	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;
}
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;
	//left and right----->找基准值mid
	//1.如何找基准值
	int keyi = _QuickSort(arr, left, right);
	//左子序列
	QuickSort(arr, left, keyi - 1);
	//右子序列
	QuickSort(arr, keyi + 1, right );
}

 

经过验证,我们的代码就实现了! 

2.3lomuto前后指针

创建前后指针,从左往右找比基准值小的进行交换,使得小的数据都排在基准值的左边。

 

前后指针法相比于前面的挖坑法和Hoare方法都要简单,但是思路着实不好想,这就需要我们积累新的方法了:

  • 定义两个指针 prev和cur;
  •  cur指向的位置的数据跟key的值比较;
  • 若arr[cur] < arr[key],prev向后走一步并和cur交换;
  • 若arr[cur] >= arr[key],cur继续向后
int Doublelomuto(int *arr, int left, int right)
{
	int prev = left, cur = left;
	int key = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[key] && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	Swap(&arr[key], &arr[prev]);
	return prev;
}

最后我们来验证一下这个代码:

int Doublelomuto(int *arr, int left, int right)
{
	int prev = left, cur = left;
	int key = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[key] && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	Swap(&arr[key], &arr[prev]);
	return prev;
}
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;
	//left and right----->找基准值mid
	//1.如何找基准值
	int keyi = Doublelomuto(arr, left, right);
	//左子序列
	QuickSort(arr, left, keyi - 1);
	//右子序列
	QuickSort(arr, keyi + 1, right );
}

快速排序的三种版本的递归排序方法就实现完了。

好今天的学习就到这里,我们下期再见,拜拜!! 

标签:arr,right,基准值,int,算法,排序,快速,left
From: https://blog.csdn.net/2302_79964175/article/details/140926039

相关文章

  • 算法·理论:Manacher 笔记
    \(\text{Manacher}\)来啦!\(\text{Manacher}\)并没有什么前置知识,比\(\text{KMP}\)简单多了。前置处理\(\text{Manacher}\)算法用于解决回文串相关问题,先看几个基本概念:回文中心、回文半径,这些看字面意思就能猜到。还有一个重要问题:对于回文串,有长度为奇数或长度为偶数之......
  • 【算法】浅析网络流算法
    网络流算法:优化资源分配,提升网络效率1.引言在网络科学、运筹学以及计算机科学等领域,网络流算法是一个重要的研究对象。它关注如何在网络中高效地分配资源,以实现最大流、最小费用流等目标。本文将带你了解网络流算法的原理、使用方法及其在实际应用中的意义,并通过代码示例......
  • P9596 冒泡排序 2 题解
    题目链接。Statement记\(f(A)\)为序列\(A\)的冒泡排序趟数,操作:单点改,全局查\(f(A)\).\(n,m\le5\cdot10^5\),值域1e9.Solution结论:\[Ans=\max_{i\in[1..n]}\left\{\sum_{j\in[1..i]}[A_j>A_i]\right\}\]怎么观察出来啊QAQ证明:对于每个位置\(p\),观察到每趟都......
  • 【数据结构】一文总结算法的时间复杂度与空间复杂度
    目录一.算法的复杂度二.时间复杂度1.概念2.大O的渐进表示法3.实践练习3.1练习13.2 练习23.3 练习33.4练习43.5练习5三.空间复杂度 1.概念2.实践练习2.1练习12.2练习22.3练习32.4练习4四.编程题练习 1. 消失的数字2.轮转数组 一.......
  • 数组的算法
    数组的算法在Java中,数组是一种基本的数据结构,常用于实现各种算法。以下是一些常见的与数组相关的算法:排序算法:冒泡排序(BubbleSort)选择排序(SelectionSort)插入排序(InsertionSort)快速排序(QuickSort)归并排序(MergeSort)堆排序(HeapSort)搜索算法:线性搜索(LinearS......
  • 呵呵算法题
    假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为timePerRoad;街道的街口(交叉点)有交通灯,灯的周期T(=lights[row][col])各不相同;车辆可直行、左转和右转,其中直行和左转需要等相应T时间的交通灯才可通行,右转无需等待。现给出n*m个街口的交通灯周期,以及起止街口......
  • 时间旅行者:LSTM算法的奥秘大揭秘!
    Hey小伙伴们,今天给大家带来一个超级有趣的主题——LSTM算法的基本结构和公式推导!......
  • Docker快速入门
    DockerDocker:快速构建、运行、管理应用的工具安装docker需要安装Linux虚拟机教程:‍⁠‬‍‍‍‍‌⁠‍‬‌‬‍‬‍‬‍‬Linux环境搭建-飞书云文档(feishu.cn)Linux虚拟机操作过于繁琐安装MobaXterm来解决这个问题在虚拟机中安装docker后进行以下操作CentOS7配置......
  • nrm -- 快速切换下载镜像
    1.简介nrm是npm的镜像源管理工具,有时候国外资源太慢,使用nrm可以加速的在npm源之间切换。2.安装npminstall-gnrm3.基本使用nrmls查看可选择的源nrmuse对应的镜像切换到对应的镜像源nrmdel对应的镜像源删除镜像源nrmaddregistryhttp://registry.n......
  • 常见的PID的算法及代码示例
    常见的PID的算法及代码示例PID(比例-积分-微分)算法是控制系统中常用的一种反馈控制算法,它通过计算误差的比例、积分和微分来调整控制输入,以达到预定的控制目标。以下是一些常见的PID算法及代码示例:一、常见的PID算法位置式PID算法位置式PID算法直接计算控制量的绝对值,每次输......