天天看點

C++11時代的标準庫快餐教程(3) - 排序排序

講完容器之後,我們迅速進入到算法部分。

首先看一下,我們這講在整個算法大圖的中位置:

C++11時代的标準庫快餐教程(3) - 排序排序

在進入排序相關之前,我們把雖然與排序無關,但是也有關聯的計數和最大值最小值部分先看一下。算是對算法部分作個預熱,将來會廣泛出場的lambda表達式也先借機會亮亮相。

計數的目的,是數一數,在容器裡,符合某一條件的元素有多少個。

算法1: std::count,數一數跟這個值相等的對象有多少個。

我們看一個例子,數數vector中有幾個1:

算法2: std::count_if,count更新版,可以提供一個條件判斷的函數來進行判斷。

我們看一個例子,判斷是奇偶還是偶數:

count_if的第三個參數,是一個可調用對象,它可以是函數,仿函數(函數對象)和lambda表達式。

lambda表達式是一種匿名函數,特别适合用于stl的算法中。這是c++11帶給算法的禮物。

lambda表達式的形式為:

如果傳回值類型可以像auto一樣被推斷出來,就不用寫了。變量捕獲用不到可以為空,參數沒有也可以為空。隻有函數體是必要的。

我們看看上例中的這個lambda表達式:

不需要捕獲變量

參數是int elem

傳回值類型因為隻可能是bool,就讓編譯器自己推斷去

函數體裡就跟普通函數一樣,就不多說了

關于function, functor和lambda表達式,我們後面會詳細再講,目前快餐教程,大家先學會能看懂,能用。

算法3: std::max_element算法求最大值

算法4: std::min_element算法求最小值

例,求1〜100中的最大值和最小值:

在《資料結構》課中,講完各種資料結構了之後,重頭戲就變成了排序和查找。

我們上一講主要是線性表、連結清單和二叉排序樹。這一講就重點說說排序和查找。

我們先來一張排序相關的總圖,然後再去看每一部分的細圖:

C++11時代的标準庫快餐教程(3) - 排序排序

從上圖中可以看到,排序相關的主要由三部分構成:

排序算法:真正做排序的算法

檢查算法:确定是否是排序的,如果不是,是從哪裡開始無序的

在已排序區間上的操作:排好序了,有什麼用處,這些算法是消費排序帶來的好處的。最重要的就是二分法查找了。

我們再聚一下焦,看看排序算法都包括哪些内容:

C++11時代的标準庫快餐教程(3) - 排序排序

在stl的排序算法中,主要有三種算法會被用到:快速排序,歸并排序和堆排序。

算法

sort

partial_sort

stable_sort

預設算法

快速排序

堆排序

歸并排序

優點

很好的平均效率

在任何情況下都保持n*log(n)的複雜度

保持相等元素的相對順序

不足

最差情況下效率較差

實際情況中比快速排序慢2~5倍

記憶體不足時複雜度提高log(n)倍

額外好處

可以隻做前n個排序就停止

快速排序在不是最差情況下的平均速度最快,是以它是預設的算法:

從小到大是預設的行為:

從大到小可以通過第三個參數,傳入一個比較函數來實作:

有一點需要注意,因為是要改變内容的,是以疊代器不能用常量的cbegin和cend.

stable_sort的參數與sort完全一緻。可以無縫替換使用,視記憶體和需求決定。

例:

部分排序除了指定起點和終點之外,還需要一個參數指定排序的終點。

比如下面的例子是:隻排出前三大的,後面就不管了。

排序後是這樣的:

堆有兩種用法,一種是随時更新堆,就像set和map一樣。後面我們會介紹priority_queue容器,就是這方面的專用容器;另一種就是将線性結構一次性建個堆,不一定随時維護。這樣就隻有一次性的成本。

比如把上例中的vector來建個堆:

堆中的組織結構是這樣的:

100是樹根,左子樹是92,右子樹是99.

然後92的左子樹是76,右子樹是91. 99的左子樹是98, 右子樹是60. 以此類推,我們畫一下前4層的圖:

C++11時代的标準庫快餐教程(3) - 排序排序

如果這麼看起來實在太費勁了,我們可以将堆重新變成排序好的結果:

列印出來就又變成有序的了。不過,堆的結構也被破壞了,還需要重建立堆。

排好序了,最常見的應用之一就是二分法查找。

我們看一個例子:

輸出當然是“found 97!”了。

因為二分查找不修改疊代器的值,是以又可以使用隻讀的疊代器cbegin和cend了。

比如,在上面排序的基礎上,我們想把比4小的排在4前面,比4大的排在後面:

排序後的結果如下:

從上面的結果可以看到,排序的結果中,并不是完全有序的。在滿足要求的情況下,并沒有對4以後的數做更進一步的有序化。這樣會帶來性能的提升。

前面的nth_element是根據某個值分成兩部分,而partition算法可以有更多的玩法。

比如下面,我們按奇偶性,把偶數排到前面:

重新排列之後變成這樣:

正如排序有穩定排序一樣,std::partition把順序排亂了,我們不喜歡。這時有内部排序穩定的std::stable_partition算法出馬:

排序結果如下:

這節我們學習了排序算法的主要架構:快速排序sort,穩定的歸并排序stable_sort,可以部分排序的堆排序算法partial_sort.

我們可以顯式建堆make_heap,也可以将堆轉化成排序sort_heap。

二分查找binary_search用在有序結構上,速度比線性查找要快得多。

nth_element可以按某個值劃分成兩部分,partition可以應用更複雜的條件,但是不穩定。保持内部排序穩定需要用stable_partition.

count和count_if用來計數。

max_element和min_element用于不想做排序隻想擷取最大最小值。

繼續閱讀