八种常见排序算法细讲
目录
常见的八种排序
直接插入排序
希尔排序
直接选择排序
堆排序
冒泡排序
快速排序
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已经是个大堆了,这里只是对堆排序的过程进行了说明,
如何将一个普通的数组建立成一个大堆这里就不作讲述
- 时间复杂度:O(N*logN)
- 空间复杂度:O(1)
- 稳定性:不稳定
冒泡排序
冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进⾏⽐较,看是否满⾜⼤⼩关系要求。如果不满⾜就让它俩互换。图中相邻的元素如果左边的元素大于右边的元素,那么就进行交换,即相邻的两个元素右边总是较大的。
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后对左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
将区间按照基准值划分为左右两半部分的常见方式有:
- hoare版本
- 挖坑法
- 前后指针版本
- 三数取中法选key(可以保证不会出现最坏的情况,而且当数据有序的时候就是最好的情况)
- 递归到小的子区间时,可以考虑使用插入排序
//快排,时间复杂度,最好的情况O(N*log2(N)),最坏O(N^2)
//优化方法1:三数取中,避免快排出现最坏的情况
int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) >> 1;//移位的效率比除以2的效率要高一点// left mid rightif (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);也可以换成其如堆排,希尔排序效果会更好}
}
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
归并排序
归并排序(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++];elsetmp[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);
}
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定
计数排序
//计数排序
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个整型的内存空间,并将内存空间置0if (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);//释放空间
}
- 时间复杂度:O(MAX(N+范围))
- 空间复杂度:O(范围)
- 稳定性:稳定
八种常见排序算法细讲相关推荐
- 八种经典排序算法总结
前言 算法和数据结构是一个程序员的内功,所以经常在一些笔试中都会要求手写一些简单的排序算法,以此考验面试者的编程水平.下面我就简单介绍八种常见的排序算法,一起学习一下. 一.冒泡排序 思路: 比较相邻 ...
- 排序 八种经典排序算法
排序(Sorting) 是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列. 我整理了以前自己所写的一些排序算法结合网上的一些资料,共介绍8 ...
- 剖析八种经典排序算法
排序(Sorting) 是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列. 我整理了以前自己所写的一些排序算法结合网上的一些资料,共介绍8 ...
- 八种常用排序算法参考
写在前面: 因为网络上有很多篇讲排序算法的,所以这里就不详细讲了,只作为参考和自己查阅 当然了,虽然篇幅也会短很多,但部分重点和要点还在 八种排序算法分别是: ①选择排序: ②冒泡排序: ③插入排序: ...
- 八种常见排序方法总结 C/C++代码实现
目前评价排序算法的好坏标准主要有两点: 1.执行时间:高效的排序算法的比较次数和移动次数都应该尽可能的少. 2.辅助空间:算法执行期间所需要的辅助空间与待排序数据量无关. 文章目录 1.冒泡排序 2. ...
- Java几种常见排序算法与代码实现
前言: 排序算法也算是每年校招.春招.社招都会问到的问题,虽然每次复习了就忘,但是也可以隔一段时间又拿出来看看. 其中,排序方式指,内部还是外部排序.只需要内部内存就可以的称为内部排序,数据量太大需要 ...
- php 各种排序算法,PHP四种常见排序算法
一.冒泡排序: 冒泡排序可以说是最常见,也是最简单,最经典的排序算法了. 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换 ...
- python 实现数据结构八种内部排序算法
目录 一.插入排序 二.希尔排序 三.冒泡排序 四.快速排序 五.选择排序 六.堆排序 七.归并排序 八.基数排序 一.插入排序 array_test = [8,3,5,1,10,4,2,6,7,9] ...
- 利用Python实现几种常见排序算法
一.排序算法概览 插入排序:直接插入排序,二分法插入排序 选择排序:直接选择排序,堆排序 交换排序:冒泡排序,快速排序 归并排序 二.代码实现 1.直接插入排序 最简单直接的一种方式,序列在排序中可分 ...
最新文章
- Java垃圾回收之老年代垃圾收集器
- 面向对象的五大原则与IOC
- CodeForces 407C
- ios 获取一个枚举的所有值_凯哥带你从零学大数据系列之Java篇---第十一章:枚举...
- MAVEN简介之——settings.xml
- 了解如何在20分钟内创建您的第一个Angular应用
- android 获取和设置屏幕亮度
- tp3.2部署在nginx主页正常,其他页面404问题解决方式
- python字符串查找某个字符_python的字符串
- python语言特点粘性扩展_【语言处理与Python】9.2处理特征结构\9.3扩展基于特征的文法...
- Lintcode--007(不同的子序列)
- oracle之三 自动任务调度
- c# 火狐浏览器怎么嵌入窗体中_.net winform程序下使用firefox作为Web浏览器
- SPSS数据转换插件v2.0
- 真正拖垮打工人的,是沉没成本
- 成为独立游戏制作人需要注意的六件事
- 使用Python对视频任意矩形区域进行裁剪
- 软件测试工程师面试的时候该怎么样介绍自己?
- STorM32 BGC 三轴云台学习(一) 通信协议分析
- R中两种常用并行方法——2. snowfall