冒泡排序,选择排序,插入排序,希尔排序,堆排序,归并排序,快速排序实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LunaW/article/details/83383505

前言:排序的定义

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程

在这里插入图片描述

各种排序的时间复杂度

排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定
简单选择排序 O(n^2) O(n^2) O(n^2) O(1) 稳定
直接插入排序 O(n^2) O(n) O(n^2) O(1) 稳定
希尔排序 O(nlogn)~O(n^2) O(n^1.3) O(n^2) O(1) 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
快速排序 O(nlogn) O(nlogn) O(n^2) O(logn)~O(n) 不稳定

各种排序的时间测试

排序方法 5万数据 50万数据 100 万数据
优化冒泡排序 8.183984s 太慢 太慢
简单选择排序 2.682234s 太慢 太慢
直接插入排序 1.496184s 太慢 太慢
希尔插入排序 0.008725s 0.120478s 0.256040s
顶堆选择排序 0.007705s 0.097579s 0.204354s
递归归并排序 0.006827s 栈溢出 栈溢出
非递归并排序 0.006666s 0.080683s 0.148987s
普通快速排序 0.007767s 0.086778s 0.188957s
优化快速排序 0.007215s 0.087385s 0.182627s

冒泡排序

  • 冒泡排序,通过两两比较相邻记录,依次浮出最小或者最大值
  • 优化方案:增加标志位判断是否已经有序,而不继续冒泡

在这里插入图片描述

实现代码

int BubbleSort1(SqList * L)
{
    int i, j;
    int flag = 1;
    for (i = 0; i < MAXSIZE - 1 && flag; i++) {
        flag = 0;   // 若标志位flag不为1,说明已经有序,这个时候就可以退出循环了
        for (j = MAXSIZE - 1; j > i; j--) {
            if (L->r[j] < L->r[j - 1]) {
                swap(L, j, j - 1);
                flag = 1;
            }
        }
    }
    return 0;
}

简单选择排序

  • 简单选择排序,通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,然后与第i的位置替换
  • 通俗的讲就是,循环比较出后面最小的值依次放在前面去,就是从小到大了

在这里插入图片描述

实现代码

int SelectSort(SqList * L)
{
    int i, j, min;
    for (i = 0; i < MAXSIZE - 1; i++) {
        min = i;    // 将最小值的下标min指向当前下标i,起始位置
        for (j = i + 1; j < MAXSIZE; j++) {
            if (L->r[min] > L->r[j]) {
                min = j;    // 将最小值的下标min指向下标j,不停的比较,min指向当前循环最小值的下标
            }
        }
        if (i != min) {
            swap(L, i, min);    // 将最小值min交换到有序的i的位置,则每次循环以后,最小的值都依次被排序
        }
    }
    return 0;
}

直接插入排序

  • 直接插入排序,从第二个数开始循环,将当前数拿出来,依次将左边大于当前值的数向后移动一位,直到不大于当前值停止,然后将当前值插入空档

在这里插入图片描述

实现代码

int InsertSort(SqList * L)
{
    int i, j, flag;
    for (i = 1; i < MAXSIZE; i++) { // 从下标1开始,假设下标0已经有序
        if (L->r[i] < L->r[i - 1]) {    // 若当前的值小于前一位,则执行下面的操作
            flag = L->r[i]; // 将当前值记录到储存位
            for (j = i - 1; j >= 0 && L->r[j] > flag; j--) {    // 将左边大于当前值的依次向后移动一位,直到不大于为止
                L->r[j + 1] = L->r[j];
            }
            L->r[j + 1] = flag; // 插入储存位到空档
        }
    }
    return 0;
}

希尔排序

  • 希尔排序,直接插入排序的高效版,先使整个序列基本有序,然后再进行插入排序,大突破,打破O[n^2]
  • 使基本有序的方法就是把顺序循环变成跳跃方式的间隔循环,例如a[i]和a[i+1]比较变成a[i]和a[i+4]比较,然后慢慢缩小a[i]和a[i+2]比较,最后为1的时候就是插入排序了,这个过程在逐渐把排序变成基本有序

在这里插入图片描述

实现代码

