C语言八大排序算法,一文带你弄清所有

目录

C语言八大排序

1、插入排序

2、希尔排序

3、简单选择排序

4、堆排序

5、冒泡排序

6、快速排序

7、归并排序

8、基数排序

时间,空间复杂度,以及稳定性对比总结

一些常见题目:


C语言八大排序

前言:感谢各位老前辈的写的算法解析,下面的图解也是由网上很多老前辈的图,收下我的膝盖。
 
代码全部由我实测,全都是能用的,不存在不能用的。
 
如果理解不了思想,代码中我凭着自己的理解加了一些注释,清细看,如果还不懂,建议搜索这个排序的详解。
 
写着这篇博客,一方面是自己搞懂八大排序的一个过程,也希望能够帮助到目前的你
 
如果有错,还请指正
 
 

1、插入排序

将第一个和第二个元素排好序,然后将第3个元素插入到已经排好序的元素中,依次类推(插入排序最好的情况就是数组已经有序了)
 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;


void print(int a[], int n,int i)
{
   cout<<i <<":";
   for(int j= 0; j<8; j++)
   {
       cout<<a[j] <<" ";
   }
   cout<<endl;
}
void InsertSort(int a[], int n)
{
   for(int i= 1; i<n; i++)
   {
       if(a[i] < a[i-1])  //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
       {
           int j= i-1;
           int x = a[i]; //复制为哨兵,即存储待排序元素
           a[i] = a[i-1]; //先后移一个元素
           while(x < a[j])  //查找在有序表的插入位置
           {
               a[j+1] = a[j];//元素后移
               j--;
               if(j==-1)//这里要跳出,不然j=-1时a[-1]会进行判断
                   break;
               //cout<<j<<endl;
           }
           //找到小于或等于x
           //cout<<a[j+1]<<endl;
           a[j+1] = x; //插入到正确位置
       }


       print(a,n,i); //打印每趟排序的结果
   }


}


int main()
{
   int a[8] = {3,11,5,7,2,4,9,6};
   InsertSort(a,8);
   print(a,8,8);
}

 

 

2、希尔排序

因为插入排序每次只能操作一个元素,效率低。元素个数N,取奇数k=N/2,将下标差值为k的数分为一组(一组元素个数看总元素个数决定),在组内构成有序序列,再取k=k/2,将下标差值为k的数分为一组,构成有序序列,直到k=1,然后再进行直接插入排序
 
 
以一个整数序列为例来说明{12,45,90,1,34,87,-3,822,23,-222,32},该组序列包含N=11个数。不少已有的说明中通常举例10个数,这里说明一下,排序算法与序列元素个数无关!
首先声明一个参数:增量gap。gap初始值设置为N/2。缩小方式一般为gap=gap/2.
第一步,gap=N/2=5,每间隔5个元素取一个数,组成一组,一共得到5组:
 
对每组使用插入排序算法,得到每组的有序数列:
 
 
至此,数列已变为:
 
第二步,缩小gap,gap=gap/2=2,每间隔2取一个数,组成一组,共两组:
 
同理,分别使用插入排序法,得到每组的有序数列:
 
至此,数列已变为:
 
第三步,进一步缩小gap,gap=gap/2=1,此时只有一组,直接使用插入排序法,玩完成排序,图略。
 
 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;


void ShellSort(int arr[],int N)
{
    int i,j,gap;
    for(gap = N/2;gap>0;gap/=2)
    {
        // 每组进行插入排序
        for(i=0;i<N;i++)
        {
            //直插排序,找到比它小的停止
            for(j=i-gap;j>=0;j-=gap)
            {
                if(arr[i]>arr[j])
                    break;
            }
            int temp = arr[i];//及记录这个位置
            for(int k=i;k>j;k-=gap)//
                 arr[k] = arr[k-gap];
            arr[j+gap] = temp;
        }
        // 打印当前的gap和序列状态
        cout<<"\ngap="<<gap;
        cout<<"\ncurrent list:";
        for(int h=0;h<N;h++)
            cout<<arr[h]<<"  ";
    }
}


