排序算法第一篇-排序算法介紹

排序算法第一篇-排序算法介紹

在面試中,現在無論大小公司都會有算法的。其中排序算法也是一種很常見的面試題。比如冒泡,快排等。這些,排序算法自己看了一次又一次,可是過一段時間,又忘掉了。所以,這次就把算法是怎麼推導出來的,詳細記錄下來。看看這次多久還會忘記。

本文主要介紹排序算法的分類、時間複雜度、空間複雜。爲了後面的學習做準備的。

通過本文學習,將收穫到:排序算法分幾類?什麼是算法的時間複雜度?是怎麼算出來的?什麼是算法的空間複雜度?常見的時間複雜度比較。

如果這些您都已經知道了,可以不用耽誤時間看了。

約定:

文中的n2表示的是n的2次方(n²),n^2也是表示n的2次方;

n3表示的是n的3次方;

n^k表示的是n的k次方;

long2n表示的是以2爲底的對數。

本文出自:凱哥Java(微信:kaigejava)學習Java版數據結構與算法筆記。

一:介紹

排序又稱排序算法(Sort Algorithm),排序是將一組數據,依據指定的順序進行排序的過程。

二:分類

排序的分類分爲兩大類

2.1:內部排序

內部排序是指將需要處理的所有數據一次性都加載到內存中進行排序的。

如:冒泡、快排等這些算法都是內部排序的

2.2:外部排序

數據量過大,無法全部加載到內存中,需要藉助於外部存儲進行排序的。

如:數據庫中數據8個G,內存只有4個G的這種。

2.3:參加分類如下圖:

編輯

三:算法的時間複雜度

3.1:分類

衡量一個程序(算法)執行時間有兩種方法

3.1.1:事後統計的方法

所謂的事後統計方法,顧名思義,就是程序(算法)已經寫完了,運行後得到的結果。

這種方法雖然是可行的,但是有兩個問題:

①:要想對設計的算法運行的性能進行評估,需要實際運行該程序(浪費時間);

②:運行所得的時間統計嚴重依賴於機器的硬件、軟件等環境因爲。

這種方法有個嚴苛的要求:要在同一臺機器在相同狀態(軟硬件)下運行,才能比較哪個算法更快。

3.1.2:事前估算的方法

通過分析某個算法的時間複雜度來判斷哪個算法更優。

3.2:時間頻度

概念:一個算法花費的時間與算法中語句執行的次數成正比。哪個算法中語句執行次數多,那麼這個算法所花費的時間就多(這不廢話嗎)。

一個算法中語句執行次數稱爲語句頻度或時間頻度。記爲:T(n).

(複雜的概念是,時間頻度:一個算法執行所消耗的時間,從理論上是 不能算出來的,想要具體數值,必須要將程序上機運行測試才能知道。但是我們不可能也沒必要對每個算法都上機進行測試的,只需要知道哪個算法花費的時間多,哪個算法花費的時間少就可以了。並且一個算法花費的時間與算法中語句執行的次數成正比的,哪個算法中語句執行次數多,那麼這個程序花費的時間就多。一個算法中的語句執行次數稱爲語句頻度或者時間頻度,記爲:T(n))

例如:我們知道的技術從1到100所有數字的和。這個就有兩種算法。分別如下:

①:使用for循環,從1到100循環出來,然後累加出來。代碼如下:

編輯

根據上面概念(注意對概念的理解,total和end這兩行相對於for循環來說,可以忽略。後面我們還會詳細講解還會忽略哪些),我們來看下這個算法的時間頻度是多少呢?

在for循環中,實際需要執行101次(+1的原因是因爲,在for循環的時候,需要做最後一次判斷,才能推出。因此n個數的計算一共是n+1次操作)。所以其時間頻度就是:T(n)=n+1;

我們再來看看第二種算法:

編輯

是不是很簡單,只要一行代碼就執行完成了。所以第二種算法的T(n)=1了。是不是很快呢?

時間頻度是不是一眼就看出來了?是不是不用在代碼運行下來比較運行時間了?

(ps:從上面簡單的從1到100求和算法中,我們是不是感受到算法的魅力了?感受到編程之美了?)

3.3:時間複雜度

在上面3.2中提到的時間頻度中,n稱爲問題的規模,當n不斷變化的時候,時間頻度T(n)也會不斷變化。但是有時我們想知道它在變化的時候呈現什麼樣的規律呢?爲此,我們引入了時間複雜度概念。

一般情況下,算法中基本操作重複執行的次數是問題規模n的某個函數,用T(n)表示。若有某個輔助函數f(n),是的當n趨近於無窮大的時候,T(n)/f(n)的極限值爲不等於零的藏書,則稱爲f(n)是T(n)的同數量級函數。記作T(n)=O(f(n)),稱O(f(n))爲算法的漸進的時間複雜度。簡稱時間複雜度。這就是大O法。

