数据结构初阶之排序(四)

今天更新完常见八大排序的最后两个排序:归并排序和计数排序

先说简单的

一、计数排序

计数排序的思路非常简单

1.先统计原数组中每个值出现的次数,存放在count数组

2.排序,遍历count数组,对应位置的值出现几次,就往原数组中写几个这个值

举个例子 1 2 2 1 5 4 5

那就创建一个数组,从0-4的下标中依次对应2 3 0 1 2,可以打印出来1 1 2 2 2 4 5 5 

里还要注意的就是count数组应当使用相对映射的方法,比如1000到1010,不能开从0到1010,而是开1000到1010,这样就可以不浪费空间

但局限性就在于此:数据范围大的时候,就不能用计数排序,还有一点,就是当数据类型是浮点型和字符串时,计数排序同样时不适用的。

可以看下面的动图

计数排序

 代码如下:思路就是先找该数据列中的最大值和最小值,然后定义range为最大值和最小值的差值,之后开辟一个数组来存放次数,接下来就要统计次数,

//计数排序
void CountSort(int*a,int n)
{
    int min=a[0],max=a[0];
    //找最大值,最小值
    for(int i=1;i<n;i++)
    {
        if(a[i]<min)
        {
            min=[i];
        }
        if(a[i]>max)
        {
            max=a[i];
        }
    }
    int range = max-min+1;//区间范围个数,用于开辟count数组
    int* count = (int*)calloc(range,sizeof(int));//开辟一个数组用来存放次数
    
    //统计次数
    for(int i=0;i<n;i++)
    {
        count[a[i]-min]++;//对相对位置进行计数
    }
    //遍历计数数组,对原数组进行排序
    int i=0;
    for(int j=0;j<range;j++)
    {
        while(count[j]--)
        {
            a[i++]=j+min;
        }
    }
}

二、归并排序

归并排序:是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行

比如下面这组数据,

归并排序

 代码如下,这里需要开辟一个大小为N的数组来存放归并排序分出来的数,不断分不断分,分到最小元素时,开始并,一直并到有序。

void _MergeSort(int*a,int left,int right,int*temp)
{
    if(left>=right)
        return;//当区间不存在或者只有一个值时,返回
    int mid = (right+left)/2;//mid用来分组,分成[left,mid],[mid+1,right]
    //[left,mid],[mid+1,right]
    _MergeSort(a,left,mid,temp);//递归左边
    _MergeSort(a,mid+1,right,temp);//递归右边
    //归并
    int begin1 = left,end1 = mid;//避免混淆,这里用begin1,end1,begin2,end2来保存									区间
    int begin2 = mid+1,end2 = right;
    int index = left;
    while(begin1<=end1 && begin2<=end2)//两组中的数据有一个结束就结束
    {
        if(a[begin1]<a[begin2])
        {
            temp[index++]=a[begin1++];//将数据拷进去,并++指向下一个位置
        }
        else
        {
            temp[index++]=a[begin2++];//将数据拷进去,并++指向下一个位置
        }
    }
    //出来后有一组数据还没有拷贝进去,如果左边的没结束,则将左边剩下的拷贝进去,右边没结束,则将右边剩下的拷贝进去
    while(begin1<=end1)
    {
    	temp[index++]=a[begin1++];//将剩下的数据全部拷贝进去   
    }
    while(begin2<=end2)
    {
    	temp[index++]=a[begin2++];//将剩下的数据全部拷贝进去    
    }
    for(int i=left,i<=right;i++)
    {
        a[i]=temp[i];//最后还需要将temp中的数据拷贝到原数组
    }
    
}
void MergeSort(int* a,int n)
{
    int *temp =(int*)malloc(sizeof(int)*n);//开辟一个临时数组来存放归并后的数据
    _MergeSort(a,0,n-1,temp);
    
    free(temp);
}

时间复杂度:O(n*logn)
空间复杂度:O(n)

归并排序的非递归思想不难,这个实现起来还是比较困难的,因为细节比较多

这里的细节在于边界修正,第一种end1越界。第二种begin2越界。第三种end2越界。

这里有两种写法,第一种是全部的边界修正,第二种是只修正end2,也就是第一种和第二种情况不修正,因为可以直接将第一种和第二种剩下的数字不归并,直接

void MergeSortNonR(int* a, int n)
{
    int* temp = (int*)malloc(sizeof(int) * n);//开辟一个临时数组
    if (temp == NULL)
    {
        return;
    }
    int groupNum = 1;
    while (groupNum < n)
    {
        for (int i = 0; i < n; i += 2 * groupNum)
        {
            //[begin1][end1] [begin2,end2]
            //控制组进行归并
            int begin1 = i, end1 = i + groupNum - 1;
            int begin2 = i + groupNum, end2 = i + groupNum * 2 - 1;
            int index = begin1;

            //数组的数据个数,并不一定是按整数倍
            //1、[begin2,end2]不存在,修正为一个不存在的区间
            //让它进不去归并
            if (begin2 >= n)//
            {
                begin2 = n + 1;
                end2 = n;
            }
            if (end1 >= n)//end1越界,修正一下
            {
                end1 = n - 1;
            }
            if (end2 >= n)//end2越界,需要修正后归并
            {
                end2 = n - 1;
            }
            while (begin1 <= end1 && begin2 <= end2)//两组中的数据有一个结束就结束
            {
                if (a[begin1] < a[begin2])
                {
                    temp[index++] = a[begin1++];//将数据拷进去,并++指向下一个位置
                }
                else
                {
                    temp[index++] = a[begin2++];//将数据拷进去,并++指向下一个位置
                }
            }
            //出来后有一组数据还没有拷贝进去,如果左边的没结束,则将左边剩下的拷贝进去,右边没结束,则将右边剩下的拷贝进去
            while (begin1 <= end1)
            {
                temp[index++] = a[begin1++];//将剩下的数据全部拷贝进去   
            }
            while (begin2 <= end2)
            {
                temp[index++] = a[begin2++];//将剩下的数据全部拷贝进去    
            }

        }
        //拷贝回原数组
        for (int i = 0; i < n; i++)
        {
            a[i] = temp[i];//最后还需要将temp中的数据拷贝到原数组
        }
        groupNum *= 2;
    }
    free(temp);
}

第二种方法: 

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	// 休息11:48继续
	int gap = 1;
	while (gap < n)
	{
		//printf("gap=%d->", gap);
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [i,i+gap-1][i+gap, i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			// end1越界或者begin2越界,则可以不归并了
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
			//printf("[%d,%d] [%d, %d]--", begin1, end1, begin2, end2);

			int m = end2 - begin1 + 1;//这里开辟数组的大小发生了变化
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int)* m);
		}

		gap *= 2;
	}

	free(tmp);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何以过春秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值