5. 归并排序
5.1.1 算法分析和思路
-
基本思想
采用分治法(Divide and Conquer)的一个典型应用
将两个或两个以上的有序子序列归并为一个有序序列。- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
-
2-路归并排序
在内部排序中,通常采用的是2-路归并排序
将两个位置相邻的有序子序列L[l…m]和L[m+1…n]归并为一个有序序列L[l…n],即将两个有序子序列归并成一个有序序列
5.1.2 算法示例
归并排序画出来像一个倒着的树,称之为“归并树”
归并需要的趟数,即“归并树”的高度:
l
o
g
2
n
log_2 n
log2n
整个归并排序仅需logn趟
- 关键问题:如何将两个有序序列合成一个有序序列?
- 增加一个空间为两个有序序列之和的辅助空间;
- 两个指针分别指向两个有序序列第一个元素;
- 比较指针指向的元素大小,较小者放入辅助空间,并移动子序列和辅助空间的指针;
- 重复步骤3,直至合并为一个有序序列。
5.1.3 算法代码
//归并排序算法
void MergeSort(SqList &L){
int len = 1;
SqList tempL;
InitList_Sq(tempL);//新建辅助顺序表tempL
SqList * LP = &L;//初始化LP指针指向顺序表L
SqList * tempLP = &tempL;//初始化tempLP指针指向顺序表tempL
SqList * tempP;//交换指针时的临时指针
while(len < L.length) {
int s = len;
len = 2 * s;
int i;
for(i = 1; i+len <= L.length; i = i + len){//有哨兵位,索引从1开始
Merge(*LP, *tempLP, i, i+s-1, i+len-1 );//对等长的两个子表合并
}
if(i+s <= L.length){
Merge(*LP, *tempLP, i, i+s-1, L.length);//对不等长的两个子表合并
}
else{
//剩余且无需归并的记录依次赋值到辅助顺序表对应位置
for(; i <= L.length; i++){
tempLP->nums[i] = LP->nums[i];
// (*tempLP).nums[i] = (*LP).nums[i];
}
}
//交换LP和tempLP,以保证下一趟归并时,仍从LP归并到tempLP
tempP = LP; LP = tempLP; tempLP = tempP;
}
/*
最后判断排序好的顺序表是否是L即可
LP指向排好序的顺序表,只需判断LP是否指向L即可
若LP不指向L,即L并非是最终有序,则需要把有序表中的记录依次赋值到L对应位置上
*/
if(&L != LP){
for(int i = 1; i <= L.length; i++){
L.nums[i] = (*LP).nums[i];
}
}
}
//归并操作:将两个有序序列合成一个有序序列
//将L[low...m]和L[m+1...high]归并到辅助空间temp[low...high]
void Merge(SqList &L, SqList &temp, int low, int m, int high){
int i = low;//i指向第一个子序列的第一个元素
int j = m+1;//j指向第二个子序列的第一个元素
int k = low;//k指向辅助空间里对应第一个子序列第一个元素的位置
while((i <= m) && (j <= high)){
if(L.nums[i].key <= L.nums[j].key){
temp.nums[k++] = L.nums[i++];
}else{
temp.nums[k++] = L.nums[j++];
}
// temp.nums[k++] = (L.nums[i].key <= L.nums[j].key) ? L.nums[i++] : L.nums[j++];
}
//若其中一个子序列没有元素时,剩下的一个子序列的剩余元素全部直接搬到辅助空间
while(i <= m){
temp.nums[k++] = L.nums[i++];
}
while(j <= high){
temp.nums[k++] = L.nums[j++];
}
}
5.1.4 算法性能分析
- 时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
- 所有元素最后都要合并一次是n,总共需要合并logn,所以总共是nlogn
- 空间复杂度:
O
(
n
)
O(n)
O(n)
- 需要一个与原始序列同样大小的辅助序列,这正是此算法的缺点。
- 是一种稳定的排序方法