图解算法系列之归并排序
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图解算法系列之归并排序相关的知识,希望对你有一定的参考价值。
(1)算法描述
对于给定的线性序列,将当前序列不断的进行分组,当每个分组的数据只有一个元素时,代表这个分组是有序的,那么向上合并。每一层两两合并,合并的过程是,开辟一个新空间,使用两个指针同时扫描两个有序的分组,使得较小的元素或者较大的元素先进入新空间。在不断的比较之后,如果一个分组的元素为空,直接拷贝另一个没有被用完的元素到新空间。
(2) 图解算法
归并排序的过程
合并函数的合并过程
(3) C/C++代码实现
CustomSort.h
// 归并排序
void MergeSort(int arr[], int number);
// 内部的归并排序
void __MergeSort(int arr[], int left, int right);
// 内部合并
void __Merge(int arr[], int left, int mid, int right);
CustomSort.cpp
/************************************************************
- 功能描述:实现归并排序
- int arr[]: 待排序的数组
- int number: 待排序数组中的元素
- 返回值:void
************************************************************/
void MergeSort(int arr[], int number) {
// 数组为空或者有一个一下的元素直接返回
if(arr == NULL || number <= 1) {
return;
}
// 调用内部排序算法
__MergeSort(arr, 0, number-1);
}
/************************************************************
- 功能描述:实现归并排序的内部排序
- int arr[]: 待排序的数组
- int left: 待排序数组的左边界
- int right: 待排序数组的右边界
- 返回值:void
************************************************************/
void __MergeSort(int arr[], int left, int right) {
// 如果 right <= left 没有排序的必要性
if(right <= left) {
return;
}
// 计算中间值
// 没有使用 (right + left) / 2 是为了避免数据太大导致内存溢出
int mid = (right - left) / 2 + left;
// 分别排序两个分组
__MergeSort(arr, left, mid);
__MergeSort(arr, mid+1, right);
// 排序完成后就合并两个分组
__Merge(arr, left, mid, right);
}
/************************************************************
- 功能描述:实现归并排序的分组合并
- int arr[]: 待排序的数组
- int left: 待合并数组的左边界
- int right: 待合并数组的右边界
- int mid: 待合并数组的中间值
- 返回值:void
************************************************************/
void __Merge(int arr[], int left, int mid, int right) {
int len = right - left + 1;
// 动态创建数组,因为每个分组的大小都不一样,使用完需要delete[]空间
int *temp = new int[len];
// 用于第一个分组的指针one
// 用于第二个分组的指针two
// 用于辅助数组的指针i
int one = left;
int two = mid + 1;
int i = 0;
// 判断只要one和two都没有越界就不断的进行比较
while(one <= mid && two <= right) {
temp[i++] = arr[one] > arr[two] ? arr[one++] : arr[two++];
}
// 判断哪个数组的指针还没到头就直接全都拷贝到temp数组
while(one <= mid) {
temp[i++] = arr[one++];
}
while(two <= right) {
temp[i++] = arr[two++];
}
// 往回拷贝数组,注意数组的位置
for(int j = 0; j < len; j++) {
arr[left + j] = temp[j];
}
delete[] temp;
}
(4) Java代码实现
public class MergeSort {
// 归并排序函数
public static void sort(int[] arr) {
// 如果数组为null或者是数组中的元素小于2, 没有排序的意义
if (arr == null || arr.length < 2) {
// 直接返回
return;
}
// 调用排序函数
mergeSort(arr, 0, arr.length - 1);
}
// 主要逻辑
public static void mergeSort(int[] arr, int l, int r) {
// 如果左边的指针等于右边指针, 也就是数组不能再分割
if (l == r) {
// 就要直接返回
return;
}
// 数组中间数值的指针位置
int mid = l + ((r - l) >> 1);
// 排序中间位置左边的数组
mergeSort(arr, l, mid);
// 排序中间位置右边的数组
mergeSort(arr, mid + 1, r);
// 合并函数
merge(arr, l, mid, r);
}
// 合并函数
public static void merge(int[] arr, int l, int m, int r) {
// 创建辅助数组
int[] help = new int[r - l + 1];
// 辅助数组的指针位置
int i = 0;
// 数组1的指针位置
int p1 = l;
// 数组2的指针位置
int p2 = m + 1;
// 判断左边和右边的数组是否越界
while (p1 <= m && p2 <= r) {
// 如果都没有越界, 向辅助数组添加数据,类似于外排的方式
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 如果第一个数组的指针还没到头, 就要拷贝数组
while (p1 <= m) {
help[i++] = arr[p1++];
}
// 如果第二个数组的指针还没到头, 就要拷贝数组
while (p2 <= r) {
help[i++] = arr[p2++];
}
// 拷贝排序后数组
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
}
}
(5) 时间复杂度分析
当函数出现递归调用的时候,一个函数A调用了本身,假定是subA,那么当前的函数A将会压入系统的栈内,系统将会保存现场(包括函数执行到哪一行代码,函数当前的调用状态以及函数中变量的值),进行下一个函数的执行,经过一层层函数的调用,遇到一个返回条件,系统中的栈中保存的状态将会一个一个的弹出,也就是函数的恢复现场,最后函数调用结束。
只要符合master公式的都可以使用一下方法计算时间复杂度:
master公式:T(N)=a*T(N/b)+O(Nd)
1) log(b, a) > d 复杂度是O(N log(b,a))
2) log(b, a) = d 复杂度是O(Nd * log(2, N))
3) log(b, a) < d 复杂度是O(Nd)
估计时间复杂度:左侧部分的规模和右侧部分的规模都是N/2,在整体外排的过程中总共划过N个数,算式为:T(N)=2T(N/2)+O(N)
,代入master公式,复杂度就是O(N*log2N)
,空间复杂度是O(N)
。
以上是关于图解算法系列之归并排序的主要内容,如果未能解决你的问题,请参考以下文章