八大排序算法及其比较

八大排序算法及其比较

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

常见的内部排序算法有:冒泡排序、选择排序、插入排序、希尔排序、堆排序、快速排序、归并排序、基数排序等。

前面三种是简单排序,后续算法是在前面基础上进行优化。本文将基于C语言,依次介绍上述八大排序算法。

1 八大排序算法

1.1 冒泡排序

主要思想:依次对两个数比较大小,较大的数冒起来,较小的数压下来。

形象理解:一队新兵N个人整齐站成一列,教官想让他们按照身高排好队,看起来更协调,于是从前走到后走一趟,每次遇到相邻的两个人身高不协调时,就让两人互换位置。当走完一趟时,个子最高的人就被排到了最后。教官回到前排后发现队伍仍然不协调,于是又按照原样走了一趟。这样循环走了N-1趟之后,教官终于满意了。(注意:每次走一趟时,之前排到后面的高个子就不参与这次排序了;有时候可能还没走完N-1趟,教官就发现队伍已经协调了,于是排序结束。)

特点:简单易懂,排序稳定,但速度慢。

完整代码

```c++
#include
#include

void Swap_Two(int *p1, int *p2); //交换两个整数
void Bubble_Sort(int A[], int N); //冒泡排序

int main()
{
int N, i;
int *p = NULL;
//将N个长整数存储至数组中
scanf("%d", &N); //读入个数N
p = malloc(sizeof(int) * N); //申请数组
for(i=0; i0; k--){ //跑N-1趟
flag = 0; //设置标记,如果发现某一趟没有交换数据,说明还未排序的序列已经有序了
for(i=0; i A[i+1]){
Swap_Two(&A[i], &A[i+1]);
flag = 1;
}
}
if(flag == 0) break;
}
return;
}

```

1.2 选择排序

主要思想:针对冒泡排序,有一个地方可以优化,即在跑一趟的过程中,没必要两两交换,可以先记下最小值,跑完一趟后直接将最小值换到前面。

特点:比冒泡更快一些,但代价是跳跃性交换,排序不稳定。


完整代码
#include <stdio.h>
#include <stdlib.h>

void Swap_Two(int *p1, int *p2); //交换两个整数
void Select_Sort(int A[], int N); //选择排序

//主函数,其余类似
int main()
{
    int N, i;
    int *p = NULL;
    //将N个整数存储至数组中
    scanf("%d", &N); //读入个数N
    p = (int *)malloc(sizeof(int) * N); //申请数组
    for(i=0; i<N; i++){
        scanf("%d", &p[i]);
    }
    //排序并打印
    Select_Sort(p, N);
    for(i=0; i<N; i++){
        printf("%d", p[i]);
        if(i != N-1)
            printf(" ");
    }

    free(p);
    return 0;
}

void Swap_Two(int *p1, int *p2){ //交换两个长整型数
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
    return;
}

void Select_Sort(int A[], int N){ //选择排序
    int i, j, min_idx;
    for(i=0; i<N-1; i++){
        min_idx = i;  //初始化最小值索引
        for(j=i+1; j<N; j++){
            if(A[j] < A[min_idx])  //若待排序列中有比当前最小值还小的,则更新最小值索引
                min_idx = j;
        }
        if(min_idx != i) //若更新过最小值索引
            Swap_Two(&A[i], &A[min_idx]);
    }
    return;
}

1.3 插入排序

主要思想:过程跟拿牌一样,依次拿N张牌,每次拿到到牌后,从后往前看,遇到合适位置就插进去。最终手上的牌从小到大。

特点:当数据规模较小或者数据基本有序时,效率较高。


完整代码
void Swap_Two(int *p1, int *p2){ //交换两个整数
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
    return;
}

void Insert_Sort(int A[], int N){ //直插排序
    int temp, i, k;
    for(i=1; i<N; i++){
        temp = A[i];
        for(k=i-1; k>=0 && temp<A[k]; k--){
            A[k+1] = A[k];
        }
        A[k+1] = temp;
    }
    return;
}

1.4 希尔排序

主要思想:设增量序列个数为k,则进行k轮排序。每一轮中,按照某个增量将数据分割成较小的若干组,每一组内部进行插入排序;各组排序完毕后,减小增量,进行下一轮的内部排序。
特点:针对插入排序的改进,当数据规模较大或无序时也比较高效。精妙之处在于,可以同时构造出两个特殊的有利条件(数据量小,基本有序),一个有利条件弱时,另外一个有利条件就强。(刚开始时虽然每组有序度低,但其数据量小;随着每轮的增量逐渐压缩,虽然各组数据量逐渐变大,但其有序度逐渐增加。)


