十大排序算法

十大排序算法

算法时间复杂度(平均)时间复杂度(最好)时间复杂度(最坏)空间复杂度稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(nlog2n)O(n)O(n^2)O(1)不稳定
快速排序O(nlog2n)O(nlog2n)O(n^2)O(log2n)不稳定
堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(1)不稳定
归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)稳定
计数排序O(n+k)O(n+k)O(n+k)O(n+k)不稳定
基数排序O(n+k)O(n+k)O(n^2)O(n+k)稳定
桶排序O(n*k)O(n*k)O(n*k)O(n+k)不稳定
1、冒泡排序
void bubble_sort(int* arr,size_t len)
{
    bool flag = true;
    for(int i=len-1; flag && i>0; i--)
    {
        flag = false;
        for(int j=0; j<i; j++)
        {
            if(arr[j] > arr[j+1])
            {
                swap(arr[j],arr[j+1]);
                flag = true;
            }
        }
    }
} 

特点&优点:对数据的有序性敏感、排序过程中有机会提前结束

缺点:数据的比较、交换次数多、效率低

2、选择排序
void select_sort(int* arr,size_t len)
{
    for(int i=0; i<len-1; i++)
    {   
        int min = i;
        for(int j=i+1; j<len; j++)
        {   
            if(arr[min] > arr[j])
                min = j;
        }
        if(min != i)
            swap(arr[min],arr[i]);
    }
}

特点&优点:比较次数是固定的,但交换的次数少,节约了很多数据交换的时间,最多N-1次

缺点:比较次数较多

3、插入排序
void insert_sort(int* arr,size_t len)
{
    for(int i=1; i<len; i++)
    {   
        int tmp = arr[i], j = i;
        while(j-1>=0 && arr[j-1] > tmp)
        {   
            arr[j] = arr[j-1];
            j--;
        }   
        arr[j] = tmp;
    }   
}

特点&优点:没有进行数据交换,节约了大量时间,直接对一个无序序列进行排序没有太大优点。和冒泡比比较次数没优势,和选择比移动次数没优势。

缺点:数据越多效率越低

4、希尔排序
void shell_sort(int* arr,size_t len)
{
    for(int k=len/2; k>0; k/=2)
    {
        for(int i=k; i<len; i++)
        {
            int tmp = arr[i], j = i;
            while(j-k>=0 && arr[j-k] > tmp)
            {
                arr[j] = arr[j-k];
                j -= k;
            }
            arr[j] = tmp;
        }
    }
}  

特点&优点:当数据量较大时,可以使数据更早的到达他应该到达的位置。

5、快速排序
void _quick_sort(int* arr,int left,int right)
{
    // 当待排序的数据量 <= 1 则结束
    if(left >= right)
        return;

    // 把标杆挖起来,并记录坑的位置
    int pi = left, pv = arr[pi];
    // 备份右边界,备份右边界
    int l = left, r = right;
    while(l<r)
    {
        // 从右向左寻找比标杆小的值,如果比标杆大就继续循环
        while(l<r && arr[r] >= pv) r--;
        // 当循环结束,且l<r,说明找到了比标杆小的数据
        if(l<r)
        {
            // 把数据放入坑里,数据的原位置就变成了新的坑
            arr[pi] = arr[r];
            // 记录新坑的位置
            pi = r;
        }

        // 从左向右寻找比标杆大的值,如果比标杆小就继续循环
        while(l<r && arr[l] <= pv) l++;
        // 当循环结束,且l<r,说明找到了比标杆大的数据
        if(l<r)
        {
            // 把数据放入坑里,数据的原位置就变成了新的坑
            arr[pi] = arr[l];
            // 记录新坑的位置
            pi = l;
        }
    }
    // 当l与r相遇,循环结束,把标杆放入坑里,标杆左边的数据都比它小,标杆右边的数据都比它大,以标杆为衡量标准,序列已经大致有序
    arr[pi] = pv;
    // 对标杆左边的数据进行快速排序
    _quick_sort(arr,left,pi-1);
    // 对标杆右边的数据进行快速排序
    _quick_sort(arr,pi+1,right);
}

