排序的目标就是将一组数据(序列)重新排列,形成有序的序列 (递增或者递减)。Donald Kuncth的经典之作《计算机程序设计艺术》第三卷专门讨论排序和查找。从无序到有序,有效减少系统的熵值,增加程序的有序度。对于一个未知系统,有序是非常有用的先验知识。因此,排序算法是其他排序算法的基础,比如二分法基于有序序列的查找算法。
一、排序分类
1.按是否涉及数据的内、外存交换
- 内排序
- 在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内部排序。
- 外排序
- 若排序过程中要进行数据的内、外存交换,则称之为外部排序。
2.按策略划分内部排序方法
二、排序算法的稳定性
排序稳定性:保证在排序前2个相等的数的位置(索引或者坐标),在经过A排序算法后,位置不变,那么A算法是稳定性排序算法;如果两个数位置变化,那么A算法是不稳定排序算法。
1、冒泡排序
冒泡排序通俗上讲小的元素前调或者大的元素后调。从定义上讲,只有后者比前者小才交换位置,所以相等时,不会变化位置,所以冒泡排序是稳定性排序算法。
2、选择排序
选择排序是从序列中获取到最小元素,然后交换位置。比如序列{5 8 5 2 9},第一遍选择5作为比较元素,2最小,然后元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
3、插入排序
插入排序是在一个已经有序的小序列基础山,一次插入一个元素。一开始是一个元素,比较是从序列末尾开始,小的插在前面,大的插在后面,关键是相等的也是插在后面,又因为从末尾开始遍历,相等元素没有变换位置,插入排序是稳定的排序算法。
4、快速排序
快速排序从两个开始进行,坐标i下标忘右走,而右边j下标向右走,目的是从两个开始进行,将小的数放在左边,大的数放在右边。由于排序过程中,有数据交换,在中枢元素在交换时候,有可能把前面元素稳定性打乱。比如序列为{5 3 3 4 7 8 9 3 11},现在中枢元素5和3(第8个元素,下标从1开始计)交换就会把元素3的稳定性打乱,快速排序不是稳定的排序算法。
5、归并排序
归并排序是把序列递归分成短序列,最后在短序列只有1或2个元素时,交换元素,跳出一个循环,然后各个有序序列合并成一个有序的长序列,不断合并知道原序列全部排好序。在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
6、希尔排序
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素无序时候,步幅很大,插入元素少,速度快;当基本有序是,步长小,插入排序对于有序序列效率很高。一次插入排序为稳定的,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,毕竟是分块插入的。希尔排序不是稳定的排序算法。
7、堆排序
堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, ... 1这些个父节点选择元素时,就会破坏稳定性。堆排序不是稳定的排序算法。
8、基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
三、排序用到的结构和函数
本系列是根据《大话数据》进行学习的,其中还有别人博客内容,加上自己总结和实验,形成这个系列博客。此处的排序的结构和函数如《大话数据》一样。
排序用的顺序表结构,此结构用于之后讲的所有排序算法。
#define MAXSIZE 100 //用于排序数组个数最大值 typedef struct { int data[MAXSIZE + 1]; //用于存储要排序数组,r[0]用作哨兵或临时变量 int length; //用于记录顺序表的长度 }SqList;
两个数组元素交换
/* 交换s中数组data的下表i和j的值 */ void swap(SqList *s, int i, int j) { int temp = s->data[i]; s->data[i] = s->data[j]; s->data[j] = temp; }