完整代码
void Shell_Sort(int A[], int N){ //希尔排序
    int k, i, j, p, temp;
    int t = 0;
    int D[33]; //假定增量序列不超过2^32
    //定义Hibbard增量序列
    for(k=1; k<33; k++){
        t = 2 * t + 1; //增量序列项
        if(t < N){
            D[k] = t;
        }else{
            break;
        }
    }
    //进行k-1(增量序列的个数)趟插排
    for(p=k-1; p>=1; p--){
        for(i=D[p]; i<N; i++){
            temp = A[i];
            for(j=i-D[p]; j>=0 && temp<A[j]; j-=D[p]){
                A[j+D[p]] = A[j];
            }
            A[j+D[p]] = temp;
        }
    }

    return;
}

1.5 堆排序

主要思想:将待排数组构建成一个最大堆,将堆顶最大元素换到后面,然后堆容量减1;类似进行N-1次操作即可。

完整代码

```c++
void Perc_Down(int A[], int N, int i){ //在由N个结点组成的完全二叉树中,下滤第i个结点(从第0个结点算起),暂不做有效性检查
int parent, child, temp;
temp = A[i];

for(parent=i, child=2*parent+1; child A[child]) child++; //若右儿子存在,且数值更大,则child为右儿子
if(temp =0; i--){
Perc_Down(A, N, i); //在由N个结点组成的完全二叉树中,下滤结点i
}
//开始从后向前进行排序,注意每次交换后下滤时排除掉刚交换的最后一个
for(i=N-1; i>0; i--){
if(A[0] > A[i]){
Swap_Two(&A[0], &A[i]);
Perc_Down(A, i, 0);
}
}

return;
}
```

1.6 快速排序

主要思想:分治思想。选一基准元素,依次将剩余元素中小于该基准元素的值放置其左侧,大于等于该基准元素的值放置其右侧;然后,取基准元素的前半部分和后半部分分别进行同样的处理;以此类推,直至各子序列剩余一个元素时,即排序完成。

注意:对于小规模数据(n<100),快排由于用了递归,其效率可能还不如插排。因此通常可以定义一个阈值,当递归的数据量很小时停止递归,直接调用插排。

完整代码

