算法學習 - 基礎排序算法

最近在學習算法與數據結構,算法是一個程序員的基本功富拗,但是我不是科班出身,所以這方面的知識有所欠缺鸣戴。在慕課網上找到一套對應的課程啃沪,主講老師是liuyubobobo,從我學習的感受和體驗來看,bobo老師對一個問題講解的相當清晰和透徹窄锅,普通話說的也好创千,適合初學者理解和學習。大家如果想學習算法與數據結構的知識入偷,我推薦這一套教程追驴。地址鏈接:https://coding.imooc.com/class/71.html。 這一系列文章主要是對這套課程的內容以文字的形式展示盯串。做這個事情一是想對視頻上的知識點在通過形成文字的過程中氯檐,加深自己的理解。二是想加強自己的表達能力体捏。

這篇文章主要講解3個基礎的排序算法,選擇排序糯崎,插入排序几缭,以及冒泡排序,其時間復雜度都是0(n^2)級別的沃呢,實現代碼使用c++語言年栓。

選擇排序

首先給定一個元素是無序的整數數組:


image

需要對這個數組中的8個整數進行從小到大的排序。

選擇排序的基本思路

首先從起始位置index = 0開始薄霜,遍歷一遍數組某抓,獲取到最小的元素值index = 4 的元素纸兔,元素值為1,然后將index = 0與index = 4 上的兩個元素交換位置否副,此時index = 0 上的元素值1汉矿。index = 4上的元素值為5。紅色為已排序完成的有序區(qū)域备禀。

image

找到了最小的元素放在數組的第一位后洲拇,接著從index = 1開始往后遍歷數組。找到第二小的元素后再與index = 1的位置交換元素曲尸,此時赋续,index = 1上的元素值為2,index = 2 上的元素為4.

image

接著再從index = 2的位置往后遍歷數組另患,找到第三小的元素后再與index = 2上的元素交換位置纽乱,交換后,index = 2上的元素為3昆箕,index = 5 上的元素為4鸦列。

image

然后依此遍歷的方法,直到數組的最后一個位置存放的是最大的元素为严。選擇排序的整體思想不難理解敛熬,就是循環(huán)一遍數組找到循環(huán)元素中最小的元素,再與已排好序的下一個索引上的元素交換位置第股。

代碼實現

我寫成一個函數应民,傳入一個數組以及元素的個數:

template <typename T>
void selectionSort(T arr[],int n){
    
    for (int i = 0; i < n; i++) {
        int minIndex = i; // minIndex保存內層循環(huán)中最小值的索引
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        swap(arr[i], arr[minIndex]);
    }
}

插入排序

插入排序的基本思路

bobo老師打個這樣一個比方,我覺得用來理解插入排序非常合適:就好像有一副牌夕吻,三個人用這副牌來斗地主诲锹,分牌階段,你需要將你摸到的牌放在手中的牌中的合適位置涉馅,手中的牌是整理好的归园,是有序的,放入后稚矿,使其整體依舊有序庸诱。我們來模擬一下這個過程。首先固定手中有一張牌晤揣,用紅色標記為手中已整理好的牌桥爽。

image

接著摸第二張牌,為4昧识,比5小钠四,所以放在5的前面,4與5交換位置跪楞。

image

接著摸第三張牌缀去,為2侣灶,2比5小,所以先放在5的前面缕碎,4的后面褥影。

image

此時這里并不是2合適的位置,繼續(xù)和前面的元素比較阎曹,2比4小伪阶,所以要放到4的前面。此時變成有序了处嫌。

[圖片上傳失敗...(image-231474-1535017882614)]

接著再摸第四張牌栅贴,為7,7比5小熏迹,發(fā)現不用交換位置檐薯,放在原地就好了。

image

就這樣注暗,按照這個方法坛缕,將摸到的牌與整理好的牌依次做比較,使其放在合適的位置捆昏,直到摸完最后一張牌赚楚。

代碼實現

template <typename T>
void insertionSort(T arr[],int n){
    
    for (int i = 1; i < n; i++) {
        
        for (int j = i; j > 0; j--) {
            if (arr[j-1] > arr[j]) {
                swap(arr[j - 1],arr[j]);
            } else {
                break;
            }
        }
    }
}

注意:第一層for循環(huán)時 i = 1,i不是從零開始骗卜。因為第一張牌不需要排序宠页,從第二張牌開始。所以設置i = 1寇仓。

在[0 i) 前閉后開這個區(qū)間中為已排好序的元素举户,i為待排序的元素的索引,第二層for循環(huán)是從待整理的元素索引i開始遍烦。在已排好序的區(qū)間[0,i)中俭嘁,從后往前遍歷。 如果發(fā)現前面的元素比它大服猪,則兩個元素交換位置供填。當發(fā)現前面的元素比它小的時候,此時它就找到了合適的位置罢猪。就不需要再遍歷了捕虽,直接結束這一層循環(huán)。

