第9章_排序

排序的基本概念

排序是對數據元素序列建立某種有序排列的過程。更確切地說,排序是把一個數據元素序列整理成按關鍵字遞增(或遞減)排列的過程。
關鍵字分主關鍵字和次關鍵字兩種。對要排序的數據元素集合來說,如果關鍵字滿足數據元素值不同時該關鍵字的值也一定不同,這樣的關鍵字稱爲主關鍵字。換句話說,主關鍵字是能夠惟一區分各個不同數據元素的關鍵字。不滿足主關鍵字定義的關鍵字稱爲次關鍵字。
排序分內部排序外部排序兩種。內部排序是把待排數據元素全部調入內存中進行的排序。如果數據元素的數量太大,需要分批導入內存,分批導入內存的數據元素排好序後再分批導出到磁盤和磁帶等外存介質上的排序方法稱作外部排序。外部排序算法的原理與內部排序算法的原理在很多地方都類同,但因內存的讀寫速度與外存的讀寫速度差別很大,所以評價標準差別很大。

插入排序

插入排序的基本思想是:從初始有序的子集合開始,不斷地把新的數據元素插入到已排列有序的子集合的合適位置上,使子集合中數據元素的個數不斷增多,當子集合等於集合時,插入排序算法結束。常用的插入排序有直接插入排序和希爾排序兩種。

直接插入排序

直接插入排序的基本思想是:順序地把待排序的數據元素按其關鍵字值的大小插入到已排序數據元素子集合的適當位置。子集合的數據元素個數從只有一個數據元素開始逐次增大,當子集合大小最終與集合大小相同時排序完畢。

Sort.h

typedef struct 
{
    KeyType key;
} DataType;