int main()
{
    int a[11] = {12,45,90,1,34,87,-3,822,23,-222,32};
    cout<<"原始数列:";
    for(int i=0;i<11;i++)
        cout<<a[i]<<"  ";
    cout<<endl;


    cout<<"希尔排序:";
    ShellSort(a,11);


    system("pause");
    return 0;
}

 

 
 
 
 

3、简单选择排序

选出最小的数和第一个数交换,再在剩余的数中又选择最小的和第二个数交换,依次类推
#include<bits/stdc++.h>
using namespace std;


typedef long long ll;


int main()
{
    int a[] = {2,3,7,3,40,34,6};
    int size = sizeof(a)/sizeof(int);
    for(int i=0;i<size-1;i++)
    {
        int mn=i;
        for(int j=i+1;j<size;j++)
        {
            if(a[mn]>a[j])
                mn = j;
        }
        if(mn!=i)
            swap(a[mn],a[i]);
    }
    for(int i=0;i<size;i++)
        cout<<a[i]<<" ";
    return 0;
}

 

4、堆排序

推荐慕课上
利用大根堆的性质(堆顶元素最小)不断输出最大元素,直到堆中没有元素
给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。
首先根据该数组元素构建一个完全二叉树,得到
 
 
 然后需要构造初始堆,则从最后一个非叶节点开始调整对应的代码for(i=size/2;i>=1;i--) ,之后i--,是之后的非叶子结点,调整过程如下:
首先因为总共有6个结点,所以第3个结点也就是最后一个非叶子结点,就是结点值为8开始和它的左孩子和右孩子进行比较,选出最大的在父节点。之后就是结点值为7的和它的左右孩子进行比较,以此类推
 
这样这个堆就建立好啦
 
然后取出最大值和最小值进行交换位置,再次把这个堆重新变成最大堆,怎样变成最大堆呢??
此时的根节点为最小值,和它的左孩子右孩子进行比较,取最大值交换,继续想下一层去找左孩子和右孩子去比较,直到找不到比它小为止。
至此,完成排序
。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。
 
 
#include<bits/stdc++.h>
using namespace std;


void HeapAdjust(int *a,int i,int size)  //调整堆
{
    int lchild=2*i;       //i的左孩子节点序号
    int rchild=2*i+1;     //i的右孩子节点序号
    int max=i;            //临时变量,记录父节点
    if(i<=size/2)          //如果i是叶节点就不用进行调整
    {
        //如果左子树大于这父节点
        if(lchild<=size&&a[lchild]>a[max])
        {
            max=lchild;
        }
        //如果右子树大于(左子树和父节点中比较大的值)
        if(rchild<=size&&a[rchild]>a[max])
        {
            max=rchild;
        }
        //如果右孩子的
        if(max!=i)//说明该有孩子节点会比父节点的值大
        {
            swap(a[i],a[max]);
            //继续向下遍历如果它的左孩子和右孩子比它大,再次交换
            HeapAdjust(a,max,size);    //避免调整之后以max为父节点的子树不是堆
        }
    }
}


void BuildHeap(int *a,int size)    //建立堆
{
    int i;
    for(i=size/2;i>=1;i--)    //非叶节点最大序号值为size/2,并且向上遍历非叶子结点
    {
        HeapAdjust(a,i,size);
    }
}


void HeapSort(int *a,int size)    //堆排序
{
    int i;
    BuildHeap(a,size);
    for(i=size;i>=1;i--)
    {
        //cout<<a[1]<<" ";
        swap(a[1],a[i]);           //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面
          HeapAdjust(a,1,i-1);      //重新调整堆顶节点成为大顶堆
    }
}


