算法复习 : 插入排序原理,记忆,时间复杂度 (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代码,不但能够计算加减乘除,还能够匹配小括号~ 代码点评: 从朴素的界面配色到简单易懂错误提示 ...
随机推荐
- linux环境jacoco接入
我们通常会将测试覆盖率分为两个部分,即“需求覆盖率”和“代码覆盖率”. 需求覆盖:指的是测试人员对需求的了解程度,根据需求的可测试性来拆分成各个子需求点,来编写相应的测试用例,最终建立一个需求和用例的 ...
- java基础之 数据类型
数据类型表示要存储在变量中的不同类型的值. 一.Java语言提供了八种基本数据类型.六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型. 1. byte byte 数据类型是8位.有 ...
- JDK的卸载和安装
Java入门 Java最大优势:基于JVM,跨平台 Java的几个版本 JavaSE:标准版,占领桌面,桌面程序,控制台开发等. JavaME:嵌入式开发,占领手机,手机,小家电等.(几乎死掉了) J ...
- mongo备份操作
数据备份mongodump 可以用mongodump 来做MongoDB 的库或表级别的备份,下面举例说明: >c:\mongo\bin\mongodump -d xxxx数据库 此时会在当前 ...
- Apache Kafka(一)- Kakfa 简介与术语
Apache Kafka 1. Kafka简介.优势.以及使用场景 Kafka的优势: 开源 分布式,弹性架构,fault tolerant 水平扩展: 可以扩展到100个brokers 可以扩展到每 ...
- 微信小程序 购物车流程
购物车流程 一.需求分析 a:全选,单选,根据选中的计算数目和总价 b:单个商品加减 c:删除一个商品 wxml 布局 <view> <view v-if="flag&qu ...
- DNS辅助
DNS服务原理详解 DNS:Domain Name Service,域名解析服务 监听端口:udp/53,tcp/53 应用程序:bind 根域:. 一级域: 组织域:.com, .org, .net ...
- Object.fromEntries
//数组转换成对象 const arr = [['foo', 1],['bar', 2]] const obj = Object.fromEntries(arr) console.log(obj.ba ...
- 2.4 【配置环境】TestNG安装
两种方法可以安装TestNG Eclipse插件: (来源:http://blog.csdn.net/hongchangfirst/article/details/7679849/) 第一种,离线安 ...
- Lc626_换座位
626. 换座位 SQL架构 小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id. 其中纵列的 id 是连续递增的 小美想改变相邻俩学生的座位. ...