void quick_sort(int* arr,int len)
{
    _quick_sort(arr,0,len-1);
}

缺点:数据较多时,递归层数会比较深,占用栈内存较多

6、堆排序

​ 把待排序的数据当作一个大根堆(完全二叉树),然后逐步把堆顶的最大值(每弹出一次重新构建大根堆,所以弹出的一直是最大值)弹出存储在序列的末尾,也就是借助大根堆这一数据结构完成的排序。

void _head_sort(int* arr,int root,size_t len)
{
    // 计算出左子树的下标:(root+1)*2-1,如果root没有左子树就肯定没有右子树,不需要再往下探索
    while(root*2+1 < len)
    {   
        // 假定左子树是左右子树中的最大值
        int max = root*2+1;
        // 判断是否有右子树,再判断右子树是否大于左子树
        if(max+1 < len && arr[max+1] > arr[max])
            max++;

        if(arr[max] > arr[root])
            swap(arr[max],arr[root]);

        // 左右子树,谁与根交换,谁就需要重新调整
        root = max;
    }   
}

void heap_sort(int* arr,size_t len)
{
    // 构建大根堆
    for(int i=len/2-1; i>=0; i--)
        _head_sort(arr,i,len);

    for(int i=len-1; i>0; i--)
    {
        swap(arr[0],arr[i]);
        _head_sort(arr,0,i);
    }
}
7、归并排序

​ 把待排序的数据以k=2为单位进行分组,每组分为左右两部分,然后按从小到大的顺序合并到另一块空间,然后k*=2重复该过程,直到k/2>=len则排序完成。

​ 归并排序需要一块额外的空间,用于存储合并的结果,它的时间复杂度与快速、堆相同,但是在排序过程中没有进行数据交换,而直接数据拷贝,因此节约了大量的数据交换的时间,但也耗费了额外的内存,所以它是一个典型的用空间换取时间的排序算法。

void _merge(int* dest,int* src,int ls,int le,int rs,int re)
{
	int i = ls;
	while(ls < le && rs < re)
	{
		if(src[ls] < src[rs])
			dest[i++] = src[ls++];
		else
			dest[i++] = src[rs++];
	}

	while(ls < le)
		dest[i++] = src[ls++];

	while(rs < re)
		dest[i++] = src[rs++];
}
//========================循环归并=========================
void merge_sort(int* arr,size_t len)
{
	int* src = arr;
	int* dest = malloc(sizeof(arr[0])*len);

	for(int k=1; k<len; k*=2)
	{
		for(int i=0; i<len; i+=2*k)
		{
			int ls = i, le = i+k>len ? len : i+k;
		   	int rs = le, re = rs+k>len ? len : rs+k;
            int j = ls;
			while(ls < le && rs < re)
			{
                dest[j++] = src[ls] < src[rs] ? src[ls++] : src[rs++];
			}

			while(ls < le)
				dest[j++] = src[ls++];

			while(rs < re)
				dest[j++] = src[rs++];
		}
		swap(src,dest);
	}

	if(arr != src)
	{
		memcpy(arr,src,sizeof(arr[0])*len);
		dest = src;
	}
	free(dest);
}

//========================递归归并=========================
void _merge_sort(int* dest,int* src,int left,int right)
{
	if(left+1 >= right)
		return;

	int pi = (left+right)/2;
	_merge_sort(dest,src,left,pi);
	_merge_sort(dest,src,pi,right);
	_merge(dest,src,left,pi,pi,right);
	memcpy(src+left,dest+left,sizeof(*src)*(right-left));
}

void merge_sort(int* arr,size_t len)
{
	int dest[len];
	_merge_sort(dest,arr,0,len);
}
8、计数排序

​ 首先定义一个计算数据出现的次数的数组count,并所有成员初始化0,然后使用数据的值作为数组的下标,然后统计每个数据出现的次数。

