归并排序
代码是Javascript语言写的(几乎是伪代码)
算法介绍
归并排序(Merge Sort)把两个排好序的序列合并成一个排好序的序列
算法原理
我们在拿到一个无序的序列时,其实用了“分治”的思想,先“分”后“治”,先将整个序列通过划分为子序列,再将子序列不断划分为子序列,直到只有单个元素为止,再通过两两合并使其有序,再将合并后的数组继续通过比较大小拷贝的方法继续合并,最终实现整体有序。
算法简单记忆说明
“分” 阶段采用递归的方法,将整个数组不断划分为最小的数组个体
代码实现:
function mergeSort(arr,l,r){
if (l == r) {
return;
}
var mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
}
与我们上一篇文章的递归思想举的例子完全一样,mid为序列的中间数的位置,通过递归让它们不断一分为二。
“治” 阶段将划分好的子序列两两归并排序合并在一起,合并后的序列继续两两合并,最终全部整合形成完整的有序序列
“治”的过程的实现时通是通过准备一个拷贝数组,比较两个待合并的序列,分别给两个索引,谁小拿下来填谁,谁的索引向后移动一位,直到有一边的序列全部被填入拷贝数组,则把剩下的序列的剩下的数填入拷贝数组,最后将拷贝后的数组拷贝回原数组。
待合并两数组
8 | 3 |
---|---|
i | j |
help辅助拷贝数组
3<8,将3填入help数组,j向后移,但是数组耗尽了,所以将8拷入help数组中得到
3 | 8 |
---|
同理2和9也通过比较拷贝得到
2 | 9 |
---|
继续合并合并后的数组
小的填入而后索引后移直到耗尽一方,然后将剩下的序列的剩下的元素填入数组中。
最后大合并
算法复杂度和稳定性
归并排序的时间复杂度是O(N*logN)
归并排序采用递归的思想来做
master公式 T(N) = a*T(N/b) + O(Nd)
从父问题与子问题的大层面关系来看,整个数组样本量为N,左边一半,样本量为N/2,右边一般,样本量为N/2,左边跑完跑右边,样本量为N/2的过程发生了2次,得到两个排好序的子样本,剩下要做的是在外排的过程中划过N个数,因为两个下标依此在动,最后整体拷贝回数组,剩下的操作为N
则a=2,b=2,d=1
由log(b,a) = d -> 复杂度为O(Nd * logN)
得到归并排序的时间复杂度是O(N*logN)
归并排序是稳定排序算法
算法稳定性的定义: 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的。
举例
1,3,2,5,2,4
排序的过程中1,2,3与2,4,5排序后为1,2,2,3,4,5
所以为稳定排序
代码实现
function mergeSort(arr,l,r){ //递归
if (l == r) {
return;
}
var mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
function merge(arr,l,m,r){
var help = new Array(r-l+1);//创立辅助数组用来拷贝数字
var i = 0;
var p1 = l;//左边部分首位
var p2 = m+1;//右边部分首位
while(p1 <= m&&p2 <= r){
help[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
//完成比较合并,p1小填p1,p2小填p2,填入数组的那个部分++向后移动一位,辅助数组也要向后移动一位
} //必定有且只有一个部分越界
while(p1<=m){
help[i++] = arr[p1++]; //p2越界,把p1剩下的填了
}
while(p2<=r){
help[i++] = arr[p2++]; //p1越界,把p2剩下的填了
}
for(i = 0;i<help.length;i++){
arr[l+i] = help[i]; //拷贝回原数组
}
}
function startMerge(arr){
if(arr == null||arr.length<2){
return arr;
}
mergeSort(arr,0,arr.length-1);
}
//对数器
function sortNumber(a,b){
return a - b
}
function rightMethod(arr) {
arr.sort(sortNumber);
}
function generateRandomArray(maxSize, maxValue) {
var arr = new Array(Math.floor((maxSize + 1) * Math.random()));
for (var i = 0; i < arr.length; i++) {
arr[i] = Math.floor((maxValue + 1) * Math.random())-Math.floor(maxValue * Math.random());
}
return arr;
}
function isEqual(arr1, arr2) {
if ((arr1 == null && arr2 != null ) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false
}
}
return true;
}
function copyArray(arr) {
if (arr == null) {
return null;
}
return [].concat(arr);
}
function Test() {
var testTimes = 10000;
var maxmaxSize = 10;
var maxValue = 100;
var succeed = true;
for (var i = 0; i < testTimes; i++) {
var arr1 = generateRandomArray(maxmaxSize,maxValue);
var arr2 = copyArray(arr1);
var arr3 = copyArray(arr1);
startMerge(arr1);
rightMethod(arr2);
console.log(arr1);
if (!isEqual(arr1, arr2)) {
succeed = false;
console.log(arr3);
break;
}
}
console.log(succeed ? "Good job!" : "Damn it!");
}
Test();
通过对数器的验证!Good job!