在計算時間複雜度的時候,我們會忽略以下幾個數據值

3.3.1:忽略常數項

比如上面,我們計算1到100的第一種算法中,有兩行int total=0;和 int end = 100;這兩行代碼,這個數值是2,我們一般計算時間複雜度的時候,會忽略這個常數項的。爲什麼呢?請看下面四個函數,隨着n的增大而增大運行時間。

T(n) = 2n+20

T(n) = 2*n

T(n)=3n+10

T(n)=3*n

請看下圖隨這n的增大鎖呈現的規律:

編輯

我們來看看,把這些數據使用折線圖展示:

編輯

圖例說明:上面兩個是3*n及3n+10的,下面兩個是2n及2n+10的

從上面兩個圖表中我們可以得到以下結論:

①:2n+20和2*n隨着n的增加,執行曲線無限接近(折線圖中下面兩個),常量值20可以忽略了

②:3n+10和3*n隨着n的增加,執行曲線無限接近(折線圖中上面兩個),常量值10可以忽略了

所以,綜上所述,在計算程序(算法)時間複雜度的時候,常量值是可以忽略的

3.3.2:忽略低次項

請看下面四個函數,隨着n的增大又會呈現什麼規律嗎?

T(n)=2n^2+3n+10

T(n)=2n^2

T(n)=n^2+5n+20

T(n)=n^2

說明:n^2表示n的2次方

我們來看看隨着n的增加,運行所消耗的時間。如下圖:

編輯

把上面數據,用折線圖表示,如下圖:

編輯

圖例說明:上面兩個是2n^2及2n^2+3n+10,下面兩個是n^2及 n^2+5n+20

從上面兩個圖中我們可以得到如下結論:

①:2n^2+3n+10和2n^2隨着n的增大,執行曲線無限接近,可以忽略低次項及常量項:3n+10

②:n^2+5n+20和n^2隨着n的增大,執行曲線無限接近,可以忽略低次項及常量項:5n+20

綜上所述,我們可以得到結論:在計算程序(算法)時間複雜度的時候,低次項(3n=3*n^1比n^2項數少)是可以忽略的

3.3.3:忽略係數

我們在來看看下面四個函數,看看它們隨着n的增大呈現出什麼樣的規律

T(n)=3n^2+2n

T(n)=5n^2+7n

T(n)=n^3+5n

T(n)=6n^3+4n

隨着n的增加,運行時間所消耗耗時如下圖:

編輯

折線圖如下:

編輯

從上圖可以得到如下:

①:隨着n值變大,5n^2+7n和3n^2+2n,執行曲線重合,說明這種情況下,係數5和3可以忽略;

②:n^3+5n和6n^3+4n,執行曲線分離,說明多少次防是關鍵

3.3.4:總結:

  • 計算時間複雜度的時候忽略常數項、忽略低次項、忽略係數
  • T(n)不同,但時間複雜度可能相同。

如:T(n)=n2+7n+6與T(n)=3n^2+2n+2它們的T(n)不同,但時間複雜相同,都爲O(n^2).

  • 計算時間複雜度的方法
    • 用常數1代替運行時間中的所有加法常數T(n)=n^2+7n+6 =>T(n)=n^2+7n+1
    • 修改後的運行次數函數中,只保留最高階項T(n)=n^2+7n+1 => T(n)=n^2
    • 去除最高階項的係數T(n)=n^2 =>T(n)=n^2 => O(n^2)

3.4:常見的時間複雜度

  • 常數階O(1)
  • 對數階O(log2n)
  • 線性階O(n)
  • 線性對數階O(nlog2n)
  • 平方階O(n^2)
  • 立方階O(n^3)
  • K次方階(n^k)
  • 指數階O(2^n)

各個時間複雜度複雜度折線圖如下圖:

編輯

總結:

  • 常見算法時間複雜度由小到大依次爲:

O(1)<O(log2n)<O(n)<O(nlong2n)<O(n^2)<O(n^3)<O(n^K)<O(2^n)。隨着問題規模n的不斷增大,上述時間複雜度不斷增大,算法的執行效率也越低;

  • 從上圖折線圖中,我們可以看出,程序(算法)儘可能的避免使用指數階段的算法。

3.5:常見算法時間複雜度舉例

3.5.1:常數階O(1)

無論代碼執行多少行,只要是沒有循環等複雜結構,那這個代碼的時間複雜度就是O(1)

(計算時間複雜度的時候,忽略常數項)

代碼demo:

編輯

