问题:用归并排序算法对n个对象的数组进行升序排序。
归并排序算法描述(来源于《算法导论》2.3节):
分解:将n个元素分解成各含n/2个元素的字序列;
解决:用归并排序算法对两个子序列递归地排序;
合并:合并两个已排序的字序列得到排序结果。
算法的伪代码描述如下:
其中,A是数组,p是子数组起始位置,也是分解后第一个子数组的起始位置,r是子数组结束位置,也是分解后第二个数组的结束位置,q为分解后第一个数组的结束位置,q+1是分解后第二个数组的起始位置。
伪代码中,p<r,意味着待排序的子数组中至少有两个元素,分解后的两个子数组中各自至少有一个元素;也意味着,如果待排序子数组中只有一个元素时,排序算法什么都不做,这是正确的,因为只有一个元素的数组本身就是有序的;也意味着,分解后的子数组只有一个元素的情况是递归的基本情况,递归在这时停止。
对两个有序的子数组合并的过程是这样的:比较两个子数组左边的元素,哪个元素小,就把它取出来并放进大数组中;不断重复上诉过程,直到其中一个数组的元素取完了;然后,依次取出未取完数组中的元素放在大数组的后面。
合并两个有序的子数组的Java源码如下:
@SuppressWarnings("unchecked")
private <T extends Comparable<T>> void merge(T[] a, int p, int q, int r) {
// 获得数组left的长度
int n1 = q - p + 1;
// 获得数组right的长度
int n2 = r - q;
// 创建数组left
T[] left = (T[]) Array.newInstance(a.getClass().getComponentType(), n1);
// 创建数组right
T[] right = (T[]) Array.newInstance(a.getClass().getComponentType(), n2);
// 把数组a的相应值赋给left
for(int i = 0; i < n1; i++){
left[i] = a[p + i];
}
// 把数组a的相应值赋给right
for(int i = 0; i < n2; i++){
right[i] = a[q + 1 + i];
}
// left数组当前待处理元素的位置
int i = 0;
// left结束标识
boolean endLeft = false;
// right数组当前待处理元素的位置
int j = 0;
// right结束标识
boolean engRight = false;
int k = p;
while(k <= r){
if (left[i].compareTo(right[j]) < 0) {
a[k] = left[i];
// 说明left的最后一个元素已被处理
if (i == (n1 - 1)) {
endLeft = true;
k++;
break;
}
i++;
} else {
a[k] = right[j];
// 说明right的最后一个元素已被处理
if (j == (n2 - 1)) {
engRight = true;
k++;
break;
}
j++;
}
k++;
}
// 把还没处理完的数组的剩余元素放进a中
if (endLeft) {
while(k <= r){
a[k] = right[j];
k++;
j++;
}
} else {
while(k <= r){
a[k] = left[i];
k++;
i++;
}
}
}
归并排序算法Java源代码如下:
@Override
public <T extends Comparable<T>> void sort(T[] a) {
// TODO Auto-generated method stub
// 默认情况下,对数组从头到尾排序
int p = 0;
int r = a.length - 1;
sort(a, p, r);
}
private <T extends Comparable<T>> void sort(T[] a, int p, int r) {
// TODO Auto-generated method stub
if (p < r) {
int q = (p + r) / 2;
sort(a, p, q);
sort(a, q + 1, r);
merge(a, p, q, r);
}
}
根据算法描述和代码执行过程,我们可以清晰看到,递归过程先是对问题不断的分解,直到子数组中只有一个元素为止(如下图中的从上到下的过程),然后从只有一个元素的数组开始,对子数组进行合并(如下图中德自下而上的过程)。