一.概念
1.1排序
排序就是使一串记录,按照某个或某些关键字的大小,递增或递减排列起来的操作。
平时的上下文中,如果提到排序,通常指的是排升序(非降序)
通常意义上的排序,都是指的原地排序(in place sort)
原地堆排序:不开辟额外的数组空间进行的排序
升序和降序
严格升序(不包含重复元素):数字有小到大依次排列i1<i2
非降序:前一个元素<=后一个元素
在指定的集合中,若发现i1>i2,则这个集合一定不是非降序集合
非升序:前一个元素>=后一个元素
严格降序:不包含重复元素):数字有大到小依次排列i1>i2
1.2稳定性
稳定性:待排序的序列中,存在值相等的元素,经过排序之后,值相等的元素先后顺序没有发生变化的排序算法,称之为稳定性的排序算法
排序之前:9,2,3,5(a),4,1,5(b),6
经过排序算法A之后:1,2,3,4,5(a),5(b),6,9,排序算法A稳定的排序,5a和5b先后顺序没有发生变化。
经过排序算法B之后:1,2,3,4,5(b),5(a),6,9,排序B算法称之为不稳定的排序,5a和5b先后顺序发生了变化。
在选择排序算法时,除了时间复杂度和空间复杂度之外,某些场景下,排序的稳定性也是我们考量的因素之一。
京东商城的订单系统
每个订单的属性
单号,金额,下单时间
默认按照单号排序
选择稳定性的排序算法,按照金额排序之后,彼此的下单顺序没有改变
假设我现在要按照订单金额由小到大进行订单的排序,最希望的是按照订单金额有小到大排序之后,时间顺序不要发生改变。
1.3,内部排序和外部排序
本文讲的七大排序都是内部排序(待排序的数据都存放在内存中)
都是基于元素的比较来进行的
存储的元素有明确的大小关系。
排序i1和i2时就是比较i1和i2的大小关系
i1=10
i2=20
外部排序:数据存储在硬盘中,每次排序都需要从硬盘读取一部分内容到内存中,把这部分数据排序之后写回硬盘
桶排序,基数排序,计数排序O(n)这三个算法不具备普遍性,每个算法都需要特殊的数据场景下才能使用。
对一个城市的所有考生进行高考成绩排序,桶排序
二.基础排序算法
一定要定义好待排序数组的区间和已经排序好的数组区间
基础排序算法O(n^2)
基础排序算法往往作为高阶排序算法的优化手段,插入排序经常用在高阶排序的优化中。
2.1选择排序
1.选择排序
每次从无序区间选择一个最大或最小值的一个元素,放在无序区间的最后或最前,直到待排序的所有元素排序完毕。堆排序就是把最大值放在最后。
大小为100w的近乎升序的数组,在100w的元素中,大部分元素都已经有序,只有极个别的部分元素乱序
任何算法都有优化的空间
2.双向选择排序
之前的选择排序一次只能选择一个最小值或者最大值,一次只选一个元素放在正确的位置上。
我现在一次选俩,每次从无序区间中选出最小值和最大值,存放在无序区间的最开始和最后面位置,重复上述过程。
2.2插入排序
插入排序:
直接插入排序:将带排序的集合看作两部分,已排序的区间[0…i),待排序的区间[i…n)每次选择无序区间的第一个元素插入到有序区间的合适位置,直到整个数组有序。
折半插入排序
选择无序区间的第一个元素插入到有序区间的位置时,优化他的插入位置的查找次数
2.3希尔排序
就是插入排序的优化,缩小增量排序
不断地将小数组调整的近乎有序,整个大数组就接近有序状态,这个时候使用插入排序效率很高。
核心思想:我们发现,当数组近乎有序时,插入排序的效率非常好。
思路:
step1.先选顶一个正数(gap),将待排序的数据分成gap组,所有距离为gap的为同一组,对每一个小数组先进行插入排序
step2. gap = gap/2(3),重复step1,当gap==1,说明整个数组被调整的近乎有序了,此时针对整个数组进行一次插入排序即可。
希尔排序是不稳定排序
2.4归并排序
归并排序:归而为一
归:不断将原数组拆分为子数组(一分为二),直到每个子数组只剩下一个元素=》归过程结束
并:不断合并相邻的两个子数组为一个大的子数组,合并的过程就是将两个已经有序的子数组合并为一个大的有序子数组,直到合并到整个数组。
最核心的merge操作:需要开辟额外空间,空间大小就是合并后的数组大小。
1.先将 两个子数组的所有内容复制到新数组中
2.遍历两个子数组,将较小值写回原数组
海量数据处理:用到外部存储器
内存只有1G,待排序的数据有100G,该如何对这100G的数据进行排序?-通用,与数据特征无关
a.先把这100G的数据分为200分,每份为0.5G
b.分别将0.5G的数据读入内存,进行内部排序(归并,快排,堆排)
c.进行200个文件的merge操作即可
整个结果就有序了
2.5快速排序
核心思路:分区
分区值:默认选择最左侧元素pivot
从无序区间中选择一个值作为分界点pivot,开始扫描原集合,将数组中所有小于该pivot的元素放在该分界点的左侧,大于等于该元素的值放在分区点的右侧,经过本轮交换,pivot放在了最终位置,pivot的左侧都是小于该值的元素,pivot的右侧都是大于该值的元素,在这两个字区间重复上述过程,知道整个集合有序
分区方法一《算法导论》中的分区思想
在橙色部分和紫色部分重复上述操作即可
时间复杂度:平均情况nlogn
n-每次分区函数的数组扫描
logn-递归次数,“递归树”的高度
空间复杂度:递归调用次数O(logn)
归并排序无论数据长啥样子,都是无脑的一分为二,保证递归的次数一定是logn级别,非常稳定的nlogn算法
快速排序的性能严格受制于初始数据的情况而定
关于分区点选择问题:极端情况下,数组就是一个完全有序的数组
关于 分区点的选择,使用随机数随机取一个数组索引的元素作为分区点,基本上不可能出现单支树的情况,避免几乎有序的数组快排退化问题
Hoare挖坑法