资讯专栏INFORMATION COLUMN

八种常见排序算法细讲

hiyang / 2801人阅读

摘要:目录常见的八种排序常见的八种排序直接插入排序直接插入排序希尔排序希尔排序直接选择排序直接选择排序堆排序堆排序冒泡排序冒泡排序快速排序快速排序版本版本挖坑法挖坑法前后指针版前后指针版快速排序代码

目录

常见的八种排序

直接插入排序

希尔排序

直接选择排序

堆排序

冒泡排序 

快速排序

hoare版本 

挖坑法

前后指针版

快速排序代码

归并排序 

计数排序


 

常见的八种排序

 直接插入排序

⾸先,我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有⼀个元素,就是数组的第⼀个元素。插⼊算法的核⼼思想是取未排序区间中的元素,在已排序区间中找到合适的插⼊位置将其插⼊,并保证已排序区间数据⼀直有序。重复这个过程,直到未排序区间中元素为空,算法结束

如图所示,要排序的数据是4,5,6,1,3,2,其中左侧为已排序区间,右侧是未排序区间。

 插⼊排序包含两种操作,⼀种是元素的⽐较,⼀种是元素的移动。比如我们需要将⼀个数据3插⼊
到已排序区间[1,4,5,6]时,需要拿3与已排序区间的元素6,5,4,1依次⽐较⼤⼩,找到合适的插⼊位置。找到插⼊点之后,我们还需要将插⼊点之后的元素顺序往后移动⼀位,这样才能腾出位置给元素3插⼊。比如3和6比较,此时就可以将3和6的位置进行交换,依次类推,3再和5交换,3再和4交换,当3和1比较发现3比1大就不用再交换了,完成了3的插入过程了

C代码

 输出结果

 时间复杂度O(N^2),空间复杂度O(1)

稳定性:稳定

稳定性的说明

 图中红色的5在排完序后依旧在蓝色的5后面,这就是稳定的表现

希尔排序

 希尔排序可以看成是对直接插入排序的优化:我们可以看到直接插入排序的缺点即对一个降序的数组进行升序那么时间复杂度为O(N^2),但是对该数组进行降序那么时间复杂度就为O(N)了,此时大家会想该数组都是降序的了,你再对其降序图啥呢?这里想阐述的观点是如果将该数组变为

接近有序的状态那么使用直接插入排序其时间复杂度不就降下来了吗,希尔排序就是用了这个思想

对直接插入排序进行了优化!!!

 

 执行结果

 时间复杂度O(N^logN),空间复杂度O(1)

稳定性:不稳定

直接选择排序

 基本思想:一次挑一个数据:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完

 排序结果:

 堆排序

 注意:使用堆排序首先需要理解什么是堆,大堆与小堆的区别,这里就不对堆的概念进行说明

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

将数组key=[20,17,4,16,5,3]建立一个大堆,对该数组排升序

 

执行结果:

 上面代码中数组arr已经是个大堆了,这里只是对堆排序的过程进行了说明,

如何将一个普通的数组建立成一个大堆这里就不作讲述

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

冒泡排序 

 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进⾏⽐较,看是否满⾜⼤⼩关系要求。如果不满⾜就让它俩互换。图中相邻的元素如果左边的元素大于右边的元素,那么就进行交换,即相邻的两个元素右边总是较大的。

 

  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

 快速排序

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

将区间按照基准值划分为左右两半部分的常见方式有:

  1. hoare版本
  2. 挖坑法
  3. 前后指针版本

 

  1. 三数取中法选key可以保证不会出现最坏的情况,而且当数据有序的时候就是最好的情况)
  2. 递归到小的子区间时,可以考虑使用插入排序

//快排,时间复杂度,最好的情况O(N*log2(N)),最坏O(N^2)//优化方法1:三数取中,避免快排出现最坏的情况int GetMidIndex(int* a, int left, int right){	int mid = (left + right) >> 1;//移位的效率比除以2的效率要高一点	// left  mid  right	if (a[left] < a[mid])	{		if (a[mid] < a[right])		{			return mid;		}		else if (a[left] > a[right])		{			return left;		}		else		{			return right;		}	}	else // a[left] >= a[mid]	{		if (a[mid] > a[right])		{			return mid;		}		else if (a[left] < a[right])		{			return left;		}		else		{			return right;		}	}}

