众所周知,冒泡、选择、插入是三种简单排序,冒泡和选择排序的时间复杂度都是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)。