算法簡單學(xué)習(xí)(二)—— 一個(gè)簡單的插入排序

版本記錄

版本號(hào) 時(shí)間
V1.0 2017.08.11

前言

將數(shù)據(jù)結(jié)構(gòu)和算法比作計(jì)算機(jī)的基石毫不為過,追求程序的高效是每一個(gè)軟件工程師的夢想。下面就是我對算法方面的基礎(chǔ)知識(shí)理論與實(shí)踐的總結(jié)。感興趣的可以看上面幾篇。
1. 算法簡單學(xué)習(xí)(一)—— 前言

插入排序 insertion - sort

插入排序是一個(gè)針對少量元素進(jìn)行排序的有效算法,下面我們先看一下插入排序的模型。

1. 問題描述

輸入數(shù)據(jù):n個(gè)數(shù) (a1, a2, ... , an)
輸出數(shù)據(jù):對這n個(gè)數(shù)據(jù)重新排序,使得a1' ≤ a2' ≤ ... ≤ an'。這里待排序的數(shù)也稱為關(guān)鍵字。

2. 算法模型

開始打牌時(shí),左邊的手是空著的,牌面朝下放在桌子上,接著,一次從桌子上摸起一張牌,并將它插入左手一把牌中的正確位置,為了找到這個(gè)牌的正確位置,要將它與手中已有從右向左進(jìn)行比較,如上圖所示。無論什么時(shí)候左手中的牌都是排序好的,而這些牌原先都是桌子上那副里最頂上的一些牌。

插入算法圖示

3. 算法偽代碼

下面我們就看一下算法偽代碼。

//insertion sort

1 for j ← 2 to length[A]
2   do key ← A[j]
3         //insert A[j] into the sorted sequence A[1 ... j - 1]
4         i ← j - 1
5         while i > 0 and A[i] > key
6            do A[i + 1] ← A[i]
7                  i ← i - 1
8         A[i + 1] ← key

4. 循環(huán)不變式與插入算法的正確性

下面看這個(gè)數(shù)組 A = [5, 2, 4, 6, 1, 3],下標(biāo)j指示了待插入到手中的當(dāng)前牌,在外層for循環(huán)(循環(huán)變量為j)的每一輪迭代的開始,包含元素A[1 ... j - 1]的子數(shù)組構(gòu)成了左手中當(dāng)前已經(jīng)排好的序的一手牌,元素A[j + 1 ... n]對應(yīng)于桌子上的那堆沒有抓起來的牌。

下面以循環(huán)不變式(loop invariant)的形式,來表達(dá)A[1 ... j - 1]的這些性質(zhì)。在過程第1 ~ 8行偽代碼中,在每一輪迭代的開始,子數(shù)組A[1 ... j - 1]中包含了最初位于A[1 ... j - 1]但是目前已經(jīng)排好序,循環(huán)過程如下所示。

循環(huán)過程

上圖中黑色方框里面的值取自A[j]的關(guān)鍵字值,在過程第5行的測試中,將它與左邊陰影框中的各個(gè)值進(jìn)行比較。

循環(huán)不變式的三個(gè)性質(zhì):

  • 初始化: 它在循環(huán)的第一輪迭代開始之前,應(yīng)該是正確的。
  • 保持 : 如果在循環(huán)的某一次迭代開始之前它是正確的,那么,在下一次迭代開始之前,它也應(yīng)該保持正確。
  • 終止 : 當(dāng)循環(huán)結(jié)束時(shí),不變式給我們有一個(gè)有用的性質(zhì),有助于表明算法是正確的。

下面就證明一下這些性質(zhì)的正確性。

  • 初始化 : 首先看第一輪迭代開始之前的正確性,此時(shí)j = 2,而子數(shù)組為A[1 ... j - 1],則只包含一個(gè)元素A[1],實(shí)際上就是最初在A[1]中的那個(gè)元素,這個(gè)子數(shù)組已經(jīng)是排序好的了,證明第一次迭代開始之前是成立的。

  • 保持 :看第二個(gè)性質(zhì),證明每一輪循環(huán)都使循環(huán)不變式保持成立,從非形式化的意義上看,外層for循環(huán)的循環(huán)體中,要將A[j - 1], A[j - 2], A[j - 3]等元素向右移動(dòng)一個(gè)位置,直到找到A[j]的適當(dāng)位置時(shí)為止(第 4 ~ 7)行,這時(shí)將A[j]的值插入(第 8 行)。如果要證明這個(gè)性質(zhì)不成立就需要證明while循環(huán)有一個(gè)循環(huán)不成立,這里就不證明了。

  • 終止: 當(dāng) j = n + 1的時(shí)候外層for循環(huán)結(jié)束,將j 替換為n + 1 ,就有子數(shù)組A[1..n]包含了原先A[1..n]中的元素,但是現(xiàn)在已經(jīng)排好序了,就已經(jīng)是整個(gè)數(shù)組了,也就意味這個(gè)數(shù)組是正確的。

