排序算法

作者:whj95

前言及总览

前言

排序舞:以下视频是由Sapientia University创作的,比较生动形象地表现了排序的步骤,供大家预习及理解
  ①冒泡排序视频
  ②选择排序视频
  ③插入排序视频
  ④希尔排序视频
  ⑤归并排序视频
  ⑥快速排序视频

可视化排序
魔性的15种排序方法
  来源于B站,可以等看完下文了解了这些排序之后看,也可以参考视频中的绿字弹幕解说看,初步对这些排序大概有个概念。
  具体关心左上角排序名称、主图的排序速度、以及上方居中的数据量

数据可视化
  强无敌的可视化过程

总览

  这里写图片描述
  基本排序方法:①选择排序;②冒泡排序;③插入排序
  进阶排序方法:希尔排序;
  高级排序方法:①归并排序;②堆排序;③快速排序
  线性排序方法:①计数排序;②基数排序;③桶排序

交换排序

选择排序

定义
找到当前元素中最小的那个放在前面

可视化过程
这里写图片描述

代码实现
一层增初值,最小值与下标默认当前,末尾交换当前与最小;二层移光标比大小,记下标

void select_sort(int a[],int n)
{
    for(int i = 0; i < n; i++)
    {
        int min = a[i]; 
        int index = i;
        for(int j = i + 1; j < n; j++)
        {
            if(a[j] < min)
            { 
                 min = a[j]; 
                 index = j;
            }       
        }      
    if (index != i)  
            swap(a[i], a[index]);
    }
}

优点:
没有优点让你考虑用它,就是它最大的优点
缺点:
不稳定
②平均时间为O(n 2 )。因此为了改进寻找最小元引出了用最小堆的堆排序。

冒泡排序 (Bubble sort)

定义
顾名思义,相邻两者两两PK,将下方的数不断“上浮”。

可视化过程
这里写图片描述

这里写图片描述

代码实现

void bubble_sort(int a[],int n)
{
    for(int i = n - 1; i >= 0; j--)//每次循环最下面的一定已经排好
    {
        bool flag = 0;//优化冒泡排序,若从头到尾没有进行过交换则已经排好,所以退出循环
        for(int j = 0; j < i; i++)
            if(a[j] > a[j+1])
            {
                swap(a[j],a[j+1])
                flag = 1;
            }
        if(flag == 0)
            break;
    }
}

