幾種排序算法的講解(二)

四、希爾排序

希爾排序其實是直接插入排序算法的一種變形,實質是分組插入排序。又稱縮小增量排序。
該方法首先要理解分組操作,其實是以間隔分組,即每間隔幾個數後就分做一組,然後進行插入排序。接着把間隔縮短一半,以此下去,直到間隔沒有,才停下操作。

如:有10個數據,開始的時候每間隔10/2 = 5 劃爲一組,即第1個數與第6個數爲一組,第2個數與第7個數爲一組。。。。。。以此類推,然後對每一組的數進行插入排序。排序好後縮短間隔,5/2 = 2,即每間隔2個數就劃爲一組,以此操作直到間隔爲0,無法縮小間隔爲止。

如,對10、49、23、1、5、50、44、8、23、2、6這10個數據進行希爾排序。
第一次分組的時候: inter = 10/2 = 5
希爾排序
第一次分組如圖,分成了5組,{A1,B1},{A2,B2},{A3,B3},{A4,B4},{A5,B5}這五個組,接着對每一組進行直接插入排序。(沒間隔幾個數,就分成幾組)

第二次分組的時候: inter = 5/2 = 2
希爾排序
現在分成了2組,同上,對每組進行直接插入排序

第三次分組的時候: inter = 2/2 = 1
這裏寫圖片描述
現在只有一組了,仍然進行插入排序

第四次無法分組了: inter = 1/2 = 0

希爾排序實現的代碼如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10000;
int a[maxn];
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=1; i<=n; i++) cin>>a[i];
        //開始不斷地縮短間隔,直到間隔爲0。第一次間隔爲n/2
        for(int inter = n/2; inter>0; inter/=2)
        {
            //每一次縮短間隔,其間隔數等於所分的組數,排序的時候從第1組到第inter組進行插入排序。
            for(int gap=1; gap<=inter; gap++)
            {
                //下面是插入排序的代碼,稍微改變了一下遞增遞減量,因爲是每次間隔inter進行排序。
                //大致的插入排序代碼並沒有改變
                for(int i=gap+inter; i<=n; i+=inter)
                {
                    int cur;
                    for(cur=i-inter; cur>=1; cur-=inter)
                    {
                        if(a[cur]<a[i])break;
                    }
                    if(cur!=i-inter)
                    {
                        for(int k=i-inter; k>cur; k-=inter)
                            swap(a[k+inter], a[k]);
                    }
                }
            }
        }
        for(int i=1; i<=n; i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
    return 0;
}

爲了讓代碼儘可能地貼近所描述的語言,上面的代碼寫得過於複雜,看得有些頭疼,我們可以把該代碼縮減爲以下代碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10000;
int a[maxn];
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=1; i<=n; i++) cin>>a[i];
        //縮短間隔,分組。
        for(int inter=n/2; inter>0; inter/=2)
        {
            //從第inter個數開始,這個與普通的直接插入排序從第二個數開始的意味相同
            for(int i=inter; i<=n; i++)
            {
                //每個數與自己組裏的數進行直接插入排序
                for(int cur=i-inter; cur>=1 && a[cur]>a[cur+inter]; cur-=inter)
                    swap(a[cur], a[cur+inter]);
            }
        }
        for(int i=1; i<=n; i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
    return 0;
}

代碼精簡,不過不易懂,再次也不多講解這一個代碼,讀者自己思考一番吧。

五、堆排序

堆排序算法在處理大數據的效率是前面四種算法不可比的。
那麼要學堆排序,首先要對樹有個瞭解,準確地說是對二叉樹要又瞭解。堆排序就是一個特殊的完全二叉樹。

我們用數組存儲一堆數據,然後要把數組當做一個二叉樹來看。二叉樹特點是,一個父節點連接兩個兒子節點。開始的時候把一堆數存入數組裏,然後把數組看成二叉樹,即存數到數組裏後就建立好了一個二叉樹,此時的樹裏面的值都是混亂的。

如何去排序一棵二叉樹?以排序一個最小堆爲例。

一棵樹的“某一個節點”父子間的值所在的位置不對的時候,即兩個兒子中有值小於父親的值時,要排序父子關係的話,也會影響後面子孫的位置。

我們首先要對這種牽連一人而禍害一窩的行爲用一種方式進行處理(即用一個函數),然後遍歷每一個節點,用這種行爲處理方式處理每一個節點。

下面說說怎麼建立這種處理方式函數。
要找一種規律,就像數學思維一樣,任意取一個值設爲變量x,繞着x這個值進行某些變化直到某條件成立爲止。
這裏就任意取一個樹的節點作爲“變量節點 ”,從這個節點開始進行排序,首先找出該節點和該節點的兩個兒子之間的最小的,讓最小的與節點的值換一換。
如果沒有換值,那麼停止,證明了該節點與他的兒子順序是正確的;如果換值了,那麼接下來取與節點交換的兒子作爲“變量節點”,繼續以上操作,直到要麼是到了最後的葉處即到底了,要麼是兒子與父親的值沒有交換爲止。可見這裏是一個遞歸操作。

好的,找到了處理一個節點關係的方式(函數),那麼我們現在要遍歷每個節點,用這個方式處理每一個節點,以達到排序一個數組的目的。
那麼,如何遍歷纔是好呢?我們找到的處理方式是用來處理有兒子的節點,是吧。那麼從只有兒子沒有孫子的節點開始處理。
這樣從底部打好基礎,節省了很多的麻煩。
根據樹的定義,有N個節點,那麼兒子是葉的節點是從第n/2個節點開始的,前面的節點都有子孫了。那就是從第n/2個節點開始遍歷到根節點。
說來說去,有點繞,結合下面的代碼有助於消化堆排序。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000;
int a[maxn];
//這個就是一個牽一髮而動全身的處理方式,一個遞歸函數
//參數即當前的節點的位置,還有樹的節點數
void softdown(int cur, int n)
{
    //flag是爲了判斷該節點的關係是否正常,如果兒子的值與父親的值沒有交換過,則flag = 1;
    //temp 爲了記錄最小值的位置
    int flag = 0, temp;
    while(cur*2<=n && flag==0)
    {
        //首先判斷左兒子與父親的值
        if(a[cur]>a[cur*2]) temp = cur*2;
        else temp = cur;
        //判斷是否有右兒子存在
        if(cur*2+1 <= n)
        {
            if(a[temp]>a[cur*2+1]) temp = cur*2+1;
        }
        //如果發現最小值的位置不在父節點處,那麼交換節點值,繼續下一個節點進行處理
        //否則遞歸停止
        if(cur != temp) swap(a[cur], a[temp]);
        else flag = 1;
        cur = temp;
    }
}
void create(int n)      //排序二叉樹
{
    for(int i=n/2; i>=1; i--)
        softdown(i, n);
}
//因爲根節點的值是最小的,所以每次輸出根節點的值
//然後把最後一位與根節點值交換,順便讓長度-1.再排序一次樹。
//這樣就成了數組裏存的值是從大到小排序,但輸出的時候是從小到大。
void Cout(int n)        //輸出函數
{
    while(n)
    {
        cout<<a[1]<<" ";
        swap(a[1], a[n]);
        n--;
        softdown(1, n);
    }
}
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=1; i<=n; i++) cin>>a[i];
        create(n);
        Cout(n);
        cout<<endl;
        //直接輸出數組裏的數進行比較一番
        for(int i=1; i<=n; i++) cout<<a[i]<<" ";
        cout<<endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章