首先,排序算法的稳定性通俗的讲就是能保证排序前两个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。
其次,说一下稳定性的好处,排序算法如果是稳定的,那么从一个键上排序,然后在从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序在高位也相同时是不会改变的。
分析常见的排序法的稳定性
稳定的排序算法:
不稳定的排序算法:选择排序 快速排序 希尔排序堆排序
总体而言,存在不相邻交换的排序算法一般是不稳定的,相邻交换的排序算法一般是稳定的,对于相邻交换的稳定排序算法,通过控制交换条件可以转换成不稳定排序算法。
1)冒泡排序
冒泡排序是通过相邻比较,实时交换,缩小范围实现排序的,第一次操作n个元素,通过相邻比较将0~n-1中的最大元素交换到位置n-1上,第二次操作n-1个元素,通过相邻比较将0~n-2中最大元素交换到位置n-2上……第n-1次操作两个元素,通过相邻比较将0~1上最大的元素交换到位置1上完成排序。在相邻比较时如果两个元素相等,一般不执行交换操作,因此冒泡排序是一种稳定排序法。
2)选择排序
选择排序是通过不断缩小排序序列长度来实现的。第1次操作N个元素,选择0~n-1中的最小者交换到位置0上,第二次操作n-1个元素,选择1~n-1中最小者交换到位置1上……第n-1次操作2个元素,选择n-2~n-1上的最小者交换到位置n-2上完成排序。在每次选择最小元素进行交换时,可能破坏稳定性。这种情况可以描述为:约定要发生交换的位置称为当前位置,被交换的位置称为被交换位置,被交换位置上的元素为选中的最小元素。如果当前位置之后和被交换位置之前存在与当前位置相等的元素,执行后就破坏的稳定性。如序列5 8 5 2 9,我们第一遍选择第一个元素5会和2交换,那么原序列中的两个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
3)插入排序
插入排序是通过不断扩大排序序列的长度来实现的。第一次操作一个元素,直接放到位置0上即可;第二次操作两个元素,在0~1上为当前元素找到合适位置并插入;第三次操作3个元素,用在0~2上为当前元素找到合适位置并插入它……第n次操作n个元素,在0~n-1上为当前元素找到合适位置并插入完成排序。讨论元素的插入过程,假设当前是第n次操作,要在0~n-1上为当前元素寻找到合适位置,设置一个工作指针出示化为n-1,向前移动工作指针知道遇到一个不大于当前元素的元素,就在这个元素的后面插入当前元素,仔细体会这个插入过程,不难理解插入排序是稳定的。
4)快速排序
时间复杂度:O(n*lgn)
最坏:O(n^2)
空间复杂度:O(n*lgn)
不稳定。
快速排序有两个方向,左边的i下标当a[i]<a[center]时一直往右走,其中center是中间元素的数组下标,一般取为当前排序段的第一个元素。而右边的j下标当a[j]>a[center]时一直往左走,如果i和j都走不动了,这时必有结论a[i]>a[center]>=a[j],我们的目的是将a分成不大于a[center]和大于a[center]的两个部分,其中前者位于左半部分后者位于右半部分。所以如果i>j表明已经分好,否则需要交换两者。当左右分好时,j指向了左侧的最后一个元素,这是需要将a[center]与a[j]交换,这个时候可能会破坏稳定性,这种情形可以这样描述:center位置之后j位置钱存在与j相等的元素,指向center与j的交换后,稳定性破坏。比如序列53343891011,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法。
假设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。一躺快速排序的算法是:
1)、设置两个变量I、J,排序开始的时候I:=1,J:=N;
2)以第一个数组元素作为关键数据,赋值给X,即X:=A[1];
3)、从J开始向前搜索,即由后开始向前搜索(J:=J-1),找到第一个小于X的值,两者交换;
4)、从I开始向后搜索,即由前开始向后搜索(I:=I+1),找到第一个大于X的值,两者交换;
5)、重复第3、4步,直到I=J;
例如:待排序的数组A的值分别是:(初始关键数据X:=49)
进行第一次交换后:
进行第二次交换后:
进行第三次交换后:
( 按照算法的第五步将又一次执行算法的第三步从后开始找
进行第四次交换后:
( 按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时J:=4 )
初始状态
进行一次快速排序之后划分为
分别对前后两部分进行快速排序
5)归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有一个元素(认为直接有序)或者两个序列(一次比较和交换),然后把各个有序的短序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在一个或两个元素时,一个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定性是否收到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我么把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
6)基数排序
基数排序是按照低位先排序,然后手机;再按照高温排序,然后再手机;以此类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先排序,再按高优先排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序法。
7)希尔排序(shell)
排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止
初始:d=5
49 38 65 97 76 13 27 49* 55 04
49 13
|-------------------|
38 27
|-------------------|
65 49*
|-------------------|
97 55
|-------------------|
76 04
|-------------------|
一趟结果
13 27 49* 55 04 49 38 65 97 76
d=3
13 27 49* 55 04 49 38 65 97 76
13 55 38 76
|------------|------------|------------|
27 04 65
|------------|------------|
49* 49 97
|------------|------------|
二趟结果
13 04 49* 38 27 49 55 65 97 76
d=1
13 04 49* 38 27 49 55 65 97 76
|----|----|----|----|----|----|----|----|----|
三趟结果
04 13 27 38 49* 49 55 65 76 97
我们知道基于堆结构,节点i的孩子为2*i+1和2*i+2节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程是从第n/2开始和其子节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1,n/2-2,……1这些歌父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这两个相同元素的稳定性就被破坏了。