​ 以i=[0,max]遍历count数组,当count[i]>0说明i出现过,然后把i往待排序的数组中存储count[i]个,然后排序完成。

​ 注意:计算排序的局限比较大,它只能对整型数据进行排序,无法对浮点型、字符串型数据进行排序,待排序的数据重复性越高,差值越小,速度就越快,反之虽然也可以排,但得不尝失。

//知道待排序的数据范围 假定0~1000
void count_sort(int* arr,size_t len)
{
    int count[100] = {}; 
    for(int i=0; i<len; i++)
    {   
        count[arr[i]]++;
    }   

    int index = 0;
    for(int i=0; i<100; i++)
    {   
        for(int j=0; j<count[i]; j++)
        {   
            arr[index++] = i;
        }   
    }   
}
//不知道待排序的数据范围  先求出范围
void count_sort(int* arr,size_t len)
{                                         
    int max = arr[0], min = arr[0];
    for(int i=1; i<len; i++)
    {
        if(arr[i] > max)
            max = arr[i];
        else if(arr[i] < min)
            min = arr[i];
    }

    int* count = calloc(max-min+1,sizeof(count[0]));
    for(int i=0; i<len; i++)
    {
        count[arr[i]-min]++;
    }

    int index = 0;
    for(int i=min; i<=max; i++)
    {
        for(int j=0; j<count[i-min]; j++)
        {
            arr[index++] = i;
        }
    }
}
9、基数排序

​ 先根据数据个位的大小对数据进行排序,然后再对排序结果的十位进行排序,然后百位、千位…,直到排序完成。

​ 使用这种方式排序的优点是不需要对待排序的数据进行比较、交换,所以它的排序速度要比普通排序快的多,但局限性很大,只能对整型数据排序,并且对正负数还有要求,还需要额外的内存空间。

​ 注意:当数据的位数不多,并且差别不大,的整形数据适合使用基数排序。

void radix_sort(int* arr,size_t len)
{
	//int10位
	int radix[10][len+1];
	bzero(radix,sizeof(radix));
	int x =1,n=0;
	while(n<len)
	{
		n=0;
		for(int i=0;i<len;i++)
		{
			int k=arr[i] / x % 10;		
			radix[k][radix[k][len]++]=arr[i];
			if(arr[i]<x)
				n++;
		}
		x*=10;

		int index=0;
		for(int i=0;i<len;i++)
		{
			for(int j=0;j<radix[i][len];j++)
			{
				arr[index++]=radix[i][j];
			}
				radix[i][len]=0;
		}
	}
}
10、桶排序

​ 桶排序就是把待排序的数据,根据值划分不同的范围存储到不同的"桶"中,然后再使用其它排序算法对桶中的数据进行排序,最终再合并桶中的数据达到排序的目的。

​ 之所以这样,是因为待排序的数据比较多时,会影响排序算法的性能,桶排序就是通过数据进行分类,降低数据的规模,从而提高排序算法的性能。

​ 注意:该算法代码简单,但实现难度很大,需要对数据有足够的了解,每个桶中数量要够均匀,才能达到预期效果。

void bucket_sort(int* arr,size_t len)
{
    int bucket[3][len];
    int cnt[3] = {};

    for(int i=0; i<len; i++)
    {
        if(arr[i] < 10000)
            bucket[0][cnt[0]++] = arr[i];
        else if(arr[i] < 50000)
            bucket[1][cnt[1]++] = arr[i];
        else
            bucket[2][cnt[2]++] = arr[i];
    }
//借助快速排序
    int index = 0;
    quick_sort(bucket[0],cnt[0]);
    for(int i=0; i<cnt[0]; i++)
        arr[index++] = bucket[0][i];

    quick_sort(bucket[1],cnt[1]);
    for(int i=0; i<cnt[1]; i++)
        arr[index++] = bucket[1][i];

    quick_sort(bucket[2],cnt[2]);
    for(int i=0; i<cnt[2]; i++)
        arr[index++] = bucket[2][i];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值