int ShellSort(SqList * L)
{
    int i, j, flag;
    int increment = MAXSIZE;
    while (increment > 1) {
        increment = increment / 3 + 1;  // 增量,目前没有最好的增量,可以调整
        for (i = increment; i < MAXSIZE; i++) { // 跳跃调整序列为基本有序,increment等于1时为普通插入排序
            if (L->r[i] < L->r[i - increment]) {
                flag = L->r[i]; // 将当前值记录到储存位
                for (j = i - increment; j >= 0 && L->r[j] > flag; j -= increment) {
                    L->r[j + increment] = L->r[j];  // 记录后移,查找插入位置
                }
                L->r[j + increment] = flag; //插入储存位到空档
            }
        }
    }
    return 0;
}

堆排序,归并排序

  • 不做仔细讲解,这两个略麻烦
  • 堆排序利用二叉树构建大顶堆取根节点
  • 归并排序采用拆分比较,再归并的方式实现排序

在这里插入图片描述

实现代码

// 见完整代码

快速排序

  • 王者段位的排序,冒泡排序的升级版
  • 中心思想就是递归不断的把小的放左边,大的放右边,最后实现排序

在这里插入图片描述

实现代码

int QuickSort(SqList * L)
{
    QSort(L, 0, MAXSIZE - 1);
    return 0;
}

int QSort(SqList * L, int low, int high)
{
    int pivot;
    if (low < high) {
        pivot = Partition(L, low, high);    // 将low..high一分为二
        QSort(L, low, pivot - 1);   // 向低子表递归排序
        QSort(L, pivot + 1, high);  // 向高子表递归排序
    }
    return 0;
}

int Partition(SqList * L, int low, int high)
{
    int pivotkey;
    pivotkey = L->r[low];   //先暂时定第一个位置为中枢。此时选取可优化,因为可能不是中间值,可能偏大偏小
    while (low < high) {    // 不停的移动下标,当上下位置移动相交以后退出循环,此时中枢前后分别小于和大于中枢值
        while (low < high && L->r[high] >= pivotkey) {  // 从上向下直到当前值小于中枢值
            high--;
        }
        swap(L, low, high); // 把上半区中比中枢记录小的交换到低端
        while (low < high && L->r[low] <= pivotkey) {   // 从下向上直到当前值大于中枢值
            low++;
        }
        swap(L, low, high); // 把下半区中比中枢记录大的交换到顶端
    }
    return low;     // 返回中枢下标位置
}

完整代码

/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

  > Author: xiaojunyu/LunaW
  > Mail  : [email protected]
  > Gmail : [email protected]
  > Blog  : http://blog.csdn.net/lunaw
  > Web   : http://lunaw.cn

 *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>

#define MAXSIZE 50000       // 设置数据量
#define IsPrintf 0      // 设置为1可以查看输出

typedef struct {
    int r[MAXSIZE + 1];
} SqList;

void Init(SqList * L);      // 初始化随机数
void Print(SqList * L);     // 输出数据
void swap(SqList * L, int i, int j);    // 交换值
double GetTime(void);       // 获取当前时间

int BubbleSort0(SqList * L);
/* 最简单的交换排序,不是真正意义上的冒泡排序 */

int BubbleSort(SqList * L);
/* 真正意义上的冒泡排序,两两交换,浮出最小或者最大值 */

int BubbleSort1(SqList * L);
/* 优化的冒泡排序,浮出最小或者最大值,增加标志位判断是否已经有序 */

int SelectSort(SqList * L);
/* 简单选择排序,通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,然后与第i的位置替换 */

int InsertSort(SqList * L);
/* 直接插入排序,从第二个数开始循环,将当前数拿出来,依次将左边大于当前值的数向后移动一位,直到不大于当前值停止,然后将当前值插入空档 */

int ShellSort(SqList * L);
/* 希尔排序,直接插入排序的高效版,先使整个序列基本有序,然后再进行插入排序,大突破,打破O[n的平方] */

int HeapAdjust(SqList * L, int s, int m);   // 构建大顶堆
int HeapSort(SqList * L);
/* 堆排序,简单选择排序的升级*/

int MSort(int SR[], int TR1[], int s, int t);
int Merge(int SR[], int TR[], int i, int m, int n);
int MergeSort(SqList * L);
/* 递归归并排序 */

int MergePass(int SR[], int TR[], int s, int n);
int MergeSort1(SqList * L);
/* 非递归并排序 */

int Partition(SqList * L, int low, int high);   // 选取中枢,然后使左边都比它小,右边都比它大
int QSort(SqList * L, int low, int high);   // 递归排序
int QuickSort(SqList * L);
/* 快速排序,冒泡排序的升级,王者段位的排序,中心思想就是递归不断的把小的放左边,大的放右边*/

