大家好🙌我是你们的好朋友,大数据老虾😀。相遇是缘,既然来了就拎着小板凳坐下来一起唠会儿😎,如果在文中有所收获,请别忘了一键三连,动动你发财的小手👍,你的鼓励,是我创作的动力😁!废话不多说,直接😎 开干吧!
PS:文末干货,记得拎着小板凳离开的时候也给它顺走 🤣
座右铭:“懒”对一个人的毁灭性有多大,早起的重要性就多大。
归并排序
算法分析
解析:
归并排序体现了“分而治之”的算法思想,具体表现为:
- 分: 指不断将数组从中心位置划分,将原数组的排序问题转换为子数组的排序问题。
- 治: 划分到子数组长度为1时,开始向上合并,不断将左右两个较段排序数组合并为一个较长排序数组,直至合并至原数组时完成排序。
如下图所示:
为数组[7, 3, 2, 6, 0, 1, 5, 4]的归并排序过程:
算法流程
递归划分:
1、计算数组中点m,递归1划分左子数组merge_sort(1, m)和右子数组merge_sort(m + 1, r);
2、当 l ≥ r时, 代表子数组长度为1或0, 此时终止划分,开始合并;
合并子数组:
1、暂存数组,nums闭区间[l, r]内的元素至辅助数组tmp;
2、循环合并:设置双指针 i, j 分别指向tmp的左 / 右子数组的首元素;
注意:
nums子数组的左边界、重点,右边界分别为 l 、m、r;
辅助数组tmp中对于的索引为0、m - l、r - l;
-
当 i = m - l + 1时: 代表左子数组已合并完,因此添加右子数组元素tmp[j],并执行 j = j + 1;
-
否则,当 j = r - l + 1时: 代表右子数组已合并完,因此添加左子数组元素 tmp[i] ,并执行 i = i + 1;
-
否则,当 tmp[i] ≤ tmp[j] 时: 添加左子数组元素 tmp[i] ,并执行 i = i + 1;
-
否则(即当 tmp[i] > tmp[j] 时): 添加右子数组元素 tmp[j] ,并执行 j = j + 1;
// java code
void mergeSort(int[] nums, int l, int r) {
// 终止条件
if (l >= r) return;
// 递归划分
int m = (l + r) / 2;
mergeSort(nums, l, m);
mergeSort(nums, m + 1, r);
// 合并子数组
int[] tmp = new int[r - l + 1]; // 暂存需合并区间元素
for (int k = l; k <= r; k++)
tmp[k - l] = nums[k];
int i = 0, j = m - l + 1; // 两指针分别指向左/右子数组的首个元素
for (int k = l; k <= r; k++) { // 遍历合并左/右子数组
if (i == m - l + 1)
nums[k] = tmp[j++];
else if (j == r - l + 1 || tmp[i] <= tmp[j])
nums[k] = tmp[i++];
else {
nums[k] = tmp[j++];
}
}
}
// 调用
int[] nums = { 3, 4, 1, 5, 2, 1 };
mergeSort(nums, 0, len(nums) - 1);
# python code
def merge_sort(nums, l, r):
# 终止条件
if l >= r: return
# 递归划分数组
m = (l + r) // 2
merge_sort(nums, l, m)
merge_sort(nums, m + 1, r)
# 合并子数组
tmp = nums[l:r + 1] # 暂存需合并区间元素
i, j = 0, m - l + 1 # 两指针分别指向左/右子数组的首个元素
for k in range(l, r + 1): # 遍历合并左/右子数组
if i == m - l + 1:
nums[k] = tmp[j]
j += 1
elif j == r - l + 1 or tmp[i] <= tmp[j]:
nums[k] = tmp[i]
i += 1
else:
nums[k] = tmp[j]
j += 1
# 调用
nums = [3, 4, 1, 5, 2, 1]
merge_sort(0, len(nums) - 1)
如下动图所示,为数组 [7,3,2,6]
的归并排序过程 :
算法特性
- 时间复杂度: 最佳 Ω(NlogN) ,平均 Θ(NlogN) ,最差 O(NlogN) 。
- 空间复杂度 O(N) : 合并过程中需要借助辅助数组 tmp ,使用 O(N) 大小的额外空间;划分的递归深度为 logN ,使用 O(logN) 大小的栈帧空间。
若输入数据是 链表 ,则归并排序的空间复杂度可被优化至 O(1) ,这是因为:
1、通过应用「双指针法」,可在 O(1) 空间下完成两个排序链表的合并,省去辅助数组 tmp 使用的额外空间;
2、通过使用「迭代」代替「递归划分」,可省去递归使用的栈帧空间;
- 非原地: 辅助数组 tmp 需要使用额外空间。
- 稳定: 归并排序不改变相等元素的相对顺序。
- 非自适应: 对于任意输入数据,归并排序的时间复杂度皆相同。
文末彩蛋🤩
🚗🤩😉💖🌹👀✨给各位朋友安利一下平时收集的各种学习资料!!!有需要的朋友点击一下⏩传送门,自行领取。程序员经典名言:“收藏了就等于学会啦”。 做人也要像蜡烛一样,在有限的一生中有一分热发一份光,给人以光明,给人以温暖!
图灵程序丛书300+
Linux实战100讲
Linux书籍
计算机基础硬核总结
计算机基础相关书籍
操作系统硬核总结
Java自学宝典
Java学习资料
Java硬核资料
Java面试必备
Java面试深度剖析
阿里巴巴Java开发手册
MySQL入门资料
MySQL进阶资料
深入浅出的SQL
Go语言书籍
我的个人仓库:私人仓库