//直接插入排序
void InsertSort(DataType a[],int n)
{
    int i,j;
    DataType temp;
    for(i = 0;i < n-1; i++)
    {
        temp = a[i+1];
        j = i;
        while(j > -1 && temp.key < a[j].key)
        {
            a[j+1] = a[j];
            j--;
        }
        a[j+1] = temp;
    }
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    InsertSort(num,length);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

希爾排序

希爾(shell)排序的基本思想是:把待排序的數據元素分成若干個小組,對同一小組內的數據元素用直接插入法排序;小組的個數逐次縮小,當完成了所有數據元素都在一個組內的排序後排序過程結束。希爾排序又稱作縮小增量排序。

//希爾排序
//用希爾排序法對元素a[0]--a[n-1]排序,d[0]--d[numOfD-1]
//爲希爾增量值
void ShellSort(DataType a[],int n,int d[],int numOfD)
{
    int i,j,k,m,span;
    DataType temp;
    for(m = 0;m < numOfD;m++)       //共numOfD次循環
    {
        span = d[m];                //取本次的增量值
        for(k = 0;k < span; k++)    //共span個小組
        {
            //組內是直接插入排序,區別是每次不是增1而是增span
            for(i = k; i < n-span; i = i + span)
            {
                temp = a[i + span];
                j = i;
                while(j > -1 && temp.key <= a[j].key)
                {
                    a[j + span] = a[j];
                    j = j - span;
                }
                a[j + span] = temp;
            }
        }
    }
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };

    int cap[2] = {1,3};

    print(num,length);

    printf("\n");
//  InsertSort(num,length);
    ShellSort(num,length,cap,2);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

選擇排序

選擇排序的基本思想是:每次從待排序的數據元素集合中選取關鍵字最小(或最大)的數據元素放到數據元素集合的最前(或最後),數據元素集合不斷縮小,當數據元素集合爲空時選擇排序結束。常用的選擇排序有直接選擇排序和堆排序兩種。堆排序是一種基於完全二叉樹的排序。

直接選擇排序

直接選擇排序的基本思想是:從待排序的數據元素集合中選取關鍵字最小的數據元素並將它與原始數據元素集合中的第一個數據元素交換位置;然後從不包括第一個位置的數據元素集合中選取關鍵字最小的數據元素並將它與原始數據集合中的第二個數據元素交換位置;如此重複,直到數據元素集合中只剩一個數據元素爲止。

//直接選擇排序 
void SelectSort(DataType a[],int n)
{
    int i,j,small;
    DataType temp;

    for(i = 0;i < n-1; i++)
    {
        small = i;                                      //設第i個數據元素關鍵字最小
        for(j = i + 1;j < n; j++)                       //尋找關鍵字最小的數據元素
            if(a[j].key < a[small].key) small = j;      //記住最小元素的下標
        if(small != i)                                  //當最小元素的下標不爲i時交換位置
        {
            temp = a[i];
            a[i] = a[small];
            a[small] = temp;
        }
    }
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    SelectSort(num,length);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

堆排序

在直接選擇排序中,待排序的數據元素集合構成一個線性表結構,要從有n個數據元素的線性表中選擇出一個最小的數據元素需要比較n-1次。如果能把待排序的數據元素集合構成一個完全二叉樹結構,則每次選擇出一個最大(或最小)的數據元素只需比較完全二叉樹的數值爲高度的次數,即log2n次,所以排序算法的時間複雜度就是O(nlog2n)。這就是堆排序的基本思想。

//堆排序
void CreateHeap(DataType a[],int n,int h)
{
    int i,j,flag;
    DataType temp;

    i = h;                  //i爲要建堆的二叉樹根結點下標
    j = 2 * i + 1;          //j爲i的左孩子結點的下標
    temp = a[i];
    flag = 0;

    //沿左右孩子中值較大者重複向下篩選
    while(j < n && flag != 1)
    {
        //尋找左右孩子結點中的較大者,j爲其下標
        if(j < n - 1 && a[j].key < a[j + 1].key) j++;
        if(temp.key > a[j].key)     //a[i].key > a[j].key
            flag = 1;               //標記結束篩選條件
        else
        {
            a[i] = a[j];
            i = j;
            j = 2 * i + 1;
        }
    }

    a[i] = temp;                    //把最初的a[i]賦予最後的a[j]
}

//初始化創建最大堆
void InitCreateHeap(DataType a[],int n)
{
    int i;
    for(i = (n - 1) / 2;i >= 0; i--)
        CreateHeap(a,n,i);
}

//堆排序算法
void HeapSort(DataType a[],int n)
{
    int i;
    DataType temp;
    InitCreateHeap(a,n);        //初始化創建最大堆

    for(i = n - 1;i > 0; i--)   //當前最大堆個數每次遞減1
    {
        //把堆頂a[0]元素與當前最大堆的最後一個元素交換
        temp = a[0];
        a[0] = a[i];
        a[i] = temp;
        CreateHeap(a,i,0);      //調整根結點滿足最大堆
    }
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    HeapSort(num,length);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

交換排序

利用交換數據元素的位置進行排序的方法稱作交換排序。常用的交換排序方法有冒泡排序法和快速排序法。快速排序法是一種分區交換排序方法。

冒泡排序

//冒泡排序
void BubbleSort(DataType a[],int n)
{
    int i,j,flag = 1;
    DataType temp;
    for(i = 1;i < n && flag == 1; i++)
    {
        flag = 0;
        for(j = 0;j < n - i; j++)
        {
            if(a[j].key > a[j + 1].key)
            {
                flag = 1;
                temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    BubbleSort(num,length);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

快速排序

快速排序是一種二叉樹結構的交換排序方法。快速排序算法的基本思想是:設數組a中存放了n個數據元素,low爲數組的低端下標,high爲數組的高端下標,從數組a中任取一個元素(通常取a[low])作爲標準,調整數組a中各個元素的位置,使排在標準元素前面元素的關鍵字均小於標準元素的關鍵字,排在標準元素後面元素的關鍵字均大於或等於標準元素的關鍵字。這樣一次過程結束後,一方面將標準元素放在了未來排好序的數組中該標準 元素應在的位置上,另一方面將數組中的元素以標準元素爲中心分成了兩個子數組,位於標準 元素左邊子數組中元素的關鍵 字均小於標準元素的關鍵 字,位於標準 元素右邊子數組中元素的關鍵字均大於等於標準元素的關鍵字。然後對這兩個子數組中的元素分別再進行方法類同的遞歸快速排序。遞歸算法的出口條件是high > low

//快速排序
//用遞歸方法對數據元素a[low]--a[high]進行快速排序
void QuickSort(DataType a[],int low,int high)
{
    int i = low,j = high;
    DataType temp = a[low];         //取第一個元素爲標準數據元素
    while(i < j)
    {
        while(i < j && temp.key <= a[j].key) j--;       //在數組的右端掃描

        if(i < j)
        {
            a[i] = a[j];
            i++;
        }

        while(i < j && a[i].key < temp.key) i++;        //在數組的左端掃描

        if(i < j)
        {
            a[j] = a[i];
            j--;
        }
    }
    a[i] = temp;

    if(low < i) QuickSort(a,low,i - 1);         //對左端子集合進行遞歸
    if(i < high) QuickSort(a,j + 1,high);           //對右端子集合進行遞歸
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    QuickSort(num,0,length-1);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

歸遞排序

歸併排序主要是二路歸併排序。二路歸併排序的基本思想是:設數組a中存放了n個數據元素,初始時把它們看成是n個長度爲1的有序子數組,然後從第一個子數組開始,把相鄰的子數組兩兩合併,得到n/2的整數上界個長度爲2的新的有序子數組(當n爲奇數時最後一個新的有序子數組的長度爲1);對這些新的有序子數組再兩兩歸併;如此重複,直到得到一個長度爲n的有序數組爲止。

//歸併排序

//k爲有序子數組的長度,一次二路歸併排序後的有序子序列存於數組swap中
void Merge(DataType a[],int n,DataType swap[],int k)
{
    int m = 0,u1,l2,i,j,u2;

    int l1 = 0;         //第一個有序子數組下界爲0
    while(l1 + k <= n - 1)
    {
        l2 = l1 + k;        //計算第二個有序子數組下界
        u1 = l2 - 1;        //計算第一個有序子數組上界
        u2 = (l2 + k - 1 <= n-1) ? l2 + k - 1 : n-1;

        //計算第二個有序子數組上界

        //兩個有序子數組合並
        for(i = l1,j = l2;i <= u1 && j <= u2; m++)
        {
            if(a[i].key <= a[j].key)
            {
                swap[m] = a[i];
                i++;
            }
            else
            {
                swap[m] = a[j];
                j++;
            }
        }

        //子數組2已歸併完,將子數組1中剩餘的元素存放到數組swap中
        while(i <= u1)
        {
            swap[m] = a[i];
            m++;
            i++;
        }

        //子數組1已歸併完,將子數組2中剩餘的元素存放到數組swap中
        while(j <= u2)
        {
            swap[m] = a[j];
            m++;
            j++;
        }
        l1 = u2 + 1;
    }

    //將原始數組中只夠一組的數據元素順序存放到數組swap中
    for(i = l1;i < n; i++,m++)
        swap[m] = a[i];
}

void MergeSort(DataType a[],int n)
{
    int i,k = 1;            //歸併長度從1開始
    DataType* swap;
    swap = (DataType*)malloc(sizeof(DataType)*n);       //申請動態數組空間
    while(k < n)
    {
        Merge(a,n,swap,k);                              //調用歸併函數
        for(i = 0;i < n; i++)
            a[i] = swap[i];                             //將元素從臨時數組swap放回數組中
        k = 2 * k;                                      //歸併長度加倍
    }
    free(swap);                                         //釋放動態數組空間
}

main.c

#include <stdio.h>
typedef int KeyType;
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    MergeSort(num,length);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/

基數排序

基數排序也稱作桶排序,是一種當關鍵字爲整數類型時非常高效的排序方法。
基數排序算法的基本思想是:設待排序的數據元素關鍵字是m位d進制整數(不中m位的關鍵字在高位補0),設置d個桶,令其編號分別爲0,1,2,…,d-1。首先,按關鍵字最低位的數值依次把各數據元素放到相應的桶中;然後,按照桶號從小到大和進入桶中數據元素的先後次序收入集分配在各桶中的數據元素;這樣,就形成了數據元素集合的一個新的排列,稱這樣的一次排序過程爲一次基數排序。再對一次基數排序得到的數據元素序列按關鍵 字次低位的數值依次把各數據元素放到相應的桶中,然後按照桶號從小到大和進入桶中數據元素的先後次序收入集分配在各桶中的數據元素。這樣的過程重複進行,當完成了第 m次基數排序後,就得到了排好序的數據元素序列。

LinQueue.h

typedef struct qnode {
    DataType data;
    struct qnode * next;
}LQNode;

typedef struct  {
    LQNode* front;  //隊頭指針
    LQNode* rear;   //隊尾指針
}LQueue;

//1.初始化
void QueueInitiate(LQueue*Q)
{
    Q->rear = NULL;     //定義初始隊尾指針
    Q->front = NULL;    //定義初始隊頭指針
}

//2.非空否
int QueueNotEmpty(LQueue Q)
{
    //判斷鏈式隊列Q非空否,非空返回1,否則返回0
    if(Q.front == NULL) return 0;
    else return 1;
}

//3.入隊列
int QueueAppend(LQueue*Q,DataType x)
{
    //把數據元素值x插入鏈式隊列Q的隊尾,入隊列成功返回1,否則返回0
    LQNode*p;
    if((p = (LQNode*)malloc(sizeof(LQNode))) == NULL)
    {
        printf("內存空間不中!");
        return 0;
    }
    p->data = x;
    p->next = NULL;

    if(Q->rear != NULL) Q->rear->next = p;
    Q->rear = p;
    if(Q->front == NULL) Q->front = p;
    return 1;
}

//4.出隊列
int QueueDelete(LQueue*Q,DataType*d)
{
    //刪除鏈式隊列Q的隊頭數據元素值到d,出隊列成功返回1,否則返回0
    LQNode*p;
    if(Q->front == NULL)
    {
        printf("隊列已空無數據元素出隊列!\n");
        return 0;
    }
    else
    {
        *d = Q->front->data;
        p = Q->front;
        Q->front = Q->front->next;
        if(Q->front == NULL) Q->rear = NULL;
        free(p);
        return 1;
    }
}

//5.取隊頭數據元素
int QueueGet(LQueue Q,DataType*d)
{
    //取鏈式隊列Q的當前隊頭數據元素值到d,成功返回1,否則返回0
    if(Q.front == NULL)
    {
        printf("隊列已空無數據元素出隊列!\n");
        return 0;
    }
    *d = Q.front->data;
    return 1;
}

//6.撤消動態申請空間
void Destroy(LQueue Q)
{
    LQNode*p,*p1;
    p = Q.front;
    while(p != NULL)
    {
        p1 = p;
        p = p->next;
        free(p1);
    }
}

//基數排序
//對數據元素a[0]--a[n-1]進行關鍵字爲m位d進制整型數值的基數排序
//桶採用鏈式隊列
void RadixSort(DataType a[],int n,int m,int d)
{
    int i,j,k,power = 1;
    LQueue* tub;

    //把d個隊列定義爲動態數組
    tub = (LQueue*)malloc(sizeof(LQueue)*d);
    for(i = 0;i < d; i++)               //d個隊列初始化
        QueueInitiate(&tub[i]);
    //進行m次放和收
    for(i = 0;i < m; i++)
    {
        if(i == 0) power = 1;
        else power = power * d;
        //將數據元素按關鍵字第k位的數值放到相應的隊列中
        for(j = 0;j < n; j++)
        {
            k = a[j].key / power - (a[j].key / (power * d)) * d;
            QueueAppend(&tub[k],a[j]);                  //把a[j]放入相應的隊列中
        }

        //順序回收各隊列中的數據元素至數組a中
        k = 0;
        for(j = 0;j < d; j++)
            while(QueueNotEmpty(tub[j]) != 0)
            {
                QueueDelete(&tub[j],&a[k]);     //從各隊列中回收
                k++;
            }
    }
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef int KeyType;
typedef struct 
{
    KeyType key;
} DataType;
#include "LinQueue.h"
#include "sort.h"
#define length 6

void print(DataType num[],int len);

void main(void)
{
    DataType num[length] = {
        64,5,7,89,6,24
    };
    print(num,length);

    printf("\n");
    RadixSort(num,length,2,10);

    print(num,length);
    printf("\n");
}

void print(DataType num[],int len)
{
    int i;
    for(i = 0;i < len; i++)
        printf("%d\t",num[i]);
}
/*
64      5       7       89      6       24
5       6       7       24      64      89
Press any key to continue
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章