归并排序理解
归并排序采用的分治的思想,所谓分治思想即分而治之,讲一个整体分为若干个子模块,然后对每个子模块进行处理,合并起来之后便对整体完成了处理;我们这里采用的是二路归并排序算法;首先对一组数据进行分割,一般我们会进行对半分,即mid = length /2;作为中间点,紧接着同样对每个分割好的模块再次进行按如上的方式分割,直至最后会被分成单个一组或者两个一组的数组,然后依次向上进行归并排序,重点的算法就在归并这里;如下图所示:
进行向上归并的算法是是将两组数据 复制出来一份,然后给定三个索引 i ,j ,k ;i指向数组左半部分的首元素,j 指向右半部分的首元素,k指向 原来数组的首元素;
代码如下:
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
template<typename T>
void __merge(T arr[], int l, int mid, int r){
//* VS不支持动态长度数组, 即不能使用 T aux[r-l+1]的方式申请aux的空间
//* 使用VS的同学, 请使用new的方式申请aux空间
//* 使用new申请空间, 不要忘了在__merge函数的最后, delete掉申请的空间:)
T aux[r-l+1]; //申请新的一个数组空间
//T *aux = new T[r-l+1];
for( int i = l ; i <= r; i ++ )
aux[i-l] = arr[i]; //将原来数组中的数值复制到新开辟的数组中
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int i = l; //i指向左半部分的起始索引位置l
int j = mid+1; //j指向右半部分起始索引位置mid+1
for( int k = l ; k <= r; k ++ ){
if( i > mid ){ // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j-l]; j ++;
}
else if( j > r ){ // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i-l]; i ++;
}
else if( aux[i-l] < aux[j-l] ) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i-l]; i ++;
}
else{ // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j-l]; j ++;
}
}
//delete[] aux;
}
// 递归使用归并排序,对arr[l...r]的范围进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r){
//if( l >= r )
//return;
if(r-l+1 <= 15)//优化点2: 由于近乎有序的数组对使用归并排序并不是 那么理想设置一定的阈值 数组长度小于一定的数值时 可以采用 插入排序
{
InsertionSort02(arr,l,r);
return ;
}
int mid = (l+r)/2;
__mergeSort(arr, l, mid);
__mergeSort(arr, mid+1, r);
if(arr[mid] > arr[mid+1]) //优化点1:如果左区间最后一个元素数值 小于 右区间首元素说明数组已经有序 不需要归并
__merge(arr, l, mid, r);
}
template<typename T>
void mergeSort(T arr[], int n){
__mergeSort( arr , 0 , n-1 );
}
// 比较InsertionSort和MergeSort两种排序算法的性能效率
// 整体而言, MergeSort的性能最优, 对于近乎有序的数组的特殊情况, 见测试2的详细注释
int main(int argc, const char * argv[]) {
// insert code here...
int n = 50000;
//cout<<"test for random array size = "<<n<<" [0 ,"<<n<<"]"<<endl;
/*
在数据量非常大的时候 归并排序的时间短 插入排序的时间长一些
在近乎有序的数组中排序 插入排序的优势较为明显
*/
//int *arr1 = SortTestHelper::generateRandArray(n, 0, n);
//int *arr2 = SortTestHelper::copyArray(arr1, n);
cout<<"test for generateNearlyOrderedArray random array size = "<<n<<" [0 ,"<<n<<"]"<<endl;
int swapTime = 800; //交换次数
int* arr1 = SortTestHelper::generateNearlyOrderedArray(n, swapTime);
//int *arr1 = SortTestHelper::generateRandArray(n, 0, n);
int* arr2 = SortTestHelper::copyArray(arr1, n);
int* arr3 = SortTestHelper::copyArray(arr1, n);
SortTestHelper::testSort("MergeSort", mergeSort, arr1, n);
SortTestHelper::testSort("InsertionSort",InsertionSort1, arr2, n);
SortTestHelper::testSort("MergeSortBU", mergeSortBU, arr3, n);
// SortTestHelper::printArray(arr1, n);
delete[] arr1;
delete[] arr2;
delete[] arr3;
return 0;
}
最差时间复杂度: 在近乎有序的数组排序的时候 会退化为 该复杂度
平均时间复杂度:
最差空间复杂度:
稳定性:稳定
(1)可优化点1:在进行两组数组merge的时候,如果左区间的尾元素小于左区间的首元素,这时候两组数组是有序的,因此可以不需进行归并操作
(2)再利用近乎有序的数组进行归并排序的时候,想象下左右区间的数组都是有序的,因此如果在进行归并排序的话会造成时间的浪费,因此可以设置下,在近乎有序的数组中可以采用插入排序的算法进行优化,插入排序算法有明显的优势。
上述部分阐述的是自顶向下的递归的方式的归并排序,因此下面将阐述自底向上的迭代的方式的归并算法