int main(int argc, char *argv[])
{
     int a[]={0,16,20,3,11,17,8};
    //int a[100];
    int size;
    size = sizeof(a)/4-1;//szie是6,sizeof进行统计的时候,一个int按4个记
    //cout<<size<<endl;
    int i;
    HeapSort(a,size);
    for(i=1;i<=size;i++)
        cout<<a[i]<<" ";
        cout<<endl;
    return 0;
}

 

 

5、冒泡排序

 

冒泡排序应该不用多说,应该都会把
相邻的两个数进行比较,一趟下来最大的在最右边
进行的是n-1趟之后,便就排序好
废话不多说上图
 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    int a[] = {4,3,2,0,7,9,2};
    int size = sizeof(a)/sizeof(int);
    for(int i=0;i<size-1;i++)
    {
        for(int j=0;j<size-i-1;j++)
        {
            if(a[j]>a[j+1])
                swap(a[j],a[j+1]);//交换
        }
    }
    for(int i=0;i<size;i++)
        cout<<a[i]<<" ";
    return 0;
}

 

 

6、快速排序

 
选择一个基准元素,比基准元素小的放基准元素的前面,比基准元素大的放基准元素的后面,这种动作叫分区,每次分区都把一个数列分成了两部分,每次分区都使得一个数字有序,然后将基准元素前面部分和后面部分继续分区,一直分区直到分区的区间中只有一个元素的时候,一个元素的序列肯定是有序的嘛,所以最后一个升序的序列就完成啦。
 
实现具体:选择第一个数为基准,安排两个哨兵,一个最左边,一个最右边,  开始 最右边 的向左跑,直到遇见比它小的数停止, 左边的向右跑,直到遇到比它大的数停止,两个哨兵停止,相互交换值,直到两个哨兵碰到一起后,基准与碰到一起的那个位置的值进行交换,之后这个两边的两个区间进行相同的操作。
详情请见:
#include <bits/stdc++.h>
using namespace std;


void quickSort(int left, int right, int arr[])


