排序算法(選擇、冒泡、插入、快速、希爾、歸並、堆排序)


一、選擇排序

算法原理

  • 比較未排序區域的元素,每次選出最大或最小的元素放到排序區域。
  • 一趟比較完成之后,再從剩下未排序的元素開始比較。
  • 反復執行以上步驟,只到排序完成。

 

時間復雜度

圖示

 

 

 

代碼:

void quick_pow(int n,int a[])
{
    for(int i=0;i<n;i++)
    {
        int pos=i;
        for(int j=i+1;j<n;j++)
        {
            if(a[j]<a[pos])
                pos=j;
        }
        int temp=a[i];
        a[i]=a[pos];
        a[pos]=temp;
    }
}

 

 

二、冒泡排序

算法原理

  1. 比較 相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最后一對。在這一點,最后的元素應該會是最大的數。
  3. 針對所有的元素重復以上的步驟,除了最后一個。
  4. 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。

這個算法的名字由來是因為越大的元素會經由交換慢慢“浮”到數列的頂端(升序或降序排列),就如同碳酸飲料中二氧化碳的氣泡最終會上浮到頂端一樣,故名“冒泡排序”。

 

時間復雜度

圖示

 

代碼

void quick_pow(int n,int a[])
{
    for(int i=0;i<n-1;i++)//n個元素進行n-1次排序
    {
        for(int j=0;j<n-i-1;j++)//每趟排序比較的次數
        {
            if(a[j]>a[j+1])
            {
                int temp=a[j];
                a[j]=a[j+1];
                a[j+1]=temp;
            }
        }
    }
}

 

三、插入排序

算法原理

插入排序的基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據,算法適用於少量數據的排序

 

時間復雜度

圖示

代碼

void quick_pow(int n,int a[])
{
    for(int i=1;i<n;i++)//從第二個元素開始插入,[0,i-1]都是有序數據
    {
        int temp=a[i];//要插入的元素
        int j;
        for(j=i-1;j>=0&&temp<a[j];j--)
            a[j+1]=a[j];//往右偏移,給插入的地方空出一個位置
        a[j+1]=temp;
    }
}

 

 

四、快速排序

算法原理

快速排序的本質就是把基准數大的都放在基准數的右邊,把比基准數小的放在基准數的左邊,這樣就找到了基准在數組中的正確位置.
以后采用遞歸的方式分別對前半部分和后半部分排序,當前半部分和后半部分均有序時該數組就自然有序了。

 

時間復雜度

 時間復雜度最壞的情況出現再已經是有序的情況下(遞增、遞減、相等)

圖示

 

代碼

int find(int le,int ri,int a[])//找基准元素位置
{
    int base=a[le];//基准元素
    while(le<ri)
    {
        while(le<ri&&a[ri]>=base)//從序列右端開始處理,大於基准的不變
            ri--;
        a[le]=a[ri];//小於基准的交換到左邊

        while(le<ri&&a[le]<=base)//處理左端,小於基准的不變
            le++;
        a[ri]=a[le];//大於基准的交換到右邊
    }
    //當左邊的元素都小於base,右邊的元素都大於base時,此時base就是基准元素,le或ri就是基准元素的位置
    a[le]=base;
    return le;
}
void quick_pow(int le,int ri,int a[])
{
    if(le>=ri)
        return;
    int pos=find(le,ri,a);
    quick_pow(le,pos-1,a);
    quick_pow(pos+1,ri,a);
}

①先從隊尾開始向前掃描且當low < high時,如果a[high] > tmp,則high–,但如果a[high] < tmp,則將high的值賦值給low,即arr[low] = a[high],同時要轉換數組掃描的方式,即需要從隊首開始向隊尾進行掃描了
②同理,當從隊首開始向隊尾進行掃描時,如果a[low] < tmp,則low++,但如果a[low] > tmp了,則就需要將low位置的值賦值給high位置,即arr[low] = arr[high],同時將數組掃描方式換為由隊尾向隊首進行掃描.
③不斷重復①和②,知道low>=high時(其實是low=high),low或high的位置就是該基准數據在數組中的正確索引位置.

五、希爾排序(優化后的插入排序)

算法原理

希爾排序是記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。

 

時間復雜度

 

圖示

 

 

 

 

 

代碼

void quick_pow(int n,int a[])
{
    for(int k=n/2;k>0;k=k/2)//k是分組距離
    {
        for(int i=k;i<n;i++)//可以分成n-k組,輪流對每個分組進行插入排序
        {
            int temp=a[i];//
            int j;
            for(j=i-k;j>=0&&a[j]>temp;j=j-k)
                a[j+k]=a[j];//前面的元素大於右邊,交換位置,
            a[j+k]=temp;
        }
    }
}

 