int Partition1(SqList * L, int low, int high);  // 三数取中法选取中枢,使左边都比它小,右边都比它大
int QSort1(SqList * L, int low, int high);  // 递归排序
int QuickSort1(SqList * L);
/* 优化快速排序,采用三数取中法选取中枢,同时减少中枢的交换次数 */

int main(void)
{
    double start, end;
    SqList H;
    SqList L;

    printf("数据量: %d\n\n", MAXSIZE);
    Init(&H);

    // printf("原始数据: \n");
    // Print(&H);

    // L = H;
    // start = GetTime();
    // BubbleSort0(&L);
    // end = GetTime();
    // printf("初级冒泡排序: \n");
    // Print(&L);
    // printf("运行时间: %lfs\n\n", end - start);

    // L = H;
    // start = GetTime();
    // BubbleSort(&L);
    // end = GetTime();
    // printf("标准冒泡排序: \n");
    // Print(&L);
    // printf("运行时间: %lfs\n\n", end - start);

    if (MAXSIZE <= 50000) { // 数据大了时间太久
        L = H;
        start = GetTime();
        BubbleSort1(&L);
        end = GetTime();
        printf("优化冒泡排序: \n");
        Print(&L);
        printf("运行时间: %lfs\n\n", end - start);

        L = H;
        start = GetTime();
        SelectSort(&L);
        end = GetTime();
        printf("简单选择排序: \n");
        Print(&L);
        printf("运行时间: %lfs\n\n", end - start);

        L = H;
        start = GetTime();
        InsertSort(&L);
        end = GetTime();
        printf("直接插入排序: \n");
        Print(&L);
        printf("运行时间: %lfs\n\n", end - start);
    }

    L = H;
    start = GetTime();
    ShellSort(&L);
    end = GetTime();
    printf("希尔插入排序: \n");
    Print(&L);
    printf("运行时间: %lfs\n\n", end - start);

    L = H;
    start = GetTime();
    HeapSort(&L);
    end = GetTime();
    printf("顶堆选择排序: \n");
    Print(&L);
    printf("运行时间: %lfs\n\n", end - start);

    if (MAXSIZE <= 100000) {    // 数组递归把栈内存占满了会报错,分配内存又很慢
        L = H;
        start = GetTime();
        MergeSort(&L);
        end = GetTime();
        printf("递归归并排序: \n");
        Print(&L);
        printf("运行时间: %lfs\n\n", end - start);
    }

    L = H;
    start = GetTime();
    MergeSort1(&L);
    end = GetTime();
    printf("非递归并排序: \n");
    Print(&L);
    printf("运行时间: %lfs\n\n", end - start);

    L = H;
    start = GetTime();
    QuickSort(&L);
    end = GetTime();
    printf("普通快速排序: \n");
    Print(&L);
    printf("运行时间: %lfs\n\n", end - start);

    L = H;
    start = GetTime();
    QuickSort1(&L);
    end = GetTime();
    printf("优化快速排序: \n");
    Print(&L);
    printf("运行时间: %lfs\n\n", end - start);

    return 0;
}

void Print(SqList * L)
{
    if (IsPrintf) {
        int i;
        for (i = 0; i < MAXSIZE; i++) {
            printf("%d ", L->r[i]);
        }
        printf("\n");
    }
}

void Init(SqList * L)
{
    int i;
    srand((unsigned)time(NULL));
    for (i = 0; i < MAXSIZE; i++) {
        L->r[i] = rand() % MAXSIZE;
    }
}

void swap(SqList * L, int i, int j)
{
    int temp = L->r[i];
    L->r[i] = L->r[j];
    L->r[j] = temp;
}

int BubbleSort1(SqList * L)
{
    int i, j;
    int flag = 1;
    for (i = 0; i < MAXSIZE - 1 && flag; i++) {
        flag = 0;   // 若标志位flag不为1,说明已经有序,这个时候就可以退出循环了
        for (j = MAXSIZE - 1; j > i; j--) {
            if (L->r[j] < L->r[j - 1]) {
                swap(L, j, j - 1);
                flag = 1;
            }
        }
    }
    return 0;
}

int BubbleSort(SqList * L)
{
    int i, j;
    for (i = 0; i < MAXSIZE - 1; i++) {
        for (j = MAXSIZE - 1; j > i; j--) {
            if (L->r[j] < L->r[j - 1]) {
                swap(L, j, j - 1);
            }
        }
    }
    return 0;
}