插入排序優(yōu)化

在交換兩個元素位置的時候,調用了swap(arr[j - 1],arr[j])坡脐,而一次交換其實是三次賦值,這句代碼其實也可以改寫為:

int temp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp;

如果一個較小的元素要插入到合適的位置房揭,肯定要一路交換很多次备闲。這無形中就增加了排序時間晌端。優(yōu)化思路就是只進行賦值操作而不進行交換操作,先保存一份待排序的元素恬砂,然后遍歷已排好序的元素咧纠,從后往前進行逐一比較,如果當遍歷到index = j的元素時泻骤,j - 1索引上的元素要比j位置上的元素大漆羔,則將j - 1索引位置的元素往后挪,往后挪也就是將j索引位置的元素賦值為j-1索引位置的元素狱掂,當遍歷到j-1索引上的元素比元素j位置上的元素小或相等時演痒,就說明上一次循環(huán)往后挪而騰出來的位置為待排序的合適位置,在將該位置的值賦值為待排序的值就好了趋惨。

假定現在按照優(yōu)化的思路對1進行排序鸟顺,紅色為已排好序的部分。

image

首先先復制一份待排序的元素1

image

然后將待排序的元素與已排好序的index = 3位置器虾,值為7的元素比較讯嫂,如果這個元素比待排序的元素要大,則直接將待排序的值復制為這個元素兆沙。7 比 1 大欧芽,7往后挪,所以待排序位置上的元素值賦值為7葛圃,而index = 3位置就騰出來了千扔。

image

然后依次往前與帶排序的元素比較。這次是index = 2 時的 5 與 1比較装悲, 5比1大昏鹃,5往后挪,將index = 3 位置上的元素賦值為5诀诊,index = 2這個位置就騰出來了洞渤。

image

再將index = 1 時的4 與 待排序的元素1比較,4 還是比1 大属瓣,4往后挪载迄,將 index = 2位置賦值為4,index = 1這個位置就騰出來了抡蛙。

image

繼續(xù)护昧。再將index = 0時的2, 與待排序的元素1比較,2還是比1大粗截,2往后挪惋耙,將index = 1位置賦值為2,index = 0這個位置騰出來了。

image

那么騰出來的位置上為元素的前面已沒有元素可以比較了绽榛。所以此時在index = 0的位置上放置上待排序的元素湿酸。

image

元素1經過一番波折,塵埃落定灭美,終于找到了他合適的位置推溃,元素1排序完畢

image

然后index = 5上的元素3按照上述方法找到合適的位置,index = 6 上的元素按照上述方法找到合適的位置届腐,接著是index = 7上的元素铁坎。將所有的元素都按照上述方法找到自己合適的位置。

代碼實現

template <typename T>
void insertionSort(T arr[],int n){
    
    for (int i = 1; i < n; i++) {
        T e = arr[i]; // 保存待插入索引為i時的元素e
        int j = i; // j保存元素e應該插入的位置
        for (j = i; j > 0; j--) {
            if (arr[j-1] > e) {
                arr[j] = arr[j-1];
            }
        }
        arr[j] = e;
    }
}

冒泡排序

冒泡排序實現思路

首先還是這一個由8個數組成的一個無序的數組

image

冒泡排序的核心思想就是將相鄰的兩個元素兩兩比較犁苏,根據大小來交換元素的位置

首先硬萍,5與4比較,5比4大傀顾,我們的需求是從小到達排列襟铭,4需要在5的前面,所以4與5交換位置短曾,紅色表示兩個要交換位置的元素寒砖。

image

交換后

image

然后, 5與2開始比較,5比2大嫉拐,交換位置

image

交換后


image

繼續(xù)哩都, 5和7比較,5比7小婉徘,位置保持不變

image

go on漠嵌, 7與1比較,7比1大盖呼,交換位置

image

交換后


image

不要停儒鹿,7與3比較,7比3大几晤,交換位置

image

交換后

image

繼續(xù)深入约炎, 7與8比較,7比8小蟹瘾,保持不變

image

還有最后一個圾浅,8與6比,8比6大憾朴,交換位置

image

交換后

image

這樣一輪下來狸捕,元素8排到了最右側,此時紅色代表?已排好序的區(qū)域

image

接著按照上述方法众雷,重新重頭開始相鄰元素兩兩遍歷灸拍,前一個元素比后一個元素大則交換位置做祝,小則保持位置不變,一路比較下來株搔。找到第二大的元素放到元素8的左側剖淀。

image

然后是

image

然后是

image