5. 算法分析

算法分析即指對一個(gè)算法所需要的資源進(jìn)行預(yù)測,內(nèi)存、通信寬帶或計(jì)算機(jī)硬件等資源偶爾會(huì)是我們主要關(guān)心的,但是通常,資源就是指我們希望測度的計(jì)算時(shí)間。在分析一個(gè)算法之前,要建立有關(guān)實(shí)現(xiàn)技術(shù)的模型,包括描述所用資源及代價(jià)的模型。

插入排序過程的時(shí)間開銷和輸入有關(guān),不同個(gè)數(shù)的排序時(shí)間消耗一定不同。即使輸入個(gè)數(shù)相同,時(shí)間也不一定相同,還和它們已排列的程度有關(guān)。

可以將運(yùn)行時(shí)間表示為運(yùn)行時(shí)間輸入規(guī)模的函數(shù)。

  • 輸入規(guī)模:這個(gè)因素與具體問題有關(guān)。
  • 運(yùn)行時(shí)間:指在特定輸入時(shí),所執(zhí)行的基本操作數(shù)或步數(shù),可以很方便的定義獨(dú)立于具體機(jī)器的步驟概念。目前先假定執(zhí)行一行偽代碼花費(fèi)的時(shí)間都是常量ci

j = 2, 3, ..., n , n = length[A],設(shè)tj為第 5行中while的測試次數(shù),循環(huán)體要比測試少一次,即不滿足條件退出,所得的占用時(shí)間如下所示。

計(jì)算時(shí)間

下面我們把這些時(shí)間相加,可得。

運(yùn)行時(shí)間總和

上面的表達(dá)式就是運(yùn)行時(shí)間的總和。

從上面式子可以看到,即使規(guī)模相同,那么時(shí)間長度也與該規(guī)模下數(shù)據(jù)的輸入情況有關(guān)。下面我們看幾種特殊情況。

  • 假定輸入數(shù)組已經(jīng)排好序

這種情況對于 j = 2, 3, ... , n中的每一個(gè)值,都會(huì)發(fā)現(xiàn),在第 5 行匯總,當(dāng) i 取其初始值j - 1時(shí),都有A[i] ≤ key,于是對應(yīng)j = 2, 3, ... , n中有tj = 1,則可以簡化最佳運(yùn)行時(shí)間為:

最佳運(yùn)行時(shí)間

這個(gè)時(shí)候時(shí)間就可以簡化為T(n) = an + b,其中a, b都是與ci有關(guān)的常數(shù)。

  • 假定輸入數(shù)組是逆序排列

這個(gè)時(shí)候就需要我們將每一個(gè)元素A[j]與整個(gè)已排序的子數(shù)組A[1 ... j - 1]中的每一個(gè)元素進(jìn)行比較,因此,對于 j = 2, 3, ... , ntj = j,那么時(shí)間函數(shù)變化為:

最壞運(yùn)行時(shí)間

我們簡化為 T(n) = an ^ 2 + bn + ca, b, c均為與ci有關(guān)的常數(shù),整個(gè)函數(shù)是關(guān)于n的二次函數(shù)。

6. 插入排序的最壞情況和平均情況分析

上面我們可以看到最壞情況就是輸入的是逆序的情況,最好的情況就是輸入的是排好順序的情況,我們一般考慮的就是最壞的運(yùn)行時(shí)間。

  • 知道一個(gè)算法的運(yùn)行最壞情況,就是知道了算法的時(shí)間上限,就不用擔(dān)心它會(huì)變的更壞了。
  • 對于某些算法來說,最壞的情況出現(xiàn)的情況還是相當(dāng)頻繁的,例如當(dāng)我們在數(shù)據(jù)庫里面檢索一條不存在的信息時(shí),就是出現(xiàn)了算法的最壞情況。
  • 這里還要說一下平均情況,這里的平均情況就是數(shù)組中A[1 ... j - 1]中一半元素小于A[j],另外一半元素大于A[j],因此,這里有tj = j / 2,在計(jì)算時(shí)間復(fù)雜度你就會(huì)發(fā)現(xiàn)它仍然是一個(gè)二次函數(shù),與最壞情況下的運(yùn)行時(shí)間是一樣的。

7. 增長的量級(jí)

下面我們對上面提出的時(shí)間表達(dá)式進(jìn)一步進(jìn)行首相,得到的就是運(yùn)行時(shí)間的增長率 (rate of growth),或稱為增長的量級(jí)(order of growth),這樣我們只考慮最高次項(xiàng)而忽略其他項(xiàng),同時(shí)也忽略最高次項(xiàng)的系數(shù),所以上面我們總結(jié)的最壞情況的表達(dá)式就為O(n^2)