{
    if(left >= right)
        return;
    int i, j, base, temp;
    i = left, j = right;
    base = arr[left];  //取最左边的数为基准数
    while (i < j)
    {
        //右边的哨兵开始向左遍历,直到遇见比基准值小的
        while (arr[j] >= base && i < j)
            j--;
        //左边哨兵开始向右边遍历,直到遇见比基准值大的
        while (arr[i] <= base && i < j)
            i++;
        if(i < j)//两个哨兵交换值
        {
            temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    //基准数归位
    arr[left] = arr[i];//最左边的数(基准值)等于停止位置的值
    arr[i] = base;//停止的位置等于基准值
    quickSort(left, i - 1, arr);//递归左边
    quickSort(i + 1, right, arr);//递归右边
}
int main()
{


    int a[] = {10,3,4,6,9,7,4};
    int size = sizeof(a)/4;
    quickSort(0,size-1,a);
    for(int i=0;i<size;i++)
        printf("%d ",a[i]);
    return 0;
}

 

 
 

7、归并排序

将一个无序的数列一直一分为二,直到分到序列中只有一个数的时候,这个序列肯定是有序的,因为只有一个数,然后将两个只含有一个数字的序列合并为含有两个数字的有序序列,这样一直进行下去,最后就变成了一个大的有序数列
 
图解:
 
#include <stdio.h>


void MergeArr(int* src,int * tmp,int start,int mid,int end)
{
    int i = start;//前半部分
    int j = mid + 1;//后半部分
    int k = start;
    while (i != mid + 1 && j != end + 1) //进行合并
    {
        if (src[i] < src[j])
            tmp[k++] = src[i++];
        else
            tmp[k++] = src[j++];
    }
    if (i == mid + 1)
    {
        while (j != end+1)
        tmp[k++] = src[j++];
    }
    else
    {
        while (i != mid + 1)
            tmp[k++] = src[i++];
    }
    while (start <= end)//这是把排好序的数组存回去
    {
        src[start] = tmp[start];
        start++;
    }
}


void MergeSort(int* arr, int * tmp,int start,int end)  //归
{
    if (start < end)
    {
        int mid = (start + end) / 2;
        MergeSort(arr, tmp,start,mid);//开始分割,前半部分
        MergeSort(arr, tmp, mid+1, end);//后半部分
        MergeArr(arr, tmp, start, mid, end);//进行合并
    }
}


int main()
{
    int a[8] = { 2,4,5,9,1,6,7,8 };
    int c[8] = {0};
    MergeSort(a, c, 0, 7);
    for (int i = 0; i < 8; i++)
    {
        printf("%d\t", c[i]);
    }
    return 0;
}

 

 

8、基数排序

 
(1)假设有欲排数据序列如下所示:
73  22  93  43  55  14  28  65  39  81
首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中。
分配结果(逻辑想象)如下图所示:
分配结束后。接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下仍然无序的数据序列:
 
81  22  73  93  43  14  55  65  28  39
 
接着,再进行一次分配,这次根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图所示:
分配结束后。接下来再将所有桶中所盛的数据(原理同上)依次重新收集串接起来,得到如下的数据序列:
 
14  22  28  39  43  55  65  73  81  93
 
观察可以看到,此时原无序数据序列已经排序完毕。如果排序的数据序列有三位数以上的数据,则重复进行以上的动作直至最高位数为止。
#include<bits/stdc++.h>
using namespace std;


#define Max_ 10      //数组个数
#define RADIX_10 10    //整形排序
#define KEYNUM_31 10     //关键字个数,这里为整形位数


// 打印结果
void Show(int  arr[], int n)
{
    int i;
    for ( i=0; i<n; i++ )
        printf("%d  ", arr[i]);
    printf("\n");
}


// 找到num的从低到高的第pos位的数据
int GetNumInPos(int num,int pos)
{
    int temp = 1;
    for (int i = 0; i < pos - 1; i++)
        temp *= 10;


    return (num / temp) % 10;
}




//基数排序  pDataArray 无序数组;iDataNum为无序数据个数
void RadixSort(int* pDataArray, int iDataNum)
{
    int *radixArrays[RADIX_10];    //分别为0~9的序列空间
    for (int i = 0; i < 10; i++)
    {
        radixArrays[i] = (int *)malloc(sizeof(int) * (iDataNum + 1));
        radixArrays[i][0] = 0;    //index为0处记录这组数据的个数
    }


    for (int pos = 1; pos <= KEYNUM_31; pos++)    //从个位开始到10位
    {
        for (int i = 0; i < iDataNum; i++)    //分配过程
        {
            int num = GetNumInPos(pDataArray[i], pos);//获取该对应位置上的值
            int index = ++radixArrays[num][0];//所储存在数组中的位置
            radixArrays[num][index] = pDataArray[i];//放进筒里
        }


        for (int i = 0, j =0; i < RADIX_10; i++)    //收集
        {
            for (int k = 1; k <= radixArrays[i][0]; k++)
                pDataArray[j++] = radixArrays[i][k];//把筒里的值倒回给原来的数组
            radixArrays[i][0] = 0;    //复位
        }
    }
}


int main()
{   //测试数据
    int arr_test[Max_] = { 8, 4, 2, 3, 5, 1, 6, 9, 0, 7 };
    //排序前数组序列
    Show( arr_test, Max_ );
    RadixSort( arr_test, Max_);
    //排序后数组序列
    Show( arr_test, Max_ );
    return 0;
}

时间,空间复杂度,以及稳定性对比总结

一些常见题目:

如果只想得到1000个元素组成的序列中第5个最小元素之前的部分排序的序列,用()方法最快

  • 起泡排序
  • 快速排序
  • 希尔排序
  • 堆排序
  • 简单选择排序

一般这种求前元素中前几个最小或者最大的元素,一般都是选择堆排序

 
  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值