29、數據結構與算法 - 排序(一)冒泡、簡單選擇、直接插入、希爾排序、堆排序

排序

  1. 冒泡
  2. 簡單選擇
  3. 直接插入
  4. 希爾排序
  5. 堆排序


確定一種排序規則,使用相應的關鍵字滿足排序規則,例如遞增、遞減。使得到序列成爲一個按關鍵字有序的序列,這樣得出操作成爲排序

 

排序的分類

  1. 內排序:是在排序整個過程中,待排序的所有記錄全部被放置在內存中
  2. 外排序:由於排序的記錄個數太多,不能同時放置在內存,整個排序過程需要再內外存之間多次賈環數據才能進行
  3.  

1、冒泡排序(Bubble sort)

冒泡排序是一種交換排序,他的基本思想就是:涼涼比較相鄰的記錄的關鍵字,如果反序則交換,知道沒有反序的記錄爲止

 

2、簡單排序

 

3、直接插入排序

直接插入排序算法的基本操作是將一個記錄插入到已經排好序的有序表中,從而得到一個新的,記錄數量增1的有序表; 

 

4、希爾排序

是對直接插入排序的優化

 

5、堆排序

思路
將無序序列構建成一個堆,根據升序間須需求選擇大頂堆或小頂堆
將對頂元素與未尾元素交換,將最大元素“沉到數組末端”
重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,知道整個序列有序;

二叉樹性質:

  • 如果對一顆有n個結點的完全二叉樹的結點按層序編號,對任一結點i(1<= i <= n)有:
  • 如果i=1,則結點是二叉樹的根,無雙親。如果i>1,則其雙親是結點[i/2];
  • 如果2i>n,則結點i無左孩子(結點i爲葉子結點);否者左孩子是結點2i;
  • 如果2i+1>n,則結點i無右孩子;否則其右孩子是結點2i+1;

 

 

 

代碼實現

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;

//1.排序算法數據結構設計
//用於要排序數組個數最大值,可根據需要修改
#define MAXSIZE 10000
typedef struct
{
    //用於存儲要排序數組,r[0]用作哨兵或臨時變量
    int r[MAXSIZE+1];
    //用於記錄順序表的長度
    int length;
}SqList;


//2.排序常用交換函數實現
//交換L中數組r的下標爲i和j的值
void swap(SqList *L,int i,int j)
{
    int temp=L->r[i];
    L->r[i]=L->r[j];
    L->r[j]=temp;
}

//3.數組打印
void print(SqList L)
{
    int i;
    for(i=1;i<L.length;i++)
        printf("%d,",L.r[i]);
    printf("%d",L.r[i]);
    printf("\n");
}

1、冒泡排序

//4. 冒泡排序-對順序表L進行交換排序(冒泡排序初級版本)
void BubbleSort0(SqList *L){
   
    int i,j;
    for (i = 1; i < L->length; i++) {
        for (j = i+1; j <= L->length; j++) {
            if(L->r[i] > L->r[j])
                swap(L, i, j);
        }
    }
}
//5.冒泡排序-對順序表L作冒泡排序(正宗冒泡排序算法)
void BubbleSort(SqList *L){
    int i,j;
    for (i = 1; i < L->length; i++) {
        //注意:j是從後面往前循環
        for (j = L->length-1; j>=i; j--) {
            
            //若前者大於後者(注意與上一個算法區別所在)
            if(L->r[j]>L->r[j+1])
                //交換L->r[j]與L->r[j+1]的值;
                swap(L, j, j+1);
        }
    }
}
//6.冒泡排序-對順序表L冒泡排序進行優化
void BubbleSort2(SqList *L){
    int i,j;
    //flag用作標記
    Status flag = TRUE;
    
    //i從[1,L->length) 遍歷;
    //如果flag爲False退出循環. 表示已經出現過一次j從L->Length-1 到 i的過程,都沒有交換的狀態;
    for (i = 1; i < L->length && flag; i++) {
        
        //flag 每次都初始化爲FALSE
        flag = FALSE;
        
        for (j = L->length-1; j>=i; j--) {
            
            if(L->r[j] > L->r[j+1]){
            //交換L->r[j]和L->r[j+1]值;
            swap(L, j, j+1);
            //如果有任何數據的交換動作,則將flag改爲true;
            flag=TRUE;
            }
        }
    }
}

