排序算法(C++代碼版)

1.插入排序

逐個(gè)處理待排序的記錄比伏,每個(gè)記錄與前面已排序已排序的子序列進(jìn)行比較,將它插入子序列中正確位置

代碼

template<class Elem>
void inssort(Elem A[],int n)
{
    for(int i = 1;i < n;i++)
        for(int j = i;j >= 1 && A[j] < A[j-1];j--)
            swap(A,j,j-1);
}

性能

  • 最佳:升序。時(shí)間復(fù)雜度為O(n)
  • 最差:降序蔼啦。時(shí)間復(fù)雜度為O(n^2)
  • 平均:對(duì)于每個(gè)元素辣吃,前面有一半元素比它大动遭。時(shí)間復(fù)雜度為O(n^2)

如果待排序數(shù)據(jù)已經(jīng)“基本有序”,使用插入排序可以獲得接近O(n)的性能

優(yōu)化

template<class Elem>
void inssort(Elem A[],int n)
{
    for(int i = 1;i < n;i++){
        int j = i;
        int tp = A[j];
        for(;j >= 1 && tp < A[j-1];j--)
            A[j] = A[j - 1];
        A[j] = tp;
    }
}


2.冒泡排序

從數(shù)組的底部比較到頂部神得,比較相鄰元素厘惦。如果下面的元素更小則交換,否則循头,上面的元素繼續(xù)往上比較绵估。這個(gè)過程每次使最小元素像個(gè)“氣泡”似地被推到數(shù)組的頂部

代碼

template<class Elem>
void bubsort(Elem A[],int n)
{
    for(int i = 0;i < n - 1;i++)
        for(int j = n - 1;j > i;j--)
            if(A[j] < A[j-1])
                swap(A,j,j-1);
}

性能

冒泡排序是一種相對(duì)較慢的排序炎疆,沒有較好的最佳情況執(zhí)行時(shí)間。通常情況下時(shí)間復(fù)雜度都是O(n^2)

優(yōu)化

增加一個(gè)變量flag国裳,用于記錄一次循環(huán)是否發(fā)生了交換形入,如果沒發(fā)生交換說明已經(jīng)有序,可以提前結(jié)束


3.選擇排序

第i次“選擇”數(shù)組中第i小的記錄缝左,并將該記錄放到數(shù)組的第i個(gè)位置亿遂。換句話說,每次從未排序的序列中找到最小元素渺杉,放到未排序數(shù)組的最前面

代碼

template<class Elem>
void selsort(Elem A[],int n)
{
    for(int i = 0;i < n - 1;i++){
        int lowindex = i;
        for(int j = i + 1;j < n;j++)
            if(A[j] < A[lowindex])
                lowindex = j;
        swap(A,i,lowindex);//n次交換
    }
}

性能

不管數(shù)組是否有序蛇数,在從未排序的序列中查找最小元素時(shí),都需要遍歷完最小序列是越,所以時(shí)間復(fù)雜度為O(n^2)

優(yōu)化

每次內(nèi)層除了找出一個(gè)最小值耳舅,同時(shí)找出一個(gè)最大值(初始為數(shù)組結(jié)尾)。將最小值與每次處理的初始位置的元素交換倚评,將最大值與每次處理的末尾位置的元素交換浦徊。這樣一次循環(huán)可以將數(shù)組規(guī)模減小2,相比于原有的方案(減小1)會(huì)更快


4.shell排序

shell排序在不相鄰的元素之間比較和交換天梧。利用了插入排序的最佳時(shí)間代價(jià)特性盔性,它試圖將待排序序列變成基本有序的,然后再用插入排序來完成排序工作

在執(zhí)行每一次循環(huán)時(shí)呢岗,Shell排序把序列分為互不相連的子序列冕香,并使各個(gè)子序列中的元素在整個(gè)數(shù)組中的間距相同,每個(gè)子序列用插入排序進(jìn)行排序后豫。每次循環(huán)增量是前一次循環(huán)的1/2悉尾,子序列元素是前一次循環(huán)的2倍

