【算法】六大排序 插入排序 希尔排序 选择排序 堆排序 冒泡排序 快速排序

news2024/11/18 15:37:57

本章的所有代码可以访问这里

排序 一

  • 一、排序的概念及其运用
    • 1.1排序的概念
    • 1.2 常见的排序算法
  • 二、常见排序算法的实现
    • 1、直接插入排序
    • 2、希尔排序
    • 3、选择排序
    • 4、堆排序
    • 5、冒泡排序
    • 6、快速排序
      • 6.1霍尔法
      • 6.2挖坑法
      • 6.3前后指针法
    • 7、快速排序非递归


一、排序的概念及其运用

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2 常见的排序算法

在这里插入图片描述

二、常见排序算法的实现

1、直接插入排序

  • 基本思想
    直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

<实际中我们玩扑克牌时,就用了插入排序的思想>
在这里插入图片描述

  • 步骤
    假设我们进行升序排序
    在这里插入图片描述
  1. 我们首先认为我们第一个数据是有序的。

  2. 取一个数据data,将已排序的元素序列从后往前遍历

  3. 如果遍历过程中发现已排序的元素序列中的某个数据largeData大于data那我们就将数据largeData向后移动一个单位,然后继续向前遍历。如果已排序的元素序列中的某个数据smallerData小于data,那我们就将数据data放到smallerData的后面,然后就不需要向前遍历了,因为已排序的元素序列经过上面的2-3步骤后新来的数据data也被放到了正确的位置。
    在这里插入图片描述
    在这里插入图片描述

  4. 所以我们对许多数据进行排序就被转化为上面2-3步骤的循环了。

动图演示

在这里插入图片描述

  • 代码实现
//直接插入排序
void InsertSort(int* a,int n)
{
	//排序 
	for (int i = 0; i + 1 < n; i++)
	{
		//end是有序数列的最后一个位置的下标
		int end = i;
		int tmp = a[end + 1];
		//end >= 0 防止在--end时数组越界
		while (end >= 0 && tmp < a[end])
		{
			a[end +1] = a[end];
			--end;
		}
		a[end+1] = tmp;
	}
}

  • 直接插入排序的特性总结:
  1. 元素集合越接近有序,直接插入排序算法的时间效率越高(因为数据越接近于有序,向后挪动的次数就越小)
  2. 最坏情况下为 O ( n 2 ) O(n^2) O(n2),此时待排序列为逆序,或者说接近逆序
    最好情况下为 O ( n ) O(n) O(n),此时待排序列为升序,或者说接近升序
  3. 空间复杂度: O ( 1 ) O(1) O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

2、希尔排序

希尔排序法的步骤是:
先选定一个小于N(N为数组的个数)的整数gap,把所有数据分成gap个组,所有距离为gap的数据分在同一组内,并对每一组的元素进行直接插入排序。然后取新的gap(新的gap要小于上一个gap),重复上述分组和排序的工作。当到达gap=1时,所有数据在同一组内排好顺序。

在这里插入图片描述

希尔排序法的思想是
希尔排序先将待排序列通过gap分组进行预排序(gap>1时都是预排序),使待排序列接近有序,然后再对该序列进行一次插入排序(gap=1时其实就是插入排序),此时插入排序的时间复杂度接近于O(N)

代码实现

//希尔排序   正常一组一组排序
void ShellSort(int* a,int n)
{
	//定义第一次gap的值  
	int gap = n;
	//多次预排序,gap=1时是直接排序
	while (gap > 1)
	{
		//减小gap的值直到1
		gap = gap / 3 + 1;
		//多趟排序
		for (int j = 0; j < gap; j++)
		{
			//单趟排序  i是当前有序序列最后一个位置的下标
			for (int i = j; i + gap < n; i += gap)
			{
				int end = i;
				//记录一下待插入的数据
				int tmp = a[end + gap];
				//end >= 0 防止在end -=gap 时数组越界
				while (end >= 0 && tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				a[end + gap] = tmp;
			}
		}
	}
}
  • 希尔排序的特性总结:
  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间都不固定。
  4. 时间复杂度平均 O ( N 1.3 ) O(N^{1.3}) O(N1.3)
  5. 空间复杂度: O ( 1 ) O(1) O(1)
  6. 稳定性:不稳定

3、选择排序

基本思想
每一次从待排序的数据元素中选出最小和最大的一个元素,存放在序列的起始位置和末尾位置,然后起始位置向后移动一个单位,末尾位置向前移动一个单位,直到全部待排序的数据元素排完 。

步骤
遍历一遍整个数组,选出最大值和最小值,第一次将最小值放到a[0]最大值a[n-1],第二次将最小值放到a[1]最大值a[n-2],直到下标 m > = n − m − 1 m >= n-m-1 m>=nm1结束程序。

动态演示
(下图演示的是一次只选一个最小值的算法,我们一次选出最大和最小,比图中的更高效一些)

在这里插入图片描述
代码实现

//选择排序
void SelectSort(int* a, int n)
{
	//多趟排序
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		//先假设 a[0] 位置既是最小值又是最大值
		//mini是最小值的下标,maxi是最大位置的下标
		int mini = begin, maxi = begin;
		//单趟遍历,选出最大值于最小值
		for (int i = begin; i <=end; ++i)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			else if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		Swap(&a[begin], &a[mini]);
		if (begin == maxi)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

4、堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆

堆排序详情请看这里

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

5、冒泡排序

基本思想
两两进行比较,左边大于右边就进行交换,走完一趟就排序好一个数。

在这里插入图片描述
代码实现

//冒泡排序
int BubbleSort(int*a,int n)
{
	for (int j = 0; j < n; j++)
	{
		//end是排序数组的最后一个需要排序元素的下标
		int end = n - 1 - j;
		//定义一个交换标志
		int exchange = 0;
		for (int i = 0; i < end; i++)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				exchange = 1;
			}
		}
		// 一趟冒泡过程中,没有发生交换,说明已经有序了,不需要再处理
		if (exchange == 0)
		{
			break;
		}
	}
}

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