一般認(rèn)為,如果一個(gè)算法最壞情況運(yùn)行時(shí)間要比另外一個(gè)算法的低,就認(rèn)為它的效率更高。

8. 代碼實(shí)現(xiàn)

下面我們就看一下代碼實(shí)現(xiàn)。

先建立一個(gè)C工程。

C工程
新建立的C工程

下面我們看一下代碼實(shí)現(xiàn)。

亂序

#include <stdio.h>
#include <string.h>
#include <time.h>

int main(int argc, const char * argv[])
{
    int a[6] = {5, 2, 4, 6, 1, 3};
    int length = sizeof(a)/sizeof(a[0]);
    clock_t startTime, endTime;

    startTime = clock();
    
    for (int j = 1; j <= length - 1; j++) {
        int i = j - 1;
        int key = a[j];
        while (i >= 0 && a[i] > key) {
            a[i + 1] = a[i];
            i --;
            a[i + 1] = key;
        }
    }
    
    endTime = clock();
    
    for (int k = 0; k < length; k++) {
        printf("%d\n",a[k]);
    }
    
    printf("運(yùn)行時(shí)間為%ld\n", endTime - startTime);
    return 0;
}

下面看輸出結(jié)果

1
2
3
4
5
6
運(yùn)行時(shí)間為3
Program ended with exit code: 0

順序

#include <stdio.h>
#include <string.h>
#include <time.h>

int main(int argc, const char * argv[])
{
    int a[6] = {1, 2, 3, 4, 5, 6};
    int length = sizeof(a)/sizeof(a[0]);
    clock_t startTime, endTime;

    startTime = clock();
    
    for (int j = 1; j <= length - 1; j++) {
        int i = j - 1;
        int key = a[j];
        while (i >= 0 && a[i] > key) {
            a[i + 1] = a[i];
            i --;
            a[i + 1] = key;
        }
    }
    
    endTime = clock();
    
    for (int k = 0; k < length; k++) {
        printf("%d\n",a[k]);
    }
    
    printf("運(yùn)行時(shí)間為%ld\n", endTime - startTime);
    return 0;
}

下面看一下輸出結(jié)果

1
2
3
4
5
6
運(yùn)行時(shí)間為1
Program ended with exit code: 0

倒序

#include <stdio.h>
#include <string.h>
#include <time.h>

int main(int argc, const char * argv[])
{
    int a[6] = {6, 5, 4, 3, 2, 1};
    int length = sizeof(a)/sizeof(a[0]);
    clock_t startTime, endTime;

    startTime = clock();
    
    for (int j = 1; j <= length - 1; j++) {
        int i = j - 1;
        int key = a[j];
        while (i >= 0 && a[i] > key) {
            a[i + 1] = a[i];
            i --;
            a[i + 1] = key;
        }
    }
    
    endTime = clock();
    
    for (int k = 0; k < length; k++) {
        printf("%d\n",a[k]);
    }
    
    printf("運(yùn)行時(shí)間為%ld\n", endTime - startTime);
    return 0;
}

下面看輸出結(jié)果

1
2
3
4
5
6
運(yùn)行時(shí)間為3
Program ended with exit code: 0

后記

未完,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 總結(jié)一下常見的排序算法。 排序分內(nèi)排序和外排序。內(nèi)排序:指在排序期間數(shù)據(jù)對象全部存放在內(nèi)存的排序。外排序:指在排序...
    jiangliang閱讀 1,361評論 0 1
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個(gè)記錄插入到已排序好...
    依依玖玥閱讀 1,265評論 0 2
  • 排序的基本概念 在計(jì)算機(jī)程序開發(fā)過程中,經(jīng)常需要一組數(shù)據(jù)元素(或記錄)按某個(gè)關(guān)鍵字進(jìn)行排序,排序完成的序列可用于快...
    Jack921閱讀 1,449評論 1 4
  • 該系列文章主要是記錄下自己暑假這段時(shí)間的學(xué)習(xí)筆記,暑期也在實(shí)習(xí),抽空學(xué)了很多,每個(gè)方面的知識(shí)我都會(huì)另起一篇博客去記...
    Yanci516閱讀 12,246評論 6 19
  • 快排上圖中空間復(fù)雜度數(shù)據(jù)錯(cuò)誤,應(yīng)該是O(log n)。 插入,堆,歸并,快排 n表示數(shù)據(jù)規(guī)模,k表示桶的個(gè)數(shù)。n:...
    hadoop_a9bb閱讀 1,625評論 2 36