最后一輪將是一次“正常的”插入排序(即對(duì)包含所有元素的序列進(jìn)行插入排序)

代碼

const int INCRGAP = 3;

template<class Elem>
void shellsort(Elem A[],int n)
{
    for(int incr = n / INCRGAP;incr > 0;incr /= INCRGAP){//遍歷所有增量大小
        for(int i = 0;i < incr;i++){
            /*對(duì)子序列進(jìn)行插入排序,當(dāng)增量為1時(shí)硬贯,對(duì)所有元素進(jìn)行最后一次插入排序*/
            for(int j = i + incr;j < n;j += incr){
                for(int k = j; k > i && A[k] < A[k - incr];k -= incr){
                    swap(A,k,k - incr);
                }
            }
        }
    }
}

性能

選擇適當(dāng)?shù)脑隽啃蛄锌墒筍hell排序比其他排序法更有效焕襟,一般來說,增量每次除以2時(shí)并沒有多大效果饭豹,而“增量每次除以3”時(shí)效果更好

當(dāng)選擇“增量每次除以3”遞減時(shí)鸵赖,Shell排序的平均運(yùn)行時(shí)間是O(n^(1.5))


5.快速排序

首先選擇一個(gè)軸值,小于軸值的元素被放在數(shù)組中軸值左側(cè)拄衰,大于軸值的元素被放在數(shù)組中軸值右側(cè)它褪,這稱為數(shù)組的一個(gè)分割(partition)∏滔ぃ快速排序再對(duì)軸值左右子數(shù)組分別進(jìn)行類似的操作

選擇軸值有多種方法茫打。最簡(jiǎn)單的方法是使用首或尾元素。但是,如果輸入的數(shù)組是正序或者逆序時(shí)老赤,會(huì)將所有元素分到軸值的一邊轮洋。較好的方法是隨機(jī)選取軸值

代碼

template <class Elem>
int partition(Elem A[],int i,int j)
{
    //這里選擇尾元素作為軸值,軸值的選擇可以設(shè)計(jì)為一個(gè)函數(shù)
    //如果選擇的軸值不是尾元素,還需將軸值與尾元素交換
    int pivot = A[j];
    int l = i - 1;
    for(int r = i;r < j;r++)
        if(A[r] <= pivot)
            swap(A,++l,r);
    swap(A,++l,j);//將軸值從末尾與++l位置的元素交換
    return l;
}

template <class Elem>
void qsort(Elem A[],int i,int j)
{
    if(j <= i)  return;
    int p = partition<Elem>(A,i,j);
    qsort<Elem>(A,i,p - 1);
    qsort<Elem>(A,p + 1,j);
}

性能

  • 最佳情況:O(nlogn)
  • 平均情況:O(nlogn)
  • 最差情況:每次處理將所有元素劃分到軸值一側(cè)抬旺,O(n^2)

快速排序平均情況下運(yùn)行時(shí)間與其最佳情況下的運(yùn)行時(shí)間很接近弊予,而不是接近其最壞情況下的運(yùn)行時(shí)間。快速排序是所有內(nèi)部排序算法中平均性能最優(yōu)的排序算法

優(yōu)化

  1. 最明顯的改進(jìn)之處是軸值的選取开财,如果軸值選取合適汉柒,每次處理可以將元素較均勻的劃分到軸值兩側(cè):

    三者取中法:三個(gè)隨機(jī)值的中間一個(gè)。為了減少隨機(jī)數(shù)生成器產(chǎn)生的延遲责鳍,可以選取首中尾三個(gè)元素作為隨機(jī)值

  2. 當(dāng)n很小時(shí)碾褂,快速排序會(huì)很慢。因此當(dāng)子數(shù)組小于某個(gè)長(zhǎng)度(經(jīng)驗(yàn)值:9)時(shí)历葛,什么也不要做正塌。此時(shí)數(shù)組已經(jīng)基本有序,最后調(diào)用一次插入排序完成最后處理