2、簡單排序

//7.選擇排序--對順序表L進行簡單選擇排序
void SelectSort(SqList *L){
    
    int i,j,min;

    for (i = 1; i < L->length; i++) {
        //① 將當前下標假設爲最小值的下標
        min = i;
        //② 循環比較i之後的所有數據
        for (j = i+1; j <= L->length; j++) {
            //③ 如果有小於當前最小值的關鍵字,將此關鍵字的下標賦值給min
            if (L->r[min] > L->r[j]) {
                min = j;
            }
        }
        
        //④ 如果min不等於i,說明找到了最小值,則交換2個位置下的關鍵字
        if(i!=min)
            swap(L, i, min);
    }
}

3、直接插入排序

//8.直接插入排序算法--對順序表L進行直接插入排序
void InsertSort(SqList *L){
    int i,j;
    //L->r[0] 哨兵 可以把temp改爲L->r[0]
    int temp=0;
    
    //假設排序的序列集是{0,5,4,3,6,2};
    //i從2開始的意思是我們假設5已經放好了. 後面的牌(4,3,6,2)是插入到它的左側或者右側
    for(i=2;i<=L->length;i++)
    {
        //需將L->r[i]插入有序子表
        if (L->r[i]<L->r[i-1])
        {
            //設置哨兵 可以把temp改爲L->r[0]
            temp = L->r[i];
            for(j=i-1;L->r[j]>temp;j--)
                    //記錄後移
                    L->r[j+1]=L->r[j];
            
            //插入到正確位置 可以把temp改爲L->r[0]
            L->r[j+1]=temp;
        }
    }
}

4、希爾排序

//9.希爾排序-對順序表L希爾排序
void shellSort(SqList *L){
    int i,j;
    int increment = L->length;
    
    //0,9,1,5,8,3,7,4,6,2
    //① 當increment 爲1時,表示希爾排序結束
    do{
        //② 增量序列
        increment = increment/3+1;
        //③ i的待插入序列數據 [increment+1 , length]
        for (i = increment+1; i <= L->length; i++) {
            //④ 如果r[i] 小於它的序列組元素則進行插入排序,例如3和9. 3比9小,所以需要將3與9的位置交換
            if (L->r[i] < L->r[i-increment]) {
                //⑤ 將需要插入的L->r[i]暫時存儲在L->r[0].和插入排序的temp 是一個概念;
                L->r[0] = L->r[i];
                
                //⑥ 記錄後移
                for (j = i-increment; j > 0 && L->r[0]<L->r[j]; j-=increment) {
                    L->r[j+increment] = L->r[j];
                }
                
                //⑦ 將L->r[0]插入到L->r[j+increment]的位置上;
                L->r[j+increment] = L->r[0];
            }
        }
    }while (increment > 1);
}

5、堆排序

//大頂堆調整函數;
/*
 條件: 在L.r[s...m] 記錄中除了下標s對應的關鍵字L.r[s]不符合大頂堆定義,其他均滿足;
 結果: 調整L.r[s]的關鍵字,使得L->r[s...m]這個範圍內符合大頂堆定義.
 */
