選擇排序

1、直接選擇排序(Straight Select Sorting)

(1)描述

從無序區(qū)選出一個(gè)最小的數(shù),與第1個(gè)數(shù)交換;
再?gòu)氖O聼o序數(shù)據(jù)中找出最小的數(shù),與第2個(gè)數(shù)交換,總共選擇n-1次。

(2)實(shí)現(xiàn)

兩層循環(huán),外層循環(huán)控制當(dāng)前輪最小值應(yīng)該存放的數(shù)組下標(biāo)i,
內(nèi)層循環(huán)從i到最后選出最小值的下標(biāo)min。
如果imin不是同一個(gè)值,則交換。

void straight_select(int data[], int length)
{
    int i, k;
    int min;
    for (i = 0; i < length-1; ++i)
    {
        min = i;
        for (k = i+1; k < length; ++k)
            if (data[k] < data[min])
                min = k;
        if (i != min)
            SWAP_INT(data[i], data[min])
    }
}

代碼鏈接
鏈表版代碼鏈接

(3) 時(shí)間復(fù)雜度

因?yàn)槊看沃荒苓x出1個(gè)元素,每次選擇需要比較無序區(qū)的所有元素,所以
時(shí)間復(fù)雜度固定為 O(n2) 。
當(dāng)然也可以優(yōu)化,比如每次選出2個(gè)元素,可以是1個(gè)max1個(gè)min,也可以是mintop2,但也只是把外層循環(huán)由n-1變成了n/2,最終只是無關(guān)緊要的項(xiàng)有變化,復(fù)雜度仍然是 O(n2) 。

(4)空間復(fù)雜度 O(1)
(5)穩(wěn)定性:不穩(wěn)定

比如 2, 2, 1,第1輪選擇把第1個(gè)2交換到了最后。


(2)堆排序(Heapsort)

(1)描述

這里所說的堆并不是內(nèi)存的堆,而是數(shù)據(jù)結(jié)構(gòu)的二叉堆。
二叉堆是一棵完全二叉樹,分為兩種:最大堆和最小堆。
最大堆的父結(jié)點(diǎn)的值總是大于或等于它的子節(jié)點(diǎn),
最小堆的父結(jié)點(diǎn)的值總是小于或等于它的子節(jié)點(diǎn),
左右子節(jié)點(diǎn)之間并無固定的大小關(guān)系。

滿二叉樹的第1層有1個(gè)節(jié)點(diǎn),第2層有2個(gè),第3層有4個(gè),
第 h 層有 2h-1 個(gè)節(jié)點(diǎn),是一個(gè)公比為 2 的等比數(shù)列。
根據(jù)等比數(shù)列求和公式: s_{n} = a1 * \frac{1-q^n}{1-q} (q \neq 1)

可以求出 S_h =2^h -1和 S_{h-1} = 2^{h-1} -1,堆的前h層節(jié)點(diǎn)數(shù)>= S_{h-1} +1,<=S_h 。

所以如果已知堆的總節(jié)點(diǎn)數(shù) n,則高度 h 的取值范圍是 [ log_{2}{n+1}, log_2{2n} ]

如果從堆頂往下只遍歷某一個(gè)子樹就可以選擇出最值,就可以把直接選擇排序的選擇最值部分的時(shí)間復(fù)雜度從 O(n) 降到了 O(logn) 。

2、實(shí)現(xiàn)

1.構(gòu)建一個(gè)二叉堆。
2.交換堆頂(第1個(gè)數(shù)據(jù))與最后一個(gè)數(shù)據(jù)。
3.利用堆的便利,輕松將前n-1個(gè)數(shù)據(jù)重新構(gòu)建堆。重復(fù)2和3步驟,一共選擇n-1次。

void build_heap(int data[], int father, int length)
{
    int child;
    for(child = 2 * father + 1; child < length; child = 2 * father + 1)
    {
        if (child+1 < length && data[child+1] > data[child])
            ++child;
        if (data[child] > data[father])
        {
            SWAP_INT(data[child], data[father])
            father = child;
            continue;
        }
        break;
    }
}