給定的一組無序的整數數組,按照排序的思路來講纤房,藍色區(qū)域屬于無序的區(qū)域,還需要比較翻诉,但是上面圖片中藍色部分已經是有序的了炮姨,這可以作為一個優(yōu)化的方面,就是當發(fā)現代排區(qū)域中的元素已經是有序了的時候碰煌,排序完成舒岸,結束排序,下面的代碼中不涉及這部分的優(yōu)化芦圾。

實現代碼

template <typename T>
void BubbleSort(T arr[],int n){
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(arr[j],arr[j+1]);
            }
        }
    }
}

代碼不難理解蛾派,雙層循環(huán),外層每一次循環(huán)確定一個最大值放在數組的右側个少,內層尋找這個最大值洪乍。先進行元素比較,滿足則交換夜焦。

冒泡排序的優(yōu)化

剛剛在逐步比較的時候壳澳,出現了這樣一種情況:藍色的無序區(qū)域中的元素已經是有序的了。

image

但是上面的冒泡排序的代碼還會繼續(xù)的比較下去茫经。每一個元素都會參與外層循環(huán)巷波,直到循環(huán)結束。所以優(yōu)化方向是在無序區(qū)域中的元素已經有序了的情況下結束循環(huán)卸伞。
優(yōu)化后的代碼,部分解釋見注釋抹镊。

void BubbleSort(T arr[],int n){
    
    for (int i = 0; i < n; i++) {
        bool isSorted = true; // 用一個bool值來標記每一層是否是有序的,默認為true.
        
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(arr[j],arr[j+1]);
                isSorted = false; // 如果有交換元素荤傲,那么不是有序垮耳,該bool值改為false.
            }
        }
        if (isSorted) { // 在內層中,如果該bool值沒有被改為false,那么就說明內層沒有元素交換弃酌,那么該數列排序完成了氨菇,直接結束循環(huán)。
            break;
        }
    }
}

這個優(yōu)化主要是用一個bool值做標記妓湘,來確定是否有序查蓉。在內層循環(huán)中如果沒有元素交換,那么這個數列肯定就是有序的了榜贴,那么外層就無需在循環(huán)了豌研。

下次學習內容:歸并排序

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末妹田,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子鹃共,更是在濱河造成了極大的恐慌鬼佣,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霜浴,死亡現場離奇詭異晶衷,居然都是意外死亡,警方通過查閱死者的電腦和手機阴孟,發(fā)現死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門晌纫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人永丝,你說我怎么就攤上這事锹漱。” “怎么了慕嚷?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵哥牍,是天一觀的道長。 經常有香客問我喝检,道長嗅辣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任蛇耀,我火速辦了婚禮辩诞,結果婚禮上,老公的妹妹穿的比我還像新娘纺涤。我一直安慰自己译暂,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布撩炊。 她就那樣靜靜地躺著外永,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拧咳。 梳的紋絲不亂的頭發(fā)上伯顶,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音骆膝,去河邊找鬼祭衩。 笑死,一個胖子當著我的面吹牛阅签,可吹牛的內容都是我干的掐暮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼政钟,長吁一口氣:“原來是場噩夢啊……” “哼路克!你這毒婦竟也來了樟结?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤精算,失蹤者是張志新(化名)和其女友劉穎瓢宦,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體灰羽,經...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡驮履,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了谦趣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疲吸。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖前鹅,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情峭梳,我是刑警寧澤舰绘,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站葱椭,受9級特大地震影響捂寿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜孵运,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一秦陋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧治笨,春花似錦驳概、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至等孵,卻和暖如春稚照,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俯萌。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工果录, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咐熙。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓弱恒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親糖声。 傳聞我的和親對象是個殘疾皇子斤彼,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容

  • 概述 排序有內部排序和外部排序分瘦,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大琉苇,一次不能容納全部...
    蟻前閱讀 5,186評論 0 52
  • 概述:排序有內部排序和外部排序嘲玫,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大并扇,一次不能容納全部...
    每天刷兩次牙閱讀 3,732評論 0 15
  • 轉載自林鑫的博客 前言 最近工作中遇到一個在前端上傳圖片后需求自動調整圖片方向的需求穷蛹,查找了一下資料土陪,結合自己的工...
    朱man閱讀 2,226評論 0 1
  • 凌晨四點半的上海 天空略微泛紅。 有一種似醒非醒肴熏、蟄伏的蠢蠢欲動鬼雀。 秋日的涼風吹來,體感很舒適蛙吏。 我更喜歡“一日之...
    我讀書少_別騙我閱讀 381評論 1 0
  • 覺得上班走路累源哩,想去買輛自行車,結果去了一看鸦做,要2500塊励烦。旁邊的人說,2500都掏了不如加點錢買輛電動泼诱。遂問電動...
    雨晴T閱讀 204評論 1 1