上述代碼在執行的時候,消耗的時間並不是隨着某個變量的增長而增長,那麼無論這類代碼有多長,即使有有幾萬幾十萬行,都是可以用O(1)來表示它的時間複雜度。

3.5.2:對數階O(log2n)

代碼敬上:

編輯

說明:

在while循環裏面,沒喫都是將i*2的。n的值是固定的,所以在i乘完之後,i距離n就越來越近了。假設循環x次之後,i就大於n了,此時這個循環就退出了。也就是說2的x次方等於n了。那麼x=log2n。也就是說當循環了log2n次以後,代碼就結束了。因此這個代碼的時間複雜度就是

O(log2n)。

O(log2n)的這個2時間上是隨着代碼變化的。如果i = i*3,那麼時間複雜度就是O(log3n)

回顧下log的理解(這是初中知識點):

如果a的x次方等於N(a>0,且a≠1),那麼熟x就叫做以a爲底的對數(logarithm),記作x=logaN.

其中,a叫做對數的底數,N叫做真數,x叫做“以a爲底N的對數”。

3.5.3:線性階O(n)

代碼如下:

編輯

說明:

這段代碼,for循環裏面的代碼會執行n次。因此它所消耗的時間隨着n的變化而變化的,因此這類代碼都是可以用O(n)來表示它的時間複雜度。

3.5.4:線性對數階O(nlogn)

代碼如下:

編輯

說明:

線性對數階O(nlogN)其實非常容易理解的。將時間複雜度爲O(logn)的代碼循環了N次的話,那麼它的時間複雜度就是n*O(logn),也就是O(nlogN)

3.5.5:平方階O(n2)

代碼:

編輯

說明:

平方階O(n2)就容易理解了。如果把O(n)的代碼再嵌套循環一遍,它的時間複雜度就是O(n2),

上圖中的代碼起始就是嵌套了2層n循環,它的時間複雜度就是O(n*n),即時O(n2)。如果將其中一層循環的n修改成m,那麼它的時間複雜度就變成了O(m*n).

3.5.6:立方階O(n3)、K次方階O(n^k)

說明:參考上面的O(n2)去理解就好了。O(n3)起始就相當於是三層n循環了。其他的一次類推。

3.6:平均時間複雜度和最壞時間複雜度

平均時間複雜度:

是指所有可能的輸入實例均以概率出現的情況下,該算法的運行時間

最壞時間複雜度:

是指在最壞情況下的時間複雜度稱爲最壞時間複雜度。一般討論時間複雜度均是最壞情況下的時間複雜度。

這樣做的原因:最壞情況下的時間複雜度是算法在任何輸入實例上運行時間的界限。這就保證了算法的運行時間不會比最壞情況更長了。

平均時間複雜度和最壞時間複雜度是否一致,和算法有關。具體如下圖:

排序算法

平均時間

最壞情況

穩定度

額外空間

備註

冒泡

O(n^2)

O(n^2)

穩定

O(1)

n小的時候比較好

交換

O(n^2)

O(n^2)

不穩定

O(1)

n小的時候比較好

選擇

O(n^2)

O(n^2)

不穩定

O(1)

n小的時候比較好

插入

O(n^2)

O(n^2)

穩定

O(1)

大部分已經排序時比較好

基數

O(logRB)

O(logRB)

穩定

O(n)

B是真書(0-9)

R是基數(個十百)

Shell(希爾)

O(nlogn)

O(n^s)

1<s<2

不穩定

O(1)

s是所選分組

快排

O(nlogn)

O(n^2)

不穩定

O(nlogn)

n大時候較好

歸併

O(nlogn)

O(nlogn)

穩定

O(1)

n大時候較好

O(nlogn)

O(nlogn)

不穩定

O(1)

n大時候較好

四:算法的空間複雜度

空間複雜度介紹

  • 類似於時間複雜度的討論。一個算法的空間複雜度(Space Complexity)定義爲該算法鎖消耗的存儲空間,它也是問題規模n的函數;
  • 空間複雜度是對一個算法在運行過程中臨時佔用存儲空間大小的量度。有的算法需要佔用臨時工作單元數與解決問題的規模n有關。它們隨着n的增大而增大,當n較大的時候,將佔用較多的存儲單元(存儲空間)。例如:在快排(快速排序)和歸併排序算法就屬於這種情況。
  • 在做算法分析的時候,主要討論的是時間的複雜度。因爲從用戶的使用體驗上來看,更看重的是程序執行的速度的快慢。一般緩存產品(比如Redis)和技術排序算法本質就是拿空間換時間的。

下節預告:

下節我們將講講冒泡排序和選擇排序。使用的是圖解+代碼一步一步推導出來演示的。歡迎大家一起學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章