> 文档中心 > java常见的排序算法(二)归并排序,TimSort

java常见的排序算法(二)归并排序,TimSort


1.归并排序

1.主要思路
归并算法采用递归的思想将数组切分为两个排好序的两个数组,然后将两个数组合并为一个。
此排序算法稳定,对象的排序一般都采用此排序
如图:
在这里插入图片描述
2.思路拆分
1⃣️将数组切分为两个排好序的数组,并定义两个指针,i与j,新开辟一个数组空间,
2⃣️比较i与j的大小,i<j则将i的值插入新数组的头部,并将i的指针+1,以此类推,就会得到一个排好序的数组。
当然这种排序的时间复杂度较低,为nlog2n,但是由于会开辟新的数组空间,所以,在空间复杂度上会高一些,为n

/** * @param needSort 需要排序的数组 * @param left 左边界值 * @param right 右边界值 * */public static void sortArray(int[] needSort,int left,int right){    if (left==right) return;    // 分成两半    int mid = left + (right-left)/2;// 避免越界    // 左边排序    sortArray(needSort,left,mid);    // 右边排序    sortArray(needSort,mid+1,right);    // 合并    mergeArray(needSort,left,mid+1,right);}/** * @param sourceArray 需要排序的数组 * @param leftPtr 左指针 * @param rightPtr 右指针 * @param rightBoundPtr 右边界 * */public static void mergeArray(int[] sourceArray,int leftPtr,int rightPtr,int rightBoundPtr){    int left = leftPtr;    int right = rightPtr;    int mid = right-1; // 中间位    int[] temp = new int[rightBoundPtr -left+1]; // 临时数组用于承载merge后的结果    int k = 0;// 临时数组当前索引    while (left <= mid && right <= rightBoundPtr){ temp[k++]= sourceArray[left] <= sourceArray[right] ? sourceArray[left++] : sourceArray[right++];    }    // 处理数组切分剩余结果    while (left <= mid) temp[k++] = sourceArray[left++];    while (right <= rightBoundPtr) temp[k++] = sourceArray[right++];    for (int i = 0; i < temp.length; i++) { sourceArray[leftPtr+i] = temp[i];    }}

2.TimSort

简介:
Tim Peter发明了一种更加快速的排序,TimSort,融合了归并算法和二分插入排序算法的精髓。关于插入排序,我们之前已经阐述过了,而二分插入其实就是新数往前面已排序的数组中插入时不用一个一个的往前挪,而是采用二分法插入,速度更快。jdk7后对象排序便默认采用了此排序。
重要概念:
run
所谓的run就是一个连续上升(此处的上升包括两个元素相等的情况)或者下降(严格递减,也就是不包括两个元素相等的情况)的子串。在实际程序中对于单调递减的run会被反转成递增的序列;
如图:
在这里插入图片描述
minrun
在合并序列的时候,如果run的数量等于或者略小于2的幂次方的时候,效率是最高的;如果略大于2的幂次方,效率就会特别低。所以为了提高合并时候的效率,需要尽量控制每个run的长度,定义一个minrun表示每个run的最小长度,如果长度太短,就用二分插入排序把run后面的元素插入到前面的run里面。
如上图的例子,假设minrun的值为4,那么会将后面的6采用二分插入法插入到前面去。而minrun这个值在jdk中是自适应的,也就是在排序前就会计算出这个值,代码如下:

private static int minRunLength(int n) {    assert n >= 0;    int r = 0;      // Becomes 1 if any 1 bits are shifted off    while (n >= MIN_MERGE) { r |= (n & 1); n >>= 1;    }    return n + r;}

合并
在归并算法中,合并是两两合并,但是在timsort中的合并是连续的,也就是每一次计算获取到一个run后就可能会触发合并。而timsort是采用栈来保存run的。而为了保证Timsort的合并平衡性,Tim制定一个合并规则,对于在栈顶的三个run,用X、Y和Z分别表示他们的长度,其中X在栈顶,必须始终维持一下的两个规则:
1⃣️Z>Y+X
2⃣️Y>X
我们按照上面的数来进行说明:
第一步
在这里插入图片描述
如图,我们初步计算出run0,为8,10,15但是run0的长度小于了minrun,所以将后面的6插入到run0中,作为一个run。然后入栈。
第二步
在这里插入图片描述
如图,计算出run1,而run1为递减的,所以需要将run1反转,现在已经有了两个run。我们对比发现,Y是小于X的无法满足规则,触发合并,将run0与run1合并成一个新的run作为run0。
第三步
在这里插入图片描述
如图,计算出新的run1,发现为递减,所以反转,且小于minrun,所以将后面两个值加入到run1中。入栈后发现满足Y>X继续下一步,计算出run2,虽然小于minrun,但由于后面没有值了,所以直接入栈。而X,Y,Z也满足Z>Y+X,所以依次合并X与Y,然后将合并后的与Z合并得到最终结果:
java常见的排序算法(二)归并排序,TimSort