STL之partial_sort算法源碼講解

本文首發(fā)于個(gè)人CSDN博客: https://blog.csdn.net/ggq89/article/details/88817085

假設(shè)有一個(gè)容器鸽捻,保存了 100 萬(wàn)個(gè)數(shù)值特咆,但我們只對(duì)其中最小的 100 個(gè)元素的順序感興趣』瑁可以對(duì)容器的全部?jī)?nèi)容排序掘剪,然后選擇前 100 個(gè)元素,但這樣處理會(huì)使效率低奈虾。這時(shí)候需要使用部分排序 partial_sort夺谁,高效的獲得這些數(shù)中的前100個(gè)最小數(shù)的順序。

1. partial_sort 接口說(shuō)明……

partial_sort有兩個(gè)版本肉微,一個(gè)默認(rèn)以小于(less-than操作符)作為比較規(guī)則匾鸥,出來(lái)的順序?yàn)檫f增排列。另一個(gè)可以傳入一個(gè)比較函數(shù)碉纳,即調(diào)用者指定一個(gè)比較規(guī)則勿负。

partial_sort(beg,mid,end)
partial_sort(beg,mid,end,comp)

對(duì)容器中的(mid-beg)個(gè)元素進(jìn)行排序,partial_sort 完成之后村象,從beg到mid(但不包括mid)范圍內(nèi)的元素時(shí)有序的笆环,且大于(或者指定的comp函數(shù))mid之后的元素。未排序元素之間的次序是不確定的厚者,即partial_port不是穩(wěn)定排序算法躁劣。

2. partial_sort 用法舉例

下面這段代碼演示了 partial_sort() 算法的工作方式:

size_t count {5}; // Number of elements to be sorted
std::vector<int> numbers {22, 7, 93, 45, 19, 56, 88, 12, 8, 7, 15, 10};
std::partial_sort(std::begin(numbers), std::begin(numbers) + count, std::end(numbers));

執(zhí)行partial__sort()后的效果如下圖所示:

partial_sort() 算法的操作

需要注意的是,不能保持未排序元素的原始順序库菲。在執(zhí)行 partial_sort() 后后面元素的順序是不確定的账忘,這取決于具體的實(shí)現(xiàn)。

也可以指定不同的比較函數(shù)熙宇。例如:

std::partial_sort(std::begin(numbers), std::begin(numbers) + count, std:: end (numbers) , std::greater<>());

現(xiàn)在鳖擒,number 中最大的 count 個(gè)元素會(huì)是容器開(kāi)始處的一個(gè)降序序列。以上語(yǔ)句的輸出結(jié)果為:

93 88 56 45 22 7 19 12 8 7 15 10

同樣的烫止,不能保持 numbers 中未排序元素的原始順序蒋荚。

3. partial_sort 原理概述

那么 partial_sort 的原理是什么呢?是堆排序馆蠕!

partial_sort的原理:

  1. 對(duì)原始容器內(nèi)區(qū)間為[first, middle)的元素執(zhí)行 make_heap() 操作構(gòu)造一個(gè)大頂堆期升,
  2. 然后遍歷剩余區(qū)間[middle, last)中的元素惊奇,剩余區(qū)間的每個(gè)元素均與大頂堆的堆頂元素進(jìn)行比較(大頂堆的堆頂元素為最大元素,該元素為第一個(gè)元素播赁,很容易獲得)颂郎,若堆頂元素較小,則交換堆頂元素和遍歷得到的元素值(pop_heap )容为,并重新調(diào)整該大頂堆以維持該堆為大頂堆(adjust_heap)乓序。
  3. 遍歷結(jié)束后,[first, middle)區(qū)間內(nèi)的元素便是排名在前的m個(gè)元素坎背,再對(duì)該堆做一次堆排序 sort_heap() 便可得到最后的結(jié)果替劈。

下圖為partial_sort 算法的步驟詳解:

partial_sort() 算法步驟詳解

4. partial_sort 源碼講解

下面是STL中 partial_sort 的源碼講解,考慮到篇幅沼瘫,這里只列出第一個(gè)版本的抬纸。