优点:
稳定。一次确定位置。可实现部分排序
缺点:
交换相邻元素的算法不高效,会与逆序对有关,复杂度为O(n + i),其中i为逆序对个数,而i的平均个数为n(n-1)/4,所以平均时间为O(n 2

插入排序

定义
类似玩扑克牌,把最后一张牌往大小合适的位置插入

可视化过程
这里写图片描述

这里写图片描述

这里写图片描述

代码实现
一层增初值;二层找到比当前元素小的,腾地插空

void insert_sort(int a[],int n)
{
    for(int i = 1; i < n; i++)//默认已有一个数
    {
        tmp = a[i];
        for(int j = i; j > 0 && a[j] > tmp; j--)
            a[i] = a[i-1];
        a[i] = tmp;
    }
}

优点:
稳定
②比较次数和移动次数与初始排列有关;最好情况下只需O(n)时间
缺点:
不够快,不过算是基本排序中最好的算法了

希尔排序

定义
步长不断变化的直接插入排序

可视化过程
这里写图片描述

这里写图片描述

这里写图片描述
代码实现
一层定步长,二层增初值,三层腾地插

void hill_sort(int a[],int n)
{
    for(int step = n/2; step > 0; step/=2)
        for(int i = step; i < n; i++)//下面都是插入排序
        {
            int tmp = a[i];
            for(int j = i; j >= step && a[j-step] > tmp; j-=step)
                a[i] = a[i-step];
            a[i] = tmp;
        }
}

优点:克服了相邻元素比较的拘束,打破了排序必O(n 2 )的断言
缺点:
不稳定。由于二分不互质,会导致有几种步长比较没用。以此改进的有奇数增量序列,Hibbard增量序列,Sedgewick增量序列等希尔排序。

归并排序 (Merge sort)

定义
分治经典例子。可以理解为国家队想挑选队员出征国际赛场,首先会从各省队中挑选,省队又从各市队挑选

可视化过程
如何对38,27,43,3,9,82,10进行归并排序
这里写图片描述

代码实现

void Merge_sort(int low, int high)
{
    int mid = (low + high) / 2;
    if (low == high)
        return;
    Merge_sort(low, mid);//前半部分
    Merge_sort(mid + 1, high);//后半部分
    int tal1 = low, tal2 = mid + 1, tal = low;
    while ((tal1 <= mid) && (tal2 <= high))//前半部分和后半部分都未遍历完
    {
        if (a[tal1] < a[tal2])
            b[tal++] = a[tal1++];
        else
            b[tal++] = a[tal2++];
    }
    while (tal1 <= mid)//只剩前半部分,直接放入目标数组
        b[tal++] = a[tal1++];
    while (tal2 <= high)//只剩后半部分,直接放入目标数组
        b[tal++] = a[tal2++];
}

复杂度分析:

T(N) = 2T(N/2)+cN = 2[2T(N/2^2) + cN/2] + cN = … = 2^kO(1) + ckN   
∵N/2^k = 1   
∴k = logN   
即T(N) = O(NlogN)

堆排序 (Heap sort)

定义
利用二叉树结构,结合了插入排序(原地排序)和归并排序(O(nlgn))各自的优点

可视化过程
我是图

流程
维护堆maxheapify
建堆bulid_maxheap
堆排序heap_sort

代码实现

                                        /*维护堆maxheapify*/
/*从上往下追溯是否满足堆结构,不满足进行交换,对交换过的子树重复递归过程。递归在无交换节点时终止*/
void maxheapify(int a[], int parent, int n)
{
    int l,r,largest;//l,r分别表示左右子节点下标,largest存储最大值下标
    l = parent * 2,r = parent * 2 + 1;
    if(l <= n && a[l] > a[parent])//有左孩子&&大于根节点
        largest = l;
    else
        largest = parent;
    if (r <= n && a[r] > a[parent])
        largest = r;
    if(largest != parent)//如果发生了交换,需递归调整交换后的子树
    {
        swap(a[largest],a[parent]);
        maxheapify(a,largest);
    }
}

void swap(int a,int b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

                                        /*建堆bulid_maxheap*/
/*自底向上,循环调用maxheapify调整数组为堆*/
void bulid_maxheap(int a[])
{
    for(int i = floor(n/2); i >= 1; --i)
        maxheapify(a, i, sizeof(a)/sizeof(int));
}
                                        /*堆排序heap_sort*/
/*交换最末叶节点和根节点值确定最大值,每次交换紧跟最大堆化,注意要排除已排序的最末叶节点*/                    
void heap_sort(int a[],int n)
{
    bulid_maxheap(int a[]);
    for(int i = n; i >= 2; --i)
    {
        swap(a[1],a[i]);
        maxheapify(a, 1, i - 1);
    }
}

复杂度分析:

①维护堆maxheapify
T(N) ≤ T(2N/3)+c ≤ [T(N*(2/3)^2) + c)] + c ≤ … ≤kO(1) + kc  
∵N*(2/3)^k = 1   
∴k = logN   
→T(N) = O(logN)

②建堆bulid_maxheap
由于调用了n次维护过程,所以时间复杂度为ON*logN),其实更为精确的解为ON)

③堆排序heap_sort
建堆+n次维护,所以时间复杂度为ON)+ ON*logN)=O(NlogN)

快速排序 (Quicksort)

定义
①取序列中的元素作为枢轴(pivot),将整个对象序列划分为左右两个子序列。然后递归执行。
②左侧子序列中所有对象小于或等于枢轴,右侧子序列中所有对象大于枢轴
每一趟枢轴位置即为最终位置

可视化过程
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

代码实现

/*下面代码是以第一个元素为枢轴。*/
/*而实际“三者取中”比较好,不容易退化*/
void qsort(int a[],int left,int right)
{
    int last;
    if(left >= right)
        return;
    swap(left,(left + right) / 2);
    last = left;
    for(int i = left + 1; i <= right; ++i)
        if(a[i] < a[left])
            swap(++last,i);
    swap(left,last);
    qsort(a,left,last - 1);
    qsort(a,last + 1,right);
}

void swap(int i,int j)
{
    int tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
}

优点:
①快。对于无序且数量大的序列非常快,O(nlogn)的速度无愧于“快速”二字。当枢轴选得好,其划分均匀,递归树的高度平衡
②每一趟总能至少确定一个元素的最终位置。
缺点:
不稳定
需要额外空间S(log 2 n),最坏时需要O(n)
③序列为正序或逆序时其退化为斜树,时间复杂度变O(n 2 ,因此当比较数量小时改用直接插入排序,或是堆排序。而使用堆排序改进的方法称为内省排序

这里写图片描述
  

线性时间排序

计数排序 (Counting sort)

定义
空间代时间,把乱序的离散数字映射到一张有序的连续表上

代码实现

void counting_sort(int a[], int b[], int n)  //所需空间为 2*n+k
{  
       int c[n];
       memset(c,0,sizeof(c)/sizeof(int)); 

       //统计数组中,每个元素出现的次数    
       for(int i = 0; i < n; ++i)
               c[a[i]] = ++c[a[i]];  

       //统计数组计数,每项存前N项和
       for(int i = 1; i <= n; ++i)
               c[i] += c[i-1];  

       //将计数排序结果转化为数组元素的真实排序结果
       for(int i = n - 1 ; i >= 0; --i)
       {  
           b[c[a[i]]] = a[i];
           --c[a[i]];
       }  
}  

优点:
当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。
缺点:
①由于计数数组的长度取决于待排序数组中数据的范围,这意味着计数排序需要大量内存。
②无法排序人名

上述两个缺点,引出了基数排序的方法。

基数排序(radix sort)

定义
简单来讲就是将数字拆分为个位十位百位,每个位依次排序。

可视化过程
千言万语不如一张图
这里写图片描述

基数排序分为MSD(Most Significant Dight)和LSD(Least Significant Dight),上述的方法即为LSD。不同于正常的比较思维,通常LSD排序更为常用,因为LSD不需要分堆对每堆单独排序,所以往往比MSD简单且开销小。

void radix_sort(int arr[],int begin,int end,int d)  
{    
    const int radix = 10;   
    int count[radix], i, j; 

    int *bucket = (int*)malloc((end-begin+1)*sizeof(int));  //所有桶的空间开辟   

    //按照分配标准依次进行排序过程
    for(int k = 1; k <= d; ++k)  
    {  
        //置空
        for(i = 0; i < radix; i++)  
            count[i] = 0;                   
        //统计各个桶中所盛数据个数
        for(i = begin; i <= end; i++) 
           count[getdigit(arr[i], k)]++;
        //count[i]表示第i个桶的右边界索引
        for(i = 1; i < radix; i++) 
            count[i] = count[i] + count[i-1];
        //把数据依次装入桶(注意装入时候的分配技巧)
        for(i = end;i >= begin; --i)        //这里要从右向左扫描,保证排序稳定性   
        {    
            j = getdigit(arr[i], k);        //求出关键码的第k位的数字, 例如:576的第3位是5   
            bucket[count[j]-1] = arr[i]; //放入对应的桶中,count[j]-1是第j个桶的右边界索引 
            --count[j];               //对应桶的装入数据索引减一  
        } 

        //注意:此时count[i]为第i个桶左边界  

        //从各个桶中收集数据
        for(i = begin,j = 0; i <= end; ++i, ++j)  
            arr[i] = bucket[j];       
    }     
}  

基数排序对每一位的排序都要使用计数排序。假设带排数字有n个,每个数字均为d位数,每一数位有k个取值,则其时间复杂度为O(d*(n+k))

桶排序 (Bucket sort)

定义
桶排序也是计数排序的变种,它将计数排序中相邻的m个”小桶”整合到一个”大桶”中,对每个大桶内部进行排序,最后合并大桶(一般用链表实现)。

伪代码
void bucket_sort(int a[], int n)
{
    int b[n];
    memset(b,0,sizeof(b)/sizeof(int));
    for(int i = 0; i < n; ++i)
        insert a[i] into b[floor(na[i])];
    for(int i = 0; i < n; ++i)
        sort b[i];
    concatenate b[0],b[1],...,b[n-1] together
}

优点:
①桶排序的平均时间复杂度为O(N+C),其中C为桶内快排的时间复杂度。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。
②桶排序是稳定的
缺点:
桶排序的空间复杂度为S(N+M),当输入数据非常庞大而桶的数量也非常多时,空间代价相当之高。

C++中的sort

#include<algorithm>

/*第一个参数是首地址,第二个是末尾地址,第三个是比较函数*/
std::sort(a, a+n, compare_function);

/*compare_function1自定义按平方从小到大返回*/
bool compare_function1(int i, int j) 
{ 
    return (i*i<j*j); 
} 

/*compare_function2自定义按类的关键字i从小到大排序*/
class obj
{
public: 
    int i;
    char ch
}; 
bool compare_function2(obj a, obj b) 
{ 
    return a.i < b.i; 
} 
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值