大牛領導單獨找我聊了兩句:搞框架的同時別忘了算法


前言

程序=數據結構+算法,好的算法能讓程序更高效的運行;在當今數據信息時代,數據分析和數據處理肯定是避免不了,而算法便成為了很多公司門檻級的要求,特別是大廠;

趕緊搞起來,說不定離進大廠就只差一步呢(算法)~~~

算法簡介

算法是一組完成任務的指令,任何代碼片段都可視為算法。如下:

image-20210324175721795

1. 算法五大特性

  • 有窮性:一個算法必須在執行有限步之后結束,且每一步都可在有限時間內完成。通俗一點理解就是不能出現類似死循環這樣,導致算法無法結束。
  • 確定性:算法中每條指令必須有確切的含義,對於相同的輸入只能得出相同的輸出
  • 可行性:算法中描述的每一步操作都可以通過已經實現的基本運算執行有限次來實現。
  • 輸入:一個算法有零個或多個輸入。就好比一個方法,可以不傳遞參數,也可以傳遞參數。零個輸入時其實代表算法本身設有初始條件。
  • 輸出:一個算法有一個或多個輸出,這些輸出是與輸入有着對應關系的量。沒有輸出的算法是毫無意義的。

一個好的算法還應該有如下特征:

  • 正確性:能正確解決問題,結果正確;
  • 可讀性:算法實現步驟容易讀懂;
  • 健壯性:算法能處理異常情況;比如輸入不合法時,算法能給出對應處理;
  • 高效率、低存儲:時間復雜度低,空間復雜度低;即運行快,占用內存少。

2. 衡量算法好壞的標准

度量一個算法好壞,可以從兩個維度進行判斷:

  • 時間復雜度:事先預估執行完算法的時間開銷數量級

    由於數據量多少、硬件配置、程序語言等因素會直接影響到算法的執行時間,比如同樣的算法,數據量少的肯定快,硬件配置高的肯定快,所以不能用算法執行完成后的具體時間來衡量一個算法的好壞。

    一個算法,可以預估其時間開銷級別(不受外界其他條件影響),通常使用大O表示法來表示,來個例子:

    image-20210325084348799

    上圖方法,為了方便理解,假設每一步需要1ms,當傳入的n=1000時,每一步耗時如下:

    ①只執行一次,所以消耗1ms;

    ②由於每次循環需要判斷,需要則需要1001次;消耗1001ms;

    ③和④在循環體中,所以分別需要執行1000次,總共消耗2000ms;

    所以總耗時為:T(1000)=1+1001+2*1000; 具體時間和傳入的n有關系,則總耗時為:

    T(n)=1+(n+1)+2n;

    這里T代表時間,通常說時間復雜度的時候都不帶單位。為了更加簡潔直觀,會使用大O表示法去掉常數部分和系數部分,如下:

    T(n)=1+(n+1)+2n=O(n);

    因為當n足夠大時,系數和常數對算法度量的影響不大;這里就不細說啦;

  • 空間復雜度:事先預估執行完算法的內存開銷數量級

    空間復雜度和時間復雜度類似,同樣可以用大O表示,只是這個表示的是算法所消耗的內存,比如int占用4個字節,上圖中用到中間變量nResult,在不考慮其他容量的情況下,消耗了4個字節,用大O表示法,依然是去掉常數和系數,對於常量的的表示為O(1);

對於時間復雜度和空間復雜度,對應的數量級別越小,算法越高效。常遇到到級別從好到差的順序如下:

