数组排序(C语言)

以下就是常见基本的排序方式
1、冒泡排序
2、选择排序
3、插入排序
4、希尔排序
5、归并排序
6、快速排序
7、堆排序
8、计数排序

菜鸟教程排序链接
该处的排序原理,讲的其实十分细致。

这里着重介绍下,快速排序和归并排序。

快速排序

快速排序原理:就是取一个值作为基准,将数组小的移到其左边,大的数据移到右边。反复此过程,就可以实现排序。
相信你看过了菜鸟的代码,快速排序的迭代版本以及递归版本,其中有一个三数取中原则。
根据快速排序原理,我们拿来作为基准值的数,如果能是该数组的中间数,那么就能实现尽量接近2分。
但是倘若一个数组全是【2,2,2,2,2,2,2,2】,再以那样的代码,三数取中也无法改变,所以这个时候,那个数据取完之后,就会导致这次排序走了一次n,并且直到最后一躺,时间复杂度就是n^2
为了解决这个问题,我们在每次排序时,需要相等的放在中间,小的放在前面,大的放在后面。
以下是实例。

int part_sort_returnArrays(int * a,int n,int* returnSize)//如果说,存在全都相等的数字,那么这里不就是单趟遍历,最后导致quiksort是n^2的时间复杂度
//因此这里提供一种修改方案,相等的全部移往中间,但是值得注意,由于返回相当于是一个数组了,那么就不仅仅要i下标,还要整个长度
{

    if(n<=1)
    {
        return 0;
    }
    int midi = GetMid_Index(a,0,n-1);
    swap(&a[0],&a[midi],sizeof(a[0]));
    int i =0;
    int temp = a[0];
    int count = 0;//计算与temp值相等的数值总个数
    int m = n -1;
    for(int j=0;j<m+1;)//前后指针下标版本
    {
        if(a[j]==temp)
        {
            count++;
            j++;
        }
        else if(a[j]<temp)
        {
            swap(&a[i++],&a[j],sizeof(a[j]));
            j++;
        }
        else
        {
            swap(&a[m--],&a[j],sizeof(a[j]));
        }
    }
    for(int k = 0;k<count;k++)//注意,由于这里每次都多走了一组,实际效率有所下降,
    {
        a[i+k] = temp;
    }
    *returnSize = count;
    return i;
}

还有一种问题存在,有一种数据,让你每次三数取中都取到比很小的数或者很大的数,那么时间复杂度基本也就是o(N^2)
所以为了解决这种困境,尽量保证每次取中数据随机,大体上取了中间值。

int GetMid_Index(int *a,int left,int right)
{
    int mid = 0;
    mid = left+ rand()%(right-left);//三数,随机取中,这里应对leetcode的一些专门设计的数据
    if(a[mid]>a[left])
    {
        if(a[mid]<a[right])
        {
            return mid;
        }
        else if(a[left]>a[right])
        {
            return left;
        }
        else
        {
            return right;
        }
    }
    else //mid<left
    {
        if(a[mid]>a[right])
        {
            return mid;
        }
        else if(a[right]<a[left])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
}

归并排序

int* arrs_sort(const int *arr,const int* brr,const int sz1,const int sz2)//这里会申请动态太内存,调用函数请记得释放 ,返回数组的大小一定就是sz1+sz2 
{
	int* rearrs = (int*)malloc(sizeof(int)*(sz1+sz2));
	int c = 0; 
	int a = 0,b = 0;
	int sign = a<sz1 ;
    sign += b<sz2;
	while(sign)
	{
		switch(sign)
	{
		case 2:
			{
				if(arr[a]>brr[b])
				{
					rearrs[c++] = brr[b++];
				}
				else
				{
					rearrs[c++] = arr[a++];
				}
				break;
				
			}
		case 1:{
			if(a<sz1)
			{
				rearrs[c++] = arr[a++];
			}
			else if (b<sz2)
			{
				rearrs[c++] = brr[b++];
			}
			break;
		}
		default :break;
	}
    sign = a<sz1;
    sign += b<sz2;
	}
	return rearrs; 
} 
//归并排序
//迭代版本

// int* arrs_sort_NonMalloc(const int *arr,const int* brr,const int arr_sz,const int brr_sz)//必须保证第一个数组大小时,sz1是arr传过来要排序
// {
// 	int a = 0,b = 0;
//     while(b<brr_sz)
//     {

//     }
// 	return arr; 
// } 
int merge_sort(int arr[],int sz)
{
    int a = 2;
    for(;a/2<sz;a*=2)
    {
        for(int i=0;i<sz;i+=a)
        {
            if(i+a<=sz)//保证还有数
            {
                int *t = arrs_sort(&arr[i],&arr[i+a/2],a/2,a/2);
                memmove(&arr[i],t,a*sizeof(int));
                free(t);
            }
            else if(i+a/2<sz)
            {
                int *t = arrs_sort(&arr[i],&arr[i+a/2],a/2,sz-a/2-i);
                memmove(&arr[i],t,(sz-i)*sizeof(int));
                free(t);
            }
        }
    }
    return 0;
}
//归并排序的迭代版本

以上是归并排序的数组迭代版本,这个还是比较好实现。问题关键就是分组去排序。
归并的核心就是合并排序有序数组,所以这里以2或者3底去分组都是可以的。

排序稳定性的思考

为什么快速排序会在某些场景需要优化,而归并排序写好在应对大多数场景都比较合适?

排序稳定性:指原本相等的数据,本来应该在前面的数据,排序完成也依然在前面。
举个例子:[5a, 2, 3a, 1, 5b, 4, 3b]
排序完后:[1, 2, 3a, 3b, 4, 5a, 5b]
上述就说明是稳定排序,否则就是不稳定。

归并和快排的稳定性。
我们回到归并和快排两者原理上去,快排一开始就是以某一个数据为基准,去分组,如此循环以往,我们知道,当只有选数据选到中间数,才能找到。在选中间数的时候,就会使得某些数据位置改变。
而我们看归并排序,由于是直接进行数据的合并,对原数据就不会产生过于大的影响。

这里留下我的一个猜测:凡是不稳定排序,数据顺序对其性能都是比较大的,相对的,稳定排序就受数据顺寻影响会很小。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值