int BubbleSort0(SqList * L)
{
    int i, j;
    for (i = 0; i < MAXSIZE - 1; i++) {
        for (j = i + 1; j < MAXSIZE; j++) {
            if (L->r[i] > L->r[j]) {
                swap(L, i, j);
            }
        }
    }
    return 0;
}

int InsertSort(SqList * L)
{
    int i, j, flag;
    for (i = 1; i < MAXSIZE; i++) { // 从下标1开始,假设下标0已经有序
        if (L->r[i] < L->r[i - 1]) {    // 若当前的值小于前一位,则执行下面的操作
            flag = L->r[i]; // 将当前值记录到储存位
            for (j = i - 1; j >= 0 && L->r[j] > flag; j--) {    // 将左边大于当前值的依次向后移动一位,直到不大于为止
                L->r[j + 1] = L->r[j];
            }
            L->r[j + 1] = flag; // 插入储存位到空档
        }
    }
    return 0;
}

int ShellSort(SqList * L)
{
    int i, j, flag;
    int increment = MAXSIZE;
    while (increment > 1) {
        increment = increment / 3 + 1;  // 增量,目前没有最好的增量,可以调整
        for (i = increment; i < MAXSIZE; i++) { // 跳跃调整序列为基本有序,increment等于1时为普通插入排序
            if (L->r[i] < L->r[i - increment]) {
                flag = L->r[i]; // 将当前值记录到储存位
                for (j = i - increment; j >= 0 && L->r[j] > flag; j -= increment) {
                    L->r[j + increment] = L->r[j];  // 记录后移,查找插入位置
                }
                L->r[j + increment] = flag; //插入储存位到空档
            }
        }
    }
    return 0;
}

int SelectSort(SqList * L)
{
    int i, j, min;
    for (i = 0; i < MAXSIZE - 1; i++) {
        min = i;    // 将最小值的下标min指向当前下标i,起始位置
        for (j = i + 1; j < MAXSIZE; j++) {
            if (L->r[min] > L->r[j]) {
                min = j;    // 将最小值的下标min指向下标j,不停的比较,min指向当前循环最小值的下标
            }
        }
        if (i != min) {
            swap(L, i, min);    // 将最小值min交换到有序的i的位置,则每次循环以后,最小的值都依次被排序
        }
    }
    return 0;
}

double GetTime(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + (double)tv.tv_usec / 1000000;
}

// 在L->r[s..m]中除l->r[s]外都满足堆的定义的情况下,调整L->r[s]的值,使其成为大顶堆
int HeapAdjust(SqList * L, int s, int m)
{
    int temp, j;
    temp = L->r[s];
    for (j = 2 * s; j <= m; j *= 2) {   // 沿关键字较大的孩子节点向下筛选
        if (j < m && L->r[j] < L->r[j + 1]) {
            ++j;    // j为关键字中较大的记录的下标
        }
        if (temp >= L->r[j]) {
            break;
        }
        L->r[s] = L->r[j];
        s = j;
    }
    L->r[s] = temp;     // 插入
    return 0;
}

int HeapSort(SqList * L)
{
    int i;
    for (i = (MAXSIZE - 1) / 2; i >= 0; i--) {  // 构建一个大顶堆
        HeapAdjust(L, i, MAXSIZE - 1);
    }
    for (i = MAXSIZE - 1; i >= 1; i--) {    // 反复将堆顶移到最后有序的位置,最后排序完成
        swap(L, 0, i);  // 将堆顶与后面未排序的最后一位交换
        HeapAdjust(L, 0, i - 1);    // 将交换后的序列重新调整为成大顶堆
    }
    return 0;
}

int MergeSort1(SqList * L)
{
    int *TR = (int *)malloc(MAXSIZE * sizeof(int));
    int k = 1;
    while (k < MAXSIZE) {
        MergePass(L->r, TR, k, MAXSIZE);
        k = 2 * k;  // 子序列长度加倍
        MergePass(TR, L->r, k, MAXSIZE);
        k = 2 * k;  // 子序列长度加倍
    }
    return 0;
}

int MergePass(int SR[], int TR[], int s, int n)
{
    int i = 1;
    int j;
    while (i <= n - 2 * s + 1) {
        Merge(SR, TR, i - 1, i + s - 1 - 1, i + 2 * s - 1 - 1);
        i = i + 2 * s;
    }
    if (i < n - s + 1) {
        Merge(SR, TR, i - 1, i + s - 1 - 1, n - 1);
    } else {
        for (j = i; j <= n; j++) {
            TR[j - 1] = SR[j - 1];
        }
    }
    return 0;
}

