希尔排序是以它的创造者( Donald Shell)命名的,他是在插入排序基础上的一个高级排序。插入排序的原理是在已经排好的序列中,逐个比较相邻元素,然后在现有序列中找到自己应该插入的位置,然后插入就好了。而希尔排序的原理是: 先将整个待排元素序列通过一定的间隔(序列中元素的间隔)分割成若干个子序列 ,对这些小的子序列分别进行直接插入排序,然后依次缩减间隔再进行排序,待整个序列中的元素基本有序(间隔足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比直接的插入排序有较大提高。
要想更好的理解还是来看看一个栗子(话说栗子是个好东西,对身体好!!)吧:
以N = 10的一个简单数组为例,我要需要把数组中的数据升序排列。如图
第一次: gap = 10 / 2 = 5
根据间隔,我们从数组中第一个元素6开始,把数组按照间隔分成五份:(6,5),(0,8),(2,0),(9,5),(3,4)。对分好的五个子序列从右到左进行插入排序,得到下面的结果
第二次 :gap = 3
第二次根据3个间隔进行排序,我们按照同样的方式(5,5), (0,3),(0,6),(5,8),(3,2),(6,9),(8,4)。通过这一次从右到左的插入排序之后,你会看到如下的结果:
第三次 :gap = 3 / 2 = 1
这一次的排序就是我们常见的普通的插入排序了。。
关于第二次的间隔你也可以设置成2,或者是多增加一次间隔排序第二次间隔为3,第三次间隔为2也行。。。只要是最后一次的间隔是1才能达到我们想要的排序效果,也就变成了普通的插入排序了
完整的代码如下:
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <script> function NumArray(numElements) { this.dataStore = []; this.pos = 0; this.numElements = numElements; this.gaps = []; /*for (var i = 0; i < numElements; ++i) { this.dataStore[i] = i; }*/ // 插入元素 this.insert = function (element){ this.dataStore[this.pos++] = element; }; // 输出序列 this.toString = function (){ var retstr = ""; for (var i = 0; i < this.dataStore.length; ++i) { retstr += this.dataStore[i] + " "; if (i > 0 && i % 10 == 0) { retstr += " "; } } return retstr; }; // 清空序列 this.clear = function (){ for (var i = 0; i < this.dataStore.length; ++i) { this.dataStore[i] = 0; } }; // 设置序列数据 this.setData = function (){ for (var i = 0; i < this.numElements; ++i) { this.dataStore[i] = Math.floor(Math.random() * (this.numElements+1)); } }; // 交换序列中的数据 this.swap = function (arr, index1, index2){ var temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; }; this.setGaps = function (arr){ this.gaps = [5, 3, 1]; }; // 硬编码间隔序列的希尔排序 this.shellSort1 = function (){ for (var gapIndex = 0; gapIndex < this.gaps.length; ++gapIndex){ for (var i = this.gaps[gapIndex]; i < this.dataStore.length; ++i){ var temp = this.dataStore[i]; for (var j = i; j >= this.gaps[gapIndex] && this.dataStore[j - this.gaps[gapIndex]] > temp; j -= this.gaps[gapIndex]){ this.dataStore[j] = this.dataStore[j - this.gaps[gapIndex]]; } this.dataStore[j] = temp; } } }; } var nums = new NumArray(10); nums.setData(); console.log(" shell before: "); console.log(nums.toString()); nums.setGaps([]); nums.shellSort1(); console.log(" shell after: "); console.log(nums.toString()); </script> </body> </html>
打开浏览器的console查看结果。
关于间隔的计算:
《算法(第 4 版)》(人民邮电出版社)的合著者 Robert Sedgewick 定义了一个shellsort()函数,在这个函数中可以通过一个公式来对希尔排序用到的间隔序列进行动态计算。Sedgewick 的算法是通过下面的代码片段来决定初始间隔值的,我们重写希尔排序过程如下:
// 动态间隔序列的希尔排序 this.shellSort2 = function (){ var length = this.dataStore.length; var gap = 1; while (gap < length/3){ gap = (3 * gap) + 1; } while (gap >= 1){ for (var i = gap; i < length; ++i){ for (var j = i; j >= gap && this.dataStore[j] < this.dataStore[j - gap]; j -= gap){ this.swap(this.dataStore, j, j - gap); } } gap = (gap - 1)/3; } };
在上面的代码中
var length = this.dataStore.length; var gap = 1; while (gap < length/3){ gap = (3 * gap) + 1; }
再加上外循环中的:
gap = (gap - 1)/3;
就是Sedgewick 动态计算间隔的完整过程了。