归并排序
归并排序(Merging Sort)也是一种使用分治法来实现的排序算法,它是现代计算机创始人John von Neumann于1945年发明的。
归并排序思路
先递归的把数组划分为两个子数组,一直递归到数组中只有一个元素,然后再调用函数把两个子数组排好序,因为该函数在递归划分数组时会被压入栈,所以归并排序真正的作用是对两个有序的子数组进行排序;
归并排序运行过程
- 判断参数的有效性,也就是递归的出口;
- 首先什么都不管,直接把数组平分成两个子数组;
- 递归调用划分数组函数,最后划分到数组中只有一个元素,这也意味着数组是有序的了;
- 调用排序函数,把两个有序的数组合并成一个有序的数组;
- 进行排序,让两个数组的元素进行比较,把大的/小的元素存放到临时数组中,如果有一个数组的元素被取光了,那就直接把另一数组的元素放到临时数组中,然后把临时数组中的元素都复制到实际的数组中;
初始值: 6 5 3 1 8 7 2 4
首先将数组分为长度为 2 的子数组,并使每个子数组有序:
[6, 5] [3, 1] [8, 7] [2, 4]
[5, 6] [1, 3] [7, 8] [2, 4]
然后再两两合并,并使每个子数组有序:
[6, 5, 3, 1] [8, 7, 2, 4]
[1, 3, 5, 6] [2, 4, 7, 8]
最后将两个子数组合并:
[6, 5, 3, 1, 8, 7, 2, 4]
[1, 2, 3, 4, 5, 6, 7, 8]
归并排序代码
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray * array = [NSMutableArray arrayWithObjects:@8,@7,@6,@5,@4,@3,@2,@1, nil];
//调用排序
[self mergeSortArray:array];
}
- (void)mergeSortArray:(NSMutableArray *)array {
//创建一个副本数组
NSMutableArray * auxiliaryArray = [[NSMutableArray alloc]initWithCapacity:array.count];
//对数组进行第一次二分,初始范围为0到array.count-1
[self mergeSort:array auxiliary:auxiliaryArray low:0 high:array.count-1];
}
- (void)mergeSort:(NSMutableArray *)array auxiliary:(NSMutableArray *)auxiliaryArray low:(int)low high:(int)high {
//递归跳出判断
if (low>=high) {
return;
}
//对分组进行二分
int middle = (high - low)/2 + low;
//对左侧的分组进行递归二分 low为第一个元素索引,middle为最后一个元素索引
[self mergeSort:array auxiliary:auxiliaryArray low:low high:middle];
//对右侧的分组进行递归二分 middle+1为第一个元素的索引,high为最后一个元素的索引
[self mergeSort:array auxiliary:auxiliaryArray low:middle + 1 high:high];
//对每个有序数组进行回归合并
[self merge:array auxiliary:auxiliaryArray low:low middel:middle high:high];
}
- (void)merge:(NSMutableArray *)array auxiliary:(NSMutableArray *)auxiliaryArray low:(int)low middel:(int)middle high:(int)high {
//将数组元素复制到副本
for (int i=low; i<=high; i++) {
auxiliaryArray[i] = array[i];
}
//左侧数组标记
int leftIndex = low;
//右侧数组标记
int rightIndex = middle + 1;
//比较完成后比较小的元素要放的位置标记
int currentIndex = low;
while (leftIndex <= middle && rightIndex <= high) {
//此处是使用NSNumber进行的比较,你也可以转成NSInteger再比较
if ([auxiliaryArray[leftIndex] compare:auxiliaryArray[rightIndex]] != NSOrderedDescending) {
//左侧标记的元素小于等于右侧标记的元素
array[currentIndex] = auxiliaryArray[leftIndex];
currentIndex++;
leftIndex++;
} else {
//右侧标记的元素小于左侧标记的元素
array[currentIndex] = auxiliaryArray[rightIndex];
currentIndex++;
rightIndex++;
}
}
//如果完成后左侧数组有剩余
if (leftIndex <= middle) {
for (int i = 0; i<=middle - leftIndex; i++) {
array[currentIndex +i] = auxiliaryArray[leftIndex +i ];
}
}
}
分析下上面代码:其实上面的代码主要的是两个函数,第一个是划分数组函数,第二个是对两个有序数组合并的归并函数;上述的实现代码中重点是递归划分数组,好久不用递归,这里是想了又想想了又想,终于想明白了。
性能(算法时间、空间复杂度、稳定性)分析
时间复杂度
时间复杂度为 O( nlogn )。
公式就不推导了,推导过程参考地址:http://blog.youkuaiyun.com/yuzhihui_no1/article/details/44198701#t2
空间复杂度
空间复杂度为O(n)。
稳定性
归并排序是稳定的。
总结
在时间上也是非常有效的(最差时间复杂度和最优时间复杂度都为 O(nlogn) ),但是这种算法很消耗空间,一般来说在内部排序不会用这种方法,而是用快速排序;外部排序才会考虑到使用这种方法;