一篇文章講透排序算法之希爾排序

希爾排序是對插入排序的優化,如果你不了解插入排序的話,可以先閱讀這篇文章:插入排序

目錄

1.插入排序的問題

2.希爾排序的思路

3.希爾排序的實現

4.希爾排序的優化

5.希爾排序的時間復雜度


1.插入排序的問題

如果用插入排序對一個逆序有序的數組排序時,時間復雜度為O(n^2),此時效率最低。

如果用插入排序對一個順序有序的數組排序時,時間復雜度為O(n),此時效率最高。

我們發現,被排序的對象越接近有序,插入排序的效率越高,這時希爾就有了一個想法:如果可以將數組變得接近有序后再用插入排序呢?

2.希爾排序的思路

希爾排序是對插入排序的優化,它的思路是先選定一個整數作為增量,這里我們以gap(間隔)表示,將間隔為gap的數據分為一組,這樣就可以分出gap組以gap為公差的等差數列的數據組。之后將這些數據組排序(把每組數據排序),之后將gap縮小,繼續分組并進行排序,重復上述動作,直到gap縮小至1,此時排完了之后剛好有序。

為了讓數組更接近有序的排序稱為預排序,而最后一次排序是直接插入排序,而由于前面的操作使數據變得接近有序,因此最后一次直接插入排序需要移動的數據很少,效率便很高了。

下面我們來實現希爾排序。

現在我們給定如下數組,并以3為gap,可將數組根據顏色分為3組以3為公差的等差數列。

之后我們對這三組數據進行插入排序

之后我們將間隔縮小, 以2為間隔,我們就可以分出兩組以2為公差的等差數列。

這里也并不一定要只減少1,減少多少看我們想減少多少。

現在我們完成第二次排序

現在我們的數組已經非常接近有序,我們最后再以1為間隔,得到一組以1為間隔的等差數列,再完成最后一次排序,也就是直接插入排序,即可使得我們的數組有序。

3.希爾排序的實現

現在我們根據我們的思路來逐步實現希爾排序

第一步:以3為間隔,排序第一組綠色的

在已經學習了插入排序的基礎上,我們來實現一下排序綠色

//代碼中的n代表數組長度,后面的代碼不再解釋。
int gap = 3;
//n-gap后的數據為最后一組數據,而當i等于我們的前一組數據時
//排序的就是最后一組數據,因此結束條件為i<n-gap
for (int i = 0; i < n - gap; i += gap)
{int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;
}

第二步:進行第一次排序??

由于我們先前已經實現了排序綠色的,而排序藍色的和排序黃色的不過是起始位置不同,因此我們再嵌套一層循環即可。

for (int j = 0; i < gap; j++)
{int gap = 3;//n-gap后的數據為最后一組數據,而當i等于我們的前一組數據時//排序的就是最后一組數據,因此結束條件為i<n-gapfor (int i = j; i < n - gap; i += gap){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}
}

現在我們已經完成了第一次排序,那么后面的排序我們控制gap即可