hoare版本 

 

int PartSort1(int* a, int left, int right)//排序一趟就返回相遇点{	int midIndex = GetMidIndex(a, left, right);//使用三数取中	Swap(&a[left], &a[midIndex]);//将三数取中后的结果与最左边的值进行交换	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]);	}	Swap(&a[keyi], &a[left]);	return left;//返回相遇的位置,也就是keyi}

挖坑法

 

int PartSort2(int* a, int left, int right){	int midIndex = GetMidIndex(a, left, right);//使用三数取中	Swap(&a[left], &a[midIndex]);//将三数取中后的结果与最左边的值进行交换	int key = a[left];//将最左边的值给key,然后将最左边的视为坑(没有数据的意思)	while (left < right)	{		// 找小		while (left < right && a[right] >= key)		{			--right;		}		// 放到左边的坑位中,右边就形成新的坑		a[left] = a[right];		// 找大		while (left < right && a[left] <= key)		{			++left;		}		// 放到右边的坑位中,左边就形成新的坑		a[right] = a[left];	}	a[left] = key;//最后相遇点一定是坑,将key放到坑中	return left;//返回相遇点,也就是key值所在的位置}

前后指针版

 

int PartSort3(int* a, int left, int right){	int midIndex = GetMidIndex(a, left, right);	Swap(&a[left], &a[midIndex]);	int keyi = left;	int prev = left, cur = left + 1;	while (cur <= right)	{		if (a[cur] < a[keyi] && ++prev != cur)//cur找比keyi小的数		{			Swap(&a[cur], &a[prev]);		}		++cur;	}	Swap(&a[keyi], &a[prev]);	return prev;//最后返回prev的位置,也就是keyi,这里的keyi表示的是下标}

快速排序代码

 上面的都是各版本进行单趟排序的代码

void QuickSort(int* a, int begin, int end)//(用的是hoare法){	if (begin >= end)//[begin,end]区间为0或者区间不存在则返回		return;	// 1、如果这个子区间是数据较多,继续选key单趟,分割子区间分治递归	// 2、如果这个子区间是数据较小,再去分治递归不太划算	//此时越往后递归,子区间就越多,每个子区间的数据就越少,每个子区间都要递归就不划算,	//可以在后面进行插入排序,因为此时每个子区间是接近有序的,接近于希尔排序了	if (end - begin > 0)//小区间优化的效果没那么明显,如果对相应数据量级进行针对性的调		//往往数据量越大,比如将20换成1000效果就明显了,20是官方给的,官方不敢给大了	{		int keyi = PartSort1(a, begin, end);		//int keyi = PartSort2(a, begin, end);		//int keyi = PartSort3(a, begin, end);		// [begin, keyi-1] keyi [keyi+1, end]		QuickSort(a, begin, keyi - 1);//递归		QuickSort(a, keyi + 1, end);//递归	}	else	{		InsertSort(a + begin, end - begin + 1);		//HeapSort(a + begin, end - begin + 1);也可以换成其如堆排,希尔排序效果会更好	}}
  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(logN)
  3. 稳定性:不稳定

归并排序 

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

 

void _MergeSort(int* a, int left, int right, int* tmp){	if (left >= right)		return;	int mid = (left + right) >> 1;	// [left, mid][mid+1,right]	_MergeSort(a, left, mid, tmp);//先递归进行分治	_MergeSort(a, mid + 1, right, tmp);	// 两段有序子区间归并tmp,并拷贝回去	int begin1 = left, end1 = mid;	int begin2 = mid + 1, end2 = right;	int i = left;	while (begin1 <= end1 && begin2 <= end2)	{		if (a[begin1] < a[begin2])			tmp[i++] = a[begin1++];		else			tmp[i++] = a[begin2++];	}	while (begin1 <= end1)		tmp[i++] = a[begin1++];	while (begin2 <= end2)		tmp[i++] = a[begin2++];	// 归并完成以后,拷贝回到原数组	for (int j = left; j <= right; ++j)		a[j] = tmp[j];}void MergeSort(int* a, int n){	int* tmp = (int*)malloc(sizeof(int)*n);//创建临时数组	if (tmp == NULL)	{		printf("malloc fail/n");		exit(-1);	}	_MergeSort(a, 0, n - 1, tmp);	free(tmp);}
  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(N)
  3. 稳定性:稳定

 计数排序

 

//计数排序void CountSort(int* a, int n){	int min = a[0];//记录数组中的最小值	int max = a[0];//记录数组中的最大值	for (int i = 0; i < n; i++)	{		if (a[i] < min)			min = a[i];		if (a[i] > max)			max = a[i];	}	int range = max - min + 1;//min和max之间的自然数个数(包括min和max本身)	int* count = (int*)calloc(range, sizeof(int));//开辟可储存range个整型的内存空间,并将内存空间置0	if (count == NULL)	{		printf("malloc fail/n");		exit(-1);	}	//统计相同元素出现次数(相对映射)	for (int i = 0; i < n; i++)	{		count[a[i] - min]++;	}	int i = 0;	//根据统计结果将序列回收到原来的序列中	for (int j = 0; j < range; j++)	{		while (count[j]--)		{			a[i++] = j + min;		}	}	free(count);//释放空间}
  1. 时间复杂度:O(MAX(N+范围))
  2. 空间复杂度:O(范围)
  3. 稳定性:稳定

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123620.html

相关文章

  • Java常用的八种排序算法与代码实现精解

    摘要:直接插入排序的算法重点在于寻找插入位置。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。简单选择排序常用于取序列中最大最小的几个数时。将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。 1.直接插入排序 直接插入排序算法是排序算法中最简单的,但在寻找插入位置时的效率不高。基本思想就是将一个待排序的数字在已经排序的序列中寻找找到一个插...

    2501207950 评论0 收藏0
  • PHP面试之四:逻辑与算法

    摘要:数据结构常见数据结构数组是最简单而且应用最广泛的数据结构特征使用连续内存空间来存储存放相同类型或着衍生类型的元素数组比较特别,可以存放八种数据类型通过下标来访问集合特征保存不重复的元素字典特征就是关联数组,以形式存储栈,与队列相似特征存储数 数据结构 常见数据结构 Array 数组是 最简单 而且 应用最广泛 的数据结构 特征: 1、使用连续内存空间来存储 2、存放相同类型或着衍生类型...

    smartlion 评论0 收藏0
  • Java 总结

    摘要:中的详解必修个多线程问题总结个多线程问题总结有哪些源代码看了后让你收获很多,代码思维和能力有较大的提升有哪些源代码看了后让你收获很多,代码思维和能力有较大的提升开源的运行原理从虚拟机工作流程看运行原理。 自己实现集合框架 (三): 单链表的实现 自己实现集合框架 (三): 单链表的实现 基于 POI 封装 ExcelUtil 精简的 Excel 导入导出 由于 poi 本身只是针对于 ...

    caspar 评论0 收藏0
  • 博客 - 收藏集 - 掘金

    摘要:技术之类加载机制掘金类加载机制是语言的一大亮点,使得类可以被动态加载到虚拟机中。玩转仿探探卡片式滑动效果掘金讲起本篇博客的历史起源,估计有一段历史了。 Java 技术之类加载机制 - Android - 掘金类加载机制是 Java 语言的一大亮点,使得 Java 类可以被动态加载到 Java 虚拟机中。 这次我们抛开术语和概念,从例子入手,由浅入深地讲解 Java 的类加载机制。 本文...

    Shimmer 评论0 收藏0
  • 博客 - 收藏集 - 掘金

    摘要:技术之类加载机制掘金类加载机制是语言的一大亮点,使得类可以被动态加载到虚拟机中。玩转仿探探卡片式滑动效果掘金讲起本篇博客的历史起源,估计有一段历史了。 Java 技术之类加载机制 - Android - 掘金类加载机制是 Java 语言的一大亮点,使得 Java 类可以被动态加载到 Java 虚拟机中。 这次我们抛开术语和概念,从例子入手,由浅入深地讲解 Java 的类加载机制。 本文...

    The question 评论0 收藏0

发表评论

0条评论

hiyang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<