961全部内容链接
排序,就是重新排列列表中的元素,使表中的元素满足按关键字有序的过程。
排序算法分为两种,稳定的和不稳定的,假设有下列一串数据:7,2,8,4,9,8。该数据中有两个8,这里使用粗细来标记。对于稳定的排序,排序后的结果中两个8不会互换位置,即2,4,7,8,8,9。 但是对于不稳定的排序,排序之后,两个8的顺序就“有可能”发生变化,如2,4,7,8,8,9
根据数据是否全部存放于内存,还可以把排序分为两种:
插入排序的基本思想:将待排序的序列分为两部分,前面是已经排好序的,后面是未排序的。每次从未排序的序列中选出一个,插入到已经排好序的序列。直到所有未排序序列为空。
根据插入排序的思想,可以很容易的想到直接排序算法。
有序[ 0, …i ] | i + 1 (待排序元素) | i + 2,…,n (无序数列) |
---|
直接插入排序思想,第一部分为有序序列,第二部分是无序序列的第一个元素,第三部分是剩下的无序数列。算法(从小到大排列)如下:
Java代码如下:
public static void insertionSort(Comparable[] array) {
if (array == null) return;
for (int i = 1; i < array.length; i++) {
// 从1位置开始,依次将后面的无序序列都插入到前面的有序序列
Comparable temp = array[i];
int j;
// 从最后一个开始比较,若已经比较到了0这个位置,或前面的元素比temp小,则说明此轮已经排序完成。
for (j = i; j > 0 && temp.compareTo(array[j-1]) < 0; j--) {
array[j] = array[j - 1]; // 这里之所以没有使用交换,是因为下一轮前面的元素又得把j-1给替换掉,所以统一在循环结束进行交换。
}
array[j] = temp; // 此时j就是要插入的位置,插入即可
}
}
复杂度分析
稳定性:稳定的。每次比较时,只有前面的元素比后面的元素大时,才会发生交换,若相等则不会交换,所以是稳定的。 但是出题的时候可能会变成大于等于都会交换,此时就会是不稳定的。
适用性:此方式适用于顺序存储(数组)和链式存储。因为不存在随机访问。
与直接插入排序差不多。直接插入排序是边比较边交换,而折半插入排序是:先对前面的有序数列进行折半查找,找出要插入的位置,然后再统一交换。其实效率差不多。但是折半插入不适用于链式存储。
代码如下:
TODO,非重点,后期补充
希尔排序也是利用了直接插入的特性(近乎有序的序列时间复杂度O(n))。基本思想为:
希尔排序难理解的地方就是这个分组,它并不是将相邻的元素分成一组。而是将相距固定距离的元素分为一组,如将下标 0,3,6,9…的元素分为一组,相应的1,4,7,11…分为一组。此时距离d=3。用专业的话就是:先将待排序的表分割成若干型如 L[i, i+d, i+2d, … , i+kd]的“特殊”子表,即把相隔某个“增量”的记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素已呈“基本有序”时,再对全体记录进行一次直接插入排序。
对于增量d,每轮都要进行递减。推荐的方式是,第一次取n/2,第二次取 n/2/2,每次除以2,直到为0。 但是出题时往往会给出不同的d的递减方式,来让你求每轮的排序结果。
举例:
9 | 6 | 7 | 4 | 1 | 6 | 2 | 9 | 3 | ||
---|---|---|---|---|---|---|---|---|---|---|
第一轮 | d=4 | 1 | 6 | 2 | 4 | 3 | 6 | 7 | 9 | 9 |
第一轮 | d=4的组号 | 1 (1) | 6 (2) | 2 (3) | 4 (4) | 3 (1) | 6 (2) | 7 (3) | 9 (4) | 9 (1) |
第二轮 | d=2 | 1 | 4 | 2 | 6 | 3 | 6 | 7 | 9 | 9 |
第二轮 | d=2的组号 | 1(1) | 4(2) | 2(1) | 6(2) | 3(1) | 6(2) | 7(1) | 9(2) | 9(1) |
第三轮 | d= 1 | 1 | 2 | 3 | 4 | 6 | 6 | 7 | 9 | 9 |
该例子中,经过了三轮将该序列排为有序序列。其中组号那一行的括号中相同的代表被分到了一组。考试中不需要单独画出分组情况。这里只是为了方便理解。
用java实现,代码如下:
public static void shellSort(Comparable[] array) {
if (array == null) return;
for (int d = array.length / 2; d > 0; d = d / 2) {
// 第一轮 d=n/2,第二轮d=n/2/2,依次类推,直到d=0结束
for (int i = d; i < array.length; i++) {
// i取d,类似直接插入排序中的i=1
Comparable temp = array[i]; // 记录第一个位置的元素,用于该组交换过后的赋值
int j;
for (j = i; j >= d && temp.compareTo(array[j - d]) < 0; j = j - d) {
// 让当前元素与其 -kd 的元素对比,然后进行插入
array[j] = array[j - d];
}
array[j] = temp;
}
}
}
注意这里的第二个for循环。希尔排序虽然将其分成了若干组,但实际执行的时候,并不是将第一组排序完成之后才进行第二组排序。 而是先对第1组的第1个元素进行插入排序,然后对第2组的第1个元素进行排序,然后第3组的第1个,依次类推。然后是第1组的第二个元素…
复杂度分析:
稳定性:不稳定。若两个相同的数据被分为了不同的组,则有可能顺序会被调换
适用性:只适用于顺序存储。因为要用到随机访问的特性。