归并排和快排的时间复杂度都是o(nlogn).属于比较高效的一种排序算法。归并排序使用的分治思想。所谓的分治:分---就是将原数组划分派别,直到划到不可分为止;
治:将各个派别两两’治理‘,合成一个有序派别。需要强调的是这里的‘治’是指原先两个本就有序的派别重新比较治理。这里涉及的两个动作第一就是递归地划分,比较容易实现;另一个就是对两个有序的子数组进行比较合并,比较时我们可能需要一个临时存储区---一个先进先出的队列。当然你也可以申请一个与原始数组等长的临时数组。比较过程如下:从两个子数组开始处比较,谁小就把谁放入临时存储区,并取下一个值继续比较。如果一方比较完毕,则把另一方全部放入临时存储区中。最后再把临时存储区里的数组(已经全部有序)赋值回原始数组。临时比较图示说明如下:原始数组
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
2 | 4 | 6 | 8 | 1 | 4 | 0 | 2 | 7 | 4 | 10 |
注:第一行为下标,第二行为元素。目标从小到大排序。这里为了说明比较过程,我选取一个中间比较过程比如当我们把这些元素递归成单一派别,再合并时可能会出现下标为0-2的元素有序,下标为3-5的元素有序即是下面这种情况。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
2 | 4 | 6 | 1 | 4 | 8 | 0 | 2 | 7 | 4 | 10 |
0 | 1 | 2 | 3 | 4 | 5 |
1 | 2 | 4 | 4 | 6 | 8 |
放回原始数组中,也就是一次合并后原始数组可能出现的情况:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
1 | 2 | 4 | 4 | 6 | 8 | 0 | 2 | 7 | 4 | 10 |
好了,基本逻辑就是这些.下面看一下代码:
public static void main(String[] args){
int a[]={2,4,6,8,1,4,0,2,7,4,10};
merge_sort(a,0,a.length-1);
ArrayUtils.printArray(a);//这里将排好后的结果输出,这个函数是写在另一个类中的静态输出函数。读者若嫌麻烦,不妨弄个for循环输出。
}
public static void merge_sort(int a[],int first,int end){
if(first<end){ //这里划分一定要分成单个元素成派。理由很简单因为合并的基础是有序子数组。只有单个元素可以认为有序,多个则不然。
int mid=(first+end)/2; //采用中间划分
//System.out.println(mid);//这里是我调试所用,不需要看
merge_sort(a,first,mid);//这里是两条递归语句,就是划分派别。注意这里划分其实是划分的下标
merge_sort(a,mid+1,end);
merge(a,first,mid,end);//合并
// ArrayUtils.printArray(a);//调试所用,不用看
}
}
public static void merge(int a[],int first,int mid,int end){
Queue<Integer> q=new LinkedList<Integer>();//声明一个临时队列先进先出
int indexA=first;//考虑一下为什么不直接使用first还需要再申请一个新的临时变量?答案在最后一句我们其实是在每合并一次就赋值回原数组一次。
int indexB=mid+1;
while(indexA<=mid&&indexB<=end){
if(a[indexA]<=a[indexB]){ //谁小把谁优先加入临时队列
q.offer(a[indexA]);
indexA++;
}
else{
q.offer(a[indexB]);
indexB++;
}
}
while(indexA<=mid){ //这个队列还没比较完,依次加入队列
q.offer(a[indexA]);
indexA++;
}
while(indexB<=end){
q.offer(a[indexB]);
indexB++;
}
int index=0;
while(q.size()>0){ //赋值回原始数组
a[first+index]=q.poll();
index++;
}
}
看到这里大家应该已经会了基数排序,但是我其实一直都对那个递归划分有些不是很清楚。所以为了弄清楚究竟如何递归的,我加了上面的两个输出语句。最后得出如下结论:
上面二叉树中的结点数字代表下标范围,分叉中间的数字就是mid值。这个二叉树有点丑,如果大家有这方面的画图工具不妨推荐一下。如果取消我的那两条调试语句注释,并对main函数中的排序后输出注释掉。可以看到输出结果如下:
5
2
1
0
{2, 4, 6, 8, 1, 4, 0, 2, 7, 4, 10}
{2, 4, 6, 8, 1, 4, 0, 2, 7, 4, 10}
4
3
{2, 4, 6, 1, 8, 4, 0, 2, 7, 4, 10}
{2, 4, 6, 1, 4, 8, 0, 2, 7, 4, 10}
{1, 2, 4, 4, 6, 8, 0, 2, 7, 4, 10}
8
7
6
{1, 2, 4, 4, 6, 8, 0, 2, 7, 4, 10}
{1, 2, 4, 4, 6, 8, 0, 2, 7, 4, 10}
9
{1, 2, 4, 4, 6, 8, 0, 2, 7, 4, 10}
{1, 2, 4, 4, 6, 8, 0, 2, 4, 7, 10}
{0, 1, 2, 2, 4, 4, 4, 6, 7, 8, 10}
从上面可以看出Java顺序执行,1.先执行二叉树的左分支一直到左侧不能划分为止,然后向上比较合并。2.边合并边赋值回原数组。所以结果才会出现乱中有序的输出啊。好了以上就是我的见解。如有不当之处,敬请留言指点。互相学习,共同提高。最后贴出两篇其他牛人写的,也是我参考过的连接。http://mp.weixin.qq.com/s?src=3×tamp=1463015893&ver=1&signature=ApxbrqJNHSesYNQ2UF1VYl3WwA0qgIacTPzCkimq1PGIb15WBWOxlrrkuiEiyzohBZcBbG8YWUPCHxPrmWu2M2urOMp-Rvpn70xOs7MSuIx5SXFo*jrFKEjT0jjr0tIxevtlBB8XQi8mqFHCiXUpvsDkEKTGahWOfSK1q5cyu3o=;
还有一篇博客http://www.cnblogs.com/jillzhang/archive/2007/09/16/894936.html。再次声明,文章仅为学习交流之用。