```c++
void Quick_Sort(int A[], int N){ //快速排序接口
Quick_Sort_Core(A, 0, N-1);
return;
}

void Quick_Sort_Core(int A[], int left, int right){ //快排递归程序
if(left >= right) return; //若待排序列不到2个元素,则直接返回
int low, high, mid, temp;

//在三个点(端点和终点)中找出中间值,作为划分点。(也可以直接将left作为划分点,但若待排序列基本有序时就容易退化成冒泡)
mid = (left + right) / 2;
if(A[left] > A[mid]) Swap_Two(&A[left], &A[mid]);
if(A[left] > A[right]) Swap_Two(&A[left], &A[right]);
if(A[mid] > A[right]) Swap_Two(&A[mid], &A[right]);
if(mid == left) return; //若待排序列只有两个元素,此时已排好序,直接返回
temp = A[mid]; //保存划分点
//至此三个点已有序
//将空格移至left右侧,开始启动大循环
A[mid] = A[left+1];
low = left + 1;
high = right - 1;
while(low = temp)
high--;
A[low] = A[high];
while(low

1.7 归并排序

主要思想:类似两个有序链表的合并,每次两两合并相邻的两个有序序列,直至整个序列有序。

完整代码

```c++
void Merge_Sort(int A[], int N){ //归并排序
int len = 1; //有序序列的长度
int i, start, count = 0;
int *temp, *A1, *A2;
temp = malloc(N * sizeof(int)); //定义一个数组,作为A的副本,用于相互倒腾

while(len start1 && end>=start2
int end1, end2, p;

end1 = start2 - 1;
end2 = end;
p = start1; //p指向A2中起始位置
//开始合并
while((start1

1.8 基排序

主要思想:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。


完整代码
#define Radix 10
#define MaxDigit 4

typedef struct ENode{ //定义元素结点
    int data;
    struct ENode *next;
}*PtrToNode;
typedef struct BucketNode{ //定义桶结点结构
    PtrToNode head;
    PtrToNode tail;
}*Bucket;

void LSDRadix_Sort(int A[], int N){ //基排序
    int i, D; //D为位, d为第D位上的数字(范围为0到9)
    //定义两个大小为Radix的桶,并初始化
    Bucket B1 = malloc(Radix * sizeof(struct BucketNode));
    Bucket B2 = malloc(Radix * sizeof(struct BucketNode));
    for(i=0; i<Radix; i++){
        B1[i].head = B1[i].tail = B2[i].head = B2[i].tail = NULL;   //注意大bug:B1为桶指针,但是B1[i]为结构体
    }
    //将数组中元素按照倒数第D位数字分别挂到桶B1上
    D = 1;
    Transfer_Array_To_Bucket(A, B1, N, D);
    D++;
    //开始相互倒腾,每倒腾一趟就往前看一位
    while(D <= MaxDigit){
        if(D % 2 == 0)
            Transfer_Bucket_To_Bucket(B1, B2, D);
        else
            Transfer_Bucket_To_Bucket(B2, B1, D);
        D++;
    }
    //倒腾结束后将桶中的元素依次倒入数组A中
    if(D % 2 == 0)
        Transfer_Bucket_To_Array(B1, A, N);
    else
        Transfer_Bucket_To_Array(B2, A, N);
    //释放两个桶及其元素
    Free_Bucket(B1);
    Free_Bucket(B2);
    return;
}

void Transfer_Array_To_Bucket(int A[], Bucket B, int N, int D){ //将数组A中元素按照第D位挂到桶B中
    int i, d;
    PtrToNode temp;
    for(i=0; i<N; i++){
        d = Get_Digit(A[i], D); //获取A[i]倒数第D位数字
        temp = malloc(sizeof(struct ENode)); //创建一个新结点并初始化
        temp->data = A[i];
        temp->next = NULL;
        if(B[d].tail == NULL){ //若桶的d位置上为空
            B[d].head = B[d].tail = temp;
        }else{
            B[d].tail->next = temp;
            B[d].tail = temp;
        }
    }
    return;
}

void Transfer_Bucket_To_Array(Bucket B, int A[], int N){ //将桶B1中元素依次倒入数组A中,最多不超过N
    int k, i = 0;
    PtrToNode p;

    for(k=0; k<Radix; k++){
        p = B[k].head;
        while(p != NULL){
            if(i < N){
                A[i++] = p->data;
                p = p->next;
            }else{
                return;
            }
        }
    }
    return;
}

void Transfer_Bucket_To_Bucket(Bucket B1, Bucket B2, int D){ //依次将桶B1中元素按照第D位挂到B2中,
    int k, d;
    PtrToNode p, temp;
    //依次从B1中取下元素结点,根据D位数字挂到B2上
    for(k=0; k<Radix; k++){
        p = B1[k].head;
        while(p != NULL){
            //从B1中取该元素,并获取第D位数字
            temp = p;
            p = p->next;
            d = Get_Digit(temp->data, D); //获取该结点第D位数字
            //将该元素挂到B2上
            temp->next = NULL; //肯定作为本次转移的尾结点
            if(B2[d].head == NULL){ //若B2[d]为空位置
                B2[d].head = B2[d].tail = temp;
            }else{ //若B2[d]不为空位置,则将temp结点接到尾结点上
                B2[d].tail->next = temp;
                B2[d].tail = temp;
            }
        }
        //取完B1[k]上的所有结点后,设置其首尾结点指针为空
        B1[k].head = B1[k].tail = NULL;
    }

    return;
}

int Get_Digit(int X, int D){  //获取X倒数第D位数字
    int k, i;
    for(i=0; i<D-1; i++)
        X = X / Radix;
    k = X % Radix;
    return k;
}

2 八大排序算法比较

复杂度与稳定性的比较

算法类别 平均时间复杂度 最好情况复杂度 最坏情况复杂度 空间复杂度 稳定性
冒泡排序 \(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(n^{1.5})\) - - \(O(1)\) 不稳定
堆排序 \(O(n log(n))\) \(O(n log(n))\) \(O(n log(n))\) \(O(1)\) 不稳定
快速排序 \(O(n log(n))\) \(O(n log(n))\) \(O(n^2)\) \(O(1)\) 不稳定
归并排序 \(O(n log(n))\) \(O(n log(n))\) \(O(n log(n))\) \(O(n)\) 稳定
基排序 \(O(d*n)\) \(O(d*n)\) \(O(d*n)\) \(O(n)\) 稳定

以下是基于浙大数据结构课练习题测试(09-排序1 排序 (25 分))。

参考:

https://blog.csdn.net/qq_39207948/article/details/80006224

https://www.cnblogs.com/onepixel/articles/7674659.html

https://www.runoob.com/w3cnote/sort-algorithm-summary.html

https://github.com/francistao/LearningNotes/blob/master/Part3/Algorithm/Sort/%E9%9D%A2%E8%AF%95%E4%B8%AD%E7%9A%84%2010%20%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93.md

原文地址:https://www.cnblogs.com/inchbyinch/p/11630388.html

时间: 2024-08-28 00:25:26

八大排序算法及其比较的相关文章

谈谈八大排序算法问题

排序算法可以说是算法的入门以及算法学习阶段的基石,排序算法显得那么的基础又是非常重要的一种算法.排序算法常常作为一些高阶算法的数据处理中间过程在实际的问题处理中被应用的最为广泛,因此算法选将阶段就从八大排序算法开始.在本节内容中既可以看到一般性的比如插入排序,冒泡排序等基础算法又可以看到比如基数排序,位图排序,快速排序等等比较难理解的算法,算法之门从排序算法说起. 1.插入排序 插入排序算法的原理很简单,默认A中有一部分数据已经排好序了,后续只要从没有排好序的序列里面每拿出一个数字就在排好序的序

八大排序算法源码 + 耗时长度比较(看到好东西转下)

八大排序算法的排序时间长度的比较,测试数据10000000时部分结果如下 输入测试数据长度: 10000000数据初始化中...数据初始化完成!        堆排序用时:    8秒 499毫秒      快速排序用时:   22秒  35毫秒      归并排序用时:   34秒 473毫秒 另外五种排序本人并未等待结果,读者可自行测试 测试时请注意内存消耗,以免数据太大,内存不够,可自行测试单一算法以便增加可测试数据数目 #include <iostream> #include <

数据结构与算法之——八大排序算法

附:关于这个主题,网上好的文章已经数不胜数,本篇是整合后的文章. 正文: 一.概述 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 本文所指八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 二.排序算法详述 1.

[Data Structure] 八大排序算法

排序有内部排序和外部排序之分,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存.我们这里说的八大排序算法均为内部排序. 下图为排序算法体系结构图: 1. 直接插入排序(Straight Insertion Sort ) 基本思想:将待排序的无序数列看成是一个仅含有一个元素的有序数列和一个无序数列,将无序数列中的元素逐次插入到有序数列中,从而获得最终的有序数列. 算法流程: 1)初始时, a[0]自成一个有序区, 无序区为a[1

(转)详解八大排序算法

概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到

排序算法(九)——八大排序算法总结

八大排序算法的稳定性及复杂度总结如下: 选择排序算法准则 每种排序算法都各有优缺点.因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用. 影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的.相反,有时平均时间复杂度高的算法可能更适合某些特殊情况.同时,选择算法时还得考虑它的可读性,以利于软件的维护.一般而言,需要考虑的因素有以下四点: 1.待排序的记录数目n的大小: 2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小: 3.关键字的结构及其分布情况:

八大排序算法学习笔记:冒泡排序

冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法. 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端. 算法原理: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 针对所有

八大排序算法学习笔记:插入排序(一)

插入排序     包括:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序).属于稳定排序的一种(通俗地讲,就是两个相等的数不会交换位置) . 直接插入排序: 1.算法的伪代码(这样便于理解):     INSERTION-SORT (A, n)             A[1 . . n] for j ←2 to n do key ← A[ j] i ← j – 1 while i > 0 and A[i] > key do A[i+1] ← A[i]

八大排序算法学习笔记:插入排序(二分插入排序)

二分插入排序   也称折半插入排序, 1.基本思想:设数列[0....n]分为两部分一部分是[0...i]为有序序列,另一部分是[i+1.....n]为无序序列,从无序序列中取一个数 x ,利用二分查找算法找到 x 在有序序列中的插入位置并插入,有序序列还是有序的,接下来重复上述步骤,直到无序序列全部插入有序序列 ,这是整个序列只剩下有序序列即有序了. 2.代码:    3.复杂度: 用二分插入排序所要进行的总比较次数为O(lgn),当n较大时,比直接插入排序的最大比较次数小得多,但大于最小比较

算法基础——经典八大排序算法的Java及Python实现

概述 八大排序算法不用多说了,程序员算法基础必须要掌握的,现在总结一下加深记忆.下图是这八大排序算法的分类.名称.时间空间复杂度,以及稳定性. 代码 以下是经典八大排序算法的Java及Python代码,都是基于经典算法书籍<算法导论>里的伪代码实现的,我在关键语句部分附上了注释. 按照上图中的顺序分别介绍八大排序算法的实现(升序),前面是Java,后面是Python.Java的排序函数写在了一个类里,Python的排序函数则直接写出来了. 直接插入排序 public class InsertS