int MergeSort(SqList * L)
{
    MSort(L->r, L->r, 0, MAXSIZE - 1);
    return 0;
}

int MSort(int SR[], int TR1[], int s, int t)
{
    int m;
    int TR2[MAXSIZE + 1];
    if (s == t) {
        TR1[s] = SR[s];
    } else {
        m = (s + t) / 2;
        MSort(SR, TR2, s, m);
        MSort(SR, TR2, m + 1, t);
        Merge(TR2, TR1, s, m, t);
    }
    return 0;
}

int Merge(int SR[], int TR[], int i, int m, int n)
{
    int j, k, l;
    for (j = m + 1, k = i; i <= m && j <= n; k++) {
        if (SR[i] < SR[j]) {
            TR[k] = SR[i++];
        } else {
            TR[k] = SR[j++];
        }
    }
    if (i <= m) {
        for (l = 0; l <= m - i; l++) {
            TR[k + l] = SR[i + l];
        }
    }
    if (i <= n) {
        for (l = 0; l <= n - j; l++) {
            TR[k + l] = SR[j + l];
        }
    }
    return 0;
}

int Partition(SqList * L, int low, int high)
{
    int pivotkey;
    pivotkey = L->r[low];   //先暂时定第一个位置为中枢。此时选取可优化,因为可能不是中间值,可能偏大偏小
    while (low < high) {    // 不停的移动下标,当上下位置移动相交以后退出循环,此时中枢前后分别小于和大于中枢值
        while (low < high && L->r[high] >= pivotkey) {  // 从上向下直到当前值小于中枢值
            high--;
        }
        swap(L, low, high); // 把上半区中比中枢记录小的交换到低端
        while (low < high && L->r[low] <= pivotkey) {   // 从下向上直到当前值大于中枢值
            low++;
        }
        swap(L, low, high); // 把下半区中比中枢记录大的交换到顶端
    }
    return low;     // 返回中枢下标位置
}

// 优化函数
int Partition1(SqList * L, int low, int high)
{
    int pivotkey;
    /* 三数取中法 使中枢更接近中间值 */
    int m = low + (high - low) / 2;
    if (L->r[low] > L->r[high]) {
        swap(L, low, high);
    }
    if (L->r[m] > L->r[high]) {
        swap(L, high, m);
    }
    if (L->r[m] > L->r[low]) {
        swap(L, m, low);
    }
    pivotkey = L->r[low];   //先暂时定第一个位置为中枢。此时选取可优化,因为可能不是中间值,可能偏大偏小
    L->r[MAXSIZE] = pivotkey;   // [减少中枢交换优化] 先保存中枢到备份位
    while (low < high) {    // 不停的移动下标,当上下位置移动相交以后退出循环,此时中枢前后分别小于和大于中枢值
        while (low < high && L->r[high] >= pivotkey) {  // 从上向下直到当前值小于中枢值
            high--;
        }
        L->r[low] = L->r[high]; // [减少中枢交换优化] 改交换为替换,可以减少交换次数
        while (low < high && L->r[low] <= pivotkey) {   // 从下向上直到当前值大于中枢值
            low++;
        }
        L->r[high] = L->r[low]; // [减少中枢交换优化] 改交换为替换,可以减少交换次数
    }
    L->r[low] = L->r[MAXSIZE];  // [减少中枢交换优化] 将中枢数据替换回来
    return low;     // 返回中枢下标位置
}

int QSort(SqList * L, int low, int high)
{
    int pivot;
    if (low < high) {
        pivot = Partition(L, low, high);    // 将low..high一分为二
        QSort(L, low, pivot - 1);   // 向低子表递归排序
        QSort(L, pivot + 1, high);  // 向高子表递归排序
    }
    return 0;
}

int QSort1(SqList * L, int low, int high)
{
    int pivot;
    if (low < high) {
        pivot = Partition1(L, low, high);   // 将low..high一分为二
        QSort(L, low, pivot - 1);   // 向低子表递归排序
        QSort(L, pivot + 1, high);  // 向高子表递归排序
    }
    return 0;
}

int QuickSort(SqList * L)
{
    QSort(L, 0, MAXSIZE - 1);
    return 0;
}

int QuickSort1(SqList * L)
{
    QSort1(L, 0, MAXSIZE - 1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/LunaW/article/details/83383505