void HeapAjust(SqList *L,int s,int m){
    
    int temp,j;
    //① 將L->r[s] 存儲到temp ,方便後面的交換過程;
    temp = L->r[s];
    
    //② j 爲什麼從2*s 開始進行循環,以及它的遞增條件爲什麼是j*2
    //因爲這是顆完全二叉樹,而s也是非葉子根結點. 所以它的左孩子一定是2*s,而右孩子則是2s+1;(二叉樹性質5)
    for (j = 2 * s; j <=m; j*=2) {
        
        //③ 判斷j是否是最後一個結點, 並且找到左右孩子中最大的結點;
        //如果左孩子小於右孩子,那麼j++; 否則不自增1. 因爲它本身就比右孩子大;
        if(j < m && L->r[j] < L->r[j+1])
            ++j;
        
        //④ 比較當前的temp 是不是比較左右孩子大; 如果大則表示我們已經構建成大頂堆了;
        if(temp >= L->r[j])
            break;
        
        //⑤ 將L->[j] 的值賦值給非葉子根結點
        L->r[s] = L->r[j];
        //⑥ 將s指向j; 因爲此時L.r[4] = 60, L.r[8]=60. 那我們需要記錄這8的索引信息.等退出循環時,能夠把temp值30 覆蓋到L.r[8] = 30. 這樣才實現了30與60的交換;
        s = j;
    }
    
    //⑦ 將L->r[s] = temp. 其實就是把L.r[8] = L.r[4] 進行交換;
    L->r[s] = temp;
}
//10.堆排序--對順序表進行堆排序
void HeapSort(SqList *L){
    int i;
   
    //1.將現在待排序的序列構建成一個大頂堆;
    //將L構建成一個大頂堆;
    //i爲什麼是從length/2.因爲在對大頂堆的調整其實是對非葉子的根結點調整.
    for(i=L->length/2; i>0;i--){
        HeapAjust(L, i, L->length);
    }
    
    
    //2.逐步將每個最大的值根結點與末尾元素進行交換,並且再調整成大頂堆
    for(i = L->length; i > 1; i--){
        
        //① 將堆頂記錄與當前未經排序子序列的最後一個記錄進行交換;
        swap(L, 1, i);
        //② 將L->r[1...i-1]重新調整成大頂堆;
        HeapAjust(L, 1, i-1);
    }
}

驗證

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 排序算法\n");
    
    int i;
    int d[N]={9,1,5,8,3,7,4,6,2};
    //int d[N]={9,8,7,6,5,4,3,2,1};
    //int d[N]={50,10,90,30,70,40,80,60,20};
    SqList l0,l1,l2,l3,l4,l5,l6,l7,l8,l9,l10;
   
    for(i=0;i<N;i++)
        l0.r[i+1]=d[i];
    
    l0.length=N;
    l1=l2=l3=l4=l5=l6=l7=l8=l9=l10=l0;
    
    printf("排序前:\n");
    print(l0);
    printf("\n");
    
    //1.初級冒泡排序
    printf("初級冒泡排序:\n");
    BubbleSort0(&l0);
    print(l0);
    printf("\n");
    
    //2.冒泡排序
    printf("冒泡排序:\n");
    BubbleSort(&l1);
    print(l1);
    printf("\n");
    
    //3.冒泡排序優化
    printf("冒泡排序(優化):\n");
    BubbleSort2(&l2);
    print(l2);
    printf("\n");
    
    //4.選擇排序
    printf("選擇排序:\n");
    SelectSort(&l3);
    print(l3);
    printf("\n");
    
    //5.直接插入排序
    printf("直接插入排序:\n");
    InsertSort(&l4);
    print(l4);
    printf("\n");
    
    //6.希爾排序
    printf("希爾排序:\n");
    shellSort(&l5);
    print(l5);
    printf("\n");
    
    //7.堆排序
    //注意:執行對排序時更換一下數據源. 這裏我爲什麼要這組數據,原因是爲了與下標個位數字講解時進行區分;因爲在這個算法講解過程,出現了很多下標的相關計算.
    /* int d[N]={50,10,90,30,70,40,80,60,20}; */
    printf("堆排序:\n");
    HeapSort(&l6);
    print(l6);
    printf("\n");
    
    return 0;
}
Hello, 排序算法
排序前:
9,1,5,8,3,7,4,6,2

初級冒泡排序:
1,2,3,4,5,6,7,8,9

冒泡排序:
1,2,3,4,5,6,7,8,9

冒泡排序(優化):
1,2,3,4,5,6,7,8,9

選擇排序:
1,2,3,4,5,6,7,8,9

直接插入排序:
1,2,3,4,5,6,7,8,9

希爾排序:
1,2,3,4,5,6,7,8,9

堆排序:
1,2,3,4,5,6,7,8,9

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章