void heap_sort(int data[], int length)
{
    for(int i = length/2 -1; i >=0; --i)
    {
        build_heap(data, i, length);
    }
    for(int i = 1; i < length; ++i)
    {
        SWAP_INT(data[0], data[length-i])
        build_heap(data, 0, length-i);
    }
}

代碼鏈接

用數(shù)組實(shí)現(xiàn)是很方便的,因?yàn)閿?shù)組可以隨機(jī)訪問,根據(jù)父節(jié)點(diǎn),訪問子節(jié)點(diǎn)的復(fù)雜度是O(1)。
如果用鏈表實(shí)現(xiàn),則需要把每個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)和左右子節(jié)點(diǎn)記錄下來,需要自定義結(jié)構(gòu)體,如果用STL中的list<int>這樣的鏈表,父子節(jié)點(diǎn)訪問對(duì)方的復(fù)雜度是O(n),這樣一來整個(gè)排序的時(shí)間復(fù)雜度上升了,與直接選擇排序相比也喪失了優(yōu)勢(shì)。
STL的algorithm有提供堆排序的算法,只需要提供一個(gè)比較元素大小的函數(shù)即可。代碼鏈接

(3) 時(shí)間復(fù)雜度

代碼大致分為2個(gè)部分:1是初始構(gòu)建堆,2是選出最值+構(gòu)建堆;

1)初始構(gòu)建堆
設(shè)節(jié)點(diǎn)總數(shù)為 n , 設(shè)堆的高度為 h ,
設(shè)最大的非葉子節(jié)點(diǎn)所在的層為第k層,則k = h - 1

初始構(gòu)建堆時(shí),
h 層,因?yàn)槿侨~子節(jié)點(diǎn),不需要構(gòu)建堆,即構(gòu)建0次。
h-1 層,構(gòu)建1次堆。
所以第 i 層需要構(gòu)建 h-i 次堆。
且第i層最多有 2(i-1) 個(gè)節(jié)點(diǎn)。

所以第 i 層一共需要構(gòu)建堆的次數(shù)為 a_i = 2^{i-1} (h-i)

i 層一共是 S_i = 2^{i-1}(h-i) + 2^{i-2}(h-(i-1)) + 2^{i-3}(h-(i-2)) + ... + 2^{1}(h-2) + 2^{0}(h-1)

初始構(gòu)建堆的過程是前 k 層,一共最多需要構(gòu)建堆多少次,接下來就是化簡(jiǎn) Si ,再把 i = k 代入計(jì)算即可。

等式兩邊同時(shí)乘以 2 得:
2S_i = 2^{i}(h-i) + 2^{i-1}(h-(i-1)) + 2^{i-2}(h-(i-2)) + ... + 2^{1}(h-1)

兩式相減(冪相同的項(xiàng)相減)得:
S_i = 2^{i}(h-i) + 2^{i-1} + ... + 2^{1} - 2^{0}(h-1)

除了首尾的兩個(gè)項(xiàng),中間是等比數(shù)列可以用等比數(shù)列求和公式計(jì)算:
S_i = 2^{i}(h-i) + (2^{i} -2) - (h -1)

所以 S_k = 2^{k}(h-k) + (2^{k} -2) - (h -1)
因?yàn)?k = h - 1 ,所以:
S_k = 2^{h} - h - 1

根據(jù)上面算過的 h 的取值范圍:
取 h = log_{2}{(n+1)} , 則 S_k = n + 1 - log_{2}{(n+1)} - 1
取 h = log_{2}{(2n)} , 則 S_k = 2n - log_{2}{(2n)} - 1

去掉低階項(xiàng),初始構(gòu)建堆的時(shí)間復(fù)雜度是 O(n) 。

2)從堆頂往下構(gòu)建堆的時(shí)間復(fù)雜度是O(logn),一共選擇(n-1)次,時(shí)間復(fù)雜度是O(nlogn) 。

兩項(xiàng)相加,初始構(gòu)建堆的時(shí)間復(fù)雜度是低階項(xiàng)可以忽略不計(jì),所以:
時(shí)間復(fù)雜度 O(nlogn) 。

(4)空間復(fù)雜度 O(1)
(5)穩(wěn)定性:不穩(wěn)定
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容