插入排序与归并排序
前言:
排序算法应该算是算法入门级的东西了,这里重新学习算法,先暂时归纳下个人对两种算法的理解。
插入排序:
插入排序可以对应到现实生活中的排队去停车场停车的场景。假设某家饭店的饭菜十分好吃(流口水),导致来这里吃饭的人特别多,后面来吃饭准备停车的车排起了长队。每次只允许一辆车过去找位置,找到位置之后才允许下一辆车进入,依此类推,直到所有的车都停好。转换成专业的数学模型就是:现有一个无序数组 A[n],要想对其进行排序。我们先从一个数开始考虑,A0肯定是排好序的。现在假设有A1,那么这时候应该将A1和A0 进行比较排序。这时候假设再来A2,A2需要与前面排好队的A0、A1 再进行比较排序。这里需要注意的是在排序的过程中可能会产生数组的移动。下面是java代码实现的升序排列的整数版本:
1 public static void main(String[] args) {
2 int[] arr = {2, 1, 23, 22, 15, 76, 43, 36};
3 ascInsertionSort(arr);
4 System.out.println(Arrays.toString(arr));
5 }
6
7 /**
8 * 升序插入排列
9 * @param arr 传入的数组
10 */
11 private static void ascInsertionSort(int[] arr) {
12 int key = 0;
13 for (int j=1; j<arr.length; j++) { // 因为如果只有一个元素根本不需要排序,所以带插入的元素的下边从1开始
14 key = arr[j]; // 用key表示当前用来插入到已排序数组中的值
15 int i = j-1;
16 for (; i>=0; i--)
17 {
18 // 如果已排完序的数组的最后一个数比当前待插入的数小,说明不需要移动,直接break,否则需要交换两个比较值的元素的位置
19 if (arr[i] > arr[i+1])
20 {
21 arr[i+1] = arr[i];
22 arr[i] = key;
23 }
24 else
25 {
26 break;
27 }
28 }
29 }
30 }
很容易算出该算法的耗时主要是两层for循环嵌套导致的,第一层for循环循环的次数为n次。第二层for循环每次运行的最坏的结果是需要把前面排序好的数组再全部循环一次,为:
1 + 2 + 3 + ... + (n-1) = (n2-1)/2。 我们知道对于n的多项式,随着n的增长,对多项式结果影响结果最大的其实是最高的次数的那个项。所以不难得到该算法的时间复杂度为:θ(n2)。
选择排序:
既然讲了插入排序,顺便讲讲选择排序。选择排序简而言之就是每次选最帅的,选到最不帅的终结排序。
java实现的代码如下:
1 /**
2 * 升序选择排序
3 *
4 * @param arr
5 */
6 private static void ascSelectionSort(int[] arr) {
7 int min;
8 int minIndex;
9 for (int i = 0; i < arr.length - 1; i++) {
10 min = arr[i]; // 假设最小的元素是当前就在该位置的元素
11 minIndex = i;
12 for (int j = i + 1; j < arr.length; j++) {
13 if (arr[j] < min) // 如果有元素比最小的元素小,则将该元素的值作为最小元素的值,依次查找下去,最终找到最小元素所在的下表
14 {
15 min = arr[j];
16 minIndex = j;
17 }
18 }
19
20 // 循环完发现最小元素的位置不是当前在该位置的元素,则交换两个元素的位置
21 if (minIndex != i) {
22 int temp = arr[i];
23 arr[i] = arr[minIndex];
24 arr[minIndex] = temp;
25 }
26 }
27 }
可以发现一个很悲伤的事实,这个算法的时间复杂度也为:θ(n2)。
归并排序:
归并排序的思路应该是源自于递归。也就是大事化小,小事化了。既然是个大的数组,我就先分成两个。两个数组还是有点大吧,那我再每个分成两个。依此下去直到最后没一组中只剩下一个元素,这时候排序应该是很简单的事了。而每两个小组排序完了之后组成大组,由于每个大组都是排序好的,这时候合并大组就简单多了。用伪公式表示为:一个大组排序的时间 = 两个小组分别排序的时间 + 两个小组合并的时间。合并的原理也很简单,就相当于两份扑克牌,正着放的,从上到下是从小到大的顺序。首先从两份扑克中各取出一个牌,将较小的那个牌倒着放到另外个地方。再从较小的扑克牌出现的那份牌里面拿出最上面的,与刚才剩下的牌比大小,同样的道理,小的牌继续倒着放到刚刚选出来的那个牌的上面。依次类推,直到有一份牌被拿完。最后将剩下的那份牌倒过来倒着放到选出的牌堆上面,就完成了排序。
java实现的代码如下:
1 /**
2 * 升序归并排列
3 *
4 * @param arr
5 */
6 private static void ascMergeSort(int[] arr) {
7 int startIndex = 0;
8 int endIndex = arr.length - 1;
9 divideConquer(arr, startIndex, endIndex);
10
11 }
12
13 private static void divideConquer(int[] arr, int startIndex, int endIndex) {
14 int midIndex = (startIndex + endIndex) / 2;
15 if (midIndex > startIndex) {
16 divideConquer(arr, startIndex, midIndex); // 排序前一部分
17 divideConquer(arr, midIndex + 1, endIndex); // 排序后一部分
18 }
19 mergeSortedArr(arr, startIndex, midIndex, endIndex); // 合并排序后的两个数组
20 }
21
22 private static void mergeSortedArr(int[] arr, int startIndex, int midIndex,
23 int endIndex) {
24 int[] newArr = new int[endIndex + 1];
25 int i = startIndex;
26 int j = midIndex + 1;
27 int k = startIndex;
28 while (i <= midIndex && j <= endIndex) {
29 // 将小的放入当前位置,并且下一个比较大小的从出现小的那一组更新
30 if (arr[i] < arr[j]) {
31 newArr[k++] = arr[i++];
32 } else {
33 newArr[k++] = arr[j++];
34 }
35 }
36
37 // 需要将还剩牌的那一组元素加到排序好的数组后面
38 while (i <= midIndex) {
39 newArr[k++] = arr[i++];
40 }
41 while (j <= endIndex) {
42 newArr[k++] = arr[j++];
43 }
44
45 // 将新数组的值复制到老数组
46 for (i = startIndex; i <= endIndex; i++) {
47 arr[i] = newArr[i];
48 }
49 }
该算法具体到每一层的时间是n的一定倍数,然后从最顶层到最下面一层可分的层次为logn.所以该算法的时间复杂度为: θ(nlogn).
冒泡排序:
因为冒泡排序不是今天的主角,所以这里不再将其代码贴出来。只是说说冒泡排序的原理:其实和选择排序有些类似,也是最小的或者最大的冒出来,不同之处在于在冒泡的过程中会发生置换,每次比较只要比较相邻的两个数即可。其时间复杂度其实和选择排序一样,这里直接跳过。
OK!算法入门之简单的排序算法到此完结!
- Hive 中内部表与外部表的区别与创建方法
- 常用统计分析 SQL 在 AWK 中的实现
- java 中 16 进制 HEX 转换成字节码形式的 UTF-8
- Hadoop 多表 join:map side join 范例
- 实战 windows7 下 eclipse 远程调试 linux hadoop
- Hive 在多维统计分析中的应用 & 技巧总结
- shell 学习笔记(18)
- Hive 中的复合数据结构简介以及一些函数的用法说明
- BloomFilter 简介及在 Hadoop reduce side join 中的应用
- 关于 hadoop reduce 阶段遍历 Iterable 的 2 个“坑”
- Hadoop Mapper 阶段将数据直接从 HDFS 导入 Hbase
- 译文 | 量化投资教程:投资组合优化与R实践
- 浅谈 java 中构建可执行 jar 包的几种方式
- python 日志模块 logging 详解
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 数据分析与数据挖掘 - 02基础操练
- 数据分析与数据挖掘 - 03智能对话
- 【深度揭秘】为什么很多语言的数组下标是从0开始的?
- Go 每日一库之 email
- Go 每日一库之 dig
- Go 每日一库之 gojsonq
- Go 每日一库之 message-bus
- Go 每日一库之 watermill
- Go 每日一库之 wire
- Go 每日一库之 mergo
- Go 每日一库之 copier
- Struts2第一天Struts2的概述,Struts2的入门,Struts2常见的配置、Struts2的Action的编写
- SQL 求平均值时去掉极值
- Go 每日一库之 jennifer
- Go 每日一库之 go-cmp