// 版本一
template <class RandomAccessIterator>
inline void partial_sort(RandomAccessIterator first,
    RandomAccessIterator middle,
    RandomAccessIterator last) {
        __partial_sort(first, middle, last, value_type(first));
}
 
template <class RandomAccessIterator, class T>
void __partial_sort(RandomAccessIterator first, RandomAccessIterator middle,
    RandomAccessIterator last, T*) {
        make_heap(first, middle); //將區(qū)間[first, middle)構(gòu)造為一個(gè)堆結(jié)構(gòu)
        for (RandomAccessIterator i = middle; i < last; ++i)
            if (*i < *first)    // 遍歷堆以外的元素,并將更優(yōu)的元素放入堆中
                __pop_heap(first, middle, i, T(*i), distance_type(first)); // first值放i中耿戚,i的原值融入heap并調(diào)整
        sort_heap(first, middle); // 對(duì)最終的堆進(jìn)行排序
}

先來(lái)分析 make_heap 算法湿故, 該算法將一段指定的數(shù)據(jù)排列出max-heap

template <class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) {
    __make_heap(first, last, value_type(first), distance_type(first));
}
 
template <class RandomAccessIterator, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*,
    Distance*) {
        if (last - first < 2) return; // 如果長(zhǎng)度為0或1,不必重新排列    
        Distance len = last - first;
        // 找出第一個(gè)需要重新排列的子樹(shù)頭部(即最后一個(gè)子樹(shù))膜蛔,以parent標(biāo)記坛猪。由于任何葉子節(jié)點(diǎn)都不需要處理。
        // parent 命名不佳皂股, 為 holeIndex 更好
        Distance parent = (len - 2)/2; 
 
        while (true) {
            // 重排以parent為首的子樹(shù)墅茉,以len為操作范圍
            __adjust_heap(first, parent, len, T(*(first + parent)));
            if (parent == 0) return; // 走完根節(jié)點(diǎn),結(jié)束
            parent--; // 向前排列前一個(gè)節(jié)點(diǎn)
        }
}

下面來(lái)重點(diǎn)分析 __adjust_heap 算法呜呐, 該算法調(diào)整從first開(kāi)始的len個(gè)元素就斤,洞號(hào)為holeIndex,洞值為value蘑辑, 最終獲得一個(gè) max-heap 洋机。

template <class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
    Distance len, T value) {
        Distance topIndex = holeIndex;
        Distance secondChild = 2 * holeIndex + 2; // holeIndex的右子節(jié)點(diǎn)
        while (secondChild < len) {
            // 比較holeIndex兩個(gè)子節(jié)點(diǎn)的值,用secondChild代表值較大的子節(jié)點(diǎn)
            if (*(first + secondChild) < *(first + (secondChild - 1)))
                secondChild--;   
            // 令較大子值為洞值洋魂,注意:原洞值已在函數(shù)形參value中得以保存
            *(first + holeIndex) = *(first + secondChild); 
            // 再讓洞號(hào)下移到左子節(jié)點(diǎn)處灾票,
            holeIndex = secondChild;
            // 算出新的洞節(jié)點(diǎn)的右子節(jié)點(diǎn)
            secondChild = 2 * (secondChild + 1);
        }
        // 如果沒(méi)有右子節(jié)點(diǎn)收夸,只有左子節(jié)點(diǎn)
        if (secondChild == len) { 
            // 令左子節(jié)點(diǎn)為洞值,然后將洞號(hào)下移到左子節(jié)點(diǎn)
            *(first + holeIndex) = *(first + (secondChild - 1));
            holeIndex = secondChild - 1;
        }
        // 將原洞值push到新的洞號(hào)中儿惫。
        // 以下語(yǔ)句的效果類(lèi)似于: *(first + holeIndex) = value; 
        __push_heap(first, holeIndex, topIndex, value);
}

進(jìn)一步講解__adjust_heap 算法 調(diào)用的 __push_heap 算法斯棒,該算實(shí)現(xiàn)將新元素value push到max-heap中topIndex到holeIndex的合適位置中府框,其中max-heap的起始位置是first蝴猪。

