复杂度介绍:
非递归版的归并排序,省略了中间的栈空间,直接申请一段O(n)的地址空间即可,因此空间复杂度为O(n),时间复杂度为O(nlogn)。
平均时间复杂度: O(NLogN)
最好情况时间复杂度: O(NLogN)
最差情况时间复杂度: O(NLogN)
所需要额外空间: 递归:O(N + LogN), 非递归:O(N)
稳定性: 稳定
算法思想:
开始以间隔为1的进行归并,也就是说,第一个元素跟第二个进行归并。第三个与第四个进行归并;
然后,再以间隔为1x2的进行归并,1-4进行归并,5-8进行归并;
再以2x2的间隔,同理,直到2*k超过数组长度为止。
递归版
public static void main(String[] args) {
int[] nums = {6, 5, 3, 8, 1, 7, 2, 9, 4};
segment(nums, 0, nums.length - 1);
System.out.println(Arrays.toString(nums));
}
/**
* 递归切分待排
*
* @param nums 待切分数组
* @param left 待切分最后第一个元素的索引
* @param right 待切分数组最后一个元素的索引
*/
private static void segment(int[] nums, int left, int right) {
if (left < right) {
// 找出中间索引
int center = (left + right) / 2;
// 对左边数组进行递归
segment(nums, left, center);
// 对右边数组进行递归
segment(nums, center + 1, right);
// 合并
merge(nums, left, center, right);
}
}
/**
* 两两归并排好序的数组(2路归并)
*
* @param nums 带排序数组对象
* @param left 左边数组的第一个索引
* @param center 左数组的最后一个索引,center + 1右数组的第一个索引
* @param right 右数组的最后一个索引
*/
private static void merge(int[] nums, int left, int center, int right) {
int[] tmpArray = new int[right - left + 1];
int leftIndex = left; //左数组第一个元素的索引
int rightIndex = center + 1; //右数组第一个元素索引
int tmpIndex = 0; //临时数组索引
// 把较小的数先移到新数组中
while (leftIndex <= center && rightIndex <= right) {
if (nums[leftIndex] <= nums[rightIndex]) {
tmpArray[tmpIndex++] = nums[leftIndex++];
} else {
tmpArray[tmpIndex++] = nums[rightIndex++];
}
}
// 把左边剩余的数移入数组
while (leftIndex <= center) {
tmpArray[tmpIndex++] = nums[leftIndex++];
}
// 把右边边剩余的数移入数组
while (rightIndex <= right) {
tmpArray[tmpIndex++] = nums[rightIndex++];
}
// 把新数组中的数覆盖nums数组
/*for (int i = 0; i < tmpArray.length; i++) {
nums[begin + i] = tmpArray[i];
}*/
//可以优化成下面的写法
System.arraycopy(tmpArray, 0, nums, left, tmpArray.length);
}
非递归版
public static void main(String[] args) {
int[] nums = {6, 5, 3, 8, 1, 7, 2, 9, 4};
// 负责将数组中的相邻的有k个元素的字序列进行归并
for (int i = 1; i <= nums.length; i *= 2) {
for (int j = 0; j + i <= nums.length; j += i * 2) {
//从前往后,将2个长度为i的子序列合并为1个
//Math.min 的目的是处理 整个待排序数组为奇数的情况
merge(nums, j, j + i - 1, Math.min(j + 2 * i - 1, nums.length - 1));
}
}
System.out.println(Arrays.toString(nums));
}
/**
* 两两归并排好序的数组(2路归并)
*
* @param nums 带排序数组对象
* @param left 左边数组的第一个索引
* @param center 左数组的最后一个索引,center + 1右数组的第一个索引
* @param right 右数组的最后一个索引
*/
private static void merge(int[] nums, int left, int center, int right) {
int[] tmpArray = new int[right - left + 1];
int leftIndex = left; //左数组第一个元素的索引
int rightIndex = center + 1; //右数组第一个元素索引
int tmpIndex = 0; //临时数组索引
// 把较小的数先移到新数组中
while (leftIndex <= center && rightIndex <= right) {
if (nums[leftIndex] <= nums[rightIndex]) {
tmpArray[tmpIndex++] = nums[leftIndex++];
} else {
tmpArray[tmpIndex++] = nums[rightIndex++];
}
}
// 把左边剩余的数移入数组
while (leftIndex <= center) {
tmpArray[tmpIndex++] = nums[leftIndex++];
}
// 把右边边剩余的数移入数组
while (rightIndex <= right) {
tmpArray[tmpIndex++] = nums[rightIndex++];
}
// 把新数组中的数覆盖nums数组
/*for (int i = 0; i < tmpArray.length; i++) {
nums[begin + i] = tmpArray[i];
}*/
//可以优化成下面的写法
System.arraycopy(tmpArray, 0, nums, left, tmpArray.length);
}