for (int gap = 3; gap > 0; gap--)
{for (int j = 0; i < gap; j++){for (int i = j; i < n - gap; i += gap){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

這時我們發現我們的代碼達到了驚人的四層循環...這段代碼未免有些過于恐怖...

那我們有沒有什么辦法優化這段代碼呢??

4.希爾排序的優化

這時有一位大佬給出了這么一個解決方法:

我們不再一次比較一個數據組,

而是先比較第一個數據組的第一個數據和第二個數據,

然后比較第二個數據組的第一個數據和第二個數據,

之后比較第三個數據組的第一個數據和第二個數據,

然后比較第二個數據組的第二個數據和第三個數據,

這么一直比較下去,就可以完成我們第一次預排序的效果。

如下圖所示,相同顏色的線表示比較的數據。

代碼如下所示:?

int gap = 3
for (int i = 0; i < n - gap; i++)
{int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;
}

現在我們已經完成了第一趟的排序,接下來我們控制gap即可。

int gap = 3;
while (gap > 0)
{for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}gap--;
}

現在這段代碼看起來就舒服多了。但是我們的gap就一定每次都減1嗎?

我們之前說過,預排序是為了讓數組更加有序,我們只要能夠讓數組更加有序就可以了,沒有必要每次讓gap減1,gap太大了反而會有一些副作用。

這時有一位大佬寫了這么一個希爾排序:?

int gap = n;
while (gap > 0)
{gap /= 2;for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}
}

這里的第一趟循環以二分之數組長度為間隔,后續的循環每次都除以2。

到了最后一次循環之時,gap要么等于2,要么等于3;而它們除2都等于1。這樣就保證了最后一次循環是直接插入排序,可謂是相當完美了。

現在我們將其封裝在函數體內,完成最終版的希爾排序

void InsertSort(int* a, int n)
{int gap = n;while (gap > 0){gap /= 2;for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

5.希爾排序的時間復雜度

我們發現我們最終版的希爾排序也擁有三層循環,于是乎我們大家就對希爾排序的效率產生了疑問.但是利用我們現有數學能力無法計算出希爾排序的時間復雜度,只能給出一個大致范圍

下面給出嚴蔚敏教授數據結構書中的相關論述:

在這里也可以給大家大概畫一下圖,由于每次排序都會對后續的排序產生影響,因此我們后續的排序移動的數據會越來越少,因此效率還是比較高的。?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15465.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15465.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15465.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

521源碼-免費代碼基礎學習-PHP如何運用變量教程

更多網站源碼學習教程&#xff0c;請點擊&#x1f449;-521源碼-&#x1f448;獲取最新資源 為什么要學習PHP&#xff1f;“我可以用JavaScript來實現程序編寫。”但JavaScript的能力是有限的&#xff0c;JavaScript通常運行在瀏覽器&#xff08;客戶端&#xff09;&#xff0…

go語言中for的4種循環形式總結

和其他語言不一樣&#xff0c;go語言中的循環語句只有for一種&#xff0c;但是go里面的for卻有3種不同的循環形式&#xff0c;總結如下&#xff1a; 1. 無限循環 for { //這個就是一個“死循環”&#xff0c;注意必須要有 break條件&#xff0c;否則就真成死循環了 } 2. fo…

Redis 源碼學習記錄:集合 (set)

無序集合 Redis 源碼版本&#xff1a;Redis-6.0.9&#xff0c;本篇文章無序集合的代碼均在 intset.h / intset.c 文件中。 Redis 通常使用字典結構保存用戶集合數據&#xff0c;字典鍵存儲集合元素&#xff0c;字典值為空。如果一個集合全是整數&#xff0c;則使用字典國語浪費…

PostgreSQL入門教程

PostgreSQL是一種開源的關系型數據庫管理系統&#xff0c;它具有高度的可靠性、可擴展性和性能。下面是一個簡單的PostgreSQL入門教程&#xff0c;幫助你開始使用這個強大的數據庫管理系統。 步驟1&#xff1a;安裝PostgreSQL 首先&#xff0c;你需要下載并安裝PostgreSQL。你…

2024年最全的信息安全、數據安全、網絡安全標準分享(可下載)

以上是資料簡介和目錄&#xff0c;如需下載&#xff0c;請前往星球獲取&#xff1a;https://t.zsxq.com/Gz1a0

【全網最全】2024電工杯數學建模A題成品論文+前三題完整解答matlab+py代碼等(后續會更新成品論文)

您的點贊收藏是我繼續更新的最大動力&#xff01; 一定要點擊如下的卡片鏈接&#xff0c;那是獲取資料的入口&#xff01; 【全網最全】2024電工杯數學建模A題成品論文前三題完整解答matlabpy代碼等&#xff08;后續會更新成品論文&#xff09;「首先來看看目前已有的資料&am…

Python 點云平面分割【RANSAC算法】

點云平面分割 一、介紹1.1 概念1.2 算法思路1.3 參數設置二、代碼示例三、結果示例其他參考鏈接:C++中實現點云平面分割 一、介紹 1.1 概念 點云平面分割:可以在點云數據中找到平面并計算平面模型系數,同時輸出平面點云及非平面點云。 1.2 算法思路 實現思路: 首先,采用…

Sass是什么?有哪些優缺點?

目錄 一、Sass是什么&#xff1f; 二、Sass的優缺點 三、Sass與SaaS 一、Sass是什么&#xff1f; Sass是世界上最成熟、最穩定、最強大的專業級CSS擴展語言。 Sass makes CSS fun again. Sass is an extension of CSS, adding nested rules, variables, mixins, selector in…

【C++高階(一)】繼承

目錄 一、繼承的概念 1.繼承的基本概念 2.繼承的定義和語法 3.繼承基類成員訪問方式的變化 ?編輯 4.總結 二、基類和派生類對象賦值轉換 三、繼承中的作用域 四、派生類的默認成員函數 1.派生類中的默認構造函數 2.派生類中的拷貝構造函數 3.派生類中的移動構造函數…

英語學習筆記25——Mrs. Smith‘s kitchen

Mrs. Smith’s kitchen 史密斯太太的廚房 詞匯 Vocabulary Mrs. 夫人【已婚】 復習&#xff1a;Mr. 先生 全名 / 姓    Mrs. 夫人 全名 / 丈夫的姓    Miss 小姐&#xff08;未婚&#xff09; 全名 / 姓    Ms. 女士 全名 / 姓 查看婚姻狀況&#xff0c;可以觀察…

springboot項目中圖片上傳之后需要重啟工程才能看到圖片?

需求背景 最近在做一個用戶自定義上傳頭像的小需求&#xff0c;用戶上傳頭像然后需要立馬回顯。 需求是很常見的、正當的需求。如果不使用到對象存儲這類服務&#xff0c;我們把用戶頭像的圖片文件僅存在本地就可以了。我們在開發的過程中為了工程管理方便通常下意識會將圖片…

freertos串口DMA隊列發送卡死

調試回調函數的時候&#xff0c;我在cube中刪除了默認的DMA通道&#xff0c;又新增了另外一個通道&#xff0c;導致NVIC中&#xff0c;該通道的優先級為0&#xff0c;后來改成了5就正常了。

Modbus TCP轉Profinet網關測試配置案例

本案例采用XD-ETHPN20網關做為Modbus TCP通信協議設備與Profinet通信協議設備連接的橋梁。Modbus TCP是一種基于TCP/IP協議的工業通信協議&#xff0c;而Profinet則是用于太網通信的協議。Modbus TCP轉Profinet網關可實現這兩種不同協議之間的數據交換和傳輸&#xff0c;極大地…

算法刷題筆記 逆序對的數量(C++實現)

文章目錄 題目描述解題代碼&#xff08;蠻力版&#xff09;解題代碼&#xff08;基于歸并排序&#xff09; 題目描述 給定一個長度為n的整數數列&#xff0c;請你計算數列中的逆序對的數量。逆序對的定義如下&#xff1a;對于數列的第i個和第j個元素&#xff0c;如果滿足i<…

Python高級進階--dict字典

dict字典?? 1. 字典簡介 dictionary&#xff08;字典&#xff09; 是 除列表以外 Python 之中 最靈活 的數據類型&#xff0c;類型為dict 字典同樣可以用來存儲多個數據字典使用鍵值對存儲數據 2. 字典的定義 字典用{}定義鍵值對之間使用,分隔鍵和值之間使用:分隔 d {中…

【ECharts】數據可視化

目錄 ECharts介紹ECharts 特點Vue2使用EChats步驟安裝 ECharts引入 ECharts創建圖表容器初始化圖表更新圖表 示例基本柱狀圖后臺代碼vue2代碼配置 組件代碼運行效果 基本折線圖示例代碼組件 基礎餅圖示例代碼后臺前端配置組件運行效果 其他 ECharts介紹 ECharts 是一個由百度開…

spring模塊(一)容器(4)ApplicationContextAware

一、介紹 1、問題引入 為了獲取已被實例化的Bean對象,如果使用再次加載配置文件的方法,可能會出現一個問題,如一些線程配置任務, 會啟動兩份,產生了冗余. ApplicationContext appContext new ClassPathXmlApplicationContext("applicationContext.xml"); UserS…

python 多線程處理圖片

thread for i in range(len(ori_path)):for filename in os.listdir(ori_path[i]):number_img number_img 1print("正在處理第" str(number_img) "張圖片")img_name ori_path[i] filenamet Thread(target deal_one_img, args [img_name, filenam…

使用.net core 調用C#WebService的三種方式

WebSerrvic代碼&#xff1a; [WebMethod]public string Test(string p1, string p2){return p1 "_" p2;} 以下是 SOAP 1.2 請求和響應示例。所顯示的占位符需替換為實際值。 POST /Service1.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charsetutf-8…

unity 制作app實現底部導航欄和頂部狀態欄

前段時間在用unity制作一個app&#xff0c;發現有個問題用unity制作的app&#xff0c;他默認是沒有頂部狀態欄的&#xff0c;也沒有底部的導航欄&#xff0c;是一個全部覆蓋的狀態。但仔細觀察可以發現&#xff0c;正常app&#xff0c;頂部狀態欄是有的&#xff0c;而且是透明的…