template <class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
    Distance topIndex, T value) {
        Distance parent = (holeIndex - 1) / 2;  // 找到父節(jié)點(diǎn)
        // 當(dāng)尚未達(dá)到頂端投蝉, 且父節(jié)點(diǎn)的值小于新值(不符合max-heap的次序特性)
        while (holeIndex > topIndex && *(first + parent) < value) {
            *(first + holeIndex) = *(first + parent); // 移動(dòng)父值到洞號(hào)處
            holeIndex = parent; // 調(diào)整洞號(hào)為父節(jié)點(diǎn)
            parent = (holeIndex - 1) / 2; // 新洞的父節(jié)點(diǎn)
        }  // 循環(huán)到頂端,或者滿(mǎn)足max-heap的順序?yàn)橹?        *(first + holeIndex) = value; // 將新值放入循環(huán)完得到的洞號(hào)心剥,完成push操作
}

再回頭看 __partial_sort 算法启搂,當(dāng)*i < *first時(shí)硼控,代碼中沒(méi)有互換i所指元素和first所指元素。實(shí)際上這一步操作是在 __pop_heap 函數(shù) 完成的胳赌,在該函數(shù)內(nèi),先將要first元素放入指定的result匙隔,然后用新值value去調(diào)整max-heap疑苫。

template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Distance*) {
  *result = *first; // 將heap頂端元素值放入 result中
  // 重新調(diào)整heap,洞號(hào)為0纷责,欲調(diào)整的新值為value
  __adjust_heap(first, Distance(0), Distance(last - first), value);
}

最后來(lái)看看 sort_heap 算法捍掺,該算法將[first, last)中的元素由堆序變?yōu)樵鲂蚺帕小C看螐棾龆训淖畲笾挡⒎湃胛膊吭偕牛缓罂s小堆的范圍挺勿,循環(huán)執(zhí)行彈出操作直至堆只剩下最后一個(gè)元素。


template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
    while (last - first > 1)  // 直到只剩一個(gè)元素為止
        pop_heap(first, last--); // 每執(zhí)行一次喂柒,范圍縮小一格
}

template <class RandomAccessIterator>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {
    __pop_heap_aux(first, last, value_type(first));
}
 
template <class RandomAccessIterator, class T>
inline void __pop_heap_aux(RandomAccessIterator first,
    RandomAccessIterator last, T*) {
        // 將first元素值(即最大值)放入last-1不瓶,然后重調(diào)[first, last-1)為max-heap
        __pop_heap(first, last-1, last-1, T(*(last-1)), distance_type(first));
}

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市灾杰,隨后出現(xiàn)的幾起案子蚊丐,更是在濱河造成了極大的恐慌,老刑警劉巖艳吠,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麦备,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡昭娩,警方通過(guò)查閱死者的電腦和手機(jī)凛篙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)栏渺,“玉大人呛梆,你說(shuō)我怎么就攤上這事÷踵冢” “怎么了削彬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)秀仲。 經(jīng)常有香客問(wèn)我融痛,道長(zhǎng),這世上最難降的妖魔是什么神僵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任雁刷,我火速辦了婚禮,結(jié)果婚禮上保礼,老公的妹妹穿的比我還像新娘沛励。我一直安慰自己责语,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布目派。 她就那樣靜靜地躺著坤候,像睡著了一般。 火紅的嫁衣襯著肌膚如雪企蹭。 梳的紋絲不亂的頭發(fā)上白筹,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音谅摄,去河邊找鬼徒河。 笑死,一個(gè)胖子當(dāng)著我的面吹牛送漠,可吹牛的內(nèi)容都是我干的顽照。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼闽寡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼代兵!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起下隧,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奢人,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后淆院,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體何乎,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年土辩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了支救。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拷淘,死狀恐怖各墨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情启涯,我是刑警寧澤贬堵,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站结洼,受9級(jí)特大地震影響黎做,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜松忍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一蒸殿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦宏所、人聲如沸酥艳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)充石。三九已至,卻和暖如春霞玄,著一層夾襖步出監(jiān)牢的瞬間赫冬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工溃列, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膛薛。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓听隐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親哄啄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雅任,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355