一文详解十大排序用法(插入排序、希尔排序、堆排序、桶排序、归并排序....)

在这里插入图片描述

一、冒泡排序

算法思想

相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。

时间复杂度O( n 2 n^2 n2) ( markdown编辑器 n 2 n^2 n2 用$ n^2 $表示)

适用:冒泡排序适用于数据量很小的排序场景

C/C++

#include<bits/stdc++.h>
using namespace std;
int a[10]={2,5,3,1,9,6,8,7,0,10};
void sort1()
{
    for(int i=0;i<10;i++)
    {
        for(int j=i+1;j<10;j++)
        {
            if(a[i]>a[j])//从小到大
            {
                swap(a[i],a[j]);
            }
            
            
            if(a[i]<a[j])//从大到小
            {
                swap(a[i],a[j]);
            }
        }
    }
    for(int i=0;i<10;i++)
        printf("%d ",a[i]);
}
int main()
{
    sort1();
    return 0;
}

Java

public static void main (String[] args)  {
      int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
      int l=arr.length;
      int t;
      for(int i=0;i<l;i++) {
          for(int j=i+1;j<l;j++){
              if(arr[i]>arr[j])
              {
                 t=arr[i];
                 arr[i]=arr[j];
                 arr[j]=t;
              }
          }
      }
      for(int i=0;i<l;i++)
          System.out.print(arr[i]+" ");
    }

二、选择排序

算法思想:

先定义第一个元素为最小(大)值,然后再从剩余的未元素中寻找到最小(大)元素,继续放在起始位直到遍历结束

时间复杂度为O( n 2 n^2 n2)

适用: 适用于数据量很小的排序

C/C++

#include<bits/stdc++.h>
using namespace std;
int a[10]= {2,5,3,1,9,6,8,7,0,10};
void sort1()
{
    int minn;
    for(int i=0; i<10; i++)
    {
        minn=i;
        for(int j=0; j<10; j++)
        {
            if(a[minn]>a[j])
            {
                minn=j;
                swap(a[i],a[j]);
            }
        }
    }
    for(int i=0; i<10; i++)
    {
        printf("%d ",a[i]);
    }
}
int main()
{
    sort1();
    return 0;
}

Java

public static void main (String[] args)  {
      int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
      int l=arr.length;
      int t,minn;
      for(int i=0;i<l;i++) {
          minn=i;
          for(int j=0;j<l;j++){
              if(arr[minn]>arr[j])
              { 
                  minn=j;
                 t=arr[i];
                 arr[i]=arr[j];
                 arr[j]=t;
              }
          }
      }
      for(int i=0;i<l;i++)
          System.out.print(arr[i]+" ");
    }

三、插入排序

直接插入排序

算法思想

每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止。

实现步骤

每次从无序部分中取出一个元素,与有序部分中的元素从后向前依次进行比较,并找到合适的位置,将该元素插到有序组当中。