6.歸并排序

將一個(gè)序列分成兩個(gè)長(zhǎng)度相等的子序列恤溶,為每一個(gè)子序列排序传货,然后再將它們合并成一個(gè)序列。合并兩個(gè)子序列的過程稱為歸并

代碼

template<class Elem>
void mergesortcore(Elem A[],Elem temp[],int i,int j)
{
    if(i == j)  return;
    int mid = (i + j)/2;

    mergesortcore(A,temp,i,mid);
    mergesortcore(A,temp,mid + 1,j);

    /*歸并*/
    int i1 = i,i2 = mid + 1,curr = i;
    while(i1 <= mid && i2 <= j){
        if(A[i1] < A[i2])
            temp[curr++] = A[i1++];
        else
            temp[curr++] = A[i2++];
    }
    while(i1 <= mid)
        temp[curr++] = A[i1++];
    while(i2 <= j)
        temp[curr++] = A[i2++];
    for(curr = i;curr <= j;curr++)
        A[curr] = temp[curr];
}

template<class Elem>
void mergesort(Elem A[],int sz)
{
    Elem *temp = new Elem[sz]();
    int i = 0,j = sz - 1;
    mergesortcore(A,temp,i,j);
    delete [] temp;
}

性能

logn層遞歸中宏娄,每一層都需要O(n)的時(shí)間代價(jià),因此總的時(shí)間復(fù)雜度是O(nlogn)逮壁,該時(shí)間復(fù)雜度不依賴于待排序數(shù)組中數(shù)值的相對(duì)順序孵坚。因此,是最佳窥淆,平均和最差情況下的運(yùn)行時(shí)間

由于需要一個(gè)和帶排序數(shù)組大小相同的輔助數(shù)組卖宠,所以空間代價(jià)為O(n)

優(yōu)化

原地歸并排序不需要輔助數(shù)組即可歸并

void reverse(int *arr,int n)
{
    int i = 0,j = n - 1;
    while(i < j)
        swap(arr[i++],arr[j--]);
}

void exchange(int *arr,int sz,int left)
{
    reverse(arr,left);//翻轉(zhuǎn)左邊部分
    reverse(arr + left,sz - left);//翻轉(zhuǎn)右邊部分
    reverse(arr,sz);//翻轉(zhuǎn)所有
}

void merge(int *arr,int begin,int mid,int end)
{
    int i = begin,j = mid,k = end;
    while(i < j && j <= k){
        int right = 0;
        while(i < j && arr[i] <= arr[j])
            ++i;
        while(j <= k && arr[j] <= arr[i]){
            ++j;
            ++right;
        }
        exchange(arr + i,j - i,j - i - right);
        i += right;
    }
}


7.堆排序

堆排序首先根據(jù)數(shù)組構(gòu)建最大堆,然后每次“刪除”堆頂元素(將堆頂元素移至末尾)忧饭。最后得到的序列就是從小到大排序的序列


al-sort-2.png

代碼

這里直接使用C++ STL中堆的構(gòu)建與刪除函數(shù)

template <class Elem>
void heapsort(Elem A[],int n)
{
    Elem mval;
    int end = n;
    make_heap(A,A + end);
    for(int i = 0;i < n;i++){
        pop_heap(A,A + end);
        end--;
    }
}

如果不能使用現(xiàn)成的庫(kù)函數(shù):

/********************************************
 * 向堆中插入元素
 *  hole:新元素所在的位置
 ********************************************/
template <class value>
void _push_heap(vector<value> &arr,int hole){
    value v = arr[hole];//取出新元素扛伍,從而產(chǎn)生一個(gè)空洞
    int parent = (hole - 1) / 2;
    //建最大堆,如果建最小堆換成 arr[parent] > value
    while(hole > 0 && arr[parent] < v){
        arr[hole] = arr[parent];
        hole = parent;
        parent = (hole - 1) / 2;
    }
    arr[hole] = v;
}

