《数据结构与算法分析(c描述)》—— 插入排序&希尔排序

一、插入排序

插入排序有点像打扑克牌,每起到一张牌,就将其插入到已经排序好的手牌中。

精确一点的描述是,插入排序由 N - 1 趟排序组成,对于第 P 趟,我们将位置 P 上的元素向左移动到它在前 P + 1 个元素中的正确位置。

非递归的代码实现如下:

void insertSort(int a[], int size) {
    int i, j;
    int temp;
    for (i = 1; i < size; i++) {
        temp = a[i];
        for (j = i; j - 1 >= 0 && a[j - 1] > temp; j--)
            a[j] = a[j - 1];
        a[j] = temp;
    }
}

对第 P 趟排序,由于插入排序保证前 P 个元素已经被排好序,可以写出递归实现代码:

void insertSortRe(int a[], int size) {
    if (size == 1)
        return;
    insertSortRe(a, --size);
    int temp = a[size];
    int i;
    for (i = size; i - 1 >= 0 && a[i - 1] > temp; i--)
        a[i] = a[i - 1];
    a[i] = temp;
}

二、希尔排序

希尔排序是插入排序的改进版。交换排序的目的,是通过交换,将逆序数降低为0。每交换一对相邻元素,逆序数变化为1。如果交换的元素不相邻,则逆序数的变化可能就大于1。希尔排序的思路,就是通过交换离得更远的两个元素,让逆序数更快地变为0(当然,并不总是管用)。

我们定义一个间隔 k (增量),每隔 k个元素选取一次,可以选出一个子数组。那么可以找出 k 个这样的子数组。对于每个子数组进行插入排序。

这样的插入排序中,每交换一次减少的逆序数可能大于1,从而提高效率。但排序以后 k 个子数组之间没有进行比较,因此我们再选取另一个间隔 k’,再来一轮。实际上,这样的间隔序列可以自己定义,只要合理即可。通过查资料可以找到前辈们总结的比较靠谱的间隔序列。一种简单直观的间隔序列,就是 {n / 2, n / 4, n / 8, ..., 1} 。这是明显的折半,但注意不要把顺序反过来。理由很明显:如果你一开始就选1作为间隔,那就是插入排序,剩余的间隔排序就没有意义了,效率自然也不会提高。关于希尔排序的复杂度,不一定是O(n^2),要根据选取的间隔序列来分析。

希尔排序实现如下:

void shellSort(int a[], int size) {
    int i, j, increse;
    int temp;
    for (increse = size/2; increse > 0; increse /= 2)
        for (i = 0; i < size; i += increse) {
            temp = a[i];
            for (j = i; j > 0 && a[j - 1] > temp; j--)
                a[j] = a[j - 1];
            a[j] = temp;
        }
}

可以发现希尔排序里层的两个循环和插入排序一致!

希尔排序时间复杂度平均 O(nlogn),最差 O(n^s) 1 < s < 2

你可能感兴趣的:(《数据结构与算法分析(c描述)》—— 插入排序&希尔排序)