一、归并排序
归并排序的思想是分治的思想,和二叉树中的思想基本是一致的。
思路是将整个数组划分为左右两个数组,分别让左右两个数组有序,再对整个进行归并。就能让整个数组有序,而要让左右两个数组分别有序,就是递归——让左右区间分别再施行上述的操作。
归并的方法是,开一个和原数组一样大的数组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;
}
}
}