/********************************************
 * 刪除堆頂元素
 ********************************************/
template <class value>
void _pop_heap(vector<value> &arr,int sz)
{
    value v = arr[sz - 1];
    arr[sz - 1] = arr[0];
    --sz;
    int hole = 0;
    int child = 2 * (hole + 1); //右孩子
    while(child < sz){
        if(arr[child] < arr[child - 1])
            --child;
        arr[hole] = arr[child];
        hole = child;
        child = 2 * (hole + 1);
    }
    if(child == sz){
        arr[hole] = arr[child - 1];
        hole = child - 1;
    }
    arr[hole] = v;
    _push_heap(arr,hole);
}

/********************************************
 * 建堆
 *  sz:刪除堆頂元素后的大小
 *  v: 被堆頂元素占據(jù)的位置原來的元素的值
 ********************************************/
template <class value>
void _make_heap(vector<value> &arr)
{
    int sz = arr.size();
    int parent = (sz - 2) / 2;
    while(parent >= 0){
        int hole = parent;
        int child = 2 * (hole + 1); //右孩子
        value v = arr[hole];
        while(child < sz){
            if(arr[child] < arr[child - 1])
                --child;
            arr[hole] = arr[child];
            hole = child;
            child = 2 * (hole + 1);
        }
        if(child == sz){
            arr[hole] = arr[child - 1];
            hole = child - 1;
        }
        arr[hole] = v;
        _push_heap(arr,hole);
        --parent;
    }
}

template <class value>
void heap_sort(vector<value> &arr)
{
    _make_heap(arr);
    for(int sz = arr.size();sz > 1;sz--)
        _pop_heap(arr,sz);
}

性能

根據(jù)已有數(shù)組構(gòu)建堆需要O(n)的時(shí)間復(fù)雜度词裤,每次刪除堆頂元素需要O(logn)的時(shí)間復(fù)雜度刺洒,所以總的時(shí)間開銷為,O(n+nlogn)吼砂,平均時(shí)間復(fù)雜度為O(nlogn)

注意根據(jù)已有元素建堆是很快的逆航,如果希望找到數(shù)組中第k大的元素,可以用O(n+klogn)的時(shí)間渔肩,如果k很小因俐,時(shí)間開銷接近O(n)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抹剩,更是在濱河造成了極大的恐慌撑帖,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澳眷,死亡現(xiàn)場(chǎng)離奇詭異胡嘿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)境蔼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門灶平,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人箍土,你說我怎么就攤上這事逢享。” “怎么了吴藻?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵瞒爬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我沟堡,道長(zhǎng)侧但,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任航罗,我火速辦了婚禮禀横,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粥血。我一直安慰自己柏锄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布复亏。 她就那樣靜靜地躺著趾娃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缔御。 梳的紋絲不亂的頭發(fā)上抬闷,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音耕突,去河邊找鬼笤成。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眷茁,可吹牛的內(nèi)容都是我干的疹启。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蔼卡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喊崖!你這毒婦竟也來了挣磨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤荤懂,失蹤者是張志新(化名)和其女友劉穎茁裙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體节仿,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晤锥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了廊宪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾瘾。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖箭启,靈堂內(nèi)的尸體忽然破棺而出壕翩,到底是詐尸還是另有隱情,我是刑警寧澤傅寡,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布放妈,位于F島的核電站,受9級(jí)特大地震影響荐操,放射性物質(zhì)發(fā)生泄漏芜抒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一托启、第九天 我趴在偏房一處隱蔽的房頂上張望宅倒。 院中可真熱鬧,春花似錦屯耸、人聲如沸唉堪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至链方,卻和暖如春持痰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祟蚀。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工工窍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人前酿。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓患雏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親罢维。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淹仑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361