常见排序
归并排序
1、基本思路:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。
2、过程:
- 把数组从中间划分成两个子数组。
- 一直递归地把子数组划分成更小的数组,直到子数组里面只有一个元素。
- 依次按照递归的返回顺序,不断合并排好序的子数组,直到最后把整个数组顺序排好。
3、时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
当序列分解到只有一个元素或是没有元素时,就可以认为是有序了,这时分解就结束了,开始合并:
递归实现
归并排序的递归过程十分类似于二叉树的后序遍历,先将区间分为左区间和右区间,划分到不能继续划分时,进行归并。
步骤:
- 创建一个临时数组,在临时数组中进行归并,防止归并时将原顺序打乱,整体归并完之后再将数据拷贝回原数组。
- 将区间划分为[begin , mid ] [mid+1,end]两段子区间,进行后序遍历递归,当划分到只有一个元素时返回。
- 进行归并。使用begin1和end1控制左区间,begin2和end2控制右区间,比较两个区间中的值将其插入到temp数组中,当其中一个区间归并结束时停止。
- 将未排完序的数组接着全放入temp数组中。
- 最后将temp中的值拷贝到原数组中。
递归实现归并代码:
void _MergeSort(int* a, int begin, int end, int* temp)
{
if (begin >= end) return;
//将区间分为左右两半
int mid = (begin + end) / 2;
//[begin,end]--->[begin , mid ] [mid+1,end]
//开始递归拆开
_MergeSort(a, begin, mid, temp);
_MergeSort(a, mid + 1, end, temp);
//合并
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
//i要从相对位置起
int i = begin;
//如果两个区间有一个结束,就停止比较归并
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
temp[i++] = a[begin1++];
}
else
{
temp[i++] = a[begin2++];
}
}
//使用两个while将未归并完的数组进行追加
while (begin1 <= end1)
{
temp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = a[begin2++];
}
//拷贝回去要使用相对位置
memcpy(a + begin, temp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc fail");
return;
}
//使用一个子函数进行排序
_MergeSort(a, 0, n - 1, temp);
free(temp);
}
非递归实现
实现思路:
- 模拟最后一层递归中排列间距为1的子序列,例[0,0]和[1,1]……,即最后一层的归并,然后再进行间距为2的子序列,直到归并完成;
- 寻找规律推出左、右区间的公式: [i , i+gap-1] [i+gap,i+2*gap-1],然后就是正常的递归合并.
- i 的变化是i+=2*gap,表示每次跳过两个区间,进入下一段区间.
- 当gap每次*=2 , 表示进入了下一层递归 , 直到gap >= n 时停止.
- 将临时数组拷贝回原数组.
非递归实现归并代码:
//归并排序--非递归
void MergeSortNonR(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc fail");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2*gap - 1;
//[begin1,end1] [begin2,end2] 归并
//如果第二组不存在,这一组就不用归并
if (end1 >= n || begin2 >= n)
{
break;
}
//如果第二组的右边界越界,修正一下
if (end2 >= n)
{
end2 = n - 1;
}
int index = i;
//如果两个区间有一个结束,就停止比较归并
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
temp[index++] = a[begin1++];
}
else
{
temp[index++] = a[begin2++];
}
}
//使用两个while将未归并完的数组进行追加
while (begin1 <= end1)
{
temp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[index++] = a[begin2++];
}
//拷贝回去要使用相对位置
memcpy(a + i, i + temp, (end2 - i + 1) * sizeof(int));
}
gap *= 2;
}
}