排序算法之归并排序
1. 自顶向下的归并排序
中心思想
将待排序的数组平均切分为两半,将前半部分和后半部分分别进行排序,再讲两个有序数组归并到一个数组中
特点
递归,需要额外的空间(辅助数组)来保存切分完成的子数组,主要难点在于合并
操作步骤
- 将待排序数组均分为两半
- 对前半部分进行排序
- 对后半部分进行排序
- 合并两个子数组
- 递归调用以上过程
代码实现
public void sort(int[] a){
int N = a.length;;
int b = new int[N];//创建辅助数组
sort(a, 0, N-1);
}
public void sort(int[] a, int low, int high){
if (low >= high) {
return;
}
int mid = (low + high) / 2;//均分数组
sort(a, low, mid);//排前半部分
sort(a, mid, high);//排后半部分
merge(a, low, high, mid);//归并
}
private void merge(int[] a, int low, int high, int mid){
for(int i = low; i <= high; i++) {
b[i] = a[i];
}//将a[low...high]复制到b[low...high]
int j = low;//扫描左半部分
int k = mid + 1;//扫描右半部分
for(int i = low; i <= high; i++){
// 从b[low...high]中依次取出较小的元素放入a[low...high]
if (j > mid) {
a[i] = b[k++];//若左半部分元素用尽,取右半部分元素
} else if (k > high) {
a[i] = b[j++]//若右半部分元素用尽,取左半部分元素
} else if (b[j] < b[k]) {
a[i] = b[j++];//两半部分元素都未用尽时,取较小的
} else {
a[i] = b[k++];
}
}
}
2. 自底向上的归并排序
特点
不切分数组,直接两两归并(将每个元素当做大小为1的子数组)、四四归并(将两个大小为2的子数组归并为一个大小为4的数组)…最后对整个数组归并
优点
相对于自顶向下的归并,代码量少
代码实现
public void sort(int[] a){
int N = a.length;
int b[] = new int[N];
sort(a, 0, N-1);
}
private void sort(int[] a, int low, int high){
//sz为子数组的大小,依次为1,2,4,8...N
for(int sz = 1; sz < N; sz = sz + sz){
//i为子数组的索引,而子数组大小为N,因而当i移动到靠近边界时边界元素的索引要满足i+sz<N
for(int i = low; i < N - sz; i = i + sz + sz){
//当数组大小不是2的偶数倍时,最后一个子数组会比前一个要小,导致该子数组的索引不满足i+2sz-1,而是N-1
merge(a, i, min(i+2sz-1, N-1), i+sz-1);
//当N为2的偶数倍时,mid=(i+(i+2sz-1)-1)/2;非偶数倍时,mid=(i+N-1-1)/2,而i<N-sz,可推出此种情况下,(i+(i+2sz-1)-1)<(i+N-1-1),即N为偶数倍,mid可取mid=(i+(i+2sz-1)-1)/2
}
}
}
归并排序的应用-求逆序数
逆序数指的是逆序数对的个数,在一个序列a[0, 1, 2, …N]中,对任意的i和j,如果i < j且a[i] > a[j],则a[i]和a[j]为逆序数对,一个队列中所有的逆序数对数量之和即为该序列的逆序数。
蛮力法求逆序数
for(int i = 1; i < N; i++){
for(int j = i - 1; j >= 0; j-- ){
if(a[i] < a[j]){
count++;
}
}
}
显然两个循环的存在导致此法复杂度为n的平方。
利用归并排序
merge过程中,由于两个子数组(假设为A和B)已各自有序,用i扫描A,用j扫描B,某次循环中,若a[i] > a[j],则该次循环中得到的逆序数count = A.length - i。对count进行累加后即可得到总逆序数。
由于归并排序时间复杂度为NlogN(数学问题,本文不做证明),因而优于蛮力法。
实现方式
只需在merge方法中计算逆序对数量即可
private void merge(int[] a, int low, int high, int mid){
for(int i = low; i <= high; i++) {
b[i] = a[i];
}//将a[low...high]复制到b[low...high]
int j = low;//扫描左半部分
int k = mid + 1;//扫描右半部分
for(int i = low; i <= high; i++){
// 从b[low...high]中依次取出较小的元素放入a[low...high]
if (j > mid) {
a[i] = b[k++];//若左半部分元素用尽,取右半部分元素
} else if (k > high) {
a[i] = b[j++]//若右半部分元素用尽,取左半部分元素
} else if (b[j] < b[k]) {
a[i] = b[j++];//两半部分元素都未用尽时,取较小的
} else {
a[i] = b[k++];
count = count + (mid - low + 1) - j;//累加该次循环的逆序对数量
}
}
}