六、歸並排序

算法原理

歸並排序的核心思想是將兩個有序的數列合並成一個大的有序的序列。通過遞歸,層層合並,即為歸並。

 

時間復雜度

圖示

先把整體二分到一,在合並成有序整體

 

代碼

void merge(int le,int mid,int ri,int a[],int temp[])//將數組a的左右兩部分合並
{
    int i=le,j=mid+1;
    int m=mid,n=ri;
    int k=0;
    while(i<=m&&j<=n)//a數組兩端更小的先加進temp數組
    {
        if(a[i]<a[j])
            temp[k++]=a[i++];
        else
            temp[k++]=a[j++];
    }
    //把a數組剩下的部分加入temp數組
    while(i<=m)
        temp[k++]=a[i++];
    while(j<=n)
        temp[k++]=a[j++];

    for(int i=0;i<k;i++)//把有序的temp數組放回a數組
        a[le+i]=temp[i];
}

void quick_pow(int le,int ri,int a[],int temp[])
{
    if(le>=ri)
        return ;
    int mid=(le+ri)/2;
    quick_pow(le,mid,a,temp);//使左邊有序
    quick_pow(mid+1,ri,a,temp);//右邊有序
    merge(le,mid,ri,a,temp);//左右有序的部分合並
}

 

七、堆排序

算法原理

堆排序可以說是一種利用堆的概念來排序的選擇排序。分為兩種方法:

  • 大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序算法中用於升序排列;

  • 小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序算法中用於降序排列;

圖示

  1. 創建一個堆 H[0……n-1],按照大頂堆的性質從左往右創建;

  2. 創建完成之后,先把堆首元素(最大值)拿走,再把堆尾元素(同一層按從右往左順序)放到堆首位置;

  3. 堆尾元素放到堆首之后,判斷是否比左右節點元素大,交換使堆頂元素大於左右節點;

  4. 重復步驟 2,直到堆的尺寸為 1。

 


代碼

#include<iostream>
#include<algorithm>
#include<math.h>
#include<string.h>
#define ll long long
#define M 0x3f3f3f3f3f
using namespace std;

void quick_pow(int a[],int pos,int len)
{
    int root=a[pos];//頂點
    int le=pos*2+1;//因為pos使從0開始,所以是左孩子
    while(le<len)
    {
        int ri=le+1;
        if(ri<len&&a[ri]>a[le])//使左孩子大於右孩子
            le=ri;
        if(root<a[le])//使頂點大於左孩子
        {
            a[pos]=a[le];
            pos=le;
            le=pos*2+1;
        }
        else
            break;
    }
    a[pos]=root;
}
void init(int n,int a[])
{
    for(int i=n/2-1;i>=0;i--)//將數組a初始化為大頂堆
        quick_pow(a,i,n);

    for(int i=n-1;i>=0;i--)//將堆頂元素和堆尾元素互換,並維護大頂堆的性質
    {
        int temp=a[0];//堆頂元素
        a[0]=a[i];
        a[i]=temp;
        quick_pow(a,0,i);
    }
}
int main()
{
    int n;
    int a[105],temp[105];
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    init(n,a);
    for(int i=0;i<n;i++)
        cout<<a[i]<<' ';
    cout<<endl;
    return 0;
}

 




 排序算法穩定性的理解

假定在待排序的記錄序列中, 存在多個具有相同關鍵字的記錄, 若經過排序, 這些記錄的相對次序保持不變

即:在原序列中, a=b, 且a在b之前, 而排序后, a仍在b之前, 則稱為這種排序算法是穩定的, 否則稱為不穩定的.

 

如1,44,22, 55,22,99.

原始數列中,有兩個22. 按顏色區分相對次序。

那么穩定性排序完成后就該為:

1, 22 22, 44, 55, 99

不穩定性排序完成后可能為:

1, 2222, 44, 55, 99

 

穩定性排序的意義或優點:
如果單純的對數字討論它的穩定性是毫無意義的(只有一個鍵),只有對多個鍵(屬性)的值穩定性才有意義(和結構體排序很像)

排序算法如果是穩定的,那么從一個鍵上排序,然后再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。

舉個例子:

對銷售數據進行排序,一個銷售數據的屬性包括銷量和單價,如今需要按照銷量高低排序,使用穩定性算法,

可以使得想同銷量的對象依舊保持着價格高低的排序展現,只有銷量不同的才會重新排序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM