常用排序算法的实现及分析

常用排序算法

排序算法的实际应用场景很多,很多排序算法的思路我们可能大致理解,但是要你用代码实现却要花些功夫,个人认为对算法掌握程度最好就是用代码把它们实现一遍。最近正好学习到排序这里,所以就把几种常用的排序算法全部写一遍,你会发现之前只是理解算法的大致思路,很快就会忘记,当然下面的描述其中还有很多可以优化,提高运行效率的地方。当然了,同一种算法写法很有多种,有些地方可以简化。

冒泡排序

冒泡排序应该是排序算法的入门算法,是一种比较简单的排序,容易理解。从第一个元素开始,依次与其他元素比较,遇见比他大的元素就交换两者的位置,这样一直重复n-1次(n表示数组的大小)
平均时间复杂度为O( n 2 n^2 n2)

void Swap(int a[],int i,int j)
{
    int temp =a[i];
    a[i]=a[j];
    a[j]=temp;
}
void BubleSort(int data[],int n)
{
    for(int i=1; i<n; i++)
    {
        for(int j=0; j<i; j++)
        {
            if(data[j]>data[i])
                	Swap(data,i,j);
    }
}

直接插入排序

插入排序就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为** O(n^2) 。是 稳定 的排序方法。
平均时间复杂度:
O(n^2) **,空间复杂度 O(1)
具体步骤:从第二个元素开始,(在数组乱序的情况下,把数组的第一个元素作为已经排好序的有序数组),依次将n-1个元素插入到前面的有序数组中。每一次插入时,依次向前遍历,如果比前面的元素小,就交换两者位置,一直比较,直到将元素插入。

void InsertSort(int data[],int n)
{
    for(int i=1; i<n; i++) //循环n-1次
    {
        int temp=data[i];
        int j=i;
        for(; j>0 && temp<data[j-1]; j--) //比前面的小就交换
        {
            data[j]=data[j-1];  //向后移动

        }
        data[j]=temp;
    }
}

希尔排序

希尔排序(Shell’s Sort)是一种带间隔的插入是对插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
平均时间复杂度:O(n^1.3)
最坏时间复杂度O( n 2 n^2 n2),最好时间复杂度** O(n) **空间复杂度 **O(1)**空间复杂度 O(1)

void ShellSort(int data[],int n)  //改进的插入排序
{
    for(int gap=(n-1)/3; gap>0; gap=(gap-1)/3) //Kuth序列h=3h+1  Kuth序列比间隔h每次缩小一半效率高些  {

        for(int i=gap; i<n; i=i+gap)   //以每个间隔做一次插入排序
        {
            for(int j=i; j>=gap; j=j-gap)
            {
                if(data[j]<data[j-gap])
                    Swap(&data[j],&data[j-gap]);
            }
        }
}

快速排序

选定数组中任意一个元素作为** 轴 **,看作基本点(base case),将要排序的数组分割成独立的两部分,其中一部分的所有数据都比轴小,另外一部分的所有数据都比轴大,然后再重复此方法对左右两部分数据分别进行快速排序,整个排序过程可以递归进行,递归出口:数组只有一个元素时,就退出。从而使得整个数据变成有序序列。


void QuickSort(int data[],int left,int right)
{
    if(left>=right)  //递归终止的边界条件
        return;
    int flag=data[right];  //最后的值作为轴
    int i=left;
    int j=right;
    while(i<j)
    {
        while(i<j &&  data[i]<=flag) i++;
        while(j>i && data[j]>=flag) j--;
        if(i<j)
            Swap(data,i,j);
    }
    Swap(data,i,right);
    QuickSort(data,left,i-1);
    QuickSort(data,i+1,right);
}

归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,采用分治法(Divide and Conquer)。将已有序的子序列合并(** merge **),得到完全有序的序列。将已排好序的子序列合并成一个有序的数组,称之为归并排序。
步骤:开一个和原数组等长的新数组,遍历每一个子序列,比较它们的值。谁比较小,谁先放入大数组中,直到数组遍历完成。

归并排序的前提是需要两个已经排好顺序的数组,但一般数组都是乱序的,所以需要用到分治思想,先将原数组分隔成一份一份的,每一个元素是一个有序的"数组",将相邻两个数组归并排序,形成一个新的有序数组,然后依次合成,再做最后一次归并。
这就是我们的分治法—>将一个大问题分成很多个小问题进行解决,最后重新组合起来。

void MergeSort(int data[],int left,int right)  //分治思想,前提是2个子数组必须排好序
{
    if(left==right)
        return;
    int mid=left+(right-left)/2;  //防止数据范围超出;
    MergeSort(data,left,mid);
    MergeSort(data,mid+1,right);
    Merge(data,left,mid,right);
}
//做合并,前提是左右两个数组分别是有序的
void Merge(int data[],int lefpo,int mid,int rigpo)
{
    int i=lefpo,j=mid+1,k=0;
    Typedata a[rigpo-lefpo+1]; //临时数组
    while(i<=mid && j<=rigpo)
        a[k++]=(data[i]<=data[j]) ? data[i++] : data[j++];
    while(i<=mid)  //右边先到边界
        a[k++]=data[i++];
    while(j<=rigpo)  //左边先到 边界
        a[k++]=data[j++];

    for(int m=0; m<=rigpo-lefpo; m++)
        data[lefpo+m]=a[m];
}

堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆分为:最大堆,最小堆
最大堆:父亲节点的元素都要大于其孩子节点,最小堆:父亲节点权重小于其左右孩子节点。

基本思想:把待排序的元素按照大小排成最大堆的形式(父节点的元素要大于等于其子节点)这个过程叫做建堆,根据这个特性(大根堆根最大,小根堆根最小),就可以把根节点拿出来,然后再堆化下,再把根节点拿出来,,,,循环到最后一个节点,就排序好了。

下面代码实现的是最大堆(根节点是最大值)

void heapfy(int data[],int n,int i)
{
    if(i>=n)  //超出节点范围,边界条件
        return;
    int lchild=2*i+1,rchild=2*i+2; //父节点为i
    int Max=i;
    if(lchild<n && data[lchild]>data[Max] )
        Max=lchild;
    if(rchild<n && data[rchild]>data[Max])
        Max=rchild;
    if(Max!=i)
    {
        Swap(data,Max,i);
        heapfy(data,n,Max);
    }

}
void buildHeap(int data[],int n)
{
    int last_node=n-1;
    int last_parent=(last_node-1)/2; //从最后一个父节点往上heapfy
    for(int i=last_parent; i>=0; i--)
    {
        heapfy(data,n,i);
    }
}

void HeapSort(int data[],int n)
{
    buildHeap(data,n);  //建堆
    for(int i=n-1;i>=0;i--)
    {
        Swap(data,0,n-1);  //把最大的根节点换到后面来
        heapfy(data,i,0); //交换后,每次调整后减掉最后一个
    }
}

基数排序

基数排序(radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort)或bin sort,通过关键字(每个元素的每一位上的值)将要排序的元素分配至某些"桶"(编号为0-9)中,以达到排序的作用,基数排序法是属于稳定的排序。
步骤:第一趟:将元素的个位数分配到桶子里面去,然后回收起来,此时数组元素已经按个位数从小到大都已经排好顺序了

第二趟:将上一趟得到的序列按元素十位数分别分配到桶子里面去,然后回收起来,此时数组元素的所有个位数和十位数都已经排好顺序了(如果没有十位数、则补0)…
依次类推向高位排
一共排i次(i是数组最大值的位数)

void radixSort(int data[],int n)
{
    //先找出最大值,才能确定要根据位数做几次排序
    int Max=data[0];
    for(int i=0; i<n; i++)
    {
        if(data[i]>Max)
            Max=data[i];
    }
    int i=1; //依次代表个、十、百...
    do
    {
        int Count[10]= {0}; //每个元素0-9的次数,这里必须要初始化为0!!
        int temp[n];
        for(int k=0; k<n; k++) //依次求每个位上的数字,记录每个数字出现的次数
        {
            int div=data[k]/i%10;
            Count[div]++;
        }
        for(int j=1;j<10; j++)
        {
            Count[j]=Count[j]+Count[j-1];  //累加计算每个数字的大小排名
        }
        for(int j=n-1; j>=0; j--)  //注意!!!这里一定要逆序复制,否则排序出错。
        {
            int div=data[j]/i%10;
            temp[--Count[div]]=data[j];
        }
        for(int j=0; j<n; j++) data[j]=temp[j]; //每一位上排一遍后将数组复制过来
         i*=10;
    }
    while(Max/i>0);

}

几种算法时空复杂度分析

算法平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
冒泡排序Bubble n 2 n^2 n2 n 2 n^2 n2 n 2 n^2 n21稳定
插入排序Insertion n 2 n^2 n2 n 2 n^2 n2 n n n1稳定
快速排序Quick n l o g 2 n nlog_2n nlog2n n 2 n^2 n2 n l o g 2 n nlog_2n nlog2n l o g 2 n log_2n log2n不稳定
希尔排序Shell n 1 . 3 n^1.3 n1.3 n 2 n^2 n2 n n n1不稳定
归并排序Merge n l o g 2 n nlog_2n nlog2n n l o g 2 n nlog_2n nlog2n n n nn稳定
基数排序Radix n ∗ k n*k nk n ∗ k n*k nk n ∗ k n*k nkn+k稳定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值