"簡單"的優化--希爾排序也沒你想象中那麼難
阿新 • • 發佈:2021-10-21
最近我們進入了排序演算法專題,上節課聊到了"簡單"插入排序,那在簡單的基礎上,我們可以怎麼做進一步的優化呢,這篇來看看優化版--**希爾排序**!
寫在前邊
大家好,我是melo,一名大二上軟體工程在讀生,經歷了一年的摸滾,現在已經在工作室裡邊準備開發後臺專案啦。
不過這篇文章呢,還是想跟大家聊一聊資料結構與演算法,學校也是大二上才開設了資料結構這門課,希望可以一邊學習資料結構一邊積累後臺專案開發經驗。
最近我們進入了排序演算法專題,上節課聊到了"簡單"插入排序,那在簡單的基礎上,我們可以怎麼做進一步的優化呢,這篇來看看優化版--希爾排序!
知識點
概念
希爾排序(Shell Sort)是插入排序的一種,它是針對直接插入排序演算法的改進。
希爾排序又稱縮小增量排序,因 DL.hell 於 1959 年提出而得名。
它通過比較相距一定間隔的元素來進行,各趟比較所用的距離隨著演算法的進行而減小,直到只比較相鄰元素的最後一趟排序為止。
引入
簡單插入排序存在問題
改進
- 分割待排序記錄的個數,分別進行插入排序
基本思想
- 希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個陣列恰被分成一組,演算法便終止
精髓
- 由於開始時每組只有很少整數,所以排序很快。之後每組含有的整數越來越多,但是由於這些數也越來越有序,所以排序速度也很快。
示意圖
按一定增量分組,然後逐漸減小增量
初始化gap為length/2,逐漸減小為gap/2,直到gap不滿足>0的條件
分組後,再對該組進行簡單插入排序
- 拿圖中的第三步舉例,陣列分成了兩組[3,1,0,9,7],[5,6,8,4,2]
- 對[3,1,0,9,7]進行簡單插入排序,看成前n-1個為有序陣列,第n個為待插入的元素(找到自己的位置後插入即可)
不夠清晰的話也可以看下邊這張
程式碼實現
力扣912排序陣列 : https://leetcode-cn。com/problems/sort-an-array/submissions/
又是這道題hhh,萬能
思路概覽
首先
- 我們要先初始化增量gap=length/2,然後不斷縮小gap=gap/2 直到不滿足gap>0
所以我們最外層會需要一個for迴圈來調控這個gap的變化
其次,再往內層走
- 對於分組後,由於我們是要對分組後的每一組進行簡單插入排序,而插入排序我們預設從待排序陣列的第二位開始,所以我們需要從每一組的第二位開始去遍歷,直到整個陣列的末尾
for迴圈讓i=gap;i<陣列;i++即可
最後,就對該陣列進行插入排序即可
- 注意不是跟前一個進行比較了,而是跟 j-gap 比較
最初版本(for)
for的話,我是先把j賦值等於i-gap,這樣的話就是跟j去比較,最後也還會去j-=gap
會導致我最後跳出迴圈的時候,得插到j+gap
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* sortArray(int* nums, int numsSize, int* returnSize){
//逐步縮小增量gap
for(int gap=numsSize/2;gap>0;gap=gap/2){
int insertValue = 0;
int j;
//從分組後的第一組的第二位開始
for(int i=gap;i<numsSize;i++){
//儲存待插入的值
insertValue=nums[i];
//因為本身有序,若待插入的數還大於最後一個數,則無須繼續遍歷下去了
//注意j>=0的條件,這裡無哨兵了
for(j=i-gap;j>=0 && insertValue<nums[j];j-=gap){
//若待插入的值小於索引值,證明要索引值需要後移,空出j這個位置給插入值
nums[j+gap]=nums[j];
}
//跳出迴圈後,把這個數插入到指定位置
nums[j+gap]=insertValue;
}
}
*returnSize=numsSize;
return nums;
}
改進for
先去判斷是否 j-gap>=0,滿足才進迴圈,才會去j-=gap,所以最後j就是要插入的位置
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* sortArray(int* nums, int numsSize, int* returnSize){
//逐步縮小增量gap
for(int gap=numsSize/2;gap>0;gap=gap/2){
int insertValue = 0;
//用於插入排序中遍歷待排序的陣列
int j;
//從分組後的第一組的第二位開始
for(int i=gap;i<numsSize;i++){
//儲存待插入的值
insertValue=nums[i];
//因為本身有序,若待插入的數還大於最後一個數,則無須繼續遍歷下去了
for(j=i;j-gap>=0 && insertValue<nums[j-gap];
j-=gap){
//若待插入的值小於索引值,證明要索引值需要後移,空出j這個位置給插入值
nums[j]=nums[j-gap];
}
//跳出迴圈後,把這個數插入到指定位置
nums[j]=insertValue;
}
}
*returnSize=numsSize;
return nums;
}
注意
- 希爾排序沒有辦法用到哨兵了,我們需要注意判斷是否走到頭了
參考
- 菜鳥教程
- 尚矽谷資料結構與演算法