归并排序 计数排序

一、归并排序

        归并排序的思想是分治的思想,和二叉树中的思想基本是一致的。

        思路是将整个数组划分为左右两个数组,分别让左右两个数组有序,再对整个进行归并。就能让整个数组有序,而要让左右两个数组分别有序,就是递归——让左右区间分别再施行上述的操作。

        归并的方法是,开一个和原数组一样大的数组tmp,用两个下标分别去指向左右两个区间的开头,取小的一个放进tmp中。取左,左++;取右,右++。

        不妨用【begin1,end1】代表左区间,【begin2,end2】代表右区间。

     因为每次递归都要传子区间的开头和结尾,我们在形参上选择:

void _Mergesort(int* a,int begin,int end,int* tmp)//用这个函数来递归

对于每个子区间:①考虑是否是不可划分的最小区间->②若不是,进行左右递归③->递归完return,进行归并,归并时把元素放进tmp中④将tmp元素拷贝到源数组(tmp数组相当于一个中介)

void _Mergesort(int* a,int begin,int end,int* tmp)
{
     //如果是不可划分的最小区间 就return
	if (begin >= end)
	{
		return;
	}
	//递归左右区间
	int mid = (begin + end) / 2;
	_Mergesort(a,begin, mid,tmp);
	_Mergesort(a,mid + 1, end,tmp);
	//进行归并
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int i = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1];
			begin1++;
		}
		else
		{
			tmp[i++] = a[begin2];
			begin2++;
		}
	}
    //循环跳出来必定有个数组是被归并完的
    //我们要把另一个数组里剩下的放进去
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1];
		begin1++;
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2];
		begin2++;
	}
    //用memcpy把tmp数组中的元素复制到源数组
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
void Mergesort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_Mergesort(a,0,n-1, tmp);
	free(tmp);
}

二、归并排序的非递归

从递归到非递归无非就是改成循环的版本。

我们递归到最后就是一个和一个进行归并,而一个一个归并的目的只是为了让左右子区间有序,

为了达到左右区间有序的目的,我们非递归的话只需要把递归的过程反着进行。也就是先一个一个归并->两两归并->四个四个归并.... 这样下去就能让左右区间有序,最后一次归并就能让整个区间有序。

我们初始的时候设定一个gap表示区间的距离。开始是一个一个比较,那么gap就是1。

我们要下标是0和1的归并,2和3的归并,3与4的归并...(gap为1)

然后是[0,1]与[2,3]归并,[3,4]与[5,6]归并.....(gap为2)

[0,3]与[4,7]归并,[8,11]与[12,15]归并。(gap为4)

对每一个固定的gap,我们要循环归并完一遍数组,不难看出gap每次循环完都要乘以2。

我们可以给定一个循环变量i,让i跑遍0到n-1,设定左区间[begin1,end1] = [i,i+gap-1],右区间[begin2,end2] = [i+gap,i+2*gap-1];

循环内部归并的部分还是和递归的版本基本一致,不过最后的拷贝部分,我们是让数组循环完再拷贝到源数组。

void MergesortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	//设置gap
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0;i < n;i += 2*gap)
		{
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1];
					begin1++;
				}
				else
				{
					tmp[j++] = a[begin2];
					begin2++;
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1];
				begin1++;
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2];
				begin2++;
			}
		}
		memcpy(a, tmp, sizeof(int)*n);
		gap = 2 * gap;
	}
}

我们不妨运行一下,随便给一个数组。我们每次循环都打印一下每个区间

void testMergesort()
{
	int a[] = { 0,5,7,10,3,6,12 };
	MergesortNonR(a, 7);
	for (int i = 0;i < 7;i++)
	{
		printf("%d ",a[i]);
	}
}

结果发现越界了,主要的原因是我们控制了begin1是小于n的,但是并没有考虑其他三个区间端点的情况。

所以我们要做的就是修正边界,因为begin1总是小于n的,不需要修正。

要修正的情况就三种,end1>=n,begin2>=n,end2>=n。

 修正后:

1、当end1>=n时候,最后一个元素可能没有进行归并,并且拷贝到源数组,导致原数组有随机值。

当然我上面的例子并没有很好的体现出这种情况,但是这确实是需要处理的情况。

我们需要把end1修正为n-1,而另一个区间设置为不存在的区间,这样能保证最后进行归并的时候,下标为n-1的元素能归并,而不对另一个区间进行任何处理。

2、当begin2 >= n 或者是 end2 >= n时候,我们要做的就是把end2修正为n-1。

void MergesortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	//设置gap
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0;i < n;i += 2*gap)
		{
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			int j = begin1;
			printf("gap->%d", gap);
			printf("[begin1,end1]= [%d,%d],[begin2,end2] = [%d,%d]", begin1, end1, begin2, end2);
			printf("\n");
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n || end2 >= n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1];
					begin1++;
				}
				else
				{
					tmp[j++] = a[begin2];
					begin2++;
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1];
				begin1++;
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2];
				begin2++;
			}	
		}
		memcpy(a, tmp, sizeof(int)*n);
		gap = 2 * gap;
	}
}

三、计数排序

计数排序的思想是开辟一个新的数组,然后建立原数组中每个元素到新数组下标的一个映射。遍历数组,在新数组下标为a[i]处++,然后我们把新数组中的元素按出现次数写回原数组。

对于第一步开数组,开多大空间呢?这暗示着我们要去找到原数组中最大的元素和最小的元素,这样才能确定空间的范围。

void CountSort(int* a, int n)
{
	int min = a[0];
	int max = a[0];
	for (int i = 1;i < n;i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	int range = max - min;
	int* k = (int*)calloc(range,sizeof(int));
	for (int i = 0;i < n;i++)
	{
		k[a[i] - min]++;//在新数组中下标为a[i]-min处++;
	}
	int j = 0;
	for (int i = 0;i<range;i++)
	{
		while (k[i]--)
		{
			a[j++] = i + min;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值