算法复习 : 插入排序原理,记忆,时间复杂度 (7行java实现)
最近啃了一遍吴伟民老师的《数据结构》,记录一些心得。
一种简洁的插入排序 :
1.重要概念 : 哨兵
1.在我们要排序的数组中,哨兵做为一个辅助的位置,一般是0下标的槽位做为哨兵
2.哨兵位置上记录的数据不是有效的数据,而是临时的数据,比如上面的 ‘ -1 ’就是一个临时数据,具体的怎么个‘临时’法,请看等下的排序过程分析
3.用哨兵的好处 : 在比较过程中,可以减少边界判断条件,无需判断下标是否小于0,书上的解释也将哨兵称为‘监视边界的哨兵’(请看稍后下方的演示)
4.坏处 : 占用了一个槽位的空间,这个槽位除了排序平时是没有用的,但如果我们的数据量远大于1的话,这个空间其实也不是那么重要,尤其是在内存容量较大的情况下
排序过程 :
1.首先我们定义一个遍历 i 来控制数组的遍历,因为0下标是哨兵,所以我们的 i 从 1 开始
我们需要做到的是,i 遍历的过程中,i 指向的下标位置及其左边区域必须都是有序的(除了哨兵),这一片左边的区域被称为有序区。
比如我们遍历过程中, i = 3时,3下标位置及其左边的区域(1, 2下标区域, 哨兵的0下标位置不算)已经是有序的了,我们的目标就是扩大这个有序区,最终让整 个数组有序
按照我们刚刚说的,我们需要先定义一个 i 变量去遍历数组,并且这个遍历从1开始(因为0是哨兵)
public void insertionSort(int[] arr){
for(int i = ;){ }
}
假想我们手里有一副牌,我们知道我们想把手上的牌整成顺序的话,4比9小,比有序的区域小,所以我们知道要把它插入到左边某个位置
但如果不是4而是10呢,我们发现它比9大,所以不移动,有序区加上我们这张10也是有序的。
类似的,每次 i + 1 下标的元素都会和 i 下标的元素比较(也就是无序区的左边界和有序区的右边界比较),如果 i 下标元素比较大,那么说明有序区的最大值(假设有序区从小到大),比将要加入有序区的元素大,说明要加入的元素必须往前插。
丰富我们的代码 :因为用到 i + 1, 而 i + 1 <= length - 1 , 所以 i <= length - 2
public void insertionSort(int[] arr){
//i < arr.length - 1 也就是 i <= arr.length - 2
for(int i = ; i < arr.length - ; i ++){ }
}
接下来是加入比较部分 :
public void insertionSort(int[] arr){
//其中的j是临时变量用来保留 i + 1,以及之后标记空位,请向下看
int j;
//i < arr.length - 1 也就是 i <= arr.length - 2
for(int i = ; i < arr.length - ; i ++){
if(arr[(j = i + )] < arr[i]){ }
}
}
这时候哨兵的功能就要发挥了,我们把 i + 1 位置的元素存在哨兵里
代码添加如下 :
public void insertionSort(int[] arr){
int j;
//i < arr.length - 1 也就是 i <= arr.length - 2
for(int i = ; i < arr.length - 1; i ++){
if(arr[(j = i + )] < arr[i]){
//设置哨兵
arr[] = arr[j];
}
}
}
哨兵位置的元素(也就是原来i + 1 位置的元素),比 i 位置的元素小,所以 我们确定了 应该是哨兵在左边,i 位置元素在右边 ,所以 i 位置元素先覆盖 i + 1 位置,但是哨兵元素插不插入到 i 位置还不知道,因为 i 位置后面的元素可能比哨兵大。
代码先加入覆盖部分,上述的描述可能比较复杂,下面有对应的图解
public void insertionSort(int[] arr){
int j;
//i < arr.length - 1 也就是 i <= arr.length - 2
for(int i = ; i < arr.length - ; i ++){
if(arr[(j = i + )] < arr[i]){
//设置哨兵
arr[] = arr[j];
do {
//j = i + 1, j - 1 = i
arr[j] = arr[j = j - ];
//覆盖后 j = i
}
}
}
}
图解 :
就像我们的一副牌,我们知道4要往左边插,那我们先把他放在一边
我们发现9比4大,那么把9往右边挪一挪,也就是上述的 i 位置元素覆盖了 i + 1 位的元素
但是4能直接插入到8和9中间(也就是 i 下标位置)吗?答案是不能,因为右边的8,5还比4大!所以我们的4,也就是哨兵,还要和左边继续比较
什么时候停止比较呢?
假如我们设上述的空位下标为 j,那么空位后面那个需要和哨兵(也就是上图我们抽出来的牌4)位置就是(j - 1)
我们发现,只要抽出来的牌,不小于 j - 1 位置的牌的大小,那么他就可以插到位置 j 上,也就是空位上
同时我们发现,j 是不断往前移动的(减小),而且仔细观察一下,是较大的牌挪动到后一个位置(也就是空位 , 位置 j )之后
j 才要向前移动,因为 j 代表的是空位, 而刚刚我们把大的牌挪到后面了,所以 j 理所应当就是大牌移走之后,大牌留下的那个空位
于是我们摸清楚了 4(哨兵) 是怎么插入到有序区的,表示空位位置的变量 j 是怎么变化
1. 当空位 j 之前的位置 (j - 1) 上的元素,大于哨兵的话,j - 1 位置上的元素就要继续往后挪,挪到空位 j 上
2.第1步,挪完后,空位 j 需要往前挪,也就是 j = j - 1
3.当 j - 1 的元素不大于哨兵元素,我们就要把哨兵插入到空位 j 上
按照上面的思想,我们来完善代码 :
public void insertionSort(int[] arr){
int j;
for(int i = ; i < arr.length - ; i ++){
//触发比较
if(arr[(j = i + )] < arr[i]){
//设置哨兵
arr[] = arr[j];
do {
//空位的前一个元素(arr[j - 1])挪到空位(arr[j])上
//以及挪动后空位 j 往前挪, 也就是j = j - 1
arr[j] = arr[j = j - ];
//比较空位前一个元素(arr[j - 1])和哨兵谁大
//当前一个元素大的时候,他将继续往后挪
}while(arr[j - ] > arr[]);
//哨兵插入空位上
arr[j] = arr[];
}
}
}
以上就是我们的直接插入排序,可能有人会问 j - 1 的位置会不会越界,看代码会发现 i 从 1 开始,而 j 从 i + 1开始,也就是至少 2 开始,而 j 每减一次 1 ,总是
要和 arr[0], 比较,当 j - 1 = 0,arr[ j - 1 ] > arr[0] 比不可能成立,while循环结束,所以 j - 1 不会有小于0访问数组的危险
算上有用的行,整个排序有7行,但是实际上JIT将这个排序编译成本地机器码之后的操作次数不见得比行数多几行的其他写法要少,当然也不能说一定多,因为基 本操作也就这些 : 比较,移位,插入,所用的寄存器和机器指令应该是没有多大数量的区别的。
分析一下时间复杂度 :
最坏情况下 : 所有元素逆序
假如我们有 n 个元素(不算哨兵),问题规模为n,那么我们的外层循环下标会从 1 遍历到 n - 1,
每一次都需要触发比较,并且置哨兵
第 i 趟在循环内部需要比较 i 次
总比较次数 = 触发比较 + 内部循环比较 = i + 1
移动次数 (图中空心粗箭头)= i 次
如果把设置哨兵和最后的插入也算成移动,那么移动了 i + 2 次
Sum(1, n - 1) [( i + 1)] + Sum(1, n - 1) [( i + 2)] = [(n+2)(n - 1) + (n + 4)(n - 1)] / 2 (通过等差数列 S = n * (a1 + an) / 2得出)
如果n 无穷大,最终会趋向于 n ^ 2, 所以最坏情况下直接插入排序时间复杂度是 n^2
虽然是指数级的算法,但是他却为我们更有效的算法 : Shell (希尔)排序奠定了基础。将在下一章讲解。
算法复习 : 插入排序原理,记忆,时间复杂度 (7行java实现)的更多相关文章
- 如何用70行Java代码实现深度神经网络算法
http://www.tuicool.com/articles/MfYjQfV 如何用70行Java代码实现深度神经网络算法 时间 2016-02-18 10:46:17 ITeye 原文 htt ...
- Java实现 蓝桥杯VIP 算法提高 插入排序
算法提高 插入排序 时间限制:1.0s 内存限制:256.0MB 插入排序 问题描述 排序,顾名思义,是将若干个元素按其大小关系排出一个顺序.形式化描述如下:有n个元素a[1],a[2],-,a[ ...
- 【算法】插入排序 insertion_sort
准备写个<STL 源代码剖析>的读书笔记,开个专栏.名为<STL 的实现>,将源代码整理一遍.非常喜欢侯捷先生写在封底的八个字:天下大事.必作于细.他在书中写到:"我 ...
- 强化学习策略梯度方法之: REINFORCE 算法(从原理到代码实现)
强化学习策略梯度方法之: REINFORCE 算法 (从原理到代码实现) 2018-04-01 15:15:42 最近在看policy gradient algorithm, 其中一种比较经典的 ...
- C#冒泡算法复习
C#冒泡算法复习 冒泡算法的意思:每一趟找到一个最小或最大的数放到最后面,比较总数的n-1次(因为比较是2个双双比较的) 第一层循环表示进行比较的次数,总共要比较(数的)-1次 (因为比较是2个双双比 ...
- php求和为s的两个数字(多复制上面写的代码,有利于检查错误)(由浅入深,先写简单算法,做题的话够用就行)
php求和为s的两个数字(多复制上面写的代码,有利于检查错误)(由浅入深,先写简单算法,做题的话够用就行) 一.总结 1.多复制上面写的代码,有利于检查错误 2.一层循环就解决了,前后两个指针,和大了 ...
- "如何用70行Java代码实现深度神经网络算法" 的delphi版本
http://blog.csdn.net/hustjoyboy/article/details/50721535 "如何用70行Java代码实现深度神经网络算法" 的delphi ...
- 只用120行Java代码写一个自己的区块链
区块链是目前最热门的话题,广大读者都听说过比特币,或许还有智能合约,相信大家都非常想了解这一切是如何工作的.这篇文章就是帮助你使用 Java 语言来实现一个简单的区块链,用不到 120 行代码来揭示区 ...
- 200行Java代码搞定计算器程序
发现了大学时候写的计算器小程序,还有个图形界面,能够图形化展示表达式语法树,哈哈;) 只有200行Java代码,不但能够计算加减乘除,还能够匹配小括号~ 代码点评: 从朴素的界面配色到简单易懂错误提示 ...
随机推荐
- CSS盒子模型探讨
盒模型 html文档中的每个元素都被描绘成矩形盒子,这些矩形盒子通过一个模型来描述其占用空间,这个模型称为盒模型.盒模型通过四个边界来描述:margin(外边距),border(边框),padding ...
- Vue(二)
---恢复内容开始--- 1.vue条件指令 可以运行加减运算 可以进行if判断 <!DOCTYPE html> <html> <head> <meta ch ...
- 什么是文件的BOM头,及BOM头有哪些坑?
1.什么是BOM? BOM是用来判断文本文件是哪一种Unicode编码的标记,其本身是一个Unicode字符("\uFEFF"),位于文本文件头部. 在不同的Unicode编码中, ...
- java项目中的异常处理总结
异常指的是运行期出现的错误,也就是当程序开始执行以后执行期出现的错误.出现错误时观察错误的名字和行号最为重要. 比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异常. 我找一个比较形象 ...
- 找到所有的txt文件并删除
1.find /oldboy/ -type f -name "*.txt" -delete 2.find /oldboy/ -type f -name "*.txt&qu ...
- JDK8新特性---stream流
项目上用到了stream流,找篇blog,转载一下,介绍下Stream流的用法. 1 流概述 流是 JDK8 新增的成员,允许以声明性方式处理数据集合,可以把 Stream 流看作是遍历数据集合的一 ...
- 素问 - REITs
摘自<小韭的学习圈> Q 一直以来对REITs感兴趣,看过您微信公众号对REITs的分析,年化8-10%,长期收益稳定,且与其他投资品种关键性低,是很不错的分散配置选择. 您推荐的广发美国 ...
- Dreamoon and WiFi
Dreamoon is standing at the position 0 on a number line. Drazil is sending a list of commands throug ...
- How To Use These LED Garden Lights
Are you considering the lighting options for the outdoor garden? Depending on how you use it, LED ga ...
- 洛谷 P3805【模板】manacher算法
题目链接:https://www.luogu.com.cn/problem/P3805 Manacher算法$O(n)$: 求以每个字符为中心的最长回文串的半径:如果要求可以以字符间隙为回文中心,就要 ...