6、快速排序

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

6.1霍尔法

步骤

  1. 首先选出一个key值作为基准值,通常选择待排序列的最左边的值作为key值。
  2. 定义两个下标,left与right,其中left指向待排序列的最左边值的下标,right指向待排序列最右边的下标。
  3. 如果我们选择了最左边作为key值,我们就要让right位置判断a[right] < key如果成立,那就让right暂时停下来;如果不成立,就让right- -,直到找到一个位置a[right] < key ,找到后,right就暂时停下来。(需要注意的是:若选择最左边的数据作为key,则需要right先走;若选择最右边的数据作为key,则需要begin先走)。
  4. right停下来之后,我们就要让左边的left的位置判断 a[left] > key 如果成立,就将a[left]与a[right]进行交换 ,如果不成立我们就将left++,直到找到一个位置a[left] > key ,找到后就与a[right]进行交换。
  5. 然后重复3-4,直到 left=right 时,我们将key与a[ left ] ( 此时a[left]=a[right] )进行交换,此时key就到了正确的位置,key将数组分成了两部分,此时key的左边都比key小,key的右边都比key大,一次单趟排序完成,一次单趟排序搞定了一个数据。
  6. 我们再对上一次的key的左右边的数组再进行上面的步骤,直到每个被分割的小区间都有序了,整个数组的排序就完成了。(最后当左右序列只有一个数据,或是左右序列不存在时,其实小区间就已经有序了)

动态演示
在这里插入图片描述
代码实现

//快速排序   hoare法   

void QuickSort(int* a, int begin, int end)
{
	//当区间只有一个值时,或区间不存在时退出递归
	if (begin >= end)
	{
		return;
	}
	//left是闭区间最最左边的下标,right是最右边位置的下标
	int left = begin, right = end;
	//选择最左边作为key值
	int keyi = left;
	//一趟排序
	while (left < right)
	{
		//右边先走,找到a[right]<key的位置
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//左边后走,找到a[left]>key的位置
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}
		//交换左右两边的值,如果left == right了,也没有必要交换了。
		if (left != right)
		{
			Swap(&a[left], &a[right]);
		}
	}
	//交换左右两边的值,如果keyi == left了,也没有必要交换了。
	if (keyi != left)
	{
		Swap(&a[keyi], &a[left]);
	}
	keyi = left;
	//此时区间被分割为[begin,keyi-1] keyi [keyi+1,end]
	//注意传递的是begin,不是0,中间递归时不一定是从0开始的区间,0是一个常量,begin是一个变量。
	QuickSort(a, begin, keyi-1);
	QuickSort(a, keyi+1, end);
}

6.2挖坑法

步骤

  1. 先将待排序列的最左边的值定为key,用一个临时变量保存key的值同时该位置的下标定为第一个坑位hole。
  2. 右边right寻找比key小的值,找到后放进坑位中,同时将坑位的位置更新为right。
  3. 左边left寻找比key大的值,找到后放进坑位中,同时将坑位的位置更新为left。
  4. 重复2-3步骤直到left==right,然后将key放到相遇位置,a[left]=key(或a[right]=key)
  5. 此时单趟排序就排序好了,key的左边的值比key小,key右边的值比key大,key到了正确的位置,区间被分为两份,一次单趟排序好一个数。
  6. 再对分割后的区间,进行多次上述的单趟排序,整个数组就有序了!
    在这里插入图片描述
