作为一名对技术充满热情的学习者,我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代,我远非专家,而是一位不断追求进步的旅行者。通过这篇博客,我想分享我在某个领域的学习经验,与大家共同探讨、共同成长。请大家以开放的心态阅读,相信你们也会在这段知识之旅中找到启示。
前言
继续学习下一个排序算法----插入排序,我们废话不多说,直接进入主题吧
一、什么是插入排序
插入排序是一种简单直观的排序算法,它的工作方式类似于我们整理手中扑克牌的方式。具体操作如下:
- 开始时我们左手为空并且桌子上的牌面朝下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右向左将它与手中已有的牌相比较。
- 数组中实现就是从数组的第二个元素开始把待插入的元素复制为一个副本(记作 key)。比较其与之前元素的大小,如果 key 更小,则之前元素向后移动一位。这个过程会一直重复,直到找到了 key 的正确位置,然后将 key 插入到这个位置。
- 算法过程逐步将每个元素加入到前面已经排序的序列中,在插入的过程中查找其位置,直到整个数组排序完成。
插入排序的算法时间复杂度为O(n^2),因为它需要比较的次数最坏情况是一个递增数列,但如果输入数组已经是部分排序的话,其性能会比较好。插入排序是稳定排序,也就是说,如果两个相等的元素之间的相对位置在排序前后不会改变。
下面是插入排序的一个简单实现示例(Java):
public class InsertionSort { public static void sort(int[] array) { for (int i = 1; i < array.length; i++) { // 选择第 i 个元素作为待插入元素 int key = array[i]; int j = i - 1; // 将比 key 大的元素向后移动 while (j >= 0 && array[j] > key) { array[j + 1] = array[j]; j = j - 1; } // 插入 key 到正确的位置 array[j + 1] = key; } } public static void main(String[] args) { int[] exampleArray = {5, 2, 4, 6, 1, 3}; sort(exampleArray); // 输出排序后的数组 for (int i : exampleArray) { System.out.print(i + " "); } } }
这段代码展示了插入排序的基本操作,通过一个外部循环逐步增加排序的范围,然后在每一步通过一个内部循环寻找当前元素的合适位置,并进行插入。
二、实战演练
简单练习
- 题目:
假设你有一个整数数组[29, 10, 14, 37, 13]
。使用插入排序算法对该数组进行排序。手写出你排序时数组的状态变化,以及解释每一步的操作。 - 解答:
这里是按照插入排序算法,对数组[29, 10, 14, 37, 13]
进行排序的逐步过程:
初始数组状态:
29, 10, 14, 37, 13
第1轮(考虑元素 10):
10
应该被插入到29
前面,因为10 < 29
。
变化后数组状态:
10, 29, 14, 37, 13
第2轮(考虑元素 14):
14
应该被插入到29
前面,找到适当位置后,就是10
和29
之间。
变化后数组状态:
10, 14, 29, 37, 13
第3轮(考虑元素 37):
37
已经比左侧的所有元素都大,所以它应该留在原位。
变化后数组状态:
10, 14, 29, 37, 13
第4轮(考虑元素 13):
13
应该被插入到14
前面,位置是10
和14
之间。
变化后数组状态:
10, 13, 14, 29, 37
操作结束,数组已经完全排序。
以上步骤展示了插入排序的核心概念:逐个取出未排序部分的元素,并将它插入到已排序部分的适当位置,直到数组完全排序。
复杂练习
- 题目:
给定一个整数数组[34, 8, 64, 51, 32, 21]
,使用插入排序算法对该数组进行排序,并记录在整个排序过程中进行了多少次比较和多少次移动。 - 解答:
我们逐步执行插入排序,同时计数比较和移动次数。
初始数组状态:
34, 8, 64, 51, 32, 21 比较次数 = 0 移动次数 = 0
第1轮(考虑元素 8):
[8, 34], 64, 51, 32, 21 比较次数 = 1 (34 和 8 比较一次) 移动次数 = 1 (34 移动到原来 8 的位置)
第2轮(考虑元素 64):
8, 34, [64], 51, 32, 21 比较次数 = 1 (64 和 34 比较一次) 移动次数 = 0 (64 已经在正确位置,无需移动)
第3轮(考虑元素 51):
8, 34, [51, 64], 32, 21 比较次数 = 2 (51 先和 64 比较,然后和 34 比较) 移动次数 = 1 (64 移动到原来 51 的位置)
第4轮(考虑元素 32):
8, [32, 34, 51, 64], 21 比较次数 = 3 (32 依次与 51、34 和 8 比较) 移动次数 = 3 (34、51 和 64 分别向后移动一位)
第5轮(考虑元素 21):
[8, 21, 32, 34, 51, 64] 比较次数 = 4 (21 依次与 32、34、51 和 8 比较) 移动次数 = 4 (32、34、51、64 向后移动)
操作结束,数组完全排序。
最终排序后的数组:
8, 21, 32, 34, 51, 64
总比较次数和移动次数:
总比较次数 = 1 + 1 + 2 + 3 + 4 = 11 总移动次数 = 1 + 0 + 1 + 3 + 4 = 9
这个例题考察了使用插入排序进行排序同时跟踪了比较和移动的微观操作,这对于理解算法的具体影响和实现细节非常有帮助。这种计数通常用于分析算法效率和它的操作复杂性。
三、经典面试题
- 题目:
你正在优化一个已经部分排序的巨大数组。该数组的开始部分(前 10%)是杂乱无序的,而余下的部分(后
90%)已经是排序好的。请提出一个优化过程使插入排序在这种特定情况下运行得更快,并解释为什么这个方法会有效。然后手动演示你的方法如何应用到以下数组:
[3, 7, 4, 9, 5, 2, 6, 1, 14, 18, 16, 17, 20, 13, 12, 11, 19, 15, 12, 10, ...]
(数组中只给出了未排序的前部分和部分已排序的后部分,假设后部分是一个巨大并且完全排序的数组)。
- 解答:优化过程:
- 我们可以分两部分来处理数组。首先,对数组前面杂乱的 10% 进行普通的插入排序,将它们整理成有序。然后将这部分有序的数据与后面已经排好序的 90% 部分进行合并。
- 由于后面的 90% 已经有序,我们只需考虑每个整理好的元素应该置于哪一个位置。对于这一步,我们可以采用二分查找来快速定位每个元素插入的位置,因为二分查找在有序数组中查找的时间复杂度为 O(log n)。
这种方法有效是因为它减少了数组前部分多余的比较和移动操作,而直接利用了后部分已经有序的属性。
现在,手动演示该方法应用到给出的数组的前半部分(未给出全部元素,只是示范过程):
初始未排序的部分:[3, 7, 4, 9, 5, 2, 6, 1, 14, 18] 初始已排序的部分:[16, 17, 20, 13, 12, 11, 19, 15, 12, 10, ...] 第一步:对未排序的部分进行插入排序 经过插入排序的未排序部分:[1, 2, 3, 4, 5, 6, 7, 9, 14, 18] 第二步:将排序好的这部分利用二分查找插入到后面的有序部分中 假设数组很长,我们只需要根据二分查找定位到具体位置并进行插入即可。 最终结果(只示范少量,实际需要做全部合并): 合并一些元素后:[1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...]
注意,实际操作时,由于数组可能非常大,不需要真的将有序部分进行物理移动,而是可以通过重新排列索引或者使用一个辅助数组来模拟插入操作。
四、思考
如果数组前部分已经有序而后部分无序,是否还可以采用其他算法进行优化?
总结
插入排序讲解结束了,很多情况下插入排序需要与其他算法结构才能够真正起到非常好的效果,所以我们还要继续学习不同的排序算法。
感谢大家抽出宝贵的时间来阅读博主的博客,新人博主,感谢大家关注点赞,祝大家未来的学习工作生活一帆风顺,加油!!!