时间复杂度O( n 2 n^2 n2

适用:待排序的元素个数不多(<=50),且元素基本有序

C/C++

#include<bits/stdc++.h>
using namespace std;
int a[10]= {2,5,3,1,9,6,8,7,0,10};
void insertsort()
{
    int t,j;
    //从小到大
    for(int i=1;i<10;i++)
    {
        t=a[i];
        for(j=i-1;i>=0&&a[j]>t;j--)
        {
            a[j+1]=a[j];
        }
        a[j+1]=t;
    }
    for(int i=0; i<10; i++)
    {
        printf("%d ",a[i]);
    }
}
int main()
{
    insertsort();
    return 0;
}

Java

public static void main (String[] args)  {
      int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
      int l=arr.length;
      int t,j;
      for(int i=1;i<l;i++) {
          t=arr[i];
          for(j=i-1;j>=0&&arr[j]>t;j--) {
              arr[j+1]=arr[j];
          }
          arr[j+1]=t;
      }
      for(int i=0;i<l;i++)
          System.out.print(arr[i]+" ");
    }

插入排序的优化有 折半插入排序2-路插入排序

折半插入排序

算法思想

二分思想,将待排序的元素与有序部分的元素比较时,不再一 一比较,采取用二分的方式进行比较

C/C++

#include<bits/stdc++.h>
using namespace std;
int a[10]= {2,5,3,1,9,6,8,7,0,10};
void insertsort()
{
    int left,right,mid,t;
    for(int i=1;i<10;i++)
    {
        left=0;
        right=i-1;
        t=a[i];
        //找到合适的插入位置
        //如果中间位置元素的值大于要插入的值,则查找区间更改为小的区间
        //否则,将区间改为大的区间
        while(left<=right)
        {
            mid=(left+right)/2;
            if(a[mid]>t)
                right=mid-1;
            else
                left=mid+1;
        }
        int j;
        for(j=i-1;j>=right+1;j--)
        {
            a[j+1]=a[j];
        }
        a[j+1]=t;
    }
    for(int i=0; i<10; i++)
    {
        printf("%d ",a[i]);
    }
}
int main()
{
    insertsort();
    return 0;
}

Java

public class Main {
    public static void main (String[] args)  {
      int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
      int l=arr.length;
      int left,right,mid,t,j;
      for(int i=1;i<l;i++) {
          left=0;
          right=i-1;
          t=arr[i];
          while(left<=right){
              mid=(left+right)/2;
              if(arr[mid]<t)
                  left=mid+1;
              else
                  right=mid-1;
          }
          for(j=i-1;j>=right+1;j--){
              arr[j+1]=arr[j];
          }
          arr[j+1]=t;
      }
      for(int i=0;i<l;i++)
          System.out.print(arr[i]+" ");
    }
}

四、快速排序

快速排序实际上是在冒泡排序基础上的递归分治,快速排序每一轮挑选一个基准元素,并让其它比它大的元素移动到数列一边,比他小的元素移动到另外一边从而把数组拆解为两部分

实现图解

选择一个元素作为基准元素,并将左右两边设置为left指针和right指针,基准元素一般选择第一个元素

在这里插入图片描述
判断a[right]与基准元素的大小,如果a[right]大于基准元素,则right指针左移,如果小于基准元素,则a[left]=a[right]
然后再判断a[left]与基准元素的大小,如果a[left]小于基准元素,则left指针右移,如果大于基准元素,则a[right]=a[left]

在这里插入图片描述
中间省略一部分while循环图解过程
在这里插入图片描述
最终,left指针会与right指针重合,把基准元素的值赋给left指针所指向的位置上
在这里插入图片描述

一轮while循环结束后,比基准元素小的都在基准元素的左边,比基准元素大的都在基准元素的右边

第一轮while循环最终结果
在这里插入图片描述

第二轮while循环最终结果
在这里插入图片描述
第三轮while循环最终结果
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int a[10]={32,11,23,43,24,12,4,31};
void quicksort(int a[],int start,int end)
{
    int basic=a[start];//基准元素
    //设置left指针和right指针,分别指向数组第一个和最后一个元素
    int left=start;
    int right=end;

    if(start>=end)
        return ;
    while(left<right)
    {
        //right指针指向所在位置的值大于等于基准元素的值
        while(left<right&&a[right]>=basic)
        {
           right--;
        }
        //right指针指向所在位置的值小于基准元素的值
        a[left]=a[right];
        //left指针指向所在位置的值小于等于基准元素的值
        while(left<right&&a[left]<=basic)
        {
            left++;
        }
        //left指针指向所在位置的值大于基准元素的值
        a[right]=a[left];
    }
    a[left]=basic;
    quicksort(a,start,left-1);
    quicksort(a,left+1,end);
}
int main()
{
    quicksort(a,0,7);
    for(int i=0;i<8;i++)
    {
        cout<<a[i]<<" ";
    }
    return 0;
}

五、希尔排序

希尔排序(又称为缩小增量排序)

算法思想:
设待排序的元素的个数为n,首先取一个整数increment(小于序列总数)作为间隔所有距离为increment的元素放在同一个逻辑数组中,在每一个逻辑数组中分别实行直接插入排序,然后缩小间隔increment,重复上述逻辑数组划分和排序,直到最后increment=1,将所有元素放在同一个数组中排序即可

实现步骤

1、选increment。划分逻辑数组,逻辑数组内进行直接插入排序
2、不断缩小increment,继续在逻辑数组内进行插入排序
3、直到increment=1,在包含所有元素的序列内继续直接插入排序

时间复杂度O( n 2 n^2 n2

C/C++

#include<bits/stdc++.h>
using namespace std;
int a[12]= {2,5,3,1,9,6,8,7,0,10};
void shellsort()
{
    //初始化increment增量
    int increment=10,j,t;
    //每次减小increment,直到increment=1
    while(increment>1){
        //增量的取法之一:除以3向下取整后加1
        increment=increment/3+1;
    //对每个按increment间距划分的逻辑数组,实行直接插入排序
        for(int i=increment;i<10;i++)
        {
            if(a[i-increment]>a[i])
            {
                t=a[i];
                j=i-increment;
                //移动元素并寻找位置
                while(j>=0&&a[j]>t){
                    a[j+increment]=a[j];
                    j-=increment;
                }
                //插入元素
                a[j+increment]=t;
            }
        }
    }
    for(int i=0; i<10; i++)
    {
        printf("%d ",a[i]);
    }
}
int main()
{
    shellsort();
    return 0;
}

六、堆排序

堆排序是指利用堆积树这种数据结构所设计的一种排序算法,是选择排序的一种

堆是具有下列性质的完全二叉树

每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

算法思想

将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,就能得到一个有序序列

需要用到的树的性质

假设父节点下标为k(k>0),那么其左孩子下标为2 * k,右孩子下标为2 * k+1

实现堆排序需要解决2个问题

如何将一个无序序列构建成一个堆?
如何在输出堆顶元素后,调整剩余元素成一个新的堆?

时间复杂度也为O(nlogn)

#include<bits/stdc++.h>
using namespace std;
int a[12]={0,3,2,5,6,1,4,7,9,0,8};
void heapadjust(int a[],int s,int m)//一次筛选
{
    int t=a[s];
    for(int j=2*s;j<=m;j=j*2)//沿较大的孩子节点向下筛选
    {
        if(j<m&&a[j]<a[j+1])
            j++;             //j记录较大的下标
        if(t>a[j])
            break;
        a[s]=a[j];
        s=j;
    }
    a[s]=t;
}
void heapsort(int a[],int n)
{
    int t;
    for(int i=n/2;i>0;i--)//通过循环初始化顶堆
    {
        heapadjust(a,i,n);
    }
    for(int i=n;i>0;i--)
    {
        swap(a[1],a[i]);//将顶堆记录与未排序的最后一个记录交换
        heapadjust(a,1,i-1);//重新调整为顶堆
    }
}
int main()
{
    int n=10;
    heapsort(a,n);
    for(int i=0;i<=10;i++)
        printf("%d ",a[i]);
    return 0;
}

七、桶排序

算法思想

假设待排序的元素输入符合某种均匀分布,例如数据均匀分布在[ 0,1)区间上,则可将此区间划分为10个小区间,称为桶,对散布到同一个桶中的元素再排序。

实现步骤

1、先将待排序元素分配n个区间
2、对应数放在对应区间的桶内
3、将桶内的数排序
4、按顺序输出

时间复杂度

平均时间复杂度:O(n + k)
最佳时间复杂度:O(n + k)
最差时间复杂度:O(n ^ 2)
空间复杂度:O(n * k)

注:n是待排序元素的个数、k是分桶的数量

桶排序的时间复杂度,取决与对桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。所以,桶划分越小,桶之间的数据越少,排序所用的时间就越少。但相应的空间消耗就会增大。桶排序最好情况下使用线性时间O(n)

图解
在这里插入图片描述

#include<bits/stdc++.h>
#include<vector>
using namespace std;
int a[12]={3,1,29,31,20,10,45,67,33,42};

void Tongsort(int a[],int n)
{
    int minn=a[0];
    int maxx=minn;
    for(int i=1;i<n;i++)
    {
        if(a[i]<minn)
            minn=a[i];
        if(a[i]>maxx)
            maxx=a[i];
    }
    //区间间隔
    int interzone=(maxx-minn)/n+1;
    //printf("%d---\n",interzone);
    vector<vector<int>>v;
    for(int i=0;i<interzone;i++)
    {
        vector<int>v1;
        v.push_back(v1);
    }
    for(int j=0;j<n;j++)
    {
        int num=(a[j]-minn)/n;
        v[num].push_back(a[j]);
    }
    for(int i=0;i<interzone;i++)
        std:sort(v[i].begin(),v[i].end());
    int index=0;
    for(int i=0;i<interzone;i++)
    {
        for(int j=0;j<v[i].size();j++)
        {
            a[index++]=v[i][j];
        }
    }
}
int main()
{
    Tongsort(a,10);
    for(int i=0;i<10;i++)
        cout<<a[i]<<" ";
    return 0;
}

八、归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,归并排序对序列的元素进行逐层折半分组,然后从最小分组开始比较排序,合并成一个大的分组,逐层进行,最终所有的元素都是有序的

时间复杂度是O(nlogn)

思路图解
在这里插入图片描述
要排序这样一个数组的时候,归并排序法首先将这个数组分成一半

在这里插入图片描述
排序左边与右边数组的元素并归并,当我们对左边的数组和右边的数组排序时,再分别将左边的数组和右边的数组分成一半,然后对每一个部分先排序,再归并
在这里插入图片描述

最终,每一个部分只有一个元素,此时便不用排序,归并即可
在这里插入图片描述
归并到上一部分后再次归并,直到最后排序完成
在这里插入图片描述

/*
int a[10]= {10,23,35,4,10,30,24,56};
10,23,35,4  ||  10,30,24,56

10,23 || 35,4    ||    10,30 || 24,56

10 23 ||  4  35 || 10 30|| 24 56

4 10 23 35 || 10 24 30 56

4 10 10 23 24 30 35 56
*/


#include<bits/stdc++.h>
using namespace std;
int a[10]= {10,23,35,4,10,30,24,56};
void merge(int a[],int l,int r,int mid)
{
    int b[r-l+1];//r-l+1为数组大小,也可以不计算自行开空间
    int i,j,index=0;
    for(int k=l;k<=r;k++)
        b[index++]=a[k];
    i=l;
    j=mid+1;
    for(int k=l;k<=r;k++)
    {
        if(i>mid)//遍历到右边数组,右边已经是排序好的,直接存储即可
        {
            a[k]=b[j-l];
            j++;
        }
        else if(j>r)//右边数组遍历结束,存储左边数组的数
        {
            a[k]=b[i-l];
            i++;
        }
        else if(b[i-l]>b[j-l])//左边的数大于右边的数,存储右边较小的数
        {
            a[k]=b[j-l];
            j++;
        }
        else //左边的数小于右边的数,存储左边较小的数
        {
            a[k]=b[i-l];
            i++;
        }
        //printf("%d--\n",a[k]);
    }
}
void merge_sort(int a[],int l,int r)
{
    if(l>=r)
        return ;
    int mid=(l+r)/2;
    merge_sort(a,l,mid);
    merge_sort(a,mid+1,r);
    merge(a,l,r,mid);
}
void mergesort(int a[],int l,int r)
{
    merge_sort(a,l,r-1);
}
int main()
{
    mergesort(a,0,8);
    for(int i=0;i<8;i++)
        cout<<a[i]<<" ";
    return 0;
}

九、计数排序

算法思想

遍历待排序的元素,将每个元素对应的计数数组的下标的元素+1,按顺序输出计数数组下标的值,元素的值是多少,就输出几次

时间复杂度是O(N+M)(N是待排序数组的大小,M是计数数组的大小)
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int a[10]= {10,23,35,4,10,30,24,56};
int main()
{
    int maxx=0;
    for(int i=0; i<8; i++)
        maxx=max(maxx,a[i]);
    int num[maxx+5];
    memset(num,0,sizeof(num));
    for(int i=0; i<8; i++)
        num[a[i]]++;
    for(int i=0; i<=maxx; i++)
    {
        if(num[i])
        {
            for(int j=0; j<num[i]; j++)
            {
                cout<<i<<" ";
            }
        }
    }
    return 0;
}

优化版

如果遇到数组里的数很大的情况,按照上面的解法计数数组需要开很大

解释:{146,143,150,144,147,153,157,156}这种情况,最大值是157,按照上面的解法计数数组的大小要至少要开158,如此看来,计数数组下标0-142都是没有用到的,这样就增加空间复杂度

那么如何降低空间复杂度?
我们可以创建一个长度为最大值-最小值+1(157-143+1)的计数数组,计数数组的偏移量为序列的最小值143

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int a[10]= {146,143,150,144,147,153,157,156};
int main()
{
    int maxx=0,minn=0x3f3f3f;
    for(int i=0; i<8; i++)
    {
          maxx=max(maxx,a[i]);
          minn=min(minn,a[i]);
    }
    int N=maxx-minn+1;
    int num[N+1];
    memset(num,0,sizeof(num));
    for(int i=0;i<8;i++)
        num[a[i]-minn]++;
    for(int i=minn;i<=maxx;i++)
    {
        for(int j=0;j<num[i-minn];j++)
        {
            cout<<i<<" ";
        }
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值