//快速排序  挖坑法
void QuickSort(int* a, int begin, int end)
{
	//当区间只有一个值时,或区间不存在时退出递归
	if (begin >= end)
	{
		return;
	}
	int left = begin, right = end;
	int keyi = left;
	//刚开始时keyi的位置作为坑位
	int hole = keyi;

	int key = a[keyi];
	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;
	keyi = hole;
	
	//此时区间被分割为[begin,keyi-1] keyi [keyi+1,end]
	//注意传递的是begin,不是0,中间递归时不一定是从0开始的区间,0是一个常量,begin是一个变量。
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

6.3前后指针法

步骤

  1. 初始时,我们选定待排序的数据最左边作为key,定义prev也在待排序的数据最左边,定义cur在待排序的数据最左边+1。
  2. cur向前找比key小的值,找到后停下来,然后++prev,交换prev位置和cur位置的值,当prev与cur拉开差距时,prev与cur之间夹的值都是大于等于key的值。
  3. 最后cur刚好越界时,数组遍历完毕,交换a[prev]与key,此时key到了正确的位置,区间被分为两份,一次单趟排序好一个数。
  4. 再对分割后的区间,进行多次上述的单趟排序,整个数组就有序了!

在这里插入图片描述

//快速排序  前后指针法
void QuickSort(int* a, int begin, int end)
{
	//当区间只有一个值时,或区间不存在时退出递归
	if (begin >= end)
	{
		return;
	}
	int prev = begin , cur = begin + 1;
	int keyi = begin;
	while (prev <= cur && cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	keyi = prev;
	//此时区间被分割为[begin,keyi-1] keyi [keyi+1,end]
	//注意传递的是begin,不是0,中间递归时不一定是从0开始的区间,0是一个常量,begin是一个变量。
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
    在这里插入图片描述
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

7、快速排序非递归

快速排序的非递归实现要依赖,我们对快速排序进行递归时传递的关键数据是区间的下标,只有拿到了区间的下标我们才能进行正确的排序。
因此我们应该用利用栈的特性将区间的下标存放起来。

在这里插入图片描述

代码实现

//单趟排序
int Partion3(int* a ,int begin ,int end)
{
	int prev = begin , cur = begin + 1;
	int keyi = begin;
	while (prev <= cur && cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	keyi = prev;
	return keyi;
}
//快速排序的非递归实现
void QuickSortNonR(int* a ,int begin ,int end)
{
	Stack 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 keyi = Partion3(a, left, right);

		//此时区间被划分为[left,keyi-1]keyi[keyi+1,right]
		if (keyi + 1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}
		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}
	StackDestroy(&st);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql中的 IFNULL 函数的详解

目录 一、概念 二、语法 三、Demo 举例说明 创建表 加入数据 运行结果 3.1举例一 3.2举例二 3.3举例三 3.4举例四 注意事项 一、概念 在mysql中IFNULL() 函数用于判断第一个表达式是否为 NULL&#xff0c;如果第一个值不为NULL就执行第一个值。第一个值为 NULL 则返…

华为OD机试题,用 Java 解【求最大数字】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

【模板进阶】

目录 1. 非类型模板参数 2. 模板的特化 2.1 概念 2.2 函数模板特化 2.3 类模板特化 2.3.1 全特化 3 模板分离编译 3.1 什么是分离编译 3.2 模板的分离编译 4. 模板总结 有需要的老哥可以先看看模板的介绍&#xff1a;http://t.csdn.cn/2TkUYhttp://t.csdn.cn/2TkUY 1. …

聊点不一样的|Be a Serendipper:Woman VS Man

很喜欢这样一句话&#xff1a;Be a serendipper&#xff0c;and find your own serendipity!可以理解为&#xff1a;做一个善于发现美好事物的人&#xff0c;找到属于你自己的那些美好。每个人的生活中都有 Serendipity&#xff0c;有时能被我们一眼看到&#xff0c;有时又会藏…

七段码 杨辉三角

题目&#xff1a; 小蓝要用七段码数码管来表示一种特殊的文字。 上图给出了七段码数码管的一个图示&#xff0c;数码管中一共有 77 段可以发光的二 极管&#xff0c;分别标记为 a,b,c,d,e,f,g。 小蓝要选择一部分二极管&#xff08;至少要有一个&#xff09;发光来表达字符。在…

AI已到,普通人的机会在哪里?

“普通人赚到钱很难 但是被骗到钱很容易”。每当火起来一个行业&#xff08;或者仅是一个概念&#xff09;&#xff0c;都会有人来问&#xff1a;现在去做点什么&#xff0c;能够踩上风口&#xff1f;普通人的赚钱机会在哪&#xff1f;怎么做能够暴富&#xff1f;让我们先来看看…

【卷积神经网络】中间层网络的参数归一化方法 | BN / LN / IN / GN

文章目录一、为什么神经网络需要归一化二、常用的归一化方法三、Batch Normalization四、Layer Normalization五、Instance Normalization六、Group Normalization本文主要介绍神经网络中常用的归一化方法&#xff0c;主要是在神经网络内部对中间层的输入进行归一化&#xff0c…

【论文阅读】Robust Multi-Instance Learning with Stable Instances

1、摘要与引言 以往的MIL算法遵循i.i.d假设&#xff1a;训练样本与测试样本都分别来自于同一分布中&#xff0c;而这一假设往往与现实应用中有所出入。研究人员通过计算训练样本与测试样本之间的密度比对训练样本进行加权&#xff0c;以解决分布变化带来的问题。 分布的变化发…

SpringBoot + Druid + Mybatis-Plus + Mysql 实现数据库监控

1. 简介 在日常的WEB开发中都会使用数据库存储信息。大多数情况我们只是使用了数据库&#xff0c;而无法感知业务对数据库的压力&#xff0c;从而无法有目的的提升性能。在使用数据库时&#xff0c;都会选用常见的C3P0、DBCP、Hikari、Druid连接池&#xff0c;虽然SpringBoot官…

JavaScript中的数据类型以及存储上的差别?

前言 在JavaScript中&#xff0c;我们可以分成两种类型&#xff1a; 基本类型复杂类型 两种类型的区别是&#xff1a;存储位置不同 一、基本类型 基本类型主要为以下6种&#xff1a; NumberStringBooleanUndefinednullsymbol Number 数值最常见的整数类型格式则为十进制…

最强分布式锁工具:Redisson

1 Redisson概述1.1 什么是Redisson&#xff1f;Redisson是一个在Redis的基础上实现的Java驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。它不仅提供了一系列的分布式的Java常用对象&#xff0c;还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, Sorted…

学python的第六天---字符串

一、只出现一次的字符其他&#xff1a;round(XXX,1)忽略大小写比较字符串大小字符串几个可以使用的函数二、去掉多余的空格写法一&#xff1a;写法二&#xff1a;三、信息加密写法一&#xff1a;写法二:写法三&#xff1a;自己的写法四、单词替换五、倒排单词写法一&#xff1a…

面试官:什么是双亲委派模型?如何打破它?

本文已经收录进 JavaGuide(「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。) 参加过校招面试的同学,应该对这个问题不陌生。一般提问 JVM 知识点的时候,就会顺带问你双亲委派模型(别扭的翻译。。。)。 就算是不准备面试,学习双亲委派模型对于我…

if-else if与switch的练习1:输入两个数,输出两个数的加减乘除的值

1.if-else if的练习 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice…

所有科研人警惕,掠夺型期刊和劫持型期刊的区别,千万别投错了

当今&#xff0c;新形式的学术出版物——例如数字式或开源式的学术期刊日益普及&#xff0c;热门期刊的数量逐年增长【1】。 人们获取学术出版物也越来越容易&#xff0c;使得更多的科研人员&#xff08;特别是在低收入国家&#xff09;能够及时了解各自研究领域的最新发展态势…

ubuntu20.04搭建detectron2环境

Ubuntu22.04安装Cuda11.3 Linux下驱动安装 # 以下命令按顺序执行 sudo apt update && sudo apt upgrade -y # or sudo apt update # 查看显卡信息 ubuntu-drivers devices sudo ubuntu-drivers autoinstall # or sudo apt install nvidia-driver-510 reboot nvidia-s…

毕业设计 基于51单片机WIFI智能家居系统设计

基于51单片机WIFI智能家居系统设计1、毕业设计选题原则说明&#xff08;重点&#xff09;2、项目资料2.1 系统框架2.2 系统功能3、部分电路设计3.1 STC89C52单片机最小系统电路设计3.2 ESP8266 WIFI电路设计3.3 DHT11温湿度传感器电路设计4、部分代码展示4.1 LCD12864显示字符串…

JavaEE简单示例——Spring的入门程序

简单介绍&#xff1a; 在之前我们简单的介绍了有关于Spring的基础知识&#xff0c;那么现在我们就来一步步的把理论融入到实践中&#xff0c;开始使用这个框架&#xff0c;使用过程也是非常的简单&#xff0c;大致可以分为几个基础的步骤&#xff1a; 1.首先引入Spring的Mave…

TypeScript深度剖析:TypeScript 中泛型的理解?应用场景?

一、是什么 泛型程序设计&#xff08;generic programming&#xff09;是程序设计语言的一种风格或范式 泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型&#xff0c;在实例化时作为参数指明这些类型 在typescript中&#xff0c;定义函数&#xff0c;…

一文快速入门 HTML 网页基础

专栏简介: 前端从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录 1.HTML 结构 1.1. 认识 HTML 标签 1.2 HTML 文件结构…