[排序算法系列] —— 希尔排序

173 阅读2分钟

众所周知,冒泡、选择、插入是三种简单排序,冒泡和选择排序的时间复杂度都是O(N^2),但插入排序不一样,它的最好时间复杂度为O(N),最坏时间复杂度为O(N^2)。

我们可以利用插入排序这个特点,想办法让数组变的基本有序,这样插入就变得快了起来。

int arr = {2,0,1,7,0,9,0,1};

我们现在有这样一个数组,长度为8,我们对它进行分组。

part1 = {2, 0};
part2 = {0, 9};
part3 = {1, 0};
part4 = {7, 1};

容易看出分组规则:将下标间隔为4的元素分到一组。我们利用插入排序让每个分组有序。

arr = {0, 0, 0, 1, 2, 9, 1, 1};

接着我们继续分组。

part1 = {0, 0, 2, 1};
part2 = {0, 1, 9, 1};

容易看出分组规则:将下标间隔为2的元素分到一组。我们利用插入排序让每个分组有序。

arr = {0, 0, 0, 1, 1, 1, 2, 9};

现在数组已经有序了,但是我们分组流程依旧要进行下去。

part = {0, 0, 0, 1, 1, 1, 2, 9};

此时只有一个分组,下标间隔为1,我们对其做插入排序,时间复杂度为O(N)。

至此,希尔排序结束,给出代码如下。

void shell_sort(int* arr, int size) {
    for(int step = size >> 1; step > 0; step >>= 1) {
        for(int begin = 0; begin < step; ++begin) {
            for(int i = begin + step; i < size; i += step) {
                int j, basic = arr[i];
                for(j = i - step; j >= begin && arr[j] > basic; j -= step) {
                    arr[j+step] = arr[j];
                }
                arr[j+step] = basic;
            }
        }
    }
}

// 为了便于理解,附上插入排序实现
void insert_sort(int* arr, int size) {
    for(int i = 1; i < size; ++i) {
        int j, basic = arr[i];
        for(j = i - 1; j >= 0 && arr[j] > basic; --j) {
            arr[j+1] = arr[j];
        }
        arr[j+1] = basic;
    }
}

希尔排序之所以会快,核心在于无序时元素少,元素多时基本有序,但是这个基本到底有多基本其实并不确定,所以希尔排序的时间复杂度是依赖于样本数据的,最坏可以到达O(N^2)。