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

前言:排序的定义

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

在这里插入图片描述

各种排序的时间复杂度

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序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.008725s0.120478s0.256040s
顶堆选择排序0.007705s0.097579s0.204354s
递归归并排序0.006827s栈溢出栈溢出
非递归并排序0.006666s0.080683s0.148987s
普通快速排序0.007767s0.086778s0.188957s
优化快速排序0.007215s0.087385s0.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  : xiaojunyu@lunaw.cn
  > Gmail : lunaw.cn@gmail.com
  > 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;
}
  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值