O(1)<O(log2 n)<O(n)<O(nlog2 n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

3. 算法的穩定性

若待排序數據中有兩個相等的元素A和B,在排序前A在B前面,如果使用某種排序算法后,A仍在B前面,則稱這種排序算法穩定,否則就不穩定。但穩定性不能用來衡量一個算法的好壞,只能算是算法的一個性質,對於一些場景,根本就不在乎兩個相等元素的順序。

從排序開始

排序在實際開發中用的比較多,就先從這入手吧;排序分為內部排序和外部排序兩種:

  • 內部排序:在排序期間元素全部存在內存中進行排序;常見的插入排序、交換排序、選擇排序都是內部排序。
  • 外部排序:在排序期間元素無法全部存放在內存中,必須在排序過程中不斷地在內、外存之間移動。

1. 先來說說直接插入排序

1.1 算法思想

插入排序就是每次將一個待排序的數據插入到一個前面已排好序的子序列中,初始認為第一個元素就是排好序的序列,依次比較,然后插入到合適位置,直到完成排序為止。

插入排序的關鍵如下:

  • 將待排序數據分為三部分,已經排好序的數據、下一個需要插入的數據、待排序的數據
  • 每一次都從待排序數據中取出一個需要插入的數據,將其放在哨兵位置;
  • 將哨兵位置的數據(其實就是要插入的數據)與已排好序的數據進行比較,如果符合條件就插入到對應位置,其他數據統一向后移位即可;
1.2 算法實現與解析

算法代碼如下(升序):

image-20210325135045509

執行結果如下:

image-20210325134622616

解析排序步驟過程,如下:

image-20210325214501972

步驟說明:

圖中綠線框部分代表是已經排好序的列表,箭頭指的元素是下一個要插入的元素,黃線框部分為剩下的無序元素。黃方塊為每次移動的數據,綠方塊表示最后有序列表騰出的位置。

  • 將原始數據array復制到新數組中arrayb中,這步的主要目的是后續不需要聲明額外臨時變量,也為了后續核心代碼實現邏輯簡單易懂,減少過多的判斷;

  • 第1步將第一個元素作為有序列表(第一元素為2),下一個要插入的元素為5,將5放入哨兵位置,即索引為0的位置;然后依次遍歷有序列表中的元素,與哨兵位的值5比較,這里只有2和5比較,2小於5,所以不需要改變位置;

  • 第2步有序列表中的元素有2和5,下一個要插入的元素為6,將6放入哨兵位置,即索引為0的位置;然后依次遍歷有序列表中的元素(2和5),與哨兵位的值6比較,都小於6,所以不需要改變位置;

  • 第3步有序列表中的元素有2、5、6,下一個要插入的元素為1,將1放入哨兵位置,即索引為0的位置;然后依次遍歷有序列表中的元素(2、5、6),與哨兵位的值1比較;

    第3-1步,由於是倒序遍歷,先用有序列表中的6與1進行比較,6大於1,所以6往后移一位;

    第3-2步,繼續遍歷,用有序列表中的5與1進行比較,5大於1,所以5往后移一位;

    第3-3步,繼續遍歷,用有序列表中的2與1進行比較,2大於1,所以2往后移一位;

    第3-4步,遍歷完有序列表中的元素,要插入的元素和哨兵位的元素相等,終止遍歷;然后將哨兵位的元素(當前哨兵位為1)賦值給騰出的空間(騰出的索引位為1);

  • 第4步有序列表中的元素有1、2、5、6,下一個要插入的元素為9,將9放入哨兵位置,即索引為0的位置;然后依次遍歷有序列表中的元素(1、2、5、6),都小於哨兵位的值9,所以不用插入,位置不變;

  • 第5步有序列表中的元素有1、2、5、6、9,下一個要插入的元素為3,將3放入哨兵位置,即索引為0的位置;然后依次遍歷有序列表中的元素(1、2、5、6、9),與哨兵位的值3比較;

    第5-1步,由於是倒序遍歷,先用有序列表中的9與3進行比較,9大於3,所以9往后移一位;

    第5-2步,繼續遍歷,用有序列表中的6與3進行比較,6大於3,所以6往后移一位;

    第5-3步,繼續遍歷,用有序列表中的5與3進行比較,5大於3,所以5往后移一位;

    第5-4步,繼續遍歷,用有序列表中的2與3進行比較,2小於3,終止遍歷;然后將哨兵位的元素(當前哨兵位為3)賦值給騰出的空間(騰出的索引位為3);

第5步完成之后,已完成黃線框中無序元素的排序,排序也就完成啦;最終的結果就是1、2 、3 、5 、6 、9。

這樣對比着圖看詳細說明,是不是好理解了很多。

如果有小伙伴不太理解上面的代碼,可以使用定義臨時變量作為哨兵的方式,步驟和上面基本一樣,只是哨兵不一樣,如下:

image-20210325235648402

1.3 算法分析

主要從時間復雜度、空間復雜度、是否穩定來進行分析:

時間復雜度

分析時間復雜度時,會從最好、平均、最壞三種情況進行分析;

最好時間復雜度:傳入的數據是有序的(和最終的結果一致),所以每次遍歷,一次就能找到位置,所以插入排序的最好時間復雜度為O(n),和傳入的元素個數有關;

最壞時間復雜度:傳入的數據完全和要的結果相反,所以每次都需要進行兩次循環進行找到合適位置插入,所以最壞時間復雜度為O(n2);

平均時間復雜度也就是:O(n2);

空間復雜度

在算法核心部分只采用了固定的幾個中間變量(i,j,arrayb[0]),所以算法過程中消耗的內存是一個常量,則空間復雜度為O(1);

穩定性

由於在算法過程中采用的是小於符號進行比較,遇見相等的數據時就終止判斷,所以不會影響原有的數據順序,則直接插入排序是穩定的。

綜上所述,插入排序的時間復雜度為O(n2),空間復雜度為O(1),是穩定算法;

總結

第一篇復習了一下關於算法相關知識,然后以簡單的直接插入排序收尾,后面會依次總結其他算法,還是圖解加說明的方式,讓每一個算法學起來更簡單。

感謝小伙伴的:點贊收藏評論,下期繼續~~~

一個被程序搞丑的帥小伙,關注"Code綜藝圈",跟我一起學~~~

img


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM