0基础学算法 第二弹 排序

Posted qj-network-box

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0基础学算法 第二弹 排序相关的知识,希望对你有一定的参考价值。

    大家好啊,这是0算法基础学算法系列第二篇,上次我在第一弹里讲了关于流程图的内容,我寻思着,这次讲些什么好呢,于是我决定,教大家一个很基础的算法,那就是排序,排序有很多方法,如果你有更多方法请在评论区里留言哦。

排序在程序中特别实用,常用的有快速排序,桶排序,冒泡排序,插入排序等等,在这里我不建议使用冒泡排序或者插入排序,建议桶排序和快速排序,这两个排序非常实用,时间复杂度低,理解起来也很容易,首先,你先思考一下,怎么用程序进行排序,然后你再来看看你的思路合理不合理,最后试着用程序实现它,实现后你就有了自己的方法,没实现的话也没关系,这篇博文会讲的,实现的也请耐心的看完,说不定有比你的方法更好的呢qaq

接下来切入正题

一,冒泡排序

    英文bubble sort,其执行方法和他的名字一样,就是像泡泡不断向上判断,自己是否大于接下来的泡泡,如果大于,就差到前面去,类似于泡泡一样向水平面冒,但是由于操作不是很简单,实现起来很复杂,造成很多情况下都会以超时结束,也就是TLE,最坏的时候,时间复杂度甚至可以达到O(n^2),是不是被震撼到了,也就是说录入100个数,他却要10000的时间,虽然不是很推荐,但还是要介绍一下,毕竟也是很经典嘛

    

#include <iostream>
using namespace std;
template<typename T>
//整数或浮点数皆可使用
void bubble_sort(T arr[], int len)
{
    int i, j;  T temp;
    for (i = 0; i < len - 1; i++)
        for (j = 0; j < len - 1 - i; j++)
        if (arr[j] > arr[j + 1])
        {
            temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
        }
}
int main()
{
    int arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    bubble_sort(arr, len);
    for (int i = 0; i < len; i++)
        cout << arr[i] <<  ;
 
    cout << endl;
 
    float arrf[] = { 17.5, 19.1, 0.6, 1.9, 10.5, 12.4, 3.8, 19.7, 1.5, 25.4, 28.6, 4.4, 23.8, 5.4 };
    len = (int) sizeof(arrf) / sizeof(*arrf);
    bubble_sort(arrf, len);
    for (int i = 0; i < len; i++)
        cout << arrf[i] <<  ;
 
    return 0;
}

上面这个例子就是非常经典的纯冒泡排序,通过程序可以清楚地看到这给了两组数据,分别是整型和浮点型,原理是从第一个数开始,如果他大于后面的数,就往后一个,如果小于他则在他前面停住,然后接着看下一个数,做同样的操作,因为有一个嵌套循环,所以时间复杂度是O(n*n),这里我也不好多说,毕竟萝卜白菜各有所爱,有时候冒泡还可以做出不可思议的陈旧呢

二,插入排序

    插入排序和冒泡排序很相似,连时间复杂度都是相同的,也一样没人用不常用,毕竟时间复杂度O(n^2)的排序很容易超时,但还是介绍一下吧

    插入排序(Insertion sort)是一种简单直观且稳定的排序方法。如果有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,插入排序的稳定程度是毋庸置疑的,但是出于时间复杂度而言,还有更多简便的排序方法兴起令插入排序逐渐被埋没了,还是像冒泡排序一样,发一个代码来说明吧

#include<iterator>
template<typename biIter>
void insertion_sort (biIter begin,biIter end)
{
    typedef typename std::iterator_traits<biIter>::value_type value_type;
    biIter bond=begin;
    std::advance(bond,1);
    for(;bond!=end;std::advance(bond,1))
    {
        value_type key=*bond;
        biIter ins=bond;
        biIter pre=ins;
        std::advance(pre,-1);
        while(ins!=begin&&*pre>key)
        {
            *ins=*pre;
            std::advance(ins,-1);
            std::advance(pre,-1);
        }
        *ins=key;
    }
}

上面的代码可以说明,插入排序也是和他的名字一样,就是不断抽出数值然后插入到合适的地方去,来达到排序的目的,超高时间复杂度,超难理解的原理,唯一的优点是稳定,但是弊大于利,所以渐渐也就不常用了

接下来是超超超推荐环节

一,快速排序

    快速排序相信大家都不陌生,因为比起冒泡和插入,快速排序会方便很多,也会快很多,快排英文quick sort,大致原理是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,由于这种排序方法好理解,实现起来又简单,更重要的是时间复杂度只有O(nlog2n),因此,该排序方法被认为是目前最好的一种内部排序方法,被广泛使用,值得一提的是,他是基于冒泡排序的一种优化排序,所以有的时候时间复杂度还是会飙到O(n^2),接下来还是展示一个代码吧

#include <iostream>
 
using namespace std;
 
void Qsort(int arr[], int low, int high){
    if (high <= low) return;
    int i = low;
    int j = high + 1;
    int key = arr[low];
    while (true)
    {
        /*从左向右找比key大的值*/
        while (arr[++i] < key)
        {
            if (i == high){
                break;
            }
        }
        /*从右向左找比key小的值*/
        while (arr[--j] > key)
        {
            if (j == low){
                break;
            }
        }
        if (i >= j) break;
        /*交换i,j对应的值*/
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    /*中枢值与j对应值交换*/
    int temp = arr[low];
    arr[low] = arr[j];
    arr[j] = temp;
    Qsort(arr, low, j - 1);
    Qsort(arr, j + 1, high);
}
 
int main()
{
    int a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};
 
    Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*这里原文第三个参数要减1否则内存越界*/
 
    for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        cout << a[i] << "";
    }
     
    return 0;
}

根据代码和注释可以看到,这期是非常眼熟,对,就是冒泡,他和冒泡的差别绝对比你想象中要小得多,因为和他同样,都是一个一个的扫描,不断往后插入,但和冒泡不同的是,他会进行几次划分,快速排序的一次划分算法从两头交替搜索,直到low和high重合,因此其时间复杂度是O(n);而整个快速排序算法的时间复杂度与划分的趟数有关,由于冒泡是单向的,但是快速排序可以通过划分大大地节省时间,这就是为什么他可以成为最好的内部排序方法。

二,桶排序

    其实我个人是非常喜欢桶排序的,因为它太简单了,理解起来又容易,不像快拍看起来那么复杂,我认为他应该是最直观的

    桶排序 (Bucket sort)或所谓的箱排序,大致的工作原理就是定义一个数组变量,然后录入要排序的数,在定义一个数组作为桶,以每个录入的数位下标,让该下标的桶的值+1,而且,他有时候比快速排序还要快很多,因为它只用根据下标加值就行了,所以他的时间复杂度是O(n),是不是很快呢,他非常快可以说,接下来我们还是结合代码说一下吧,注意,我已给出两个桶排序,一个是简易版,一个是真正的桶排序。

#include<stdio.h>
int main()
 {
     int buc[6]={0};        //定义木桶并初始化,因为我们要在012345个分数中排序所以要定义6个桶。
     int i,j,t,n;           
     scanf("%d",&n);        //输入一个数n+1,表示接下来有n个数要排序
     for(i=1;i<n;i++)       //循环读入n个数,并进行排序
     {
         scanf("%d",&t);    //把每一个数读到变量t中;
         buc[t]++;          //进行计数;
     }
     for(i=0;i<=5;i++)      //判断0到5的桶
         for(j=1;j<=buc[i];j++)//出现了几次就将桶的编号打印几次;
            printf("%d",i);
 
}

————————————————
版权声明:本文为CSDN博主「颜十三郎」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36156580/article/details/91415941

??注意,这个的代码非原创,但博文原创(主要是实在没时间自己去写一个了)

这就是简易版的桶排序,可以说非常简单,就是将对应下标的桶里面数值加一,输出的时候就只要输出桶里的下标桶里值次,所以运行起来很简单,就让他占据的一定的地位

最后,肯定要把正规的桶排序代码放出来了,留个思考题吧,好好想想为什么桶排序要这么做吧,记得自己去运行一下哦

#include<iostream>
usingnamespace std;
int a[]={1,255,8,6,25,47,14,35,58,75,96,158,657};
const int len=sizeof(a)/sizeof(int);
int b[10][len+1]={0};//将b全部置0
void bucketSort(int a[]);//桶排序函数
void distribute Elments(int a[],int b[10][len+1],int digits);
void collectElments(int a[],int b[10][len+1]);
int numOfDigits(int a[]);
void zeroBucket(int b[10][len+1]);//将b数组中的全部元素置0
int main()
{
cout<<"原始数组:";
for(int i=0;i<len;i++)
cout<<a[i]<<",";
cout<<endl;
bucketSort(a);
cout<<"排序后数组:";
for(int i=0;i<len;i++)
cout<<a[i]<<",";
cout<<endl;
return 0;
}
void bucketSort(int a[])
{
int digits=numOfDigits(a);
for(int i=1;i<=digits;i++)
{
distributeElments(a,b,i);
collectElments(a,b);
if(i!=digits)
zeroBucket(b);
}
}
int numOfDigits(int a[])
{
int largest=0;
for(int i=0;i<len;i++)//获取最大值
if(a[i]>largest)
largest=a[i];
int digits=0;//digits为最大值的位数
while(largest)
{
digits++;
largest/=10;
}
return digits;
}
void distributeElments(int a[],int b[10][len+1],int digits)
{
int divisor=10;//除数
for(int i=1;i<digits;i++)
divisor*=10;
for(int j=0;j<len;j++)
{
int numOfDigist=(a[j]%divisor-a[j]%(divisor/10))/(divisor/10);
//numOfDigits为相应的(divisor/10)位的值,如当divisor=10时,求的是个位数
int num=++b[numOfDigist][0];//用b中第一列的元素来储存每行中元素的个数
b[numOfDigist][num]=a[j];
}
}
void collectElments(int a[],int b[10][len+1])
{
int k=0;
for(int i=0;i<10;i++)
for(int j=1;j<=b[i][0];j++)
a[k++]=b[i][j];
}
void zeroBucket(int b[][len+1])
{
for(int i=0;i<10;i++)
for(int j=0;j<len+1;j++)
b[i][j]=0;
}

三,无敌快排sort

    如果上面的排序方式你都不能接受,我还有杀手锏呢,那就是sort,sort是一个函数,不需要自己编写,只要加一个头文件 <algorithm>,就可以快乐的直接用sort了,真是的,sort函数不香吗,介绍一下他的用法吧

sort(数组名,数组名+数字个数)

注意一下,上面的数组默认为第一位下标为0,如果想下标为一,那么就必须写成这样

sort(数组名+1,数组名+数字个数+1)

以此类推,要是第一位下标为2,为3也可以进行相似的操作

今天就到这里了,如果你觉得不错,请点赞??,关注?,以后还会持续更新0算法基础学算法系列,如果期待下一弹,麻烦评论一下期待.ing,谢谢你能沉下心把这篇博文看完,留几个简单的作业吧,都是洛谷上的题目,还没洛谷的可以先去注册一个,网址luogu.org,最后再强调一下,一定要点在关注啊!

课后练习:

洛谷更新了提单功能,这是专门针对排序练习的,多练习才会更熟练啊https://www.luogu.com.cn/training/107

ps:我洛谷名叫schein,入坑时间不久,所以没做多少题,不知道哪位大佬可以关注我一下呢??

 

以上是关于0基础学算法 第二弹 排序的主要内容,如果未能解决你的问题,请参考以下文章

分享几个实用的代码片段(第二弹)

从0到1学算法选择排序

基于python Knn 算法识别手写数字,计算准确率 ——第二弹

暴力美学2—BFS广度优先搜索(算法图解第二弹)

文末福利算法萌新如何学好动态规划(第二弹)

AI面试题第二弹(神经网络基础)