算法思想:
希爾排序又叫縮小增量,也是一種插入排序。希爾排序中有一個步長,通過這個步長將數組分化爲步長個序列,然後分別對這些序列進行直接插入排序,然後改變步長,重複操作,直到步長爲1,而此時數組基本有序,很快。步長的取法一般是開始爲(數組長度/2),然後是對步長一直除2直到步長爲1。
算法運行過程圖:
代碼實現思路:
- 思路1:按照算法思想,有一個循環步長的循環,然後對某個步長劃分的那些子序列進行插入排序。
- 思路2:相對於上面的每次直接插入排序從序列的第一個元素開始,直到該序列的最後一個元素,其實直接插入排序時可以從下標爲步長的元素開始,然後相當於混合這對劃分的每個序列進行直接排序,直接排序其實是從序列的第2個元素開始,然後每次判斷是否比該序列的前一個數小,如果小的話就進行插入操作。見shellSort1()註釋。
優化方法:
- 優化步長。有點麻煩。
- 優化直接插入排序。見shellSort()註釋。
時間複雜度:
根據步長的不同取法不一定。有點複雜。
空間複雜度:
O(1)
穩定性:
不穩定。
在子序列的插入過程中可能打亂穩定性。
代碼實現:
package sort;
/**
* @作者:dhc
* @創建時間:20:41 2018/8/14
* @排序方法:希爾排序
* @時間複雜度:有點複雜。
* @空間複雜度:O(1)
* @穩定性:不穩定。
*/
public class ShellSort {
/**
* 直接插入排序說明:這裏直接插入排序因爲有幾種不同的寫法,1,常規的先找插入位置,在後移 插入。2,對1插入位置查找採用二分查找。3,不用先查找
* 位置,將兩個循環合成一個,直接後移。4,同3一樣,用一個循環,不過用交換來實現。因此希爾排序也可有對應的寫法。採用不同的寫法,對於希爾排序的結果
* 也稍有點點優化。
* 步長說明:取數組長度的一半,然後循環取一半直到步長爲1.
* @param nums
*/
public static void shellSort(int[] nums) {
int tem = 0;
for(int ss = nums.length/2;ss >= 1; ss /= 2){
for (int k = 0; k < ss; k++) {
//直接插入排序
for(int i = ss;i < nums.length;i += ss){
tem = nums[i];
int j = i;
for(j = i;j > 0 && nums[j - ss] > tem;j -= ss){
nums[j] = nums[j-ss];
}
nums[j] = tem;
}
}
}
}
/**
* 優化循環次數
* 相對於上面的每次直接插入排序從序列的第一個元素開始,直到該序列的最後一個元素,其實可以直接插入排序時可以從下標爲步長的元素開始,然後
* 相當於混合這對劃分的每個序列進行直接排序,直接排序其實是從序列的第2個元素開始,然後每次判斷是否比該序列的前一個數小,如果小的話就進行
* 插入操作。
* @param nums
*/
public static void shellSort1(int[] nums) {
int tem = 0;
for(int ss = nums.length/2;ss >= 1; ss /= 2){
//這裏取i等於步長,相當於直接進入插入排序,用劃分的第一個序列的第二個元素開始,當i+1時,如不步長不爲1,則相當於
// 進入下一個序列的插入排序,這個i+1同樣是第二個元素,直到i+ss,有進入第一個序列的第三個數,循環往復,直到數組最後
for (int i = ss; i < nums.length; i++) {
//這裏判斷是否比該序列的前一個數小,如果小的話就要插入到該序列的前面
if(nums[i] < nums[i - ss]){
tem = nums[i];
int j = i;
for(j = i - ss;j >=0 && nums[j] > tem;j-=ss){
nums[j+ss] = nums[j];
}
nums[j + ss] = tem;
}
}
}
}
public static void main(String[] args) {
int[] nums = new int[]{5,4,3,2,1,1,7,6};
shellSort1(nums);
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i]+" ");
}
}
}