归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。(速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。)
算法描述
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾
特点
归并排序是稳定的排序.即相等的元素的顺序不会改变。
如输入记录 1(1) 3(2) 2(3) 2(4) 5(5) (括号中是记录的关键字)时,输出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按输入的顺序。
这对要排序数据包含多个信息而要按其中的某一个信息排序,要求其它信息尽量按输入的顺序排列时很重要。
归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。
package 归并排序;
import java.util.ArrayList;
import java.util.List;
/**
* 归并排序
*/
//顾名思义,先进行递归,后进行合并。
public class Collections {
/**
* 递归
*
* @param list
*/
public static void sort(List list) {
int l = 0;
//list.size()-1指的是最后一个索引
int r = list.size() - 1;
//链表用的是数组,索引从0开始。
}
/**
* 归:分治
*/
//先将集合传进来,通过数组或链表将其分成两个数组或两个链表。
//实际上是将两个数组或链表的索引范围从逻辑上分治出来。
//比如:0~10的数组,我们将其分为0~5的数组和6~10的数组。
//而实际上还是一个数组,并没有真正分开。
//list;链表\left;起始索引\right:结束索引
//在这里要对List进行分,而分的条件是:left < right
//而left == right的时候就说明只剩一个数组了,
//即数组里剩下的未比较的元素就剩一个元素了。
private static void devide(List list, int left, int right) {
//这里分的时候,要先计算一下中值索引。
int mid = (left + right) / 2;
if (left < right) {
//左边
devide( list, left, mid );
//右边
devide( list, mid + 1, right );
//这里不断进行递归,只要不像等,就一直在归。
//一旦left和right相等的时候,就要并了。
//} else {
//这里要注意,当左右两边都分完的时候,
//原数组里就剩下一个元素了。
//此时,递归也就自动终止并返回分好ed数组了。
//所以这里不用再判断,而是直接进行合并。
/*并(merge)*/
//我们可以将该方法写到下面的块里。
//但是,为了方便,我们就在下面写并的方法。
//合并两个数组
merge( list, left, mid, mid + 1, right );
//这里我们在写的时候,我们可以反着来写这个方法。
//将光标放到上面哪一行代码。组合键alt+enter,
//然后选择Creat method 'merge' in 'Collections'
}
}
/**
* 并
*
* @param list
* @param left
* @param mid
* @param i
* @param right
*/
private static void merge(List list, int left, int mid, int i, int right) {
//合并的时候,我们可以创建一个临时集合。
Object[] temp = new Object[list.size()];
//list.size()指的是数组长度
int index = left;
int ls = left, le = mid;//左边的起始索引和结束索引
int rs = i, re = right;//右边的起始索引和结束索引
//左又都是从第一个开始比,比完后。
//如果左边的元素小的话将其放到临时数组里,
//然后左边的索引加一,再比下一个。
//如果右边的元素小的话将其放到临时数组里,
//然后右边的索引加一,再比下一个。
//如此一直到结束。
//所以我们初始化一个索引为left,来记录索引。
//index不能从0开始,而应该从left开始。
//因为,先在我们比较的数组是逻辑数组,
//它们的起始索引不一定是从0开始的。
//至少把一边的所有数组元素按顺序放入临时数组。
while (ls <= le && rs <= re) {
//这个两个条件都必须满足,不然就退出了。
//因为这样就意味着有一边已经排完了。
//if(list.get(ls)<list.get(rs)){...}
//这里对象是不能直接比的,比的应该是数据。
//但是我们在这里可以实现Comparable接口。
Comparable o1 = (Comparable) list.get( ls );
Comparable o2 = (Comparable) list.get( rs );
if (o1.compareTo( o2 ) == -1) {
//这里判断o1是否小于o2
//o1小于o2时则排左边
//因为是升序排,所以先将o1放进临时数组里。
temp[index] = o1;
ls++;
} else {
//o1大于o2时则排右边
temp[index] = o2;
rs++;
}
index++;
//至少把一边的所有数组元素按顺序放入临时数组。
//但是还是会有剩下的元素。
// 判断左边是否有剩余的元素。
if (ls <= le) {
for (int j = ls; j <= le; j++) {
temp[index++] = list.get( j );
}
}
//也可能左边没有剩余的元素,那么我们就
//判断右边是否有剩余元素
//这里之所以还要等于re的原因是因为
// temp[index] = o1;如果在这里放完了
//ls++;会多加一,ls就比le大了。
//所以,省的那个元素的索引ls正好是等于le的。
if (rs <= re) {
for (int k = rs; k <= re; k++) {
temp[index++] = list.get( k );
}
}
//将临时数组里的元素放回原来的数组里
for (int l = left; l <= right; l++) {
list.set( l, temp[l] );
}
}
}
public static void main(String[] args) {
List s = new ArrayList();
s.add( new Student( 121, "张三", 'm' ) );
s.add( new Student( 123, "李四", 'm' ) );
s.add( new Student( 122, "王五", 'm' ) );
java.util.Collections.sort( s );
System.out.println( s );
}
}
[Student{id=121, name='张三', sex=m}, Student{id=122, name='王五', sex=m}, Student{id=123, name='李四', sex=m}]