You are on page 1of 458

更多电子书资料请搜索「书行天下」:http://www.sxpdf.

com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
图书在版编目(CIP)数据
漫 法: 灰 法之 / 梦舒著. — 北京: 工业 版 ,
2019.5

ISBN 978-7-121-36197-5

Ⅰ. ①漫… Ⅱ. ① … Ⅲ. ① 法分 ② 结 Ⅳ.
①TP301.6②TP311.12

中国版本图书馆CIP 字(2019) 057355号

编辑:张月萍

编辑:牛勇

印  刷:北京 彩色印刷有限公司

   :北京 彩色印刷有限公司

版发行: 工业 版

     北京 海淀区万 173 箱 邮编:100036

开  本:720×1000 1/16  印张:17.5 字 :404千字

版  次:2019年5月 1版

印  次:2019年6月 2次印刷

印   :20001~28000

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
量投 发邮件至zlts@phei.com.cn, 版 举报 发邮件至
dbqq@phei.com.cn。

本书咨 联系 式:010-51260888-819 faq@phei.com.cn。

未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。
版权所有,侵权必究。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
目 录

Preface 推荐序
Preface 前
1章 法概述
1.1 法和 结
1.1.1 灰和
1.1.2 什么 法
1.1.3 什么 结
1.2 间 度
1.2.1 法 与坏
1.2.2 本 执行次
1.2.3 渐进 间 度
1.2.4 间 度 巨 异
1.3 间 度
1.3.1 什么 间 度
1.3.2 间 度
1.3.3 间与 间 取舍
1.4 结
2章 结
2.1 什么 组
2.1.1 组
2.1.2 组 本
2.1.3 组 优势和劣势
2.2 什么 链

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2.2.1 “正 ”和“地下 ”
2.2.2 链 本
2.2.3 组VS链
2.3 栈和队
2.3.1 物 结 和逻辑结
2.3.2 什么 栈
2.3.3 栈 本
2.3.4 什么 队
2.3.5 队 本
2.3.6 栈和队 应
22.4
2.4.1 为什么需
2.4.2 哈
2.4.3
2.5 结
3章
3.1 和二叉
3.1.1 什么
3.1.2 什么 二叉
3.1.3 二叉 应
3.2 二叉 遍历
3.2.1 为什么 遍历
3.2.2 深度优先遍历
3.2.3 广度优先遍历
3.3 什么 二叉
3.3.1 二叉
3.3.2 二叉 自我

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3.3.3 二叉 代 现
3.4 什么 优先队
3.4.1 优先队 特点
3.4.2 优先队 现
3.5 结
4章 序 法
4.1 引
4.2 什么 泡 序
4.2.1 泡 序
4.2.2 泡 序 优化
4.2.3 尾酒 序
4.3 什么 快速 序
4.3.1 快速 序
4.3.2 素 选择
4.3.3 素 交
4.3.4 单边循环法
4.3.5 非递归 现
4.4 什么 序
4.4.1 中 序
4.4.2 序 代 现
4.5 序和 序
4.5.1 线性 间 序
4.5.2 序
4.5.3 序 优化
4.5.4 什么 序
4.6 结
5章 面 中 法

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5.1 满志 灰
5.2 链 有环
5.2.1 一场与链 关 面
5.2.2 题思
5.2.3 问题扩
5.3 最 栈 现
5.3.1 一场关于栈 面
5.3.2 题思
5.4 求 最 公约
5.4.1 一场求最 公约 面
5.4.2 题思
5.5 一个 否为2 次
5.5.1 一场很“2” 面
5.5.2 题思
5.6 序 组 序后 最 邻
5.6.1 一道 葩 面 题
5.6.2 题思
5.7 栈 现队
5.7.1 又 一道关于栈 面 题
5.7.2 题思
5.8 寻找 下一个
5.8.1 一道关于 字 题
5.8.2 题思
5.9 去k个 字后 最
5.9.1 又 一道关于 字 题
5.9.2 题思
5.10 现 加

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5.10.1 加法, 会不会
5.10.2 题思
5.11 求 金 问题
5.11.1 一个关于 自 问题
5.11.2 题思
5.12 寻找缺
5.12.1 “五行”缺一个
5.12.2 问题扩
6章 法 际应
6.1 灰上 1天
6.2 Bitmap 巧
6.2.1 一个关于 户标 需求
6.2.2 法 决问题
6.3 LRU 法 应
6.3.1 一个关于 户 息 需求
6.3.2 法 决问题
6.4 什么 A星寻 法
6.4.1 一个关于迷 寻 需求
6.4.2 法 决问题
6.5 现红包 法
6.5.1 一个关于钱 需求
6.5.2 法 决问题
6.6 法之 止

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
内容简介
本书通过 拟 主人公 灰 心 历 , 漫 形式 述了
法和 结 、 变 法面 题 及 法 际应
场景。

1章 介绍了 法和 结 关概念,告 法 什
么, 结 又 什么, 有哪些 途, 分 间 度,
分 间 度。

2章 介绍了最 本 结 ,包括 组、链 、栈、队 、


哈 概念和 。

3章 介绍了 和二叉 概念、二叉 各 遍历 式、二叉


特殊形式——二叉 和优先队 应 。

4章 介绍了几 典 序 法,包括 泡 序、快速 序、


序、 序、 序。

5章 介绍了10 道职场上流行 法面 题及详细 题思


。 怎 链 有环、怎 加 。

6章 介绍了 法在职场上 一些应 , LRU 法 淘


汰 , Bitmap 法 统 户特征 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
Preface
推荐序
灰 因为在 微 公众号 一篇 动态 章,
当 觉得 意 ,没想 还能有人 漫 释动态 法。

所 法,其 个很 泛 概念。有 起 难度 ,烧脑


“爆炸” ;也有 单 ,一 了然 ; 却 , 然
起 , 只 法得当, 清原 , 起 还 很 那
法。

可 很 人 “ 法”二字“狰狞” 吓 了,久久不
。 不 胆翻翻 法书,结果 不 篇 篇 代 ,
乱七八糟 号。这都 什么呀? 了, 不会 法了,
弃吧……

凡书籍 章,最难 ,肯 公式 号;而最 ,


乎图 、 。本书 者以可爱 灰和 两个漫 形 为主人
公,把 法 述过 之中,并辅之以图形
观 式 达 结 和 步 ——这 达形式 天然 亲和
力, 没有 背景 者 也不觉得 。

灰所 事情, 给 法这颗“炮弹”包上了“糖 ”,
法 力潜 于 , 不 吓人,反而变得萌萌哒,Q弹可爱,清
新怡人。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
先干为 , 我 一起吞了这颗包 “炸药” “糖丸”吧!

李 , 高级 件工

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
Preface
前言
序员 法 而 , 为 法 一门 深莫测 问。

以前我曾经面 过一个求职者,起 考 技 功底和项 经


, 都回 得不错。 下 我 :“OK,那我考 一下
法水平吧。”

题 还没 口,该求职者 马 手 :“不 不 ,我 法
不行 !”

我还 有些不 心, 道:“我只考 最 ,
泡 序 本思 吧!”

仍 :“我不 道,我 法一点都不会……”

法真 那么难,真 那么 吗?

恰恰 反, 法 编 领 中最有意思 一 ,也不
人想 那 难以 驭。

人把 法比 序员 “ 功”, 者觉得这个比喻并不
很恰当。 功 在在,没有 巧 可 ,而 法天马行 ,千
变万化, 金庸 下令狐冲 一 独 九剑。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
习 法,我 不需 死 背那些 长 背景 、底
原 、 令 法……需 领悟 法思想、 法 存 间
和性能 影响,以及开动脑 去寻求 决问题 最 。 比编
领 其 技 , 法 纯粹, 近 ,也 具有 味性。

我一 一些东 , IT同行能 领 法
力,可 什么 式 呢?

2016年9月,一次 其 灵感 我创造了一个 茅庐 菜
序员形 ,这个菜 序员名叫 灰。

序员 灰 事活 在同名 微 公众号上,该公众号 漫
形式 灰一次又一次 面 经历, 强 灰 战 ,
战。 灰 我刚刚 行 真 照, 序员也能从中
自己 影 。

终于,在 友 和 励下, 序员 灰 事从微 公众


号 了纸 图书上。能 同行 灰 事,我感 十分欣
慰。

本书特色
这本书通过漫 形式, 述了 灰 习 法和 结
心 历 。书中 源于本人 微 公众号, 比公众号上所
现 加系统、 面,也 加严 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
本书 前4章 法 ,没有 法和 结
者可以从 开 进行系统 习。

于有一 者,也可以选择从 5章面 题 开 阅


,每一道面 题 都 独 ,并不需 严 地 顺序 习。
同 ,也推荐 适当 前面 , 固一下自己 法
系。

这不 一本编 门书。在编 面 零 者,建 至


先了 一门编 。

这也不 一本 限于 个编 书, 然书中 代 都
Java 现 , 法思想 通 。在 现代 ,书中尽可
能 避了Java 特殊 法和工具类, 熟悉其 开发者
也不难 懂。

勘误和支持
除书中所 代 以 , 也可以关注微 公众号“
序员 灰”,在后台回 “漫 法”,获得 书 、可运行
代 。为了 代 洁,在部分代 现中 了烦 参
和 逻辑。

于 者水平有限,书中难 会 现一些错 ,恳 广 者批
正。 者 果在阅 过 中产 问或发现Bug,欢迎随 微
公众号 后台 。“ 序员 灰”微 公众号二维 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
致谢
感 微 公众号“ 序员 灰” 者。 励和 ,给
了我 创 动力。

感 成都道然 技有限 公司 新 老 。有了 肯 、


和 导意见,才有了这本书 正式 版。

感 、单耳和康慧三位 所 精彩 ,
灰 形 丰满、 可爱。感 为本书 道 先 ,感 为本
书 序 烨老 ,感 在 忙之中阅 书 并 书 专 ,
欣、张洪亮、 辉、 艳 、翟永 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
特 感 我 父母, 把我 进了 门。在我上
, ,才 我有 会 习 ,参加 ,并
和逻辑产 了兴 。在这本书 过 中,又 辛苦努
力 活 事 我 干扰, 我能 心地投 本书 当
中。

以此书 给我 人,我 者,以及热爱编 友 !

魏梦舒, 众 “ 小 ” 作者

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第1章 算法概述
1.1 算法和数据结构
1.1.1 小灰和大黄
在 四临近毕业 , 专业 同 都 了满意 offer,
可 灰却还在 急上火。 然 这几天面 了很 IT公司,可每
次都 面 “ ”得很惨很惨。

在心灰意 之际, 灰忽然想 , 系里有一位 霸名叫


, 不 技 很强,而且很乐意 助同 。于 , 灰赶紧去找
, 能 得 一些 点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1.1.2 什么是算法
法, 应 英 单 algorithm,这 一个很古老 概念,最
自 领 。

有一个关于 法 事, 都有耳闻。

在很久很久以前,曾经有一个顽 又聪 “熊 ”,天天在
上 。

终于有一天,老 忍 可忍, “熊 ” :

臭小子,你又 啊!今天罚你算加 ,算出

1+2+3+4+5+6+7……一 加 10000 结果,算不完不 回家!

嘿嘿,我算 是了。

老 以为,“熊 ”会 部 地一步一步 , 下面这


1 + 2 = 3

3 + 3 = 6

6 + 4 = 10

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
10 + 5 = 15

……

这还不得 天天亮? 这 受 !老 心里幸灾乐 地想


几分钟后……

老 ,我算完了!结果是50 005 000,对不

对?

这、这、这……你小子 么算 这么 ?我 书

多,你骗不了我 !

老 惊 情,“熊 ”微微一 , 了
法。

先把从1 10 000这10 000个 字两两分组 加, 下。

1 + 10 000 = 10 001

2 + 9999 = 10 001

3 + 9998 = 10 001

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4 + 9997 = 10 001

……

一 有 组这 结果 同 和呢?有10 000÷2即5000组。

所以1 10 000 加 总和可以这 :

(1+10 000)×10 000 ÷ 2 = 50 005 000

这个“熊 ” 后 著名 犹太 约翰·卡尔·弗里德
里希·高斯,而 所采 这 求和 法, 称为高斯算
法。(上 事情节与史 有 。)

这 领 中 法 一个 单 。在 领 里, 法
于 决 一类问题 公式和思想。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
而本书所涉及 法, 领 法, 本 一
系 序 令, 于 决特 运 和逻辑问题。

从 观上 , 领 法和 领 法有很 通
之 。

算法有简 的,也有 杂的。

单 法, 给 一组 ,找 其中最 。

法, 在 物品里选择 背包 物品, 背包里


物品总价 最 ,或找 从一个 另一个 最 线。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
算法有 效的,也有 劣的。

刚才所 从1加 10000 事中, 斯所 法 然 加


法, 律,四两拨千 , 力地求
了最终结果。

而老 心中所想 法, 部 地一个 一个 进行累加,则


一 低 、 拙 法。 然这 法也能得 最终结果, 其
过 低 得 。

在 领 ,我 同 会遇 各 和拙劣 法。 量
法 坏 重 标 有两个。

时间复杂度
空间复杂度
具 概念会在本章进行详细 。

算法的 用 种 样。

法可以应 在很 不同 领 中,其应 场景 ,
下面这些。

1. 运算

有人或 会觉得,不 运 吗?这还不 单?

其 还真不 单。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
求 两个 最 公约 , 致, 需 动
一 脑 。

两个 和, 照正 式 肯 会导致变
量溢 。这又该 求 呢?

2. 查

当 歌、 度 索 一个关键 ,或在 库中执行 一


SQL 句 , 有没有思考过 和 息 呢?

3.

序 法 现 序 。 ,当浏览 商网
,我 商品可以 价 从低 进行 序;当浏览 网

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
,我 可以 照 号 进行 序。

序 法有很 , 性能和优缺点各不 同,这里面


问可 呢。

4. 最优决策

有些 法可以 助我 找 最优 决 。

在游戏中,可以 AI 色找 迷 最 线,这涉及A星寻
法。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
于一个 量有限 背包 , 决 才可以 物
品总价 最 ,这涉及动态 法。

5. ( 果 条也算的 )

凡 已 上工 位 序员,在面 过 中 都经历过
法问题 考 。

为什么面 那么喜欢考 法呢?

考 法问题,一 面可以检 序员 底 了
,另一 面也可以 量一下 序员 逻辑思维能力。

1.1.3 什么是数据结构
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
算 概 我大致明白了, 数据结构又是

什么呢?

数据结构是算 基 。如果 算 比喻成美

丽 动 舞者, 么数据结构 是舞者脚下 阔而坚实 舞


台。

结 , 应 英 单 data structure , 组织、


和存 式,其 为了 地访问和 。

结 都有哪些组成 式呢?

1. 线 结构

线性结 最 单 结 ,包括 组、链 ,以及 衍


栈、队 、哈 。

2. 树

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
结 ,其中比较有代 性 二叉 ,
又衍 了二叉 之类 结 。

3.

图 为 结 ,因为在图中会呈现 关联关
系。

4. 其他数 结构

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
除上述所 几 本 结 以 ,还有一些其 千 怪
结 。 本 结 变形而 , 于 决 些特 问
题, 、哈 链 、位图 。

有了 结 这个舞台, 法才可以尽情舞 。在 决问题 ,


不同 法会选 不同 结 。 序 法中 序,
二叉 这 一 结 ; 缓存淘汰 法LRU(Least
Recently Used,最近最 ), 特殊 结 哈 链

关于 法在不同 结 上 过 ,在后续 章节中我 会


一一进行 习。

不 算 和数据结构 这么多丰富多

容,大黄,我以后要好好 你 !

嘿嘿,我 掌握 也只是 阔 算 中

一个小 , 我们一步一步来体验算 无穷魅 吧!

1.2 时间复杂度
1.2.1 算法的好与坏
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
间 度和 间 度 竟 什么呢? 先, 我 想 一
个场景。

一天, 灰和 同 加 了同一 公司。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
一天后, 灰和 交 了各自 代 ,两人 代 现 功能
不 。

代 运行一次 花100ms,占 存 5MB。


灰 代 运行一次 花100s,占 存500MB。

于 ……

在上述场景中, 灰 然也 照老板 求 现了功能,


代 存在两个很严重 问题。

1. 运 时

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
运 别人的代码 100ms,而运 灰的代码则 100s,使用者
肯 是无法 的。

2. 用空

别人的代码 消耗5MB的内 ,而 灰的代码 消耗500MB的内


, 会给使用者 烦。

由此 ,运 时 的 短 用内 空 的 ,是 程
的 素。

可是,如果代 还 有运行,我 么能预

代 运行 花 时间呢?

于受运行环境和 模 响,代 绝

对 行时间是无 预估 。但我们却可以预估代 基本操作


行次数。

1.2.2 基本操作执行次数
关于代 本 执行次 ,下面 活中 4个场景 进行

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
场景1 给 灰1个长度为10cm 面包, 灰每3分钟吃 1cm,那么
吃 个面包需 久?

自然 3×10即30分钟。

果面包 长度 n cm呢?

此 吃 个面包,需 3乘以n即3n分钟。

果 一个 达吃 个面包所需 间,可以
T(n) = 3n,n为面包 长度。
场景2 给 灰1个长度为16cm 面包, 灰每5分钟吃 面包剩
长度 一半,即 5分钟吃 8cm, 10分钟吃 4cm, 15分钟吃
2cm……那么 灰把面包吃得只剩1cm,需 久呢?

这个问题 式 达 , 字16不 地除以2,那么除几次


以后 结果 于1?这里涉及 中 ,即以2为底16
log216。(注:本书下 中 底 部 。)

因此,把面包吃得只剩下1cm,需 5×log16即20分钟。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
果面包 长度 n cm呢?

此 ,需 5乘以logn即5logn分钟, T(n) = 5logn。


场景3 给 灰1个长度为10cm 面包和1个 腿, 灰每2分钟吃
1个 腿。那么 灰吃 个 腿需 久呢?

自然 2分钟。因为这里只 求吃 腿,和10cm 面包没有


关系。

果面包 长度 n cm呢?

面包 长,吃 腿 间都 2分钟, T(n) = 2。


场景4 给 灰1个长度为10cm 面包, 灰吃 1个1cm需 1分
钟 间,吃 2个1cm需 2分钟 间,吃 3个1cm需 3分钟
间……每吃1cm所花 间 比吃上一个1cm 1分钟。那么 灰吃
个面包需 久呢?

从1累加 10 总和,也 55分钟。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
果面包 长度 n cm呢?

斯 法,此 吃 个面包需 1+2+3+…+(n-1)+ n 即


(1+n)×n/2 分 钟 , 也 0.5n2 + 0.5n 分 钟 , T(n) = 0.5n2 +
0.5n。

么除了吃还是吃啊?这还不 撑死?

上面所 吃东 所花 间,这一思想同 适 于 序
基本操作执行次数 统 。设T(n)为 序 本 执行次
(也可以 为 序 执行 间 ),n为输 模,刚才 4
个场景分 应了 序中最 见 4 执行 式。

场景1 T(n) = 3n,执行次 线性 。

1. void eat1(int n){


2. for(int i=0; i<n; i++){;
3. System.out.println(" 待1分钟");
4. System.out.println(" 待1分钟");
5. System.out.println("吃1cm 面包");
6. }
7. }

场景2 T(n) = 5logn,执行次 对数 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. void eat2(int n){
2. for(int i=n; i>1; i/=2){
3. System.out.println(" 待1分钟");
4. System.out.println(" 待1分钟");
5. System.out.println(" 待1分钟");
6. System.out.println(" 待1分钟");
7. System.out.println("吃一半面包");
8. }
9. }

场景3 T(n) = 2,执行次 常量。

1. void eat3(int n){


2. System.out.println(" 待1分钟");
3. System.out.println(" 吃1个 腿");
4. }

场景4 T(n) = 0.5n 2 + 0.5n,执行次 多项式 。

1. void eat4(int n){


2. for(int i=0; i<n; i++){
3. for(int j=0; j<i; j++){

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. System.out.println(" 待1分钟");
5. }
6. System.out.println("吃1cm 面包");
7. }
8. }

1.2.3 渐进时间复杂度
有了 本 执行次 T(n), 否 可以分 和比较代
运行 间了呢?还 有一 困难 。

法A 执行次 T(n)= 100n, 法B 执行次 T(n)=


5n2,这两个 底 运行 间 长一些呢?这 n 取 了。

因此,为了 决 间分 难题,有了 渐进时间复杂度


(asymptotic time complexity) 概念,其 义 下。

若 函数f(n),使 当n 近于无穷 时,T(n)/f(n)的极 值


为不等于 的 数 , 则 称 f(n) 是 T(n) 的 数 级函数。 作
T(n)=O(f(n)),称为O(f(n)),O为算法的渐 时 杂 ,简称为时
杂 。

因为渐进 间 度 O ,所以也 称为 大O表示法。

这个定义好晦 呀, 不明白。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
白地 ,时间复杂度 是 对 行

时间 数T(n)简 为一个数 级,这个数 级可以是n、n2、n3


等。

推导 间 度呢?有 下几个原则。

如果运行时间是常数量级,则用常数1表示
只保留时间函数中的最高阶项
如果最高阶项存在,则省去最高阶项前面的系数
我 回 刚才 4个场景。

场景1
T(n) = 3n,

最 阶 项 为 3n , 去系 3,则 化 间 度为:
T(n)=O(n)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
场景2
T(n) = 5logn,

最 阶项为5logn, 去系 5,则 化 间 度为: T(n)


=O(logn)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
场景3
T(n) = 2,

只有 量级,则 化 间 度为: T(n) =O(1)。

场景4
T(n) = 0.5n2+ 0.5n,

最 阶项为0.5n2 , 去系 0.5,则 化 间 度为: T(n)


=O(n2)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这4 间 度 竟 度执行 长, 节 间呢?
当n 取 ,不难得 下面 结 :

O(1)<O(logn)<O(n)<O(n2)

在编 世 中有各 各 法,除了上述4个场景,还有
不同形式 间 度, :

O(nlogn)、O(n3)、O(mn)、O(2n)、O(n!)

今后当我 遨游在代 海洋中 ,会陆续遇 上述 间 度


法。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1.2.4 时间复杂度的巨大差异
大黄,我还有一个问题,现在 算机 件

能越来越 了,我们为什么还这么重 时间复杂度呢?

问 好, 我们 两个算 来 一个对比,

一 高效算 和低效算 有多大 。

举 下。

法A 执行次 T(n)= 100n, 间 度 O(n)。

法B 执行次 T(n)= 5n2, 间 度 O(n2)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
法A运行在 灰 里 老 脑上, 法B运行在 台 级
上, 级 运行速度 老 脑 100 。

那么,随 输 模n 长,两 法 运行速度 快呢?

从上面 可以 ,当n 很 , 法A 运行 远
于 法B;当n 在1000左右 , 法A和 法B 运行 间已经比
较 近;随 n , 至达 十万、 万 , 法A 优势
开 现 , 法B 运行速度则 慢, 。

这 不同 间 度 。

要 学好算 , 须 时间复杂度这个重

要 基 概 。有关时间复杂度 介绍 这里,我们下
一节 见!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1.3 空间复杂度
1.3.1 什么是空间复杂度

在运行一段 序 ,我 不 执行各 运 令,同 也会


需 ,存 一些临 中间数据,以 后续 令可以 地继续
执行。

在什么情 下需 这些中间 呢? 我 下面 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
给 下图所 n个 ,其中有两个 重 , 求找 这
两个重 。

于这个 单 需求,可以 很 思 决,其中最 素


法 双重循环,具 下。

遍历 个 ,每遍历 一个新 开 回顾之前遍历过


所有 , 这些 里有没有与之 同 。

1步,遍历 3,前面没有 字,所以 须回顾比较。

2步,遍历 1,回顾前面 字3,没有发现重 字。

3步,遍历 2,回顾前面 字3、1,没有发现重 字。

后续步 类 ,一 遍历 最后 2,发现和前面 2重

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
双重循环 然可以得 最终结果, 然并不 一个
法。

间 度 呢?

上一节所 法,我 不难得 结 ,这个 法 间


度 O(n2)。

么, 样 能提高算 效 呢?

在这 下,我们 有 要 一些 中间数
据了。
中间 呢?

当遍历 个 ,每遍历一个 , 把该 存 起 ,
字典中一 。当遍历下一个 ,不必 慢慢向前回溯比

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
较,而 去“字典”中 找, 有没有 应 即可。

已经遍历了 前7个 ,那么字典里存 息 下。

“字典”左 Key代 ,“字典”右 Value代 该


现 次 (也可以只 录Key)。

下 ,当遍历 最后一个 2 ,从“字典”中可以 松找


2曾经 现过,问题也 迎 而 了。

于 “字典”本 间 度 O(1),所以 个 法
间 度 O(n),和最 双重循环 比,运行 了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
而这个所 “字典”, 一 特殊 结 ,叫 散列表。
这个 结 需 开辟一 存 间 存 有 息。

, 存 间 有限 ,在 间 度 同 情 下, 法占
存 间自然 。 述一个 法占 存 间
呢?这 了 法 另一个重 标—— 空间复杂度(space
complexity)。
和 间 度类 , 间 度 一个 法在运行过 中临
占 存 间 量度, 同 了 O 法。

序占 间 公式 S(n)=O(f(n)),其中n为问题
模,f(n)为 法所占存 间 。

1.3.2 空间复杂度的计算
基本 概 已经明白了, 么,我们如何来

算空间复杂度呢?

具体 要具体分析。和时间复杂度类似,空

间复杂度也有几 不同 增 趋 。

见 间 度有下面几 情形。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. 空

当 法 存 间 固 ,和输 模没有 关系 ,
间 度 O(1)。 下面这段 序:

1. void fun1(int n){


2. int var = 3;
3. …
4. }

2. 线 空

当 法分配 间 一个线性 集合( 组),并且集合


和输 模n成正比 , 间 度 O(n)。
下面这段 序:

1. void fun2(int n){


2. int[] array = new int[n];
3. …
4. }

3. 二维空

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当 法分配 间 一个二维 组集合,并且集合 长度和 度
都与输 模n成正比 , 间 度 O(n2)。
下面这段 序:

1. void fun3(int n){


2. int[][] matrix = new int[n][n];
3. …
4. }

4. 归空

递归 一个比较特殊 场景。 然递归代 中并没有 式地


变量或集合, 在执行 序 ,会专门分配一 存,
存 “ 法 栈”。

“ 法 栈”包括 进栈和出栈两个行为。
当进 一个新 法 ,执行 栈 ,把 法和参 息
压 栈中。

当 法返回 ,执行 栈 ,把 法和参 息从栈中


弹 。

下面这段 序 一个标 递归 序:

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. void fun4(int n){
2. if(n<=1){
3. return;
4. }
5. fun4(n-1);
6. …
7. }

参 n=5,那么 法fun4(参 n=5) 息


先 栈。

下 递归 同 法, 法fun4(参 n=4) 息
栈。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以此类推,递归 深, 栈 素 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当n=1 ,达 递归结 件,执行return 令, 法 栈。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
最终,“ 法 栈” 部 素会一一 栈。

上面“ 法 栈” 栈过 可以 ,执行递归 所
需 存 间和递归 深度成正比。纯粹 递归 间 度
也 线性 , 果递归 深度 n,那么 间 度 O(n)。

1.3.3 时间与空间的取舍
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
人 之所以花 力气去 法 间 度和 间 度,其
本原因 运 速度和 间 源 有限 。

一个 主, 本不必为 花销 脑 ;而一个没 积
蓄 普通人,则不得不为 花销精打细 。

于 系统 也 此。 然 前 CPU 速度不
飙升, 存和 间也 , 面 庞 而 和
业务,我 仍然 精打细 ,选择最有 式。

,正所 和熊 不可 得。很 ,我 不得不在 间


度和 间 度之间进行取舍。

在1.3.1 节寻找重 中,双重循环 间 度


O(n2), 间 度 O(1),这 于 牺牲时间来换取空间 情 。

反,字典法 间 度 O(n), 间 度 O(n),这 于


牺牲空间来换取时间 情 。

在绝 , 间 度 为重 一些,我 可 分配一
些 存 间,也 升 序 执行速度。

此 , 起 间 度 不开 结 。在本章中,我 及
、 组、二维 组这些 集合。 果 这些 结
不 很了 ,可以 细 本书 2章关于 本 数据结构 ,里
面有详细 介绍。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
关于空间复杂度 ,我们 介绍 这里。

时间复杂度和空间复杂度 是学好算 重要 提,一定要


掌握哦!

1.4 小结
什么 法

在 领 里, 法 一系 序 令, 于 特 运
和逻辑问题。

量 法优劣 主 标 间 度和 间 度。

什么 结

结 组织、 和存 式,其 为了
地访问和 。

结 包含 组、链 这 线性 结 ,也包含 、图这


结 。

什么 间 度

间 度 一个 法运行 间长 量度, O ,
T(n)=O(f(n))。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
见 间 度 照从低 顺序,包括O(1)、O(logn)、
O(n)、O(nlogn)、O(n2) 。

什么 间 度

间 度 一个 法在运行过 中临 占 存 间
量度, O , S(n)=O(f(n))。

见 间 度 照从低 顺序,包括O(1)、O(n)、O(n2)
。其中递归 法 间 度和递归深度成正比。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第2章 数据结构基础
2.1 什么是数组
2.1.1 初识数组

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
些特点是 何体现的 ?

加 军 的 者,一 样的 景。

在 队里,每一个 兵都有自己固 位置、固 编号。众 兵紧


团结在一起, 地执行 一个个命令。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
大黄,咱们为什么要 这么多关于 队 事 呢?

因为有一个数据结构 队一样整齐、有 ,这个

数据结构叫作 数组。
什么 组?

组 应 英 array, 有限个 同类 变量所组成 有序集合,


组中 每一个变量 称为 素。 组 最为 单、最为 结 。

以 组为 , 组 存 形式 下图所 。

正 队里 兵存在编号一 , 组中 每一个 素也有 自己 下


标,只不过这个下标从0开 ,一 组长度-1。

组 另一个特点, 在 存中 顺序存储,因此可以很 地 现逻辑上


顺序表。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
组在 存中 顺序存 ,具 什么 呢?

存 一个个连续 存单 组成 ,每一个 存单 都有自己 地


址。在这些 存单 中,有些 其 占 了,有些 闲 。

组中 每一个 素,都存 在 存单 中,并且 素之间紧


, 不能打乱 素 存 顺序,也不能 过 个存 单 进行存 。

在上图中,橙色 代 闲 存 单 ,灰色 代 已占
存 单 ,而红色 连续 代 组在 存中 位置。

不同类 组,每个 素所占 字节个 也不同,本图只 一个 单


意图。

么,我们 样来使 一个数组呢?

数据结构 操作无非是增、 、改、查4 ,下面

我们来 一 数组 基本操作。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2.1.2 数组的基本操作
1. 元素

于 组 , 取 素 最 单 。 于 组在 存中顺序存
,所以只 给 一个 组下标, 可以 取 应 组 素。

设有一个名称为array 组,我 取 组下标为3 素,


array[3]; 取 组下标为5 素, array[5]。需 注意 ,输
下标必须在 组 长度范围之 ,否则会 现 组 。

这 下标 取 素 式叫 随机读取。
单 代 下:

1. int[] array = new int[]{3,1,2,5,4,9,7,2};

2. // 输 组中下标为3 素

3. System.out.println(array[3]);

2. 更新元素

把 组中 一个 素 替 为一个新 ,也 非 单 。
组下标, 可以把新 给该 素。

单 代 下:

1. int[] array = new int[]{3,1,2,5,4,9,7,2};

2. // 给 组下标为5 素

3. array[5] = 10;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. // 输 组中下标为5 素

5. System.out.println(array[5]);

小 ,咱们刚刚 时间复杂度 概 ,你 数组

取 素和更新 素 时间复杂度分 是多少?

嘿嘿,这难不 我。数组 取 素和更新 素 时

间复杂度 是 O(1)。
3. 入元素

在介绍 组 素 之前,我 需 一个概念,那 组


际 素 量有可能 于 组 长度, 下面 情形。

因此, 组 素 存在3 情 。

尾部插入
中间插入
超范围插入
尾部 , 最 单 情 , 把 素 在 组尾部 闲位
置即可, 同于 新 素 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
中间 , 微 一些。 于 组 每一个 素都有其固 下标,所
以不得不 先把 位置及后面 素向后 动,腾 地 , 把
素 应 组位置上。

中间 现代 下:

1. private int[] array;

2. private int size;


3.

4. public MyArray(int capacity){


5. this.array = new int[capacity];

6. size = 0;
7. }

8.
9. /**

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
10. * 组 素

11. * @param element 素


12. * @param index 位置

13. */
14. public void insert(int element, int index) throws Exception {

15. // 访问下标 否 范围
16. if(index<0 || index>size){

17. throw new IndexOutOfBoundsException(" 组 际 素范围!");


18. }

19. //从右向左循环, 素逐个向右 1位


20. for(int i=size-1; i>=index; i--){
21. array[i+1] = array[i];

22. }
23. //腾 位置 新 素

24. array[index] = element;


25. size++;

26. }
27.

28. /**
29. * 输 组
30. */

31. public void output(){


32. for(int i=0; i<size; i++){

33. System.out.println(array[i]);
34. }

35. }
36.

37. public static void main(String[] args) throws Exception {


38. MyArray myArray = new MyArray(10);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
39. myArray.insert(3,0);

40. myArray.insert(7,1);
41. myArray.insert(9,2);

42. myArray.insert(5,3);
43. myArray.insert(6,1);
44. myArray.output();

45. }

代 中 成员变量size 组 际 素 量。 果 素在 组尾
部, 下标参 index 于size; 果 素在 组中间或 部,则
index 于size。

果 下标参 index 于size或 于0,则 为 非法输 ,会


抛 异 。

可是,如果数组不断插 新 素, 素数

了数组 最大 度,数组 不是要“撑 ”了?

问 好,这 是接下来要 —— 范围插

范围 ,又 什么意思呢?

现在有一个长度为6 组,已经 满了 素,这 还想 一个新


素。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 涉及 组 扩容了。可 组 长度在创建 已经 了, 法
孙悟 金 棒那 随意变长或变 。这该 呢?

此 可以创建一个新 组,长度 组 2 , 把 组中 素统
统 制过去,这 现了 组 扩 。

此一 ,我 素 法也需 了, 后 代 下:

1. private int[] array;

2. private int size;


3.

4. public MyArray(int capacity){

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5. this.array = new int[capacity];
6. size = 0;

7. }
8.

9. /**
10. * 组 素
11. * @param element 素

12. * @param index 位置


13. */

14. public void insert(int element, int index) throws Exception {


15. // 访问下标 否 范围

16. if(index<0 || index>size){


17. throw new IndexOutOfBoundsException(" 组 际 素范围!");

18. }
19. // 果 际 素达 组 量上限,则 组进行扩
20. if(size >= array.length){

21. resize();
22. }

23. //从右向左循环, 素逐个向右 1位


24. for(int i=size-1; i>=index; i--){

25. array[i+1] = array[i];


26. }

27. //腾 位置 新 素
28. array[index] = element;

29. size++;
30. }
31.

32. /**
33. * 组扩

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
34. */

35. public void resize(){


36. int[] arrayNew = new int[array.length*2];

37. //从 组 制 新 组
38. System.arraycopy(array, 0, arrayNew, 0, array.length);

39. array = arrayNew;


40. }

41.
42. /**

43. * 输 组
44. */
45. public void output(){

46. for(int i=0; i<size; i++){


47. System.out.println(array[i]);

48. }
49. }

50.
51. public static void main(String[] args) throws Exception {

52. MyArray myArray = new MyArray(4);

53. myArray.insert(3,0);
54. myArray.insert(7,1);

55. myArray.insert(9,2);

56. myArray.insert(5,3);
57. myArray.insert(6,1);

58. myArray.output();

59. }

4. 删 元素

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
组 除 和 过 反, 果 除 素位于 组中
间,其后 素都需 向前 动1位。

于不涉及扩 问题,所以 除 代 现比 单:

1. /**
2. * 组 除 素

3. * @param index 除 位置

4. */

5. public int delete(int index) throws Exception {


6. // 访问下标 否 范围

7. if(index<0 || index>=size){

8. throw new IndexOutOfBoundsException(" 组 际 素范围!");

9. }
10. int deletedElement = array[index];

11. //从左向右循环, 素逐个向左 1位

12. for(int i=index; i<size-1; i++){


13. array[i] = array[i+1];

14. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
15. size--;

16. return deletedElement;

17. }

小 ,我 考考你,数组 插 和 除操作,时间复

杂度分 是多少?

插 操作,数组 容 时间复杂度是O(n),

插 动 素 时间复杂度也是O(n),综 起来插 操作 时间复


杂度是 O(n) 。至于 除操作,只涉及 素 动,时间复杂度也是
O(n)。

。对于 除操作,其实还存在一 取巧 方

, 提是数组 素 有顺 要 。

下图所 ,需 除 组中 素2,可以把最后一个 素 制
素2所在 位置,然后 除 最后一个 素。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 一 , 须进行 量 素 动, 间 度降低为O(1)。当然,
这 式只 参考,并不 除 素 主流 式。

2.1.3 数组的优势和劣势
数组 基本 我 了, 么,使 数组这 数据

结构有什么优 和 呢?

数组 有非 高效 随机访问能 ,只要给出下标,

可以 时间 对应 素。有一 高效查 素 算 叫作二


分查 , 是 了数组 这个优 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
至于数组 ,体现在插 和 除 素方面。 于

数组 素 续紧密地存 在 存中,插 、 除 素 会导致大


素被 动, 响效 。

来 ,数组 是 读操作多、写操作少 场

景,下一节我们要 表则 反。好了, 我们下一节


会!

2.2 什么是链表
2.2.1 “正规军”和“地下党”

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下党 是一些什么样的人物 ?

作 中, 们 能 到 下 作者的经典 :

“上级的 、住 , 知 ,下级的 、住 , 也知 ,但是


些 是 们党的秘 ,不能 你们!”

地下 助这 单线联络 式,灵活隐 地 递 各 重 息。

在 领 里,有一 结 也恰恰具 这 特征,这


结 链表。
链 什么 ?为什么 地下 呢?

我 一 单向链 结 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
链 (linked list) 一 在物 上非连续、非顺序 结 , 若
干节点(node)所组成。

单向链 每一个节点又包含两部分,一部分 存 变量data,


另一部分 向下一个节点 针next。

1. private static class Node {


2. int data;

3. Node next;

4. }

链 1个节点 称为 节点,最后1个节点 称为尾节点,尾节点


next 针 向 。

与 组 照下标 随 寻找 素不同, 于链 其中一个节点A,我


只能 节点A next 针 找 该节点 下一个节点B, 节点B next
针找 下一个节点C……

这正 地下 联络 式,一级一级,单线 递。

么, 表 一个节 ,如何能 它

一个节 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
要 每个节 能回 它 置节 ,我们可以

使 双向链表。
什么 双向链 ?

双向链 比单向链 微 一些, 每一个节点除了拥有data和


next 针,还拥有 向前置节点 prev 针。

下 我 一 链 存 式。

果 组在 存中 存 式 顺序存 ,那么链 在 存中 存
式则 随机存储。
什么叫随 存 呢?

上一节我 了 组 存分配 式, 组在 存中占 了连续


存 间。而链 则采 了见缝 针 式,链 每一个节点分 在
存 不同位置, 靠next 针关联起 。这 可以灵活有 地 零
片 间。

我 一 下面两张图, 比一下 组和链 在 存中分配 式 不


同。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
数组 存分 方

表 存分 方

图中 代 链 节点 next 针。

么,我们 样来使 一个 表呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
上一节刚刚 数组 增、 、改、查,这一次来

表 关操作。

2.2.2 链表的基本操作
1. 查 节点

在 找 素 ,链 不 组那 可以通过下标快速进行 位,只能从
节点开 向后一个一个节点逐一 找。

给 一个链 ,需 找从 节点开 3个节点。

1步, 找 针 位 节点。

2步, 节点 next 针, 位 2个节点。

3步, 2个节点 next 针, 位 3个节点, 找 毕。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
小 ,你 查 表节 时间复杂度是多少?

表中 数据只能按顺 进行访问,最坏 时间复

杂度是 O(n)。
2. 更新节点

果不考 找节点 过 ,链 新过 会 组那 单,
把 替 成新 即可。

3. 入节点

与 组类 ,链 节点 ,同 分为3 情 。

尾部插入
头部插入
中间插入

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
尾部 , 最 单 情 ,把最后一个节点 next 针 向新
节点即可。

部 ,可以分成两个步 。

1步,把新节点 next 针 向原先 节点。

2步,把新节点变为链 节点。

中间 ,同 分为两个步 。

1步,新节点 next 针, 向 位置 节点。

2步, 位置前置节点 next 针, 向新节点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
只 存 间 ,能 链 素 尽 ,不需 组
那 考 扩 问题。

4. 删 元素

链 除 同 分为3 情 。

尾部删除
头部删除
中间删除
尾部 除, 最 单 情 ,把 2个节点 next 针 向 即可。

部 除,也很 单,把链 节点设为原先 节点 next 针即


可。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
中间 除,同 很 单,把 除节点 前置节点 next 针, 向
除 素 下一个节点即可。

这里需 注意 , 级 , Java,拥有自动化 圾回
制,所以我 不 刻意去释 除 节点,只 没有 部引 向 ,
除 节点会 自动回 。

小 ,我 考考你, 表 插 和 除操作,时间复

杂度分 是多少?

如果不考虑插 、 除操作之 查 素 ,

只考虑纯粹 插 和 除操作,时间复杂度 是 O(1)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好,接下来 一 实现 表 完整代 。

1. // 节点 针

2. private Node head;


3. // 尾节点 针
4. private Node last;

5. // 链 际长度
6. private int size;

7.
8. /**
9. * 链 素

10. * @param data 素


11. * @param index 位置
12. */

13. public void insert(int data, int index) throws Exception {


14. if (index<0 || index>size) {
15. throw new IndexOutOfBoundsException(" 链 节点范围!");

16. }
17. Node insertedNode = new Node(data);

18. if(size == 0){


19. // 链
20. head = insertedNode;

21. last = insertedNode;


22. } else if(index == 0){
23. // 部

24. insertedNode.next = head;


25. head = insertedNode;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
26. }else if(size == index){

27. // 尾部
28. last.next = insertedNode;
29. last = insertedNode;

30. }else {
31. // 中间
32. Node prevNode = get(index-1);

33. insertedNode.next = prevNode.next;


34. prevNode.next = insertedNode;
35. }

36. size++;
37. }

38.
39. /**
40. * 链 除 素

41. * @param index 除 位置


42. */
43. public Node remove(int index) throws Exception {

44. if (index<0 || index>=size) {


45. throw new IndexOutOfBoundsException(" 链 节点范围!");
46. }

47. Node removedNode = null;


48. if(index == 0){

49. // 除 节点
50. removedNode = head;
51. head = head.next;

52. }else if(index == size-1){


53. // 除尾节点
54. Node prevNode = get(index-1);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
55. removedNode = prevNode.next;

56. prevNode.next = null;


57. last = prevNode;
58. }else {

59. // 除中间节点
60. Node prevNode = get(index-1);

61. Node nextNode = prevNode.next.next;


62. removedNode = prevNode.next;
63. prevNode.next = nextNode;

64. }
65. size--;
66. return removedNode;

67. }
68.
69. /**

70. * 链 找 素
71. * @param index 找 位置

72. */
73. public Node get(int index) throws Exception {
74. if (index<0 || index>=size) {

75. throw new IndexOutOfBoundsException(" 链 节点范围!");


76. }
77. Node temp = head;

78. for(int i=0; i<index; i++){


79. temp = temp.next;
80. }

81. return temp;


82. }

83.

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
84. /**
85. * 输 链
86. */

87. public void output(){


88. Node temp = head;
89. while (temp!=null) {

90. System.out.println(temp.data);
91. temp = temp.next;
92. }

93. }
94.

95. /**
96. * 链 节点
97. */

98. private static class Node {


99. int data;
100. Node next;

101. Node(int data) {


102. this.data = data;
103. }

104.}
105.

106.public static void main(String[] args) throws Exception {


107. MyLinkedList myLinkedList = new MyLinkedList();
108. myLinkedList.insert(3,0);

109. myLinkedList.insert(7,1);
110. myLinkedList.insert(9,2);
111. myLinkedList.insert(5,3);

112. myLinkedList.insert(6,1);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
113. myLinkedList.remove(0);
114. myLinkedList.output();
115.}

以上 单链 关 代 现。为了尾部 ,代 中额
加了 向链 尾节点 针last。

2.2.3 数组VS链表
表 基本 我 了。数组和 表 于线

数据结构, 哪一个更好呢?

数据结构 有绝对 好与坏,数组和 表各有 。

下面我 结了数组和 表 关操作 能,我们来对比一下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
从表格可以 出,数组 优 在于能够 定位

素,对于 操作多、 操作少 场景来 , 数组更 一些。

反地, 表 优 在于能够 地进行插 和 除

操作,如果需要在 频繁插 、 除 素, 表更 一些。

关于 表 我们 介绍 这里,咱们下一节

见!

2.3 栈和队列
2.3.1 物理结构和逻辑结构

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
什么 存 物 结 呢?

果把 结 比 活 人,那么物 结 人 肉和 ,
得见, 得 , 在在。 我 刚刚 过 组和链 ,都 存中

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在在 存 结 。

而在物 人 之上,还存在 人 思想和精 , 不见、 不


。 过 影《阿凡达》 吗? 主 思想意 从一个 弱残 人类 上
植 一个 猛 蓝 肤 星人 上, 然承载思想意 肉 变
了, 人 却 唯一 。

果把物 面 人 比 存 物 结 ,那么精 面 人
则 存 逻辑结构。逻辑结 抽 概念, 于物 结 而存
在。

下面我 两个 结 :栈和队 。这两者都 于逻辑结


, 物 现 可以 组,也可以 链 成。

在后面 章节中,我 会 习 二叉 ,这也 一 逻辑结 。同


地,二叉 也可以 托于物 上 组或链 现。

2.3.2 什么是栈
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
弄 什么 栈,我 需 先举一个 活中 。

有一个又细又长 圆 ,圆 一 闭,另一 开口。往圆 里


乒乓 ,先 靠近圆 底部,后 靠近圆 口。

那么, 想取 这些乒乓 ,则只能 照和 顺序 反 顺序 取,


先取 后 , 取 先 ,而不可能把最里面最先 乒乓 优
先取 。

栈(stack) 一 线性 结 , 一个上图所 乒乓
圆 器,栈中 素只能先入后出(First In Last Out, 称FILO)。最
进 素存 位置叫 栈底 (bottom),最后进 素存 位置
叫 栈顶(top)。
栈这 结 可以 组 现,也可以 链 现。

栈 组 现 下。

栈 链 现 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么,栈可以进行哪些操作呢?

栈 最基本操作是 栈和出栈,下面 我们来 一

2.3.3 栈的基本操作
1. 入栈

栈 (push) 把新 素 栈中,只 从栈顶一


素,新 素 位置 会成为新 栈顶。

这里我 以 组 现为 。

2. 出栈

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
栈 (pop) 把 素从栈中弹 ,只有栈顶 素才 栈,
栈 素 前一个 素 会成为新 栈顶。

这里我 以 组 现为 。

于栈 代 现比较 单,这里 不 代 了,有兴


者可以自己 。

小 ,你 , 栈和出栈操作,时间复杂度分 是

多少?

栈和出栈只会 响 最后一个 素,不涉及其他

素 整体 动, 以无 是以数组还是以 表实现, 栈、出栈 时


间复杂度 是 O(1)。

2.3.4 什么是队列
弄 什么 队 ,我 同 可以 一个 活中 。

公 上有一 单行隧道,所有通过隧道 辆只 从隧道 口


,从隧道 口 ,不 逆行。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
因此, 想 辆 隧道,只能 照 隧道 顺序,先
辆先 ,后 辆后 , 辆都 法 过 前面 辆 前

队 (queue) 一 线性 结 , 特征和行 辆 单行隧道很


。不同于栈 先 后 ,队 中 先入先出(First In First
素只能
Out, 称 FIFO)。队 口 叫 队头(front),队 口 叫 队
尾(rear)。
与栈类 ,队 这 结 可以 组 现,也可以 链
现。

组 现 ,为了 队 ,把队尾位置 为最后 队 素


下一个位置。
队 组 现 下。

队 链 现 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么,队 可以进行哪些操作呢?

和栈操作 对应,队 最基本操作是 队和出队。

2.3.5 队列的基本操作
于链 现 式,队 队、 队 和栈 同 异 。 于
组 现 式 ,队 队和 队 有了一些有 变化。怎么有
呢?我 后面会 。

1. 入

队(enqueue) 把新 素 队 中,只 在队尾 位置


素,新 素 下一个位置 会成为新 队尾。

2. 出

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
队 (dequeue) 把 素 队 ,只 在队 一
素, 队 素 后一个 素 会成为新 队 。

如果 这样不断出队,队头左 空间失 作 , 队

容 不是越来越小了?例如 下面这样。

问 好,这正是我后面要 。 数组实现 队

可以 循环队列 方 来维 队 容 定。

循环队 什么意思呢? 我 下面 。

设一个队 经过反 队和 队 ,还剩下2个 素,在“物


”上分 于 组 末尾位置。这 又有一个新 素 队。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在 组不 扩 前 下, 新 素 队并 新 队尾位置呢?
我 可以 已 队 素 下 间, 队尾 针重新 回 组 位。

这 一 , 个队 素 “循环”起 了。在物 存 上,队尾


位置也可以在队 之前。当 有 素 队 , 其 组 位,队尾
针继续后 即可。

一 (队尾下标+1)%数组长度 = 队头下标 ,代 此队 真 已经
满了。需 注意 ,队尾 针 向 位置永远 1位,所以队 最 量
比 组长度 1。

这 是 环队 ,下面 我们来 一 它 代

实现。

1. private int[] array;


2. private int front;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. private int rear;
4.

5. public MyQueue(int capacity){


6. this.array = new int[capacity];
7. }

8.
9. /**
10. * 队

11. * @param element 队 素


12. */
13. public void enQueue(int element) throws Exception {

14. if((rear+1)%array.length == front){


15. throw new Exception(" 队 已满!");

16. }
17. array[rear] = element;
18. rear =(rear+1)%array.length;

19. }
20.
21. /**

22. * 队
23. */
24. public int deQueue() throws Exception {

25. if(rear == front){


26. throw new Exception(" 队 已 !");

27. }
28. int deQueueElement = array[front];
29. front =(front+1)%array.length;

30. return deQueueElement;


31. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
32.
33. /**

34. * 输 队
35. */
36. public void output(){

37. for(int i=front; i!=rear; i=(i+1)%array.length){


38. System.out.println(array[i]);

39. }
40. }
41.

42. public static void main(String[] args) throws Exception {


43. MyQueue myQueue = new MyQueue(6);
44. myQueue.enQueue(3);

45. myQueue.enQueue(5);
46. myQueue.enQueue(6);
47. myQueue.enQueue(8);

48. myQueue.enQueue(1);
49. myQueue.deQueue();

50. myQueue.deQueue();
51. myQueue.deQueue();
52. myQueue.enQueue(2);

53. myQueue.enQueue(4);
54. myQueue.enQueue(9);
55. myQueue.output();

56. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
环队 不但 分 了数组 空间,还 了数组

素整体 动 麻 ,还 是有 呢!至于 队和出队 时间复


杂度,也同样是 O(1)吧?

完 正 !下面我们来 一 栈和队 可以应

在哪些地方。

2.3.6 栈和队列的应用
1. 栈的 用

栈 输 顺序和输 顺序 反,所以栈通 于 “历史” 回溯,也


逆流而上追溯“历史”。

现递归 逻辑, 可以 栈 代替,因为栈可以回溯 法


链。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
栈还有一个著名 应 场景 面包 导航, 户在浏览页面 可以
松地回溯 上一级或 上一级页面。

2. 列的 用

队 输 顺序和输 顺序 同,所以队 通 于 “历史” 回


,也 照“历史”顺序,把“历史”重演一遍。

在 线 中,争 公平锁 待队 , 照访问顺序 决 线


在队 中 次序 。

网络爬 现网 抓取 ,也 把待抓取 网 URL存 队 中,


照存 队 顺序 次抓取和 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. 端 列

么,有 有办 栈和队 结 起来,既

可以 出,也可以 后出呢?

还 有,这 数据结构叫作 双端队列(deque)。

双 队 这 结 ,可以 综合了栈和队 优点, 双 队


,从队 一 可以 队或 队,从队尾一 也可以 队或 队。

有关双 队 细节,感兴 者可以 阅 了 。

4. 优先 列

还有一 队 , 遵循 不 先 先 ,而 优先级最 , 先
队。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 队 叫作 优先队列。

优先队 已经不 于线性 结 范 了, 于二叉 现


。关于优先队 原 和 情 ,我 会在下一章进行详细介绍。

好了,关于栈和队 我们 介绍 这里,下一

节 见!

22.4 神奇的散列表
2.4.1 为什么需要散列表

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
起 习英 , 灰上 可没有那么丰 习 源和工具。当 有
一款很流行 典, 伙 遇 不会 单 ,只 输
典里, 可以 中 含义。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当 英 老 强烈反 这 工具,因为 典 中
太有限,而 统 纸 典可以 单 含义、 性、 句 。

,同 还 向于 典。因为 典 在太 了,
只 输 单 ,一 间 可以得 结果,而不需 纸 典那 烦
地进行人工 找。

在我 序世 里,往往也需 在 存中存 这 一个“ 典”,


我 进行 和统 。

开发一个 系统,需 有通过输 号快速 应


名 功能。这里不必每次都去 库,而可以在 存中建 一个缓存
,这 可以 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 需 统 一本英 书里 些单 现 频 , 需 遍历 本
书 ,把这些单 现 次 录在 存中。

因为这些需求,一个重 结 了,这个 结 叫 散列
表。
也叫 哈希表(hash table),这 结 了 键(Key)和
值(Value) 映 关系。只 给 一个Key, 可以 找 所匹配
Value, 间 度 近于 O(1)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么,散 表是如何根据Key来 它 匹

Value呢?

这 是我下面要 散 表 基本 。

2.4.2 哈希函数
小 ,在咱们之 学 几个数据结构中, 查

效 最高?

当 是数组喽,数组可以根据下标,进行 素 随

机访问。

,散 表在本 上也是一个数组。

可是数组只能根据下标, a[0]、a[1]、a[2]、a[3]、

a[4]这样来访问,而散 表 Key则是以字符串类型为主 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
例如以学 学 作为Key, 002123,查 李四;

者以 为Key, by,查 数字46……

以我们需要一个“中 站”, 某 方 , Key

和数组下标进行 换。这个中 站 叫作 哈希函数。

这个所 哈 怎么 现 呢?

在不同 中,哈 现 式 不一 。这里以Java


集合HashMap为 , 一 哈 在Java中 现。

在 Java 及 面向 中,每一个 都有 于自己


hashcode,这个hashcode 区分不同 重 标 。 自 类
什么, hashcode都 一个 变量。

然都 变量,想 化成 组 下标也 不难 现了。最 单


化 式 什么呢? 照 组长度进行取模运 。

index = HashCode (Key) % Array.length

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
际上,JDK(Java Development Kit,Java 件开发工具包)中
哈 并没有 采 取模运 ,而 了位运 式 优化性
能。不过在这里可以 且 单 成取模 。

通过哈 ,我 可以把字 串或其 类 Key, 化成 组 下标


index。

给 一个长度为8 组,则当

key=001121 ,

index = HashCode ("001121") % Array.length =


1420036703 % 8 = 7

而当key=this ,

index = HashCode ("this") % Array.length = 3559070 %


8 = 6

2.4.3 散列表的读写操作
有了哈 , 可以在 中进行 了。

1. 写操作(put)

在 中 新 键 (在JDK中叫 Entry)。

hashMap.put("002931", " 五 ") , 意 思 一 组 Key 为


002931、Value为 五 键 。

具 该怎么 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1步,通过哈 ,把Key 化成 组下标5。

2步, 果 组下标5 应 位置没有 素, 把这个Entry 组


下标5 位置。

, 于 组 长度 有限 ,当 Entry ,不同
Key通过哈 获得 下标有可能 同 。 002936这个Key 应
组下标 2;002947这个Key 应 组下标也 2。

这 情 , 叫 哈希冲突。

哎呀,哈 数“撞衫”了,这该 么办呢?

哈 冲突是无 ,既 不能 ,我们 要

办 来 决。 决哈 冲突 方 主要有两 ,一 是 放寻址 ,
一 是 表 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
开 寻址法 原 很 单,当一个Key通过哈 获得 应 组下标
已 占 ,我 可以“另 ”,寻找下一个 位置。

以上面 情 为 ,Entry6通过哈 得 下标2,该下标在 组中已


经有了其 素,那么 向后 动1位, 组下标3 位置 否有 。

很不巧,下标3也已经 占 ,那么 向后 动1位, 组下标4


位置 否有 。

幸运 , 组下标4 位置还没有 占 ,因此把Entry6存 组下标


4 位置。

这 开 寻址法 本思 。当然,在遇 哈 冲 ,寻址 式有


很 ,并不一 只 单地寻找当前 素 后一个 素,这里只 举一个
单 而已。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在Java中,ThreadLocal所 开 寻址法。

下 ,重点 一下 决哈 冲 另一 法——链 法。这 法


应 在了Java 集合类HashMap当中。

HashMap 组 每一个 素不 一个Entry ,还 一个链 节


点。每一个Entry 通过next 针 向 下一个Entry节点。当新
Entry映 与之冲 组位置 ,只需 应 链 中即可。

2. 操作(get)

了 ,我 一 。 通过给 Key,在
中 找 应 Value。

hashMap.get("002936"),意思 找Key为002936 Entry在


中所 应 。

具 该怎么 呢?下面以链 法为 一下。

1步,通过哈 ,把Key 化成 组下标2。

2步,找 组下标2所 应 素, 果这个 素 Key 002936,那


么 找 了; 果这个Key不 002936也没关系, 于 组 每个 素都与一
个链 应,我 可以顺 链 慢慢往下找, 能否找 与Key 匹配 节
点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在上图中, 先 节 点 Entry6 Key 002947 , 和 待 找 Key
002936不 。 位 链 下一个节点Entry1,发现Entry1 Key 002936
正 我 寻找 ,所以返回Entry1 Value即可。

3. (resize)

在 组 ,曾经介绍过 组 扩 。 然 于 组 现
,那么 也 涉及扩 问题。

先,什么 需 进行扩 呢?

当经过 次 素 , 达 一 饱和度 ,Key映 位置发 冲


概 会逐渐 。这 一 , 量 素拥 在 同 组下标位置,形成
很长 链 , 后续 和 性能都有很 影响。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 , 需 扩 长度,也 进行 扩容。
于JDK中 现类HashMap ,影响其扩 因素有两个。

Capacity,即HashMap 当前长度
LoadFactor,即HashMap 载因 , 为0.75f

量HashMap需 进行扩 件 下。

HashMap.Size >= Capacity×LoadFactor

散 表 容操作,具体 了什么事 呢?

容不是简 地 散 表 度 大,而是经 了下

面两个步骤。

1.扩容,创建一个新 Entry 组,长度 原 组 2 。


2.重新Hash ,遍历原Entry 组,把所有 Entry重新Hash 新 组中。
为什么 重新Hash呢?因为长度扩 以后,Hash 则也随之 变。

经过扩 ,原本拥 重新变得 ,原有 Entry也重新得 了


尽可能均匀 分配。

扩容前的HashMap。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
扩容后的HashMap。

以上 各 本 原 。 于HashMap 现代 比较
,这里 不 源 了,有兴 者 可 以 在 JDK 中 阅
HashMap类 源 。

需 注意 ,关于HashMap 现,JDK 8和以前 版本有 很 不


同。当 个Entry Hash 同一个 组下标位置 ,为了 升 和 找
,HashMap会把Entry 链 化为红 这 结 。建 者把两个
版本 现都 真地 一 ,这会 受 匪浅。

基本明白了,散 表还 是个 奇 数据结构!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
散 表可以 是数组和 表 结 ,它在算 中 应

普 ,是一 非 重要 数据结构,大家一定要 掌握哦。

这一次 这里,咱们下一章 见。

2.5 小结
什么 组

组 有限个 同类 变量所组成 有序集合, 物 存 式


顺序存 ,访问 式 随 访问。 下标 找 组 素 间 度
O(1),中间 、 除 组 素 间 度 O(n)。

什么 链

链 一 链式 结 , 若干节点组成,每个节点包含 向下一节
点 针。链 物 存 式 随 存 ,访问 式 顺序访问。 找链
节点 间 度 O(n),中间 、 除节点 间 度 O(1)。

什么 栈

栈 一 线性逻辑结 ,可以 组 现,也可以 链 现。栈包含


栈和 栈 ,遵循先 后 原则(FILO)。

什么 队

队 也 一 线性逻辑结 ,可以 组 现,也可以 链 现。队


包含 队和 队 ,遵循先 先 原则(FIFO)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
什么

也叫哈 , 存 Key-Value映 集合。 于 一个Key,


可以在 近O(1) 间 进行 。 通过哈 现Key和
组下标 ,通过开 寻址法和链 法 决哈 冲 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第3章 树
3.1 树和二叉树
3.1.1 什么是树

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
灰的“ ”是 样 的。

以 ,有 多 关系 不是简 线 关系,在实

际场景中, 存在 一对多, 至是多对多 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
其中 树和图 是典型 非线 数据结构,我们首 一

树 。

什么 呢?在现 活中有很 现 逻辑 。

前面 灰 “ ”, 一个“ ”。

业里 职级关系,也 一个“ ”。

除人与人之间 关系之 , 抽 东 也可以成为一个“ ”, 一


本书 录。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以上这些 有什么 同点呢?为什么可以称 为“ ”呢?

因为 都 自然 中 一 ,从同一个“ ”衍 “枝干”,
从每一个“枝干”衍 “枝干”,最后衍 “叶
”。

在 结 中, 义 下。

树(tree)是n(n≥0)个节点的有 。当n=0时,称为空树。 任 一
个 空树中,有 下特点。

1. 有且仅有一个特 的称为根的节点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2. 当n>1时,其余节点 分为m(m>0)个互不相交的有 ,每一个
本 又是一个树, 称为根的 树。

下面这张图, 一个标 结 。

在上图中,节点1 根节点(root) ;节点5、6、7、8 末 ,没


有“ ”, 称为叶子节点(leaf)。图中 线部分, 节点1 其中一
个 子树。
同 , 结 从 节点 叶 节点,分为不同 级。从一个节点
度 , 上下级和同级节点关系 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在上图中,节点4 上一级节点, 节点4 父节点(parent);从节点4衍
节点, 节点4 孩子节点(child);和节点4同级, 同一个父节点
衍 节点, 节点4 兄弟节点(sibling)。

最 级 , 称为 度或深度。 然,上图这个 度
4。

哎呀,这么多 概 还 是不好 。

这些 是树 基本术 ,多 几次 住啦。下面我们

来介绍一 典型 树—— 二叉树。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3.1.2 什么是二叉树
二叉 (binary tree) 一 特殊形式。二叉,顾名思义,这
每个节点 最多有2个孩子节点。注意,这里 最 有2个,也可能只有1个,
或者没有 节点。

二叉 结 图所 。

二叉 节点 两个 节点,一个 称为 左孩子(left child),一个 称


为右孩子(right child)。这两个 节点 顺序 固 , 人 左手
左手,右手 右手,不能 颠 或混淆。

此 ,二叉 还有两 特殊形式,一个叫 满二叉树,另一个叫 完全二


叉树。
什么 满二叉 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
一个二叉树的 有 叶 节点 , 且 有叶 节点
一 级上, 么 个树 是满二叉树。

单点 ,满二叉 每一个分 都 满 。

什么又 二叉 呢? 二叉 义很有意思。

一个有n个节点的二叉树, 级 编号,则 有节点的编号为从1到


n。 果 个树 有节点 样深 的满二叉树的编号为从1到n的节点位置相
,则 个二叉树为 全二叉树。

这个 义还真绕, 下图 很 了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在上图中,二叉 编号从1 12 12个节点,和前面满二叉 编号从1 12
节点位置 应。因此这个 二叉 。

二叉 件没有满二叉 那么苛刻:满二叉 求所有分 都 满


;而 二叉 只需 最后一个节点之前 节点都 即可。

么,二叉树在 存中是 样存 呢?

上一章咱们 ,数据结构可以 分为 结构和

结构。二叉树 于 结构,它可以 多 结构来表 。

二叉 可以 哪些物 存 结 达呢?

1. 储结构。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2. 数组。

我 分 二叉 这两 结 进行存 吧。

先 一 链式存储结构。

链式存 二叉 最 观 存 式。

上一章 过链 ,链 一 一 存 式,每一个链 节点拥有data变


量和一个 向下一节点 next 针。

而二叉 微 一些,一个节点最 可以 向左右两个 节点,所以


二叉 每一个节点包含3部分。

存 data变量
向左 left 针
向右 right 针

数组 存 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
组存 ,会 照 级顺序把二叉 节点 组中 应 位置
上。 果 一个节点 左 或右 缺,则 组 应位置也 。

为什么这 设 呢?因为这 可以 地在 组中 位二叉 节


点和父节点。

设一个父节点 下标 parent,那么 左 节点下标 2×parent


+ 1;右 节点下标 2×parent + 2。
反过 , 设一个左 节点 下标 leftChild,那么 父节点下标
(leftChild-1)/ 2。
节点4在 组中 下标 3,节点4 节点2 左 ,节点2 下标可
以 通过 得 。

节点2 下标 = (3-1)/2 = 1

然, 于一个 二叉 , 组 法 非 浪 间 。

什么 二叉 最适合 组 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 后面即 二叉 ,一 特殊 二叉 , 组 存

3.1.3 二叉树的应用
咱们 了这么多 ,二叉树 竟有什么 处呢?

二叉树 处有 多, 我们来具体 一 。

二叉 包含 特殊 形式,每一 形式都有自己 , 其最主


应 还在于进行 查找操作和维持相对顺序这两个 面。

1. 查

二叉 形结 很适合扮演索引 色。

这里我 介绍一 特殊 二叉 : 二叉查找树(binary search tree)。光


名字 可以 道,这 二叉 主 进行 找 。

二叉 找 在二叉 上 加了以下几个 件。

如果左子树不为空,则左子树上所有节点的值均小于根节点的值
如果右子树不为空,则右子树上所有节点的值均大于根节点的值
左、右子树也都是二叉查找树
下图 一个标 二叉 找 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
二叉 找 这些 件有什么 呢?当然 为了 找 。

找 为4 节点,步 下。

1. 访问 节点6,发现4<6。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2. 访问节点6 左 节点3,发现4>3。

3. 访问节点3 右 节点4,发现4=4,这正 找 节点。

于一个 节点分布相对均衡 二叉 找 , 果节点总 n,那么


索节点 间 度 O(logn),和 深度 一 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 靠比较 逐步 找 式,和二分 找 法非 。

2. 维 相

这一点仍然 从二叉 找 起。二叉 找 求左 于父节点,右


于父节点,正 这 了二叉 有序性。

因此二叉 找 还有另一个名字—— 二叉排序树(binary sort tree)。


新 节点,同 遵循二叉 序 原则。 新 素5, 于
5<6,5>3,5>4,所以5最终会 节点4 右 位置。

新 素10, 于10>6,10>8,10>9,所以10最终会 节点9


右 位置。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这一切 起 很顺 ,然而却隐 一个致命 问题。什么问题呢?下面
在二叉 找 中 次 9、8、7、6、5、4, 会 现什么结果。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哈哈,好好 一个二叉树,变成“ 脚”啦!

不只是外观 起来变 异了,查 节 时间复杂度

也 成了O(n)。

怎么 决这个问题呢?这 涉及二叉 自平衡了。二叉 自平 式


有 , 红 、AVL 、 。 于篇 有限,本书 不一一详细
了,感兴 者可以 一 关 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
除二叉 找 以 , 二叉堆也维 顺序。不过二叉 件
松一些,只 求父节点比 左右 都 ,这一点在后面 章节中我 会详
细 。

好了,有关树和二叉树 基本 ,我们 这里。

本节 容 于 方面, 有涉及代 。但是下

一节 二叉树 时,会涉及大 代 ,大家要 好 备哦!

3.2 二叉树的遍历
3.2.1 为什么要研究遍历

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当 们介绍数组、 时,为什么没有着 研究他们的 程 ?

二叉树的 又有什么特殊之 ?

在 序中,遍历本 一个线性 。所以遍历同 具有线性结


组或链 , 一件 而 举 事情。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
反观二叉 , 典 非线性 结 ,遍历 需 把非线性关联 节点
化成一个线性 序 ,以不同 式 遍历,遍历 序 顺序也不同。

那么,二叉 都有哪些遍历 式呢?

从节点之间位置关系 度 ,二叉 遍历分为4 。

1. 前 。

2. 中 。

3. 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. 。

从 观 度 ,二叉 遍历归结为两 类。

1. 深度优先遍历(前序遍历、中序遍历、后序遍历)。
2. 广度优先遍历( 序遍历)。
下面 具 一 这些不同 遍历 式。

3.2.2 深度优先遍历
深度优先和广度优先这两个概念不止 限于二叉 , 一 抽
法思想,决 了访问 些 结 顺序。在访问 、图,或其 一些
结 ,这两个概念 。

所 深度优先,顾名思义, 向于纵深,“一 扎 底” 访问
式。可能这 法有些抽 ,下面 通过二叉 前序遍历、中序遍历、后
序遍历, 一 深度优先 怎么回事吧。

1. 前

二叉 前序遍历,输 顺序 节点、左 、右 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
上图 一个二叉 前序遍历,每个节点左 序号代 该节点 输
顺序,详细步 下。

1. 先输 节点1。

2. 于 节点1存在左 ,输 左 节点2。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. 于节点2也存在左 ,输 左 节点4。

4. 节点4 没有左 ,也没有右 ,那么回 节点2,输 节点2 右


节点5。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5. 节点5 没有左 ,也没有右 ,那么回 节点1,输 节点1 右
节点3。

6. 节点3没有左 , 有右 ,因此输 节点3 右 节点6。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此为止,所有 节点都遍历输 毕。

2. 中

二叉 中序遍历,输 顺序 左 、 节点、右 。

上图 一个二叉 中序遍历,每个节点左 序号代 该节点 输


顺序,详细步 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. 先访问 节点 左 , 果这个左 还拥有左 ,则继续深
访问下去,一 找 不 有左 节点,并输 该节点。 然, 一个没
有左 节点 节点4。

2. 照中序遍历 次序, 下 输 节点4 父节点2。

3. 输 节点2 右 节点5。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. 以节点2为 左 已经输 毕,这 输 个二叉 节点
1。

5. 于节点3没有左 ,所以 输 节点1 右 节点3。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
6. 最后输 节点3 右 节点6。

此为止,所有 节点都遍历输 毕。

3.

二叉 后序遍历,输 顺序 左 、右 、 节点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
上图 一个二叉 后序遍历,每个节点左 序号代 该节点 输
顺序。

于二叉 后序遍历和前序、中序遍历 思想 致 同, 聪
者已经可以推测 分 步 ,这里 不 举细节了。

么,二叉树 、中 、后 代 么

呢?

二叉树 这3 方 , 归 可以非 简

地实现出来, 我们 一 代 。

1. /**

2. * 建二叉

3. * @param inputList 输 序

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. */

5. public static TreeNode createBinaryTree(LinkedList<Integer>

inputList){

6. TreeNode node = null;

7. if(inputList==null || inputList.isEmpty()){

8. return null;

9. }

10. Integer data = inputList.removeFirst();

11. if(data != null){


12. node = new TreeNode(data);

13. node.leftChild = createBinaryTree(inputList);

14. node.rightChild = createBinaryTree(inputList);

15. }

16. return node;

17. }

18.

19. /**

20. * 二叉 前序遍历
21. * @param node 二叉 节点

22. */

23. public static void preOrderTraveral(TreeNode node){

24. if(node == null){

25. return;

26. }

27. System.out.println(node.data);

28. preOrderTraveral(node.leftChild);

29. preOrderTraveral(node.rightChild);

30. }
31.

32. /**

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
33. * 二叉 中序遍历

34. * @param node 二叉 节点

35. */

36. public static void inOrderTraveral(TreeNode node){

37. if(node == null){


38. return;

39. }

40. inOrderTraveral(node.leftChild);

41. System.out.println(node.data);

42. inOrderTraveral(node.rightChild);

43. }

44.

45.

46. /**
47. * 二叉 后序遍历

48. * @param node 二叉 节点

49. */

50. public static void postOrderTraveral(TreeNode node){

51. if(node == null){

52. return;

53. }

54. postOrderTraveral(node.leftChild);

55. postOrderTraveral(node.rightChild);

56. System.out.println(node.data);
57. }

58.

59.

60. /**

61. * 二叉 节点

62. */

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
63. private static class TreeNode {
64. int data;

65. TreeNode leftChild;

66. TreeNode rightChild;

67.

68. TreeNode(int data) {

69. this.data = data;

70. }

71. }

72.
73. public static void main(String[] args) {

74. LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.

asList(new Integer[]{3,2,9,null,null,10,null,

null,8,null,4}));

75. TreeNode treeNode = createBinaryTree(inputList);

76. System.out.println(" 前序遍历:");

77. preOrderTraveral(treeNode);

78. System.out.println(" 中序遍历:");

79. inOrderTraveral(treeNode);

80. System.out.println(" 后序遍历:");


81. postOrderTraveral(treeNode);

82. }

二叉 递归 式 现前序、中序、后序遍历, 最为自然 式,因


此代 也非 单。

这3 遍历 式 区 , 输 执行位置不同:前序遍历 输 在
前,中序遍历 输 在中间,后序遍历 输 在最后。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
代 中 得注意 一点 二叉 建。二叉 建 法有很 ,这里
把一个线性 链 化成非线性 二叉 ,链 节点 顺序恰恰 二叉 前序
遍历 顺序。链 中 ,代 二叉 节点 左 或右 为 情 。

在代 main 中,通过{3,2,9,null,null,10,null,null,8,null,4}
这 一个线性序 , 建成 二叉 下。

除使 归以外,二叉树 度优 还能 其

他方 实现吗?

当 也可以 非 归 方 来实现,不 要 复杂一

些。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
绝 可以 递归 决 问题,其 都可以 另一 结 决,
这 结 栈。因为递归和栈都有回溯 特性。

助栈 现二叉 非递归遍历呢?下面以二叉 前序遍历为


, 一 具 过 。

1. 先遍历二叉 节点1, 栈中。

2. 遍历 节点1 左 节点2, 栈中。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. 遍历节点2 左 节点4, 栈中。

4. 节点4 没有左 ,也没有右 ,我 需 回溯 上一个节点2。


可 现在并不 递归 ,怎么回溯呢?

担心,栈已经存 了刚才遍历 径。 栈顶 素4 栈, 可以
重新访问节点2,得 节点2 右 节点5。

此 节点2已经没有 价 (已经访问过左 和右 ),节点2


栈,节点5 栈。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5. 节点5 没有左 ,也没有右 ,我 需 次回溯,一 回溯
节点1。所以 节点5 栈。

节点1 右 节点3,节点1 栈,节点3 栈。

6. 节点3 右 节点6,节点3 栈,节点6 栈。

7. 节点6 没有左 ,也没有右 ,所以节点6 栈。此 栈为 ,


遍历结 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
二叉树非 归 代 已经 好了, 我们来

一 。

1. /**

2. * 二叉 非递归前序遍历

3. * @param root 二叉 节点

4. */

5. public static void preOrderTraveralWithStack(TreeNode root){

6. Stack<TreeNode> stack = new Stack<TreeNode>();

7. TreeNode treeNode = root;

8. while(treeNode!=null || !stack.isEmpty()){

9. //迭代访问节点 左 ,并 栈
10. while (treeNode != null){

11. System.out.println(treeNode.data);

12. stack.push(treeNode);

13. treeNode = treeNode.leftChild;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
14. }

15. // 果节点没有左 ,则弹 栈顶节点,访问节点右

16. if(!stack.isEmpty()){

17. treeNode = stack.pop();

18. treeNode = treeNode.rightChild;

19. }
20. }

21. }

至于二叉 中序、后序遍历 非递归 现,思 和前序遍历 不太 ,


都 栈 进行回溯。各位 者 有兴 ,可以自己 代 现
一下。

3.2.3 广度优先遍历
果 深度优先遍历 在一个 向上“一 扎 底”,那么广度优先遍历
则恰恰 反:先在各个 向上各 1步, 在各个 向上 2步、 3
步……一 各个 向 部 。听起 有些抽 ,下面 我 通过二叉
层序遍历, 一 广度优先 怎么回事。

序遍历,顾名思义, 二叉 照从 节点 叶 节点 次关系,
一 一 横向遍历各个节点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
上图 一个二叉 序遍历,每个节点左 序号代 该节点 输
顺序。

可 ,二叉 同一 次 节点之间 没有 关联 , 现这 序
遍历呢?

这里同 需 助一个 结 辅助工 ,这个 结 队列。


详细遍历步 下。

1. 节点1进 队 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2. 节点1 队,输 节点1,并得 节点1 左 节点2、右 节点3。
节点2和节点3 队。

3. 节点2 队,输 节点2,并得 节点2 左 节点4、右 节点5。


节点4和节点5 队。

4. 节点3 队,输 节点3,并得 节点3 右 节点6。 节点6 队。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5. 节点4 队,输 节点4, 于节点4没有 节点,所以没有新节点
队。

6. 节点5 队,输 节点5, 于节点5同 没有 节点,所以没有新节


点 队。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
7. 节点6 队,输 节点6,节点6没有 节点,没有新节点 队。

此为止,所有 节点都遍历输 毕。

这个 起来有 ,代 么 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
代 不难 , 我们来 一 。

1. /**

2. * 二叉 序遍历

3. * @param root 二叉 节点

4. */

5. public static void levelOrderTraversal(TreeNode root){

6. Queue<TreeNode> queue = new LinkedList<TreeNode>();

7. queue.offer(root);

8. while(!queue.isEmpty()){

9. TreeNode node = queue.poll();


10. System.out.println(node.data);

11. if(node.leftChild != null){

12. queue.offer(node.leftChild);

13. }

14. if(node.rightChild != null){

15. queue.offer(node.rightChild);

16. }

17. }

18. }

基本上明白了,最后 问问,二叉树 可以

归方 来实现吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
可以,不 在 上有一 绕。我们 这个作为 考

题,聪明 者如果有兴 ,可以 一 归实现方 哦!

好了,有关二叉树 问题, 这里,咱们下一

节 见!

3.3 什么是二叉堆
3.3.1 初识二叉堆

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
什么 二叉 ?

二叉 本 上 一 二叉 , 分为两个类 。

1. 最 。

2. 最 。

什么 最 呢?最 一个父节点 ,都 大于或等于 左、右


节点 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
什么 最 呢?最 一个父节点 ,都 于或 于 左、右
节点 。

二叉 节点叫 堆顶。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
最 和最 特点决 了:最 顶 个 中 最大元素;最
顶 个 中 最小元素。

么,我们如何构 一个堆呢?

这 需要依靠二叉堆 自我 整了。

3.3.2 二叉堆的自我调整
于二叉 ,有 下几 。

1. 入节点。

2. 删 节点。

3. 构 二叉 。

这几 都 于 自我 。所 自我 , 把一个不 合
性 二叉 , 成一个 。下面 我 以最 为 , 一 二叉
进行自我 。

1. 入节点

当二叉 节点 , 位置 二叉 最后一个位置。
一个新节点, 0。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 ,新节点 父节点5比0 , 然不 合最 性 。于 新节
点“上浮”,和父节点交 位置。

继续 节点0和父节点3 比较,因为0 于3,则 新节点继续“上浮”。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
继续比较,最终新节点0“上浮” 了 顶位置。

2. 删 节点

二叉 除节点 过 和 节点 过 正 反,所 除 于 顶
节点。 除最 顶节点1。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 ,为了继续维 二叉 结 ,我 把 最后一个节点10临
原本 顶 位置。

下 , 顶位置 节点10和 左、右 进行比较, 果左、


右 节点中最 一个( 然 节点2)比节点10 ,那么 节点10“下
沉”。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
继续 节点10和 左、右 比较,左、右 中最 节点7,
于10 于7, 节点10继续“下沉”。

这 一 ,二叉 重新得 了 。

3. 构 二叉

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
建二叉 ,也 把一个 序 二叉 为二叉 ,本
所有非叶子节点依次“下沉”。
下面举一个 序 二叉 , 下图所 。

先,从最后一个非叶 节点开 ,也 从节点10开 。 果节点10


于 左、右 节点中最 一个,则节点10“下沉”。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下 节点3, 果节点3 于 左、右 节点中最 一个,则节
点3“下沉”。

然后 节点1, 果节点1 于 左、右 节点中最 一个,则节点


1“下沉”。事 上节点1 于 左、右 ,所以不 变。

下 节点7, 果节点7 于 左、右 节点中最 一个,则节


点7“下沉”。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
节点7继续比较,继续“下沉”。

经过上述几 比较和“下沉” ,最终每一节点都 于 左、右


节点,一个 序 二叉 建成了一个最 。

小 ,你来 考一下,堆 插 、 除、构 操作 时

间复杂度各是多少?

堆 插 操作是 一节 “上 ”,堆 除操作

是 一节 “下 ”,这两个操作 平均交换次数 是堆高度 一


, 以时间复杂度是O(logn)。至于堆 构 ,需要 有非 子节
依次“下 ”, 以我 时间复杂度应该是O(nlogn)吧?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
关于堆 插 和 除操作,你 有 ,时间复杂度

实 是 O(logn) 。 但 构 堆 时间复杂度却 不 是 O(nlogn) , 而 是


O(n)。这涉及数学推导 ,有兴 ,你可以自己 一下哦。

这二叉堆还 有 , 么 么 代 来实现呢?

3.3.3 二叉堆的代码实现
在 代 之前,我 还需 一点:二叉 然 一个 二叉 ,
存 式并不 链式存 ,而 顺序存 。 句 ,二叉 所有节
点都存 在 组中。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在 组中,在没有左、右 针 情 下, 位一个父节点 左 和
右 呢?

上图那 ,可以 靠 组下标 。

设父节点 下标 parent,那么 左 下标 2×parent+1;右


下标 2×parent+2。
上面 中,节点6包含9和10两个 节点,节点6在 组中 下
标 3,节点9在 组中 下标 7,节点10在 组中 下标 8。

那么,

7 = 3×2+1,

8 = 3×2+2,

刚 合 律。

有了这个前 ,下面 代 了。

1. /**

2. * “上浮”
3. * @param array 待

4. */
5. public static void upAdjust(int[] array) {

6. int childIndex = array.length-1;


7. int parentIndex = (childIndex-1)/2;
8. // temp 存 叶 节点 , 于最后

9. int temp = array[childIndex];


10. while (childIndex > 0 && temp < array[parentIndex])

11. {

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
12. // 须真正交 ,单向 即可
13. array[childIndex] = array[parentIndex];

14. childIndex = parentIndex;


15. parentIndex = (parentIndex-1) / 2;

16. }
17. array[childIndex] = temp;

18. }
19.

20.
21. /**
22. * “下沉”

23. * @param array 待


24. * @param parentIndex “下沉” 父节点

25. * @param length 有


26. */

27. public static void downAdjust(int[] array, int parentIndex,


int length) {

28. // temp 存父节点 , 于最后


29. int temp = array[parentIndex];
30. int childIndex = 2 * parentIndex + 1;

31. while (childIndex < length) {


32. // 果有右 ,且右 于左 ,则 位 右

33. if (childIndex + 1 < length && array[childIndex + 1] <


array[childIndex]) {

34. childIndex++;
35. }

36. // 果父节点 于 一个 ,则
37. if (temp <= array[childIndex])

38. break;
39. // 须真正交 ,单向 即可

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
40. array[parentIndex] = array[childIndex];

41. parentIndex = childIndex;


42. childIndex = 2 * childIndex + 1;

43. }
44. array[parentIndex] = temp;
45. }

46.
47. /**

48. * 建
49. * @param array 待

50. */
51. public static void buildHeap(int[] array) {

52. // 从最后一个非叶 节点开 , 次 “下沉”


53. for (int i = (array.length-2)/2; i>=0; i--) {

54. downAdjust(array, i, array.length);


55. }
56. }

57.
58. public static void main(String[] args) {

59. int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};


60. upAdjust(array);

61. System.out.println(Arrays.toString(array));
62.

63. array = new int[] {7,1,3,10,5,2,8,9,6};


64. buildHeap(array);
65. System.out.println(Arrays.toString(array));

66. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
代 中有一个优化 点, 在父节点和 节点 连续交 ,并不一
真 交 ,只需 先把交 一 存 temp变量, 单向 ,循环结
后, 把temp 存 交 后 最终位置即可。

咱们 了这么多关于二叉堆 ,二叉堆 竟有什

么 处呢?

二叉堆是实现 堆排序及优先队列 基 。关于这两者,

我们会在后续 章节中详细介绍。

3.4 什么是优先队列
3.4.1 优先队列的特点

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
队 特点 什么?

在之前 章节中已经 过,队 特点 先进先出(FIFO)。


入 列, 新元素置于 :

出 列, 元素最先 移出:

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
那么,优先队 又 什么 呢?

优先队 不 遵循先 先 原则,而 分为两 情 。

最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队
最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队
有一个最 优先队 ,其中 最 素 8,那么 然8并不 队
素, 队 仍然 素8 先 队。

现以上需求, 线性 结 并非不能 现, 间 度较

哎呀, 该 么办呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
,这时 我们 二叉堆 上 场了。

3.4.2 优先队列的实现
先 回顾一下二叉 特性。

1. 最 的 是整个 中的最 元素。

2. 最 的 是整个 中的最 元素。

因此,可以 最 现最 优先队 ,这 ,每一次 队


,每一次 队 除 顶节点。

入队操作具 步 下。

1. 新节点5。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2. 新节点5“上浮” 合适位置。

出队操作具 步 下。

1. 原 顶节点10 队。

2. 把最后一个节点1替 顶位置。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. 节点1“下沉”,节点9成为新 顶。

小 ,你 这个优 队 队和出队操作,时间复

杂度分 是多少?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
二叉堆节 “上 ”和“下 ” 时间复杂度 是

O(logn), 以优 队 队和出队 时间复杂度也是 O(logn)!

,下面 我们来 一 代 实现。

1. private int[] array;

2. private int size;


3. public PriorityQueue(){

4. //队 长度为32
5. array = new int[32];

6. }
7. /**
8. * 队

9. * @param key 队 素
10. */

11. public void enQueue(int key) {


12. //队 长度 范围,扩

13. if(size >= array.length){


14. resize();

15. }
16. array[size++] = key;
17. upAdjust();

18. }
19.

20. /**

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
21. * 队
22. */

23. public int deQueue() throws Exception {


24. if(size <= 0){
25. throw new Exception("the queue is empty !");

26. }
27. //获取 顶 素

28. int head = array[0];


29. // 最后一个 素 动 顶

30. array[0] = array[--size];


31. downAdjust();

32. return head;


33. }
34. /**

35. * “上浮”
36. */

37. private void upAdjust() {


38. int childIndex = size-1;

39. int parentIndex = (childIndex-1)/2;


40. // temp 存 叶 节点 , 于最后

41. int temp = array[childIndex];


42. while (childIndex > 0 && temp > array[parentIndex])
43. {

44. // 须真正交 ,单向 即可


45. array[childIndex] = array[parentIndex];

46. childIndex = parentIndex;


47. parentIndex = parentIndex / 2;

48. }
49. array[childIndex] = temp;

50. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
51. /**
52. * “下沉”

53. */
54. private void downAdjust() {

55. // temp 存父节点 , 于最后


56. int parentIndex = 0;

57. int temp = array[parentIndex];


58. int childIndex = 1;

59. while (childIndex < size) {


60. // 果有右 ,且右 于左 ,则 位 右
61. if (childIndex + 1 < size && array[childIndex + 1] >

array[childIndex]) {
62. childIndex++;

63. }
64. // 果父节点 于 一个 ,

65. if (temp >= array[childIndex])


66. break;

67. // 须真正交 ,单向 即可


68. array[parentIndex] = array[childIndex];
69. parentIndex = childIndex;

70. childIndex = 2 * childIndex + 1;


71. }

72. array[parentIndex] = temp;


73. }

74.
75. /**

76. * 队 扩
77. */
78. private void resize() {

79. //队 量翻

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
80. int newSize = this.size * 2;

81. this.array = Arrays.copyOf(this.array, newSize);


82. }

83.
84. public static void main(String[] args) throws Exception {
85. PriorityQueue priorityQueue = new PriorityQueue();

86. priorityQueue.enQueue(3);
87. priorityQueue.enQueue(5);

88. priorityQueue.enQueue(10);
89. priorityQueue.enQueue(2);

90. priorityQueue.enQueue(7);
91. System.out.println(" 队 素:" + priorityQueue.deQueue());

92. System.out.println(" 队 素:" + priorityQueue.deQueue());


93. }

上述代 采 组 存 二叉 素,因此当 素 量 过 组长度


,需 进行扩 扩 组长度。

好了,关于优 队 我们 介绍 这里,下一章 见!

3.5 小结
什么

n个节点 有限集,有且 有一个特 称为 节点。当n>1 ,其


节点可分为m个互不 交 有限集,每一个集合本 又 一个 ,并称为

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
什么 二叉

二叉 一 特殊形式,每一个节点最 有两个 节点。二叉 包


含 二叉 和满二叉 两 特殊形式。

二叉 遍历 式有几

遍历节点之间 关系,可以分为前序遍历、中序遍历、后序遍历、
序遍历这4 式;从 观 度 分,可以 分为深度优先遍历和广度优
先遍历两 类。

什么 二叉

二叉 一 特殊 二叉 ,分为最 和最 。

在最 中, 一个父节点 ,都 于或 于 左、右 节点

在最 中, 一个父节点 ,都 于或 于 左、右 节点

什么 优先队

优先队 分为最 优先队 和最 优先队 。

在最 优先队 中, 队顺序 ,当前最 素都会优先 队,


这 于最 现 。

在最 优先队 中, 队顺序 ,当前最 素都会优先 队,


这 于最 现 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第4章 排序算法
4.1 引言
在 活中,我 不开 序。 上 育 ,同 会 照 顺序
进行 队;又 每一场考 后,老 会 照考 成绩 名次。

在编 世 中,应 序 场景也比比 。 当开发一个


系统 ,需 照 号从 进行 序;当开发一个 商平台 ,需
把同类商品 价 从低 进行 序;当开发一款游戏 ,需 照游戏
得分从 进行 序, 名 一 本场比 MVP, 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此可见, 序 不在。

序 单, 背后却隐 法和思想。那么
序 法都有哪些呢?

间 度 不同,主流 序 法可以分为3 类。

1. 时 杂 为O(n2)的 算法

泡 序
选择 序

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
序( 序比较特殊, 性能 优于O(n2), 又比不上
O(nlogn), 且把 归 本类)

2. 时 杂 为O(nlogn)的 算法

快速 序
归并 序

3. 时 杂 为线 的 算法



当然,以上 举 只 最主流 序 法,在 法 还存在 五花


八门 序, 有些 于 统 序变形而 ;有些则 脑洞 开, 尾
酒 序、 序、 眠 序 。

此 , 序 法还可以 其 性, 分为 稳定排序和不稳定排序。
即 果 同 素在 序后仍然 序前 顺序,则这 序
法 序; 果 同 素在 序后打乱了 序前 顺序,则这
序 法 不 序。 下面 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在 场景中, 同 素 先 后 所 。 在 些场景
下, 同 素必须 原有 顺序。

于篇 所限,我 法把所有 序 法都一一详细 述。在本章


中, 只 述几个具有代 性 序 法: 泡 序、快速 序、 序、
序、 序。

下面 领 进 有 序世 了, “坐 扶 ”!

4.2 什么是冒泡排序
4.2.1 初识冒泡排序

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
什么 泡 序?

泡 序 英 bubble sort, 一 交换排序。


一 都喝过汽水,汽水中 有 气泡哗啦哗啦飘 上面
。这 因为组成 气泡 二氧化 比水 ,所以 气泡可以一点一点地向
上浮动。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
而 泡 序之所以叫 泡 序,正 因为这 序 法 每一个 素都
可以 气泡一 , 自 ,一点一点地向 组 一 动。

具 动呢? 我 先 一个 。

有8个 字组成一个 序 {5,8,6,3,9,2,1,7}, 照从


顺序 其进行 序。

照 泡 序把相邻的元素两两比较,当一个元素大
思想,我
于右侧相邻元素时,交换它们的位置;当一个元素小于或等于右侧相邻元
素时,位置不变。详细过 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 一 , 素9 为 中最 素, 汽水里 气泡一
,“漂” 了最右 。

这 , 泡 序 1 结 了。 最右 素9 位置可以 为
一个有序区 ,有序区 前只有1个 素。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面, 我 进行 2 序。

2 序结 后, 右 有序区有了2个 素,顺序 下。

后续 交 细节,这里 不详细 述了, 3 7 状态 下。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此为止,所有 素都 有序 了,这 泡 序 思 。

泡 序 一 稳定排序, 素并不会打乱原本 顺序。 于


该 序 法 每一 都 遍历所有 素,总 遍历( 元素数量-1) ,所以
平均 间 度 O(n2)。

OK, 排 我大概明白了, 么, 么

代 来实现呢?

始 排 代 我 了一下,你来 一 。

冒泡排序第1版代 下:

1. public static void sort(int array[])

2. {

3. for(int i = 0; i < array.length - 1; i++)

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. {

5. for(int j = 0; j < array.length - i - 1; j++)

6. {

7. int tmp = 0;

8. if(array[j] > array[j+1])

9. {
10. tmp = array[j];

11. array[j] = array[j+1];

12. array[j+1] = tmp;

13. }

14. }

15. }

16. }

17.

18. public static void main(String[] args){

19. int[] array = new int[]{5,8,6,3,9,2,1,7};

20. sort(array);

21. System.out.println(Arrays.toString(array));

22. }

代 非 单, 双循环进行 序。 部循环控制所有 回合, 部


循环 现每一 泡 ,先进行 素比较, 进行 素交 。

来如此, 排 代 不难 呢。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这只是 排 始实现,还存在 大 优 空间

呢。

4.2.2 冒泡排序的优化
原 泡 序有哪些可以优化 点呢?

我 回顾一下刚才 述 序细节,仍然以{5,8,6,3,9,2,1,7}这个
为 ,当 序 法分 执行 6、 7 , 状态 下。

很 可以 ,经过 6 序后, 个 已然 有序 了。可


序 法仍然 业业地继续执行了 7 序。

在这 情 下, 果能 已经有序,并 标 ,那么剩下
几 序 不必执行了,可以 前结 工 。

冒泡排序第2版代 下:

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. public static void sort(int array[])

2. {

3. for(int i = 0; i < array.length - 1; i++)

4. {

5. //有序标 ,每一 都 true

6. boolean isSorted = true;

7. for(int j = 0; j < array.length - i - 1; j++)

8. {

9. int tmp = 0;

10. if(array[j] > array[j+1])

11. {

12. tmp = array[j];

13. array[j] = array[j+1];

14. array[j+1] = tmp;

15. //因为有 素进行交 ,所以不 有序 ,标 变为false

16. isSorted = false;


17. }

18. }

19. if(isSorted){

20. break;
21. }

22. }
23. }

24.
25. public static void main(String[] args){
26. int[] array = new int[]{5,8,6,3,9,2,1,7};

27. sort(array);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
28. System.out.println(Arrays.toString(array));
29. }

与 1版代 比, 2版代 了 动, 变量isSorted


为标 。 果在本 序中, 素有交 ,则 序; 果没有
素交 ,则 已然有序,然后 循环。

不 呀, 来 排 还可以这样优 。

这只是 排 优 第一步,我们还可以进一步来

提 它 能。

为了 问题,这次以一个新 为 。

这个 特点 前半部分 素(3、4、2、1) 序,后半部分


素(5、6、7、8) 升序 ,并且后半部分 素中 最 也 于前半部
分 素 最 。

下面 照 泡 序 思 进行 序, 一 具 果。

第1

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
素4和5比较,发现4 于5,所以位置不变。

素5和6比较,发现5 于6,所以位置不变。

素6和7比较,发现6 于7,所以位置不变。

素7和8比较,发现7 于8,所以位置不变。

1 结 , 有序区包含1个 素。

第2

素3和2比较,发现3 于2,所以3和2交 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
素3和4比较,发现3 于4,所以位置不变。

素4和5比较,发现4 于5,所以位置不变。

素5和6比较,发现5 于6,所位位置不变。

素6和7比较,发现6 于7,所以位置不变。

素7和8比较,发现7 于8,所以位置不变。

2 结 , 有序区包含2个 素。

小 ,你 现其中 问题了吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
其实 面 多 素已经是有 了,可是每一

还是白白地比 了 多次。

,这正是 排 中另一个需要优 。

这个问题 关键点在于 有序区 。

照现有 逻辑,有序区 长度和 序 。 1


序过后 有序区长度 1, 2 序过后 有序区长度 2 ……

际上, 真正 有序区可能会 于这个长度, 上述 中在 2


序 ,后面 5个 素 际上都已经 于有序区了。因此后面 次
素比较 没有意义 。

那么,该 避 这 情 呢?我 可以在每一 序后, 录下 最


后一次 素交 位置,该位置即为 序 边 , 往后 有序区
了。

冒泡排序第3版代 下:

1. public static void sort(int array[])

2. {
3. // 录最后一次交 位置

4. int lastExchangeIndex = 0;
5. // 序 边 ,每次比较只需 比 这里为止

6. int sortBorder = array.length - 1;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
7. for(int i = 0; i < array.length - 1; i++)

8. {
9. //有序标 ,每一 都 true

10. boolean isSorted = true;


11. for(int j = 0; j < sortBorder; j++)

12. {
13. int tmp = 0;

14. if(array[j] > array[j+1])


15. {

16. tmp = array[j];


17. array[j] = array[j+1];

18. array[j+1] = tmp;


19. // 因为有 素进行交 ,所以不 有序 ,标 变为false
20. isSorted = false;

21. // 新为最后一次交 素 位置
22. lastExchangeIndex = j;

23. }
24. }

25. sortBorder = lastExchangeIndex;


26. if(isSorted){

27. break;
28. }

29. }
30. }

31.
32. public static void main(String[] args){

33. int[] array = new int[]{3,4,2,1,5,6,7,8};


34. sort(array);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
35. System.out.println(Arrays.toString(array));
36. }

在 3版代 中,sortBorder 序 边 。在每一 序过


中, 于sortBorder之后 素 不需 进行比较了,肯 有序 。

是学 了 多 , 不 排 可以 出这

么多花样!

其实这仍 不是最优 ,还有一 排 算 叫作 鸡尾


酒排序,是基于 排 一 级排 。

4.2.3 鸡尾酒排序
泡 序 每一个 素都可以 气泡一 , 自 ,一点一点
地向 组 一 动。 法 每一 都 从左到右来比较元素,进行单向
的位置交换的。
那么 尾酒 序 了怎 优化呢?

尾酒 序 素比较和交 过 双向 。

下面举一个 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
8个 字组成一个 序 {2,3,4,5,6,7,8,1}, 其进行从
序。

果 照 泡 序 思想, 序过 下。

素2、3、4、5、6、7、8已经是有 了,只有

素1 位置不对,却还要进行7 排 ,这也太“ ”了吧!

,鸡 排 正是要 决这个问题 。

那么 尾酒 序 什么 呢? 我 一 详细过 。

第1 ( 冒泡 一样,8 1交 )

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第2

此 开 不一 了,我 反过 从右往左比较并进行交 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第3 (虽然 上 经有 ,但是流程 没有结束)

在 尾酒 序 3 ,需 重新从左向右比较并进行交 。

1和2比较,位置不变;2和3比较,位置不变;3和4比较,位置不变……
6和7比较,位置不变。

没有 素位置进行交 , 已经有序, 序结 。

这 尾酒 序 思 。 序过 钟 一 , 1 从左 右,
2 从右 左, 3 从左 右……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哇,本来要 7 排 场景, 3 决了,鸡

排 可 是巧妙 算 !

实挺巧妙 , 我们来 一下它 代 实现吧。

1. public static void sort(int array[])

2. {
3. int tmp = 0;

4. for(int i=0; i<array.length/2; i++)


5. {

6. //有序标 ,每一 都 true


7. boolean isSorted = true;

8. // ,从左向右比较和交
9. for(int j=i; j<array.length-i-1; j++)

10. {
11. if(array[j] > array[j+1])
12. {

13. tmp = array[j];


14. array[j] = array[j+1];

15. array[j+1] = tmp;


16. // 有 素交 ,所以不 有序 ,标 变为false

17. isSorted = false;


18. }

19. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
20. if(isSorted){

21. break;
22. }

// 在 之前, isSorted重新标 为true


23. isSorted = true;
24. // ,从右向左比较和交

25. for(int j=array.length-i-1; j>i; j--)


26. {

27. if(array[j] < array[j-1])


28. {

29. tmp = array[j];


30. array[j] = array[j-1];

31. array[j-1] = tmp;


32. // 因为有 素进行交 ,所以不 有序 ,标 变为false

33. isSorted = false;


34. }

35. }
36. if(isSorted){

37. break;
38. }

39. }
40. }

41.
42. public static void main(String[] args){
43. int[] array = new int[]{2,3,4,5,6,7,8,1};

44. sort(array);
45. System.out.println(Arrays.toString(array));

46. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这段代 尾酒 序 原 现。代 循环控制 所有 序
回合, 循环 包含2个 循环, 1个 循环从左向右比较并交 素,
2个 循环从右向左比较并交 素。

代 大致 明白了。之 排 时,有一

对有 区 优 ,鸡 排 是不是也能 呢?

当 喽!鸡 排 也可以和之 学 优 方 结

使 ,只不 代 实现会 复杂一些,这里 不 了,


有兴 ,可以自己 一下代 实现哦。

OK,最后我 问问,鸡 排 优 和缺 是什

么? 于什么样 场景?

鸡 排 优 是能够在 定条件下, 少排

回 数;而缺 也 明显, 是代 几乎增加了1 。

至于它能 挥出优 场景,是 大部分元素已经有序


。好了,关于 排 和鸡 排 ,我们 介绍 这里喽。
下一节 见!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4.3 什么是快速排序
4.3.1 初识快速排序

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
同 泡 序一 ,快速 序也 于 交换排序,通过 素之间 比较和交
位置 达 序 。

不同 , 泡 序在每一 中只把1个 素 泡 一 ,而快


速 在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列
序则
一边,比它小的元素移动到数列的另一边,从而把数列拆解成两个部分。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 思 叫 分治法。
每次把 分成两部分, 竟有什么 呢?

给 一个8个 素 ,一般情 下, 泡 序需 比较7


,每一 把1个 素 动 一 , 间 度 O(n2)。

而快速 序 流 什么 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
图所 ,在分治法 思想下,原 在每一 都 拆分成两部分,每
一部分在下一 又分 拆分成两部分, 不可 分为止。

每一 比较和交 ,需 把 组 部 素都遍历一遍, 间 度
O(n)。这 遍历一 需 呢? 素个 n,那么平均情 下
需 logn ,因此快速 序 法总 平均 间 度 O(nlogn)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
分 果 奇! 么基 素是如何 呢?又

如何 其他 素 动 基 素 两端?

基 素 ,以及 素 交换, 是 排

核 问题。 我们 来 如何 基 素。

4.3.2 基准元素的选择
素,英 pivot,在分治过 中,以 素为中心,把其
素 动 左右两边。

那么 选择 素呢?

最 单 式 选择 1个 素。

这 选择在绝 情 下 没有问题 。 , 有一个原本逆序


, 序成顺序 ,那么会 现什么情 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哎呀,整个数 有被分成两 ,每一 只

定了基 素 位置。

是呀,在这 下,数 第1个 素要么是最小

,要么是最大 ,根本无 挥分 优 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在这 极端 下, 排 需要进行n ,时间复

杂度 成了 O(n2)。
那么,该怎么避 这 情 发 呢?

其 很 单,我 可以 随机选择一个元素作为基准元素,并且
素和 素交 位置。

这 一 ,即 在 逆序 情 下,也可以有 地 分成两
部分。

当然,即 随 选择 素,也会有 几 选 最
或最 ,同 会影响分治 果。

所以, 然快速 序 平均 间 度 O(nlogn), 最坏情 下


间 度 O(n2)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在后 中,为了 化步 , 去了随 选择 素 过 , 把
素 为 素。

4.3.3 元素的交换
选 了 素以后,我 把其 素中 于 素 都
交 素一边, 于 素 都交 素另一边。

具 现呢?有两 法。

1. 环法。

2. 环法。

双边循环法?下面 一 详细过 。

给 原 下, 求 其从 进行 序。

先,选 素pivot,并且设置两个 针left和right, 向


最左和最右两个 素。

下 进行第1次循环,从right 针开 , 针所 向 素和
素 比较。 果大于或等于pivot,则 针向左 动; 果 小于pivot,则
right 针 止 动,切 left 针。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在当前 中,1<4,所以right 止 动, left 针,进行下
一步行动。

left 针行动, 针所 向 素和 素 比较。 果 小于


或等于pivot,则 针向 右 动; 果 大于pivot,则left 针 止 动。

于left开 向 素, 肯 ,所以left右 1位。

于7>4,left 针在 素7 位置 下。这 , left和right指针所指


向的元素进行交换。

下 ,进 第2次循环,重新切 right 针,向左 动。right


针先 动 8,8>4,继续左 。 于2<4, 止在2 位置。

照这个思 ,后续步 图所 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
大致明白了, 么 排 样 代 来实现呢?

我们来 一下 双 环 实现 排 ,代 使

了 归 方 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. public static void quickSort(int[] arr, int startIndex,
int endIndex) {

2. // 递归结 件:startIndex 于或 于endIndex


3. if (startIndex >= endIndex) {
4. return;
5. }

6. // 得 素位置
7. int pivotIndex = partition(arr, startIndex, endIndex);
8. // 素,分成两部分进行递归 序
9. quickSort(arr, startIndex, pivotIndex - 1);
10. quickSort(arr, pivotIndex + 1, endIndex);

11. }
12.
13. /**
14. * 分治(双边循环法)

15. * @param arr 待交 组


16. * @param startIndex 起 下标
17. * @param endIndex 结 下标
18. */

19. private static int partition(int[] arr, int startIndex,


int endIndex) {
20. // 取 1个位置(也可以选择随 位置) 素 为 素
21. int pivot = arr[startIndex];

22. int left = startIndex;


23. int right = endIndex;
24.
25. while( left != right) {

26. //控制right 针比较并左

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
27. while(left<right && arr[right] > pivot){
28. right--;
29. }
30. //控制left 针比较并右

31. while( left<right && arr[left] <= pivot) {


32. left++;
33. }
34. //交 left和right 针所 向 素

35. if(left<right) {
36. int p = arr[left];
37. arr[left] = arr[right];
38. arr[right] = p;

39. }
40. }
41.
42. //pivot 和 针重合点交
43. arr[startIndex] = arr[left];

44. arr[left] = pivot;


45.
46. return left;
47. }

48.
49. public static void main(String[] args) {
50. int[] arr = new int[] {4,4,6,5,3,2,8,1};
51. quickSort(arr, 0, arr.length-1);

52. System.out.println(Arrays.toString(arr));
53. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在上述代 中,quickSort 法通过递归 式, 现了分而治之 思
想。

partition 法则 现了 素 交 , 中 素 自 ,
分 交 素 左右两边。在这里,我 交 式 双边循环
法。

partition 代 实现好复杂呢,在一个大 环里

还 套 两个子 环…… 我仔细消 消 。

双 环 代 实有些 。除了这 方 ,要

实现 素 交换也可以 单边循环法,下一节我们来仔细 一 。

4.3.4 单边循环法
双边循环法从 组 两边交替遍历 素, 然 加 观, 代 现
烦 。而单边循环法则 单得 ,只从 组 一边 素进行遍历和交
。我 一 详细过 。

给 原 下, 求 其从 进行 序。

开 和双边循环法 , 先选 素pivot。同 ,设置一个


mark 针 向 起 位置,这个mark 针代 小于基准元素的区域边界。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下 ,从 素 下一个位置开 遍历 组。

果遍历 素 于 素, 继续往后遍历。

果遍历 素 于 素,则需 两件事: 一,把mark 针


右 1位,因为 于pivot 区 边 了1; 二, 最新遍历 素
和mark 针所在位置 素交 位置,因为最新遍历 素归 于 于
pivot 区 。

先遍历 素7,7>4,所以继续遍历。

下 遍历 素 3,3<4,所以mark 针右 1位。

随后, 素3和mark 针所在位置 素交 ,因为 素3归 于 于


pivot 区 。

照这个思 ,继续遍历,后续步 图所 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
明白了,这个方 只需要 环, 实简 了

多呢! 么 代 来实现呢?

双 环 和 环 区 在于partition 数

实现, 我们来 一下代 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. public static void quickSort(int[] arr, int startIndex,
int endIndex) {

2. // 递归结 件:startIndex 于或 于endIndex


3. if (startIndex >= endIndex) {
4. return;
5. }

6. // 得 素位置
7. int pivotIndex = partition(arr, startIndex, endIndex);
8. // 素,分成两部分进行递归 序
9. quickSort(arr, startIndex, pivotIndex - 1);

10. quickSort(arr, pivotIndex + 1, endIndex);


11. }
12.
13. /**

14. * 分治(单边循环法)
15. * @param arr 待交 组
16. * @param startIndex 起 下标
17. * @param endIndex 结 下标
18. */

19. private static int partition(int[] arr, int startIndex,


int endIndex) {
20. // 取 1个位置(也可以选择随 位置) 素 为 素
21. int pivot = arr[startIndex];

22. int mark = startIndex;


23.
24. for(int i=startIndex+1; i<=endIndex; i++){
25. if(arr[i]<pivot){

26. mark ++;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
27. int p = arr[mark];
28. arr[mark] = arr[i];
29. arr[i] = p;
30. }

31. }
32.
33. arr[startIndex] = arr[mark];
34. arr[mark] = pivot;

35. return mark;


36. }
37.
38. public static void main(String[] args) {

39. int[] arr = new int[] {4,4,6,5,3,2,8,1};


40. quickSort(arr, 0, arr.length-1);
41. System.out.println(Arrays.toString(arr));
42. }

可以很 ,partition 法只 一个 循环 了, 比双
边循环法 单 了。

以上 排 实现方 , 是以 归为基

。其实 排 也可以基于 非递归 方 来实现。

4.3.5 非递归实现

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么样 非 归 方 来实现呢?

绝大多数 归 , 可以 栈 方 来代替。

为什么这 呢?

在 1章介绍 间 度 我 曾经 过,代 中一 一 法
,本 了一个 法 栈。每次进 一个新 法, 当于 栈;
每次有 法返回, 当于 栈。

所以,可以把原本 递归 现 化成一个栈 现,在栈中存 每一次


法 参 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面 一下具 代 :

1. public static void quickSort(int[] arr, int startIndex,

int endIndex) {
2. // 一个集合栈 代替递归 栈
3. Stack<Map<String, Integer>> quickSortStack = new
Stack<Map<String, Integer>>();

4. // 个 起止下标,以哈 形式 栈
5. Map rootParam = new HashMap();
6. rootParam.put("startIndex", startIndex);
7. rootParam.put("endIndex", endIndex);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
8. quickSortStack.push(rootParam);
9.

10. // 循环结 件:栈为


11. while (!quickSortStack.isEmpty()) {
12. // 栈顶 素 栈,得 起止下标
13. Map<String, Integer> param = quickSortStack.pop();

14. // 得 素位置
15. int pivotIndex = partition(arr, param.get("startIndex"),
param.get("endIndex"));
16. // 素分成两部分, 把每一部分 起止下标 栈

17. if(param.get("startIndex") < pivotIndex -1){


18. Map<String, Integer> leftParam = new HashMap<String,
Integer>();
19. leftParam.put("startIndex", param.get("startIndex"));

20. leftParam.put("endIndex", pivotIndex-1);


21. quickSortStack.push(leftParam);
22. }
23. if(pivotIndex + 1 < param.get("endIndex")){
24. Map<String, Integer> rightParam = new HashMap<String,

Integer>();
25. rightParam.put("startIndex", pivotIndex + 1);
26. rightParam.put("endIndex", param.get("endIndex"));
27. quickSortStack.push(rightParam);

28. }
29. }
30. }
31.

32. /**
33. * 分治(单边循环法)

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
34. * @param arr 待交 组
35. * @param startIndex 起 下标

36. * @param endIndex 结 下标


37. */
38. private static int partition(int[] arr, int startIndex,
int endIndex) {
39. // 取 1个位置(也可以选择随 位置) 素 为 素

40. int pivot = arr[startIndex];


41. int mark = startIndex;
42.
43. for(int i=startIndex+1; i<=endIndex; i++){

44. if(arr[i]<pivot){
45. mark ++;
46. int p = arr[mark];
47. arr[mark] = arr[i];

48. arr[i] = p;
49. }
50. }
51.

52. arr[startIndex] = arr[mark];


53. arr[mark] = pivot;
54. return mark;
55. }
56.

57. public static void main(String[] args) {


58. int[] arr = new int[] {4,7,6,5,3,2,8,1};
59. quickSort(arr, 0, arr.length-1);
60. System.out.println(Arrays.toString(arr));

61. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
和刚才 递归 现 比,非递归 式代 变动只发 在quickSort
法中。该 法引 了一个存 Map类 素 栈, 于存 每一次交
起 下标和结 下标。

每一次循环,都会 栈顶 素 栈,通过partition 法进行分治,并


且 照 素 位置分成左右两部分,左右两部分 分 栈。当栈为
, 序已经 毕,退 循环。

实现了非 归方 ,好棒!

嘿嘿, 排 是 重要 算 ,与 里 变换等算

为二十世纪十大算法。

有关 排 我们 介绍 这里, 望大家

这个算 吃 ,未来会受 无穷!

4.4 什么是堆排序
4.4.1 传说中的堆排序

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
还 得二叉 特性 什么吗?

1. 最 的 是整个 中的最 元素。

2. 最 的 是整个 中的最 元素。

以最 为 , 果 除一个最 顶(并不 除,而


末尾 节点交 位置),经过自我 , 2 素 会 交 上 ,成
为最 新 顶。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
正 上图所 ,在 除 为10 顶节点后,经过 , 为9 新节
点 会顶替上 ;在 除 为9 顶节点后,经过 , 为8 新节点
会顶替上 ……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
于二叉 这个特性,每一次 除 顶, 后 新 顶都
次于 顶 节点。那么只 反 除 顶,反 二叉 ,所得
集合 会成为一个有序集合,过 下。

除节点9,节点8成为新 顶。

除节点8,节点7成为新 顶。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
除节点7,节点6成为新 顶。

除节点6,节点5成为新 顶。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
除节点5,节点4成为新 顶。

除节点4,节点3成为新 顶。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
除节点3,节点2成为新 顶。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此为止,原本 最 二叉 已经变成了一个从 有序集合。之
前 过,二叉 际存 在 组中, 组中 素 下。

此,可以归纳 序 法 步 。

1. 无 数组构 二叉 。 从 到 ,则构 最 ;
从 到 ,则构 最 。

2. 环删 元素,替 到二叉 的末 , 整 产生新的 。

大体 明白了, 么该如何 代 来实现呢?

二叉堆时,我们 了二叉堆操作 关代 。现在

只要在 代 基 上 改动一 , 可以实现堆排 了。

4.4.2 堆排序的代码实现
1. /**

2. * “下沉”
3. * @param array 待

4. * @param parentIndex “下沉” 父节点


5. * @param length 有

6. */

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
7. public static void downAdjust(int[] array, int parentIndex,
int length) {

8. // temp 存父节点 , 于最后

9. int temp = array[parentIndex];


10. int childIndex = 2 * parentIndex + 1;

11. while (childIndex < length) {


12. // 果有右 ,且右 于左 ,则 位 右

13. if (childIndex + 1 < length && array[childIndex + 1] >

array[childIndex]) {
14. childIndex++;

15. }
16. // 果父节点 于 一个 ,则

17. if (temp >= array[childIndex])

18. break;
19. // 须真正交 ,单向 即可

20. array[parentIndex] = array[childIndex];

21. parentIndex = childIndex;


22. childIndex = 2 * childIndex + 1;

23. }
24. array[parentIndex] = temp;

25. }

26.
27.

28. /**

29. * 序(升序)
30. * @param array 待

31. */
32. public static void heapSort(int[] array) {

33. // 1. 把 序 组 建成最

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
34. for (int i = (array.length-2)/2; i >= 0; i--) {
35. downAdjust(array, i, array.length);

36. }
37. System.out.println(Arrays.toString(array));

38. // 2. 循环 除 顶 素, 集合尾部, 产 新 顶

39. for (int i = array.length - 1; i > 0; i--) {


40. // 最后1个 素和 1个 素进行交

41. int temp = array[i];

42. array[i] = array[0];


43. array[0] = temp;

44. // “下沉” 最
45. downAdjust(array, 0, i);

46. }

47. }
48.

49.
50. public static void main(String[] args) {

51. int[] arr = new int[] {1,3,2,6,5,7,8,9,10,0};

52. heapSort(arr);
53. System.out.println(Arrays.toString(arr));

54. }

来如此,现在明白了! 么堆排 时间复杂度

和空间复杂度各是多少呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
无 问,空间复杂度是O(1),因为 有 额外

集 空间。t至于时间复杂度,我们来分析一下。

二叉 节点“下沉” (downAdjust 法) 序 法
,这个 节 本 间 度在上一章 过, O(log n)。

我 回顾一下 序 法 步 。

1. 把 序 组 建成二叉 。

2. 循环 除 顶 素,并 该 素 集合尾部, 产 新
顶。

1步,把 序 组 建成二叉 ,这一步 间 度 O(n)。


2步,需 进行n-1次循环。每次循环 一次downAdjust 法,所以
2步 模 (n-1)×logn , 间 度为 O(nlogn)。
两个步 并 关系,所以 间 度 O(nlogn)。

最后一个问题,从宏观上 ,堆排 和 排

比,有什么区 和联系呢?

同 ,堆排 和 排 平均时间复杂度

O(nlogn), 且 是不稳定排序。至于不同
是 , 排 最坏
时间复杂度是O(n2),而堆排 最坏时间复杂度 定在 O(nlogn)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此外, 排 归和非 归方 平均空间复杂度

是O(logn),而堆排 空间复杂度是 O(1)。

好了,关于堆排 算 ,我们 介绍 这里。 大

家!

4.5 计数排序和桶排序
4.5.1 线性时间的排序

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哇,什么样 排 算 可以这么 害?

我们 来回顾一下以 学 排 算 ,无 是

排 ,还是 排 , 是基于元素之间的比较来进行排 。

泡 序。

下图所 ,因为8>3,所以8和3 位置交 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
序。

下图所 ,因为10>7,所以10和7 位置交 。

排 当 要 比 呀,难 还有不需要比 排 算

有一些 殊 排 不基于 素比 ,如 计数排序、


桶排序、基数排序。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以 数排 来 ,这 排 算 是 数组下标来

定 素 正 位置 。

4.5.2 初识计数排序
还是不明白, 素下标 么能 来 助排 呢?

我们来 一个例子。

设 组中有20个随 ,取 范围为0~10, 求 最快 速度把


这20个 从 进行 序。

给这些 序 随 进行 序呢?

考 这些 只能 在0、1、2、3、4、5、6、7、8、9、10这11个
中取 ,取 范围有限。所以,可以 这有限 范围,建 一个长度为11
组。 组下标从0 10, 素 为0。

设20个随 下所 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9

下面 开 遍历这个 序 随 ,每一个 照其 号 座,
同 , 应 组下标 素进行加1 。

1个 9,那么 组下标为9 素加1。

2个 3,那么 组下标为3 素加1。

继续遍历 并 组……

最终,当 遍历 毕 , 组 状态 下。

该 组中每一个下标位置 代 中 应 现 次 。

有了这个统 结果, 序 很 单了。 遍历 组,输 组 素


下标 , 素 几, 输 几次。

0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10

然,现在输 已经 有序 了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 是 数排 基本 ,它 于一定范围

整数排 。在取 范围不是 大 下,它 能 至 些时


间复杂度为O(nlogn) 排 。

明白了, 数排 还 是个 奇 算 ! 么,

代 么实现呢?

我 了一个 数排 步实现代 ,我们来 一

下。

1. public static int[] countSort(int[] array) {


2. //1.得 最

3. int max = array[0];

4. for(int i=1; i<array.length; i++){


5. if(array[i] > max){

6. max = array[i];

7. }
8. }

9. //2. 最 统 组 长度
10. int[] countArray = new int[max+1];

11. //3.遍历 , 统 组

12. for(int i=0; i<array.length; i++){


13. countArray[array[i]]++;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
14. }
15. //4.遍历统 组,输 结果

16. int index = 0;

17. int[] sortedArray = new int[array.length];


18. for(int i=0; i<countArray.length; i++){

19. for(int j=0; j<countArray[i]; j++){

20. sortedArray[index++] = i;
21. }

22. }
23. return sortedArray;

24. }

25.
26.

27. public static void main(String[] args) {

28. int[] array = new int[] {4,4,6,5,3,2,8,1,7,5,6,0,10};


29. int[] sortedArray = countSort(array);

30. System.out.println(Arrays.toString(sortedArray));
31. }

这段代 在开 有一个步 , 求 最 max。后面创建


统 组countArray,长度 max+1,以此 组 最后一个下标
max。

4.5.3 计数排序的优化

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
从实现功能 度来 ,这段代 可以实现整数 排

。但是这段代 也存在一些问题,你 现了吗?

哦, 我 ……

对了!我们只以数 最大 来决定统 数组

度,其实 不严 。例如下面 数 。

95,94,91,98,99,90,99,93,91,92

这个数 最大 是99,但最小 整数是90。如果创

度为100 数组, 么 面从0 89 空间位置 了!

怎么 决这个问题呢?

很 单,只 不 以 输入数列的最大值+1 为统 组 长度,而


以数列最大值-最小值+1 为统 组 长度即可。

同 , 最 为一个 量, 于 在统 组中 下
标。

以刚才 为 ,统 组 长度为99-90+1=10, 量 于
最 90。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
于 1个 95, 应 统 组下标 95-90 = 5, 图所 。

是 ,这 实对 数排 进行了优 。此外,朴素

数排 只是简 地按 统 数组 下标 出 素 , 有 正
给 始数 进行排 。

如果只是 纯地给整数排 ,这样 有问题。但

如果在现实业务里,例如给学 考 分数进行排 , 同 分
数 会分不 是 。

什么意思呢? 我 下面 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
给 一个 成绩 , 求 成绩从低 进行 序, 果成绩 同,
则遵循原 固有顺序。

那么,当我 统 组以后,只 道有两个成绩并 为95分 同


,却不 道哪一个 红,哪一个 绿。

明白你 例子了,但为什么我 成绩最低呀…… 么,

这 分数 同 要 么 决?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在这 下,需要 改变之 ,在填 完

统 数组以后,对统 数组 一下变 。

仍然以刚才 成绩 为 , 之前 统 组变形成下面 。

这 变形 呢?其 从统 组 2个 素开 ,每一个
素都加上前面所有 素之和。

为什么 加呢? 次 者可能会觉得莫名其 。

这 加 , 统 组存 素 , 于 应 最终
序位置 序号。 下标 9 素 为5,代 原 9,最终
序在 5位。

下 ,创建输 组sortedArray,长度和输 一致。然后从后


向前遍历输 。

1步,遍历成绩 最后一行 绿同 成绩。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
绿 成绩 95分,找 countArray下标 5 素, 4,代 绿
成绩 名位置在 4位。

同 ,给countArray下标 5 素 1,从4变成3,代 下次 遇
95分 成绩 ,最终 名 3。

2步,遍历成绩 2行 同 成绩。

成绩 94分,找 countArray下标 4 素, 2,代


成绩 名位置在 2位。

同 ,给countArray下标 4 素 1,从2变成1,代 下次 遇
94分 成绩 ( 际上已经遇不 了),最终 名 1。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3步,遍历成绩 3行 红同 成绩。

红 成绩 95分,找 countArray下标 5 素, 3(最


4, 1变成了3),代 红 成绩 名位置在 3位。

同 ,给countArray下标 5 素 1,从3变成2,代 下次 遇
95分 成绩 ( 际上已经遇不 了),最终 名 2。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 一 ,同 95分 红和 绿 能 清楚地 顺序了,也正因
为此,优化版本 序 于 稳定排序。
后面 遍历过 以此类推,这里 不 详细 述了。

还 是够绕 ,不 大体上明白了。 么,优 之

后 数排 如何 代 实现呢?

起来复杂,其实代 简 , 我们来 一 。

1. public static int[] countSort(int[] array) {

2. //1.得 最 和最 ,并 d

3. int max = array[0];


4. int min = array[0];

5. for(int i=1; i<array.length; i++) {


6. if(array[i] > max) {

7. max = array[i];

8. }
9. if(array[i] < min) {

10. min = array[i];

11. }
12. }

13. int d = max - min;


14. //2.创建统 组并统 应 素 个

15. int[] countArray = new int[d+1];

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
16. for(int i=0; i<array.length; i++) {

17. countArray[array[i]-min]++;
18. }

19.

20. //3.统 组 变形,后面 素 于前面 素之和


21. for(int i=1;i<countArray.length;i++) {

22.
23. countArray[i] += countArray[i-1];

24. }

25. //4. 序遍历原 ,从统 组找 正 位置,输 结果 组


26. int[] sortedArray = new int[array.length];

27. for(int i=array.length-1;i>=0;i--) {

28. sortedArray[countArray[array[i]-min]-1]=array[i];
29. countArray[array[i]-min]--;

30. }
31. return sortedArray;

32. }

33.
34. public static void main(String[] args) {

35. int[] array = new int[] {95,94,91,98,99,90,99,93,91,92};

36. int[] sortedArray = countSort(array);


37. System.out.println(Arrays.toString(sortedArray));

38. }

小 ,如果 始数 模是n,最大和最小整数

是m,你 数排 时间复杂度和空间复杂度是多少?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
代 第1、2、4步 涉及 始数 ,运算

是n,第3步 统 数 ,运算 是m, 以 体运算 是3n+m,


掉系数,时间复杂度是O(n+m)。

至于空间复杂度,如果不考虑结果数组,只考虑统

数组大小 ,空间复杂度是O(m)。

不 哦,回答 !

不 我有一 不太明白,既 数排 这么 大,

为什么 少被大家使 呢?

因为 数排 有它 限 ,主要表现为如下两 。

1. 当数列最 最 值 时, 不 用 数 。

给 20个随 ,范围在0 1亿之间,这 果 序,


需 创建长度为1亿 组。不 严重浪 间,而且 间 度也会随之
升 。

2. 当数列元素不是整数时,也不 用 数 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
果 中 素都 , 25.213,或0.00 000 001这 字,
则 法创建 应 统 组。这 然 法进行 序。

对于这些 限 ,另一 线 时间排 算 出了

补,这 排 算 叫作 桶排序。

4.5.4 什么是桶排序
桶排 ? 又是什么鬼?

桶排 同样是一 线 时间 排 算 。类似于 数

排 创 统 数组,桶排 需要创 若干个桶来 助排 。

那么, 序中所 “ ”,又 什么呢?

每一个 (bucket)代 一个区间范围,里面可以承载一个或 个


素。

设有一个非 下:

4.5,0.84,3.25,2.18,0.5

我 序 工 原 。

序 1步, 创建这些 ,并 每一个 区间范围。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
具 需 建 个 , 区间范围,有很 不同
式。我 这里创建 量 于原 素 量,除最后一个 只包含
最 ,前面各个 区间 照比 。

区间 度 = (最 -最 )/ ( 量 - 1)

2步,遍历原 ,把 素 号 座 各个 中。

3步, 每个 部 素分 进行 序( 然,只有 1个 需
序)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4步,遍历所有 ,输 所有 素。

0.5,0.84,2.18,3.25,4.5

此为止, 序结 。

大体明白了, 么,代 么 呢?

我们来 一 桶排 代 实现。

1. public static double[] bucketSort(double[] array){

2.
3. //1.得 最 和最 ,并 d

4. double max = array[0];

5. double min = array[0];


6. for(int i=1; i<array.length; i++) {

7. if(array[i] > max) {


8. max = array[i];

9. }

10. if(array[i] < min) {


11. min = array[i];

12. }

13. }
14. double d = max - min;

15.

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
16. //2. 化
17. int bucketNum = array.length;

18. ArrayList<LinkedList<Double>> bucketList = new

ArrayList<LinkedList<Double>>(bucketNum);
19. for(int i = 0; i < bucketNum; i++){

20. bucketList.add(new LinkedList<Double>());


21. }

22.

23. //3.遍历原 组, 每个 素 中
24. for(int i = 0; i < array.length; i++){

25. int num = (int)((array[i] - min) * (bucketNum-1) / d);


26. bucketList.get(num).add(array[i]);

27. }

28.
29. //4. 每个 部进行 序

30. for(int i = 0; i < bucketList.size(); i++){

31. //JDK 底 采 了归并 序或归并 优化版本


32. Collections.sort(bucketList.get(i));

33. }
34.

35. //5.输 部 素

36. double[] sortedArray = new double[array.length];


37. int index = 0;

38. for(LinkedList<Double> list : bucketList){

39. for(double element : list){


40. sortedArray[index] = element;

41. index++;
42. }

43. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
44. return sortedArray;
45. }

46.
47. public static void main(String[] args) {

48. double[] array = new double[]

{4.12,6.421,0.0023,3.0,2.123,8.122,4.12, 10.09};
49. double[] sortedArray = bucketSort(array);

50. System.out.println(Arrays.toString(sortedArray));

51. }

在上述代 中,所有 都 存在ArrayList集合中,每一个 都


义成一个链 (LinkedList<Double>),这 于在尾部 素。

同 ,上述代 了JDK 集合工具类Collections.sort 为 部


素进行 序。Collections.sort底 采 归并 序或Timsort,各
位 者可以 单地把 当 一 间 度为O(nlogn) 序。

么,桶排 时间复杂度是多少呢?

桶排 时间复杂度有些复杂, 我们来 算一下。

设原 有n个 素,分成n个 。

下面逐步 分 一下 法 度。

1步,求 最 、最 ,运 量为n。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2步,创建 ,运 量为n。

3步,把原 素分配 各个 中,运 量为n。

4步,在每个 部 序,在 素分 均匀 情 下,所有


运 量之和为n。

5步,输 序 ,运 量为n。

因此, 序 总 间 度为O(n)。

至于 间 度 很 得 了,同 O(n)。

桶排 能 非绝对 定。如果 素 分 极不均

衡,在极端 下,第一个桶中有n-1个 素,最后一个桶中有1个


素。此时 时间复杂度将 为O(nlogn),而且还白白创 了 多空
桶。

此可见, 有绝对好 算 ,也 有绝对不好

算 ,关 要 具体 场景。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
关于 数排 和桶排 ,我们 介绍 这里,

下一章 见!

4.6 小结
本章我 习了一些具有代 性 序 法。下面 法 间
度、 间 度、 否 维度 一个归纳。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第5章 面试中的算法
5.1 踌躇满志的小灰

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这一章,我 开 形形色色 法面 题,其中有 面 过 中
遇 经典题 。 灰 竟能不能面 成功呢? 我 为 加油吧!

5.2 如何判断链表有环
5.2.1 一场与链表相关的面试

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面我来考查你一 算 题。

有一个单向链 ,链 中有可能 现“环”, 下图这 。

那么, 序 该链 否为有环链 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哦, 我 啊……

有了!我可以从头节 始 整个 表……

法1:

先从 节点开 , 次遍历单链 中 每一个节点。每遍历一个新节点,


从 检 新节点之前 所有节点, 新节点和此节点之前所有节点 次 比
较。 果发现新节点和之前 个节点 同,则 该节点 遍历过两次,链
有环; 果之前 所有节点中不存在与新节点 同 节点, 继续遍历下一个新
节点,继续重 刚才 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
图中这 ,当遍历链 节点7 ,从 访问节点5和节点3,发现已遍历
节点中并不存在节点7,则继续往下遍历。

当 2次遍历 节点2 ,从 访问曾经遍历过 节点,发现已经遍历过节点


2, 链 有环。

设链 节点 量为n,则该 法 间 度为 O(n2)。 于并没有创建


额 存 间,所以 间 度为O(1)。

OK,这姑且算是一 方 ,有 有效 更高 ?

哦, 我 啊……

者,我创 一个哈 表, 后……

法2:

先创建一个以节点ID为Key HashSet集合, 存 曾经遍历过 节点。


然后同 从 节点开 , 次遍历单链 中 每一个节点。每遍历一个新节点,
都 新节点和HashSet集合中存 节点进行比较, 果发现HashSet中存在与之
同 节点ID,则 链 有环, 果HashSet中不存在与新节点 同 节点ID,
把这个新节点ID存 HashSet中,之后进 下一节点,继续重 刚才 。

遍历过5、3。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
遍历过5、3、7、2、6、8、1。

当 一次遍历节点2 , 找HashSet,发现节点已存在。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此可 ,链 有环。

这个 法在流 上和 法1类 ,本 区 了HashSet 为额 缓


存。

设链 节点 量为n,则该 法 间 度 O(n)。 于 了额
存 间,所以 法 间 度同 O(n)。

OK,这 方 在时间上已经是最优了。有 有可能在空间上

也 优 ?

哦, 我 啊……

不出来啊, 么能 时间复杂度不变,同时 空间复杂度

降低呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
呵呵, 关系,今天 这里,你回家等 吧。

5.2.2 解题思路
小 ,你刚刚 面 了?结果 么样?

唉……

大黄,你给我 呗, 么能够更高效地 断一个 表

是否有环呀?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哈哈,小 ,有环 表 断问题是 基 算 题,

多面 官 喜欢考查,你 须要掌握哦!

对于这 题,有一个 巧妙 方 ,这个方 了两个

指 。

法3:

先创建两个 针p1和p2(在Java里 两个 引 ), 同 向
这个链 节点。然后开 一个 循环,在循环 中, 针p1每次向后 动1
个节点, 针p2每次向后 动2个节点,然后比较两个 针 向 节点 否
同。 果 同,则可以 链 有环, 果不同,则继续下一次循环。

1步,p1和p2都 向节点5。

2步,p1 向节点3,p2 向节点7。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3步,p1 向节点7,p2 向节点6。

4步,p1 向节点2,p2 向节点1。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5步,p1 向节点6,p2也 向节点6,p1和p2所 同, 链 有环。

过 者,一 听 过 上 追及问题。此 法 类 于一个


追及问题。

在一个环形 道上,两个运动员从同一地点起 ,一个运动员速度快,另一


个运动员速度慢。当两人 了一段 间后,速度快 运动员必然会 次追上并
过速度慢 运动员,原因很 单,因为 道 环形 。

设链 节点 量为n,则该 法 间 度为O(n)。除两个 针 ,没
有 额 存 间,所以 间 度 O(1)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么,这个算 代 么实现呢?

代 实现 简 , 我们来 一下。

1. /**
2. * 否有环

3. * @param head 链 节点
4. */

5. public static boolean isCycle(Node head) {


6. Node p1 = head;

7. Node p2 = head;
8. while (p2!=null && p2.next!=null){

9. p1 = p1.next;
10. p2 = p2.next.next;

11. if(p1 == p2){

12. return true;

13. }
14. }
15. return false;
16. }

17.
18. /**
19. * 链 节点
20. */

21. private static class Node {


22. int data;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
23. Node next;
24. Node(int data) {
25. this.data = data;
26. }

27. }
28.
29. public static void main(String[] args) throws Exception {
30. Node node1 = new Node(5);

31. Node node2 = new Node(3);


32. Node node3 = new Node(7);
33. Node node4 = new Node(2);
34. Node node5 = new Node(6);

35. node1.next = node2;


36. node2.next = node3;
37. node3.next = node4;
38. node4.next = node5;

39. node5.next = node2;


40.
41. System.out.println(isCycle(node1));

42. }

明白了,这 是个好方 !

5.2.3 问题扩展

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这个题 其实还可以 出 多有 问题,例如下面

这些。

扩 问题1:

果链 有环, 求 环 长度?

扩 问题2:

果链 有环, 求 环节点?

哎呀,这两个问题 么 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第1个问题 环 ,非 简 , 如下。

当两个 针 次 遇, 链 有环 , 两个 针从 遇点继续循环
前进,并统 前进 循环次 , 两个 针 2次 遇。此 ,统 前进
次 环长。

因为 针p1每次 1步, 针p2每次 2步,两者 速度 1步。当两个 针


次 遇 ,p2比p1 了 1圈。

因此,环长 = 每一次速度 × 前进次 = 前进次 。

第2个问题是 环 ,有些难度,我们可以 一个

推断。

上图 有环链 所 一个抽 意图。 设从链 节点 环点


D,从 环点 两个 针 次 遇点 S1,从 次 遇点回 环点

S2。

那么,当两个 针 次 遇 ,各自所 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
针p1一次只 1步,所 D+S1。

针p2一次 2步, 了n(n>=1) 圈,所 D+S1+n(S1+S1)。

于p2 速度 p1 2 ,所以所 也 p1 2 ,因此:

2(D+S1) = D+S1+n(S1+S2)

式经过 得 :

D = (n-1)(S1+S2)+S2

也 ,从链 结点 环点 , 于从 次 遇点绕环n-1圈 回
环点 。

这 一 ,只 把其中一个 针 回 节点位置,另一个 针 在 次
遇点,两个 针都 每次向前 1步。那么, 最终 遇 节点, 环节
点。

哇, 这么 奇?

我们不妨 题中 表 例子来 一下。

先, 针p1回 链 节点, 针p2 在 次 遇点。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
针p1和p2各自前进1步。

针p1和p2 2次前进。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
针p1和p2 3次前进, 向了同一个节点2,节点2正 有环链 环点。

果 在 环 了呢,这下明白了!

好了,关于 断 表是否有环及其 题 ,我们 介绍 这里。咱们


下一节 见!

5.3 最小栈的实现
5.3.1 一场关于栈的面试

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面我来考查你一 算 题。

现一个栈,该栈 有 栈(pop)、 栈(push)、取最 素(getMin)


3个 法。 这3个 法 间 度都 O(1)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哦, 我 ……

我 啦!可以 栈中 最小 素下标暂存起来……

灰的 下。

1. 创建一个 变量min, 存 栈中 最 素。当 1个 素进栈 ,


把进栈 素 给min,即把栈中唯一 素当 最 。

2. 之后每当一个新 素进栈, 新 素和min比较 。 果新 素 于


min,则min 于新进栈 素; 果新 素 于或 于min,则不 变。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. 当 getMin 法 , 返回min 即可。

小 ,你有 有 这个 存在什么问题?

有问题呀?这个 杠杠 !

呵呵,今天面 这里,回家等 吧!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5.3.2 解题思路
小 ,你刚刚 面 了?结果 么样?

唉……

大黄, 么 能实现一个最小栈呀?我 临时变 暂

存栈 最小 , 竟存在什么问题呢?

小 ,你 太简 啦!你只考虑了进栈场景,却 有考

虑出栈场景。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哦?出栈场景有什么问题吗?

我来给你 一下。

原本,栈中最 素 3,min变量 录 也 3。

这 ,栈顶 素 栈了。

此 min变量应该 于几呢?

然此 最 素 4, 序并不 道。

哎呀,还 是……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以 ,只暂存一个最小 是不够 ,我们需要存 栈中

曾经 最小 ,作为“备胎”。

细的 法步 下。

1. 设原有 栈叫 栈A,此 创建一个额 “ 胎”栈B, 于辅助栈A。

2. 当 1个 素进 栈A , 新 素也进 栈B。这个唯一 素 栈A 当
前最 。

3. 之后,每当新 素进 栈A ,比较新 素和栈A当前最 , 果


于栈A当前最 ,则 新 素进 栈B,此 栈B 栈顶 素 栈A当前最

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
4. 每当栈A有 素 栈 , 果 栈 素 栈A当前最 ,则 栈B 栈顶
素也 栈。此 栈B 下 栈顶 素所 向 , 栈A当中原本 2 素,代
替刚才 栈 素成为栈A 当前最 。( 胎 正。)

5. 当 getMin 法 ,返回栈B 栈顶所存 ,这也 栈A 最 。

然,这个 法中进栈、 栈、取最 间 度都 O(1),最坏情


间 度 O(n)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这下明白了! 么代 么来实现呢?

代 不难实现, 我们来 一 。

1. private Stack<Integer> mainStack = new Stack<Integer>();


2. private Stack<Integer> minStack = new Stack<Integer>();
3.
4. /**
5. * 栈
6. * @param element 栈 素
7. */

8. public void push(int element) {


9. mainStack.push(element);
10. // 果辅助栈为 ,或者新 素 于或 于辅助栈栈顶,则 新 素压 辅助栈
11. if (minStack.empty() || element <= minStack.peek()) {
12. minStack.push(element);
13. }
14. }
15.

16. /**
17. * 栈
18. */
19. public Integer pop() {
20. // 果 栈 素和辅助栈栈顶 素 ,辅助栈 栈
21. if (mainStack.peek().equals(minStack.peek())) {
22. minStack.pop();

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
23. }
24. return mainStack.pop();
25. }
26.
27. /**
28. * 获取栈 最 素
29. */

30. public int getMin() throws Exception {


31. if (mainStack.empty()) {
32. throw new Exception("stack is empty");
33. }
34.
35. return minStack.peek();
36. }

37.
38. public static void main(String[] args) throws Exception {
39. MinStack stack = new MinStack();
40. stack.push(4);
41. stack.push(9);
42. stack.push(7);
43. stack.push(3);
44. stack.push(8);

45. stack.push(5);
46. System.out.println(stack.getMin());
47. stack.pop();
48. stack.pop();
49. stack.pop();
50. System.out.println(stack.getMin());
51. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
代 1行输 3,因为当 最 3。

代 2行输 4,因为 素3 栈后,最 4。

好了,关于最小栈题 介绍 这里,咱们下一节

见!

5.4 如何求出最大公约数
5.4.1 一场求最大公约数的面试

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面我来考查你一 算 题,数学里面 最大 约数,

吧?

这个我 ,小学 学 。

么, 下面这个算 题。

一段代 ,求 两个 最 公约 , 尽量优化 法 性能。

哦, 我 ……

出来啦!你 。

灰 代 下:

1. public static int getGreatestCommonDivisor(int a, int b){

2. int big = a>b ? a:b;


3. int small = a<b ? a:b;
4. if(big%small == 0){
5. return small;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
6. }
7. for(int i= small/2; i>1; i--){
8. if(small%i==0 && big%i==0){
9. return i;

10. }
11. }
12. return 1;
13. }
14.
15. public static void main(String[] args) {
16. System.out.println(getGreatestCommonDivisor(25, 5));

17. System.out.println(getGreatestCommonDivisor(100, 80));


18. System.out.println(getGreatestCommonDivisor(27, 14));
19. }

灰 思 十分 单。 力 举 法,从较 一半开 ,
图找 一个合适 i, 这个 能否 a和b同 除。

你这个方 虽 实现了 要 功能,但是效 不行啊。

,如果我传 整数是10 000和10 001, 你 方 需要 环10


000/2-1=4999次!

哎呀,这 是个问题。

不出更好 方 了……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
呵呵, 关系,回家等 吧!

5.4.2 解题思路
小 ,你刚刚 面 了?结果 么样?

唉……

大黄, 么 能更高效地 出两个整数 最大 约数

呀?

小 ,你听 辗转相除法吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
……什么除 ?

是 除 !又叫作欧几里 算 。

辗 除法, 又名欧几里得 法(Euclidean algorithm),该 法


求 两个正 最 公约 。 已 最古老 法, 其产 间可追溯至
公 前300年前。

这 法 两个正整数a和b(a>b),它们的最大公约数等于a
于一个 :
除以b的余数c和b之间的最大公约数。
10和25,25除以10商2 5,那么10和25 最 公约 , 同于10和5 最
公约 。

有了这 ,求最 公约 变得 单了。我 可以 递归 法把问


题逐步 化。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
先, a除以b c,把问题 化成求b和c 最 公约 ;然后
b除以c d,把问题 化成求c和d 最 公约 ; c除以d
e,把问题 化成求d和e 最 公约 ……

以此类推,逐渐把两个较 之间 运 化成两个较 之间 运
, 两个 可以 除,或者其中一个 1为止。

了这么多 不如 接 代 ,小 ,你按 除

改改你 代 吧。

好 , 我 !

辗 除法 现代 下:

1. public static int getGreatestCommonDivisorV2(int a, int b){


2. int big = a>b ? a:b;
3. int small = a<b ? a:b;
4. if(big%small == 0){
5. return small;
6. }

7. return getGreatestCommonDivisorV2(big%small, small);


8. }
9.
10. public static void main(String[] args) {
11. System.out.println(getGreatestCommonDivisorV2(25, 5));
12. System.out.println(getGreatestCommonDivisorV2(100, 80));

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
13. System.out.println(getGreatestCommonDivisorV2(27, 14));
14. }

,这 实是 除 。不 有一个问题,当

两个整数 大时, a%b取模运算 能会比 。

这我也明白,可是不取模 ,还能 么办呢?

这里,另一个算 要 场了,它叫作 更相减损术。

, 自中国古代 《九章 》,也 一 求最 公约


法。古 腊人很聪 ,可 我 炎 孙也不 。

原 两个正整数a和b(a>b),它们的最大公约数等于a-b的
加 单:
差值c和较小数b的最大公约数。 10和25,25 10 15,那么10和25 最
公约 , 同于10和15 最 公约 。

此,我 同 可以通过递归 化问题。 先, a和b c( 设


a>b),把问题 化成求b和c 最 公约 ;然后 c和b d( 设
c>b),把问题 化成求b和d 最 公约 ; b和d e( 设b>d),
把问题 化成求d和e 最 公约 ……

以此类推,逐渐把两个较 之间 运 化成两个较 之间 运
, 两个 可以 为止,最 公约 最终 这两个 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
OK,这 是更 损术 ,你按 这个 一段

代 。

好 , 我 !

现代 下:

1. public static int getGreatestCommonDivisorV3(int a, int b){


2. if(a == b){

3. return a;
4. }
5. int big = a>b ? a:b;
6. int small = a<b ? a:b;
7. return getGreatestCommonDivisorV3(big-small, small);
8. }
9.
10. public static void main(String[] args) {

11. System.out.println(getGreatestCommonDivisorV3(25, 5));


12. System.out.println(getGreatestCommonDivisorV3(100, 80));
13. System.out.println(getGreatestCommonDivisorV3(27, 14));
14. }

好,更 损术 是这样。我们 了大整数取

模可能出现 能问题,已经越来越接近最优 决方案了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
但是,更 损术依靠两数 方 来 归,运算次数肯

定远大于 除 取模方 吧?

能 现问题, 来你进步了。更 损术是不 定 算

,当两数 殊时,如 算10000和1 最大 约数, 要 归9999


次!

有什么办 可以既 大整数取模,又能 可能地 少

运算次数呢?

下面 是我要 最优方 : 除 和更 损术

优 结 起来,在更 损术 基 上使 位运算。

众所周 , 位运 性能非 。 于给 正 a和b,不难得 下


结 。

(从下 开 ,获得最 公约 法getGreatestCommonDivisor 为


gcd。)

当a b 为偶数时,gcd(a,b) = 2×gcd(a/2, b/2) = 2×gcd(a>>1,b>>1)。

当a为偶数,b为 数时,gcd(a,b) = gcd(a/2,b) = gcd(a>>1,b)。

当a为 数,b为偶数时,gcd(a,b) = gcd(a,b/2) = gcd(a,b>>1)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当a b 为 数时,先利用更相减 术运算一次,gcd(a,b) = gcd(b,a-b),
此时a-b 然是偶数,然 又 以继续 移位运算。

10和25 最 公约 步 下。

1. 10通过 位,可以 成求5和25 最 公约 。

2. , 25-5=20, 成求5和20 最 公约 。

3. 20通过 位,可以 成求5和10 最 公约 。

4. 10通过 位,可以 成求5和5 最 公约 。

5. ,因为两 ,所以最 公约 5。

这 式在两 都比较 ,可能 不 次 优势;当两 ,


次 会 。

了这么多,来 代 吧,这是最终 本 代 。

1. public static int gcd(int a, int b){


2. if(a == b){
3. return a;
4. }
5. if((a&1)==0 && (b&1)==0){
6. return gcd(a>>1, b>>1)<<1;
7. } else if((a&1)==0 && (b&1)!=0){

8. return gcd(a>>1, b);


9. } else if((a&1)!=0 && (b&1)==0){
10. return gcd(a, b>>1);
11. } else {

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
12. int big = a>b ? a:b;
13. int small = a<b ? a:b;
14. return gcd(big-small, small);
15. }
16. }
17.

18. public static void main(String[] args) {


19. System.out.println(gcd(25, 5));
20. System.out.println(gcd(100, 80));
21. System.out.println(gcd(27, 14));
22. }

在上述代 中, 性 式 和1进行与运 , 果
(a&1)==0,则 a ; 果(a&1)!=0,则 a 。

不容易呀,终于 了最优 !

嘿嘿,作为 员, 是需要反复推敲, 代 极

致!

我还有最后一个问题,咱们使 这些方 ,时间复杂

度分 是多少呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我们来 结一下上 时间复杂度。

1. 暴力枚举法: 间 度 O(min(a, b))。

2. 辗转相除法: 间 度不太 ,可以近 为O(log(max(a, b))),


取模运 性能较 。

3. 更相减损术:避 了取模运 , 法性能不 ,最坏 间 度为


O(max(a, b))。

4. 更相减损术与移位相结合:不 避 了取模运 ,而且 法性能 ,


间 度为O(log(max(a, b)))。

好了,有关最大 约数 ,我们 介绍 这里。咱们

下一节 会!

5.5 如何判断一个数是否为2的整数次

5.5.1 一场很“2”的面试

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面我来考查你一 算 题,给你一个正整数,如何 断它

是不是2 整数次 ?

现一个 法, 一个正 否 2 次 ( 16 2 4次 ,返
回true;18不 2 次 ,则返回false)。 求性能尽可能 。

哦, 我 ……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 了! 一个整型变 , 它从1 始不断乘以2,

将每一次乘2 结果和 标整数进行比 。

灰 具 想法 下。

创建一个中间变量temp, 1。然后进 一个循环,每次循环都 temp


和 标 比较, 果 ,则 标 2 次 ; 果不 ,则
temp 1 ,继续循环并进行比较。当temp 于 标 , 标
不 2 次 。

举个 。

给 一个 19,则

1X2 = 2,

2X2 = 4,

4X2 = 8,

8X2 = 16,

16X2 = 32,

于32>19,所以19不 2 次 。

果 标 n,则此 法 间 度 O(logn)。

代 已经 好了, 来 !

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. public static boolean isPowerOf2(int num) {
2. int temp = 1;
3. while(temp<=num){
4. if(temp == num){
5. return true;
6. }

7. temp = temp*2;
8. }
9. return false;
10. }
11.
12. public static void main(String[] args) {
13. System.out.println(isPowerOf2(32));
14. System.out.println(isPowerOf2(19));

15. }

OK,这样 实现了 要 功能,你 考一下该 么来提高

其 能呢?

哦, 我 ……

我 了,可以 之 乘以2 操作改成向左 位, 位

能比乘 高 多。来 改变之后 代 吧。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. public static boolean isPowerOf2V2(int num) {
2. int temp = 1;
3. while(temp<=num){
4. if(temp == num){

5. return true;
6. }
7. temp = temp<<1;
8. }
9. return false;
10. }

OK,这 有一 优化。 前 法 间 度仍然 O(logn),本 上


没有变。

如何 能在 能上有 飞 呢?

哦, 我 ……

不出来啦,时间复杂度为O(logn)已经 了,难 还

能有O(1) 方 ?

呵呵, 关系,今天面 这 ,回家等 吧。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
5.5.2 解题思路
小 ,你刚刚 面 了?结果 么样?

唉……

大黄, 么 能更高效地 断一个整数是否是2 整数次

呢?难 存在时间复杂度只有O(1) 方 ?

小 呀,这个题 还 有O(1) 。

Really? 么 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
你 一 ,如果 2 整数次 换成二进 数,会有什

么样 同 ?

我 , 进 2 换成二进 是10B,4 换成二进

是100B,8 成二进 是1000B……

我 了!如果一个整数是2 整数次 , 么当它 成

二进 时,只有最高位是1,其他位 是0!

,是这样 。接下来如果 这些2 整数次 各自

1, 成二进 ,会有什么样 呢?

1? 我 啊!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 现了,2 整数次 一旦 1,它 二进 数字

变成了1!

好,这时 如果 数 (2 整数次 )和它 1 结

果进行按位与运算,也 是n&(n-1),会是什么结果呢?

0和1按位与运算 结果是0, 以凡是2 整数次 和它本

1 结果进行与运算,结果 定是0。反之,如果一个整数不是2 整
数次 ,结果一定不是0!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么, 决这个问题 方 已经 明显了,你 样来

断一个整数是否是2 整数次 。

简 ,对于一个整数n,只需要 算n&(n-1) 结果是不

是0。这个方 时间复杂度只有O(1)。

代 我已经 好了,除方 声明外,只有1行哦!

1. public static boolean isPowerOf2(int num) {

2. return (num&num-1) == 0;
3. }

非 好,这 是位运算 妙 。关于这 题 我们

这里,下一节 会!

5.6 无序数组排序后的最大相邻差
5.6.1 一道奇葩的面试题

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面我来考查你一 算 题,有一个无 整型数组……

有一个 序 组, 求 该 组 序后 意两个 邻 素 最
? 求 间和 间 度尽可能低。

可能题 有点绕, 我 一个 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哦, 我 ……

嗨,这还不简 吗? 使 时间复杂度为O(nlogn) 排

算 给 来 数组排 , 后 数组,对每两个 素 ,最大


不 出来了吗?

法1:

意一 间 度为O(nlogn) 序 法( 快速 序)给原 组
序,然后遍历 序 组,并 每两个 邻 素求 ,最终得 最 。

该 法 间 度 O(nlogn),在不 变原 组 情 下, 间 度
O(n)。

唉,我出这样 题 ,显 不是为了 你来排 。你

,有 有更 ?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
有了呀。不排 还能 么 呢?

呵呵, 你回家等 吧!

5.6.2 解题思路
小 ,你刚刚 面 了?结果 么样?

唉……

大黄,我今天 见一 题, 样 能 算出无 数组

排 后 最大 ?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
嗯……这 题 实 有 。虽 对数组排 以后肯定能

正 结果,但我们 有 要 进行排 。

不排 ,该 么办呢?

小 ,你 不 ,有哪些排 算 时间复杂度是线

好 有 数排 、桶排 ,还有个什么基数排 ……可你刚

不是 不 排 吗?

,我们仅仅是 助一下这些排 而已。小

你 一下,这 题能不能 数排 一样, 数组下标来 决?

数排 一样? 我 啊……

有了!我可以使 数排 , 出 数组最大

和最小 ……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
法2:

1. 序 思想,先求 原 组 最 max与最 min 区间长度


k(k=max-min+1),以及 量d=min。

2. 创建一个长度为k 新 组Array。

3. 遍历原 组,每遍历一个 素, 把新 组Array 应下标 +1。


原 组 素 为n,则 Array[n-min] 加1。遍历结 后,Array 一部分
素 变成了1或 ,一部分 素 仍然 0。

4. 遍历新 组Array,统 Array中最 连续 现0 次 +1,即为 邻


素最 。

给 一个 序 组 { 2, 6, 3, 4, 5, 10, 9 }, 过 下图。

1步, k( 组长度)和d( 量)。

2步,创建 组。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3步,遍历原 组, 号 座。

4步, 0 最 连续 现 次 , 最 邻 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好,我们已经进步了 多。这个 在数组 素 不

殊 时 , 实效 高。

可是设 一下,如果 数组只有3个 素:1、2、1 000

000, 要创 度是1 000 000 数组! 一 还能如何优 ?

我 啊……

对了!桶排 正好 决了这个问题!

法3:

1. 序 思想, 原 组 长度n,创建 n个 ,每一个 代 一


个区间范围。其中 1个 从原 组 最 min开 ,区间 度 (max-min)/
(n-1)。

2. 遍历原 组,把原 组每一个 素 应 中, 录每一个 最


和最 。

3. 遍历所有 ,统 每一个 最 ,和这个 右 非 最


, 最 即为原 组 序后 邻最 。

给 一个 序 组 { 2, 6, 3, 4, 5, 10, 9 }, 过 下图。

1步, 原 组,创建 , 每个 区间范围。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2步,遍历原 组, 每个 最 和最 。

3步,遍历所有 ,找 最 邻 。

这个方 不需要 标 桶排 样给每一个桶 进行排

,只需要 桶 最大和最小 即可, 以时间复杂度 定在 O(n)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好, 我们来 一下代 吧。

好 ,我 。

1. public static int getMaxSortedDistance(int[] array){


2.
3. //1.得 最 和最
4. int max = array[0];
5. int min = array[0];
6. for(int i=1; i<array.length; i++) {
7. if(array[i] > max) {
8. max = array[i];
9. }
10. if(array[i] < min) {
11. min = array[i];
12. }
13. }

14. int d = max - min;


15. // 果max 和min , 组所有 素都 ,返回0
16. if(d == 0){
17. return 0;
18. }
19.
20. //2. 化
21. int bucketNum = array.length;
22. Bucket[] buckets = new Bucket[bucketNum];

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
23. for(int i = 0; i < bucketNum; i++){
24. buckets[i] = new Bucket();
25. }
26.
27. //3.遍历原 组, 每个 最 最

28. for(int i = 0; i < array.length; i++){


29. // 组 素所归 下标
30. int index = ((array[i] - min) * (bucketNum-1) / d);
31. if(buckets[index].min==null || buckets[index].
min>array[i]){
32. buckets[index].min = array[i];
33. }
34. if(buckets[index].max==null || buckets[index].
max<array[i]){
35. buckets[index].max = array[i];
36. }
37. }
38.
39. //4.遍历 ,找 最
40. int leftMax = buckets[0].max;

41. int maxDistance = 0;


42. for (int i=1; i<buckets.length; i++) {
43. if (buckets[i].min == null) {
44. continue;
45. }
46. if (buckets[i].min - leftMax > maxDistance) {
47. maxDistance = buckets[i].min - leftMax;
48. }
49. leftMax = buckets[i].max;
50. }
51.

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
52. return maxDistance;

53. }
54.
55. /**
56. *
57. */
58. private static class Bucket {
59. Integer min;
60. Integer max;
61. }
62.
63. public static void main(String[] args) {
64. int[] array = new int[] {2,6,3,4,5,10,9};
65. System.out.println(getMaxSortedDistance(array));
66. }

代 前几步都比较 观,唯独 4步 微有些不 : 临 变量


leftMax,在每一 迭代 存 当前左 最 。而两个 之间 ,则
buckets[i].minleftMax。

,这 是这 题 最优 决方 。关于无 数组排

后最大 问题 介绍 这里,咱们下一节 见!

5.7 如何用栈实现队列
5.7.1 又是一道关于栈的面试题

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么下面我来考查你一 算 题, 样 栈来实现一个队

栈 模拟一个队 , 求 现队 两个 本 : 队、 队。

哦……栈是 后出,队 是 出, 栈 办 实现队

吧?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
提 你一下, 一个栈肯定是 办 实现队 ,但如果我

们有两个栈呢?

我 啊……

出来, 算给我8个栈,我也不 么实现队 。

呵呵, 事,回家等 吧!

5.7.2 解题思路
小 ,你刚刚 面 了?结果 么样?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
唉……

大黄,你能不能给我 , 样可以 两个栈来实现一

个队 呀?

要 决这个问题,我们 来回顾一下栈和队 不同

栈 特点 先 后 , 素都 在同一 (栈顶)。

栈:

栈:

队 特点 先 先 , 素 在不同 两 (队 和队尾)。

队:

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
队:

然我 拥有两个栈,那么可以 其中一个栈 为队 口, 新
素;另一个栈 为队 口, 除老 素。

可是,两个栈是各自 立 , 么能 它们有效地关联起来

呢?

, 我来具体 一下。

队 主 非有两个: 队和 队。

在模拟 队 ,每一个新 素都 压 栈A当中。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
素1 队。

素2 队。

素3 队。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 ,我 最先 队 素1 队,需 怎么 呢?

栈A中 所有 素 顺序 栈, 照 栈顺序压 栈B。这 一 , 素


从栈A弹 并压 栈B 顺序 3、2、1,和当 进 栈A 顺序1、2、3 反 。

此 素1 队,也 素1从栈B中弹 。

素2 队。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
如果这个时 又 队操作了呢?

简 ,当有新 素 队时,重新 新 素 栈A。

素4 队。

此 队 仍然从栈B中弹 素。

素3 队。

现在栈B已经空了,如果 出队该 么办呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
也不难,只要栈A中还有 素, 刚 一样, 栈A中

素 出 栈B即可。

素4 队。

么样,这回你绕明白了吗?

哦,基本上明白了, 么代 么来实现呢?

代 好 , 我们来 一 。

1. private Stack<Integer> stackA = new Stack<Integer>();


2. private Stack<Integer> stackB = new Stack<Integer>();

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. /**
4. * 队
5. * @param element 队 素
6. */
7. public void enQueue(int element) {
8. stackA.push(element);
9. }
10. /**
11. * 队
12. */
13. public Integer deQueue() {
14. if(stackB.isEmpty()){
15. if(stackA.isEmpty()){
16. return null;
17. }
18. transfer();

19. }
20. return stackB.pop();
21. }
22.
23. /**
24. * 栈A 素 栈B
25. */
26. private void transfer(){
27. while (!stackA.isEmpty()){
28. stackB.push(stackA.pop());
29. }
30. }
31. public static void main(String[] args) throws Exception {
32. StackQueue stackQueue = new StackQueue();
33. stackQueue.enQueue(1);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
34. stackQueue.enQueue(2);
35. stackQueue.enQueue(3);
36. System.out.println(stackQueue.deQueue());
37. System.out.println(stackQueue.deQueue());
38. stackQueue.enQueue(4);
39. System.out.println(stackQueue.deQueue());
40. System.out.println(stackQueue.deQueue());
41. }

小 ,你 ,这个队 队和出队操作,时间复杂度

分 是多少?

队操作 时间复杂度显 是 O(1)。至于出队操作,如


果涉及栈A和栈B 素 , 么一次出队 时间复杂度是O(n);如果不
,时间复杂度是O(1)。咦,在这 下,出队 时间复杂度 竟
应该是多少呢?

这里涉及一个新 概 ,叫作 均摊时间复杂度。需要 素

出队操作只有少数 , 且不可能 续出现,其后 大多数出队


操作 不需要 素 。

以 时间均摊 每一次出队操作上面,其时间复杂度是

O(1)。这个概 不 , 了 即可。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好了, 栈实现队 题 ,我们 介绍 这里,咱们下

一节 见!

5.8 寻找全排列的下一个数
5.8.1 一道关于数字的题目

下面我来考查你一 算 题, 设给出一个正整数, 出

这个正整数 有数字 排 下一个数。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com

给 一个正 ,找 这个正 所有 字 下一个 。

通 点 在一个 所包含 字 部组合中,找 一个 于且 于


原 新 。 我 举几个 。

果输 12345,则返回12354。

果输 12354,则返回12435。

果输 12435,则返回12453。

我 一 啊……

我 现了,这里面有个 ! 我来 一下。

灰 现的“ ” 下。

输 12345,返回12354,那么

12354 - 12345 = 9,

刚 9 一次 。

输 12354,返回12435,那么

12435 - 12354 = 81,

刚 9 二次 。

所以,每次 最近 位 ,只需 加上9 n次 即可。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
么样,我是不是 机智?

这算哪门子 ? 12453-12435= 18,24135-23541=594,

也 不 是9 整数次 啊!

啊, 了……

呵呵,今天 这里,回家等 吧!

5.8.2 解题思路
小 ,你刚刚 面 了?结果 么样?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
唉……

大黄,你能不能给我 , 么样寻 一个整数 有数

字 排 下一个数?

好啊,在给出具体 之 ,小 你 考一个问题:

固定几个数字组成 整数, 样排 最大? 样排 最小?

我 一 啊……

了,如果是固定 几个数字,应该是在 逆序排列


下最大,在 顺序排列 下最小。

举一个 。

给 1、2、3、4、5这几个 字。

最 组合:54321。
最 组合:12345。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
,数字 顺 和 ,是 排 中 两 极端 。

么普 下,一个数和它最近 排 数存在什么关联呢?

给 12354, 包含 字 1、2、3、4、5, 找 这些 字
之后 于原 新 呢?

为了和原 近,我 需 尽量保持高位不变,低位在最小的范围内变换顺


序。
至于变 顺序 范围 ,则取决于当前 逆序区域。

图所 ,12354 逆序区 最后两位, 这两位已经 当前 最 组


合。若想最 近原 ,又比原 ,必须从 倒数第3位开 变。

怎 变呢?12345 3位 3,我 需 从后面 逆序区 中找 于


3 最 字, 其和3 位置进行互 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
互 后 临 结果 12453, 3位已经 ,这个 最后两位仍然
逆序状态。我 需 把最后两位 转变为顺序状态,以此 在 3位 为4
情 下,后两位尽可能 。

这 一 , 得 了想 结果 12435。

有些明白了,不 还 是复杂呀!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
起来复杂,其实只要3个步骤。

获 全 列下一个数的3个步 。

1. 从后向前 逆序区 ,找 逆序区 前一位,也 字置 边


2. 逆序区 前一位和逆序区 中 于 最 字交 位置。

3. 把原 逆序区 为顺序状态 。

最后 我们 代 来实现一下。这里为了方便数字位置

交换, 和 回 类型 了整型数组。

1. public static int[] findNearestNumber(int[] numbers){


2. //1. 从后向前 逆序区 ,找 逆序区 前一位,也 字置 边
3. int index = findTransferPoint(numbers);
4. // 果 字置 边 0, 个 组已经逆序, 法得 同
5. // 字组成 ,返回null
6. if(index == 0){
7. return null;

8. }
9. //2.把逆序区 前一位和逆序区 中刚刚 于 字交 位置
10. // 制并 参,避 参
11. int[] numbersCopy = Arrays.copyOf(numbers, numbers.length);
12. exchangeHead(numbersCopy, index);
13. //3.把原 逆序区 为顺序
14. reverse(numbersCopy, index);

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
15. return numbersCopy;
16. }
17.
18. private static int findTransferPoint(int[] numbers){
19. for(int i=numbers.length-1; i>0; i--){
20. if(numbers[i] > numbers[i-1]){
21. return i;
22. }
23. }
24. return 0;
25. }
26.
27. private static int[] exchangeHead(int[] numbers, int index){
28. int head = numbers[index-1];
29. for(int i=numbers.length-1; i>0; i--){

30. if(head < numbers[i]){


31. numbers[index-1] = numbers[i];
32. numbers[i] = head;
33. break;
34. }
35. }
36. return numbers;
37. }
38.
39. private static int[] reverse(int[] num, int index){
40. for(int i=index,j=num.length-1; i<j; i++,j--){
41. int temp = num[i];
42. num[i] = num[j];
43. num[j] = temp;
44. }

45. return num;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
46. }
47.
48. public static void main(String[] args) {
49. int[] numbers = {1,2,3,4,5};
50. //打印12345 之后 10个
51. for(int i=0; i<10;i++){
52. numbers = findNearestNumber(numbers);
53. outputNumbers(numbers);
54. }
55. }
56.

57. // 输 组
58. private static void outputNumbers(int[] numbers){
59. for(int i : numbers){
60. System.out.print(i);
61. }
62. System.out.println();
63. }

这 法拥有一个“ 上” 名字:字典序算法。

小 ,你 这个 时间复杂度是多少?

该算 3个步骤每一步 时间复杂度 是O(n), 以整体

时间复杂度也是 O(n)!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
完 正 。关于这 算 题 答 介绍 这里,咱们下

一节 会!

5.9 删去k个数字后的最小值
5.9.1 又是一道关于数字的题目

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好吧,下面考你一 算 题:给出一个整数,从该整数中

掉k个数字,要 下 数字 成 新整数 可能小。

给 一个 ,从该 中去 k个 字, 求剩下 字形成 新 尽可


能 。应该 选取 去 字?

其中 长度 于或 于k,给 可以 过long类 字范
围。

什么意思呢? 我 举几个 。

设给 一个 1 593 212, 去3个 字,新 最 情 1212。

设给 一个 30 200, 去1个 字,新 最 情 200。

设给 一个 10, 2
去 个 字(注意,这里 求 去 不 1个 字,
而 2个),新 最 情 0。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 题听起来还挺有 , 我 ……

你可以 你 第一 ,为了 新整数 可能小,什么

样 数字应该优 除?

我 了!肯定要优 除最大 数字!如 除9,

除8, 除7……

可不一定,如整数3549, 除1个数字 ,是应该 除

数字9吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哎呀,还 是! 我 ……

呵呵,不 了,回家等 吧!

5.9.2 解题思路
小 ,你刚刚 面 了?结果 么样?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
唉……

大黄,你能不能给我 , 样寻 k个数字后 最

小 呀?

这个题 要 我们 k个数字,但我们不妨 问题简 一

下:如果只 除1个数字,如何 新整数 最小?

我 第一 是优 除最大 数字,可是这个策 似乎不

对……

数字 大小固 重要,数字 位置则更加重要。你 ,

一个整数 最高位哪 只 少1,对数 响也是非 大 。

我 举一个 。

给 一个 541 270 936, 求 去1个 字, 剩下 尽可能 。

此 , 除哪一个 字,最后 结果都 从9位 变成8位 。 然


同 8位 , 然应该优先把 位 字降低,这 新 影响最 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
把 位 原整数的所有数字从左到右进行比
字降低呢?很 单,把
较,如果发现某一位数字大于它右面的数字,那么在删除该数字后,必然会使
该数位的值降低,因为右面比 字顶替了 位置。

在上面这个 中, 字5右 字4 于5,所以 除 字5,最 位 字


降低成了4。

对于整数541 270 936, 除一个数字 能 最小 是

41 270 936。 么对于41 270 936, 除一个数字 最小 ,你 是多


少。

我 了,是 除数字4!因为从左向 ,数字4是第1

个比 侧数字大 数(4>1)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好, 么接下来呢?从刚 结果1 270 936中 除一

个数字,能 最小 是多少?

这一次 复杂,因为1<2、2<7、7>0, 以被 除

数字应该是7!

不 ,这里每一步 要 除一个数字后 最小 ,

经 3次, 当于 出了 除k(k=3)个数字后 最小 。

这样依次 局部最优解 ,最终 全局最优解


,叫作贪心算法。

小 ,按 这个 ,你 代 来实现一下吧。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好 ,我来 一 吧。

1. /**
2. * 除 k个 字,获得 除后 最
3. * @param num 原

4. * @param k 除 量
5. */
6. public static String removeKDigits(String num, int k) {
7. String numNew = num;
8. for(int i=0; i<k; i++){
9. boolean hasCut = false;
10. //从左向右遍历,找 比自己右 字 字并 除
11. for(int j=0; j<numNew.length()-1;j++){
12. if(numNew.charAt(j) > numNew.charAt(j+1)){
13. numNew = numNew.substring(0, j) +
numNew.substring(j+1,numNew.length());
14. hasCut = true;
15. break;
16. }
17. }
18. // 果没有找 除 字,则 除最后一个 字

19. if(!hasCut){
20. numNew = numNew.substring(0, numNew.length()-1);
21. }
22. //清除 左 字0
23. numNew = removeZero(numNew);
24. }
25. // 果 所有 字都 除了, 返回0

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
26. if(numNew.length() == 0){
27. return "0";
28. }
29. return numNew;
30. }
31.
32. private static String removeZero(String num){
33. for(int i=0; i<num.length()-1; i++){
34. if(num.charAt(0) != '0'){
35. break;

36. }
37. num = num.substring(1, num.length()) ;
38. }
39. return num;
40. }
41.
42. public static void main(String[] args) {
43. System.out.println(removeKDigits("1593212",3));
44. System.out.println(removeKDigits("30200",1));
45. System.out.println(removeKDigits("10",2));
46. System.out.println(removeKDigits("541270936",3));
47. }

灰 代 了两 循环, 循环次 除 字个 k, 循
环从左 右遍历所有 字。当遍历 需 除 字 , 字 串 自 法
subString()把 应 字 除,并重新 字 串。

然,这段代 间 度 O(kn)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
OK,这段代 在功能实现上是 有问题 ,但是 能却不

么好。主要问题在于以下两个方面。

1. 每一次内 环 从 有数 。

给 11 111 111 111 114 132,在 1 循环中,需 遍历 部


分 字,一 遍历 字4,发现4>1,从而 除4。

以 前 代 逻辑,下一 循环 ,还 从 开 遍历, 次重 遍历 部
分 字,一 遍历 字3,发现3>2,从而 除3。

事 上,我 应该 在上一次 除 位置继续进行比较,而不 次从


开 遍历。

2.subString方法本 能不 。

subString 法 底 现,涉及新字 串 创建,以及逐个字 制。这


个 法自 间 度 O(n)。

因此,我 应该避 在每 除一个 字后 subString 法。

哎呀, 应该 么来优 呢?

以k作为外 环, 数字作为 环,需要额外考虑 东

西非 多。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以我们换一个 ,以 数字作为外 环,以k作为

环,这样可以 出非 简 代 , 我们来 一 。

1. /**
2. * 除 k个 字,获得 除后 最
3. * @param num 原
4. * @param k 除 量
5. */
6. public static String removeKDigits(String num, int k) {
7. //新 最终长度 = 原 长度-k

8. int newLength = num.length() - k;


9. //创建一个栈, 于 所有 字
10. char[] stack = new char[num.length()];
11. int top = 0;
12. for (int i = 0; i < num.length(); ++i) {
13. //遍历当前 字
14. char c = num.charAt(i);
15. //当栈顶 字 于遍历 当前 字 ,栈顶 字 栈( 当于 除 字)
16. while (top > 0 && stack[top-1] > c && k > 0) {
17. top -= 1;
18. k -= 1;
19. }
20. //遍历 当前 字 栈
21. stack[top++] = c;
22. }

23. // 找 栈中 1个非零 字 位置,以此 建新 字 串


24. int offset = 0;
25. while (offset < newLength && stack[offset] == '0') {

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
26. offset++;
27. }
28. return offset == newLength? "0": new String(stack,
offset, newLength - offset);
29. }
30.
31.
32. public static void main(String[] args) {
33. System.out.println(removeKDigits("1593212",3));
34. System.out.println(removeKDigits("30200",1));
35. System.out.println(removeKDigits("10",2));
36. System.out.println(removeKDigits("541270936",3));
37. }

上述代 非 巧 地运 了栈 特性,在遍历原 字 , 所有 字
一个一个 栈,当 个 字需 除 , 该 字 栈。最后, 序把栈中
素 化为字 串类 结果。

下面仍然以 541 270 936,k=3为 。

当遍历 字5 , 字5 栈。

当遍历 字4 ,发现栈顶5>4,栈顶5 栈, 字4 栈。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当遍历 字1 ,发现栈顶4>1,栈顶4 栈, 字1 栈。

然后继续遍历 字2、 字7,并 次 栈。

最后,遍历 字0,发现栈顶7>0,栈顶7 栈, 字0 栈。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
此 k 次 已经 , 须 比较, 剩下 字一起 栈即可。

此 栈中 素 最终 结果。

上面 法只 所有 字遍历了一次,遍历 间 度 O(n),把栈 化
为字 串 间 度也 O(n),所以最终 间 度 O(n)。
同 , 序中 栈 回溯遍历过 字及 除 字,所以 序 间
度 O(n)。

哇,这段代 好巧妙啊!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这段代 其实仍 有优 空间,各位 者可以 考一下。

好了,关于这 题 我们 介绍 这里, 大家!

5.10 如何实现大整数相加
5.10.1 加法,你会不会

好吧,下面考你一 算 题,给你两个 大 大 整数,如

何 出它们 和?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com

给 两个很 , 求 现 序求 两个 之和。

这还不简 ? 接 long类型存 ,在 里 加不 行

了?

如果这两个整数大 long类型 装不下呢,如两个100位

整数?

啊, 么可能算 出来呢?是不是题 出 了呀?

呵呵,题 出 ,回家等 吧!

5.10.2 解题思路
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
小 ,你刚刚 面 了?结果 么样?

唉……

大黄,你能不能给我 , 么实现大整数 加呀?

好啊,在 大整数 加之 ,我们 来回顾一下小学数

学 。小 ,你在上小学时,如何 算两个 大数 加、 、乘、除?

我 啊…… 小学 时 ,老 好 教我们 竖 进行

算, 下面这样。

么,我们为什么需要 出竖 来运算呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
因为对于这么大 整数,我们无 一步 位 接算出结

果, 以不 不 算 成一个一个子步骤。

。其实不仅仅是人脑,对于 算机来 同样如

此。

不可能 一条指令 算出两个大整数之和,但我们

却可以 大运算 成若干小运算, 小学 竖 一样进行按位 算。

可是,如果大整数 出了long类型 范围,我们如何来

存 这样 整数呢?

这 好 决, 数组存 即可。数组 每一个 素,对应

大整数 每一个数位。

在 序中 “ 式” 竟 什么 呢?我 以426 709 752 31 8 +95


481 253 129 为 , 加 详细步 。

第1步,创建两个 组, 组长度 较 位 +1。把每一个


序存 组中, 个位存于 组下标为0 位置,最 位存于 组 尾部。
之所以 序存 , 因为这 合从左 右访问 组 习惯。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第2步,创建结果 组,结果 组 长度同 较 位 +1,+1
很 , 给最 位进位预 。

第3步,遍历两个 组,从左 右 照 应下标把 素两两 加,


式一 。

在本 中,最先 加 组A 1个 素8和 组B 1个 素9,结果


7,进位1。把7 result 组 应下标位置,进位 1 下一个位
置。

2组 加 组A 2个 素1和 组B 2个 素2,结果 3, 加上
刚才 进位1,把4 result 组 应下标位置。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3组 加 组A 3个 素3和 组B 3个 素1,结果 4,把4
result 组 应下标位置。

4组 加 组A 4个 素2和 组B 4个 素3,结果 5,把5


result 组 应下标位置。

以此类推……一 把 组 所有 素都 加 毕。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第4步,把result 组 部 素 次逆序,去 位 0, 最终结果。

需 ,为两个 建 临 组, 一 观 决 。若想
节 存 间,也可以不创建这两个临 组。

明白了, 是个好方 ! 么, 么 代 来实现呢?

代 简 ,我们一起来 。

1. /**
2. * 求和
3. * @param bigNumberA A
4. * @param bigNumberB B
5. */
6. public static String bigNumberSum(String bigNumberA,
String bigNumberB) {

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
7. //1.把两个 组逆序存 , 组长度 于较 位 +1
8. int maxLength = bigNumberA.length() > bigNumberB.length()
? bigNumberA.length() : bigNumberB.length();
9. int[] arrayA = new int[maxLength+1];
10. for(int i=0; i< bigNumberA.length(); i++){
11. arrayA[i] = bigNumberA.charAt(bigNumberA.length()-1-i) - ‘0’;
12. }
13. int[] arrayB = new int[maxLength+1];
14. for(int i=0; i< bigNumberB.length(); i++){
15. arrayB[i] = bigNumberB.charAt(bigNumberB.length()-1-i) - ‘0’;
16. }

17. //2. 建result 组, 组长度 于较 位 +1


18. int[] result = new int[maxLength+1];
19. //3.遍历 组, 位 加
20. for(int i=0; i<result.length; i++){
21. int temp = result[i];
22. temp += arrayA[i];
23. temp += arrayB[i];
24. // 否进位
25. if(temp >= 10){
26. temp = temp-10;
27. result[i+1] = 1;
28. }
29. result[i] = temp;
30. }
31. //4.把result 组 次逆序并 成String

32. StringBuilder sb = new StringBuilder();


33. // 否找 最 有 位
34. boolean findFirst = false;
35. for (int i = result.length - 1; i >= 0; i--) {
36. if(!findFirst){

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
37. if(result[i] == 0){
38. continue;
39. }
40. findFirst = true;
41. }
42. sb.append(result[i]);
43. }

44. return sb.toString();


45. }
46.
47. public static void main(String[] args) {
48. System.out.println(bigNumberSum("426709752318", "95481253129"));
49. }

小 ,你 这个算 时间复杂度是多少?

如果给出 大整数 最 位数是n, 么创 数组、按位

算、结果 时间复杂度各自 是O(n),整体 时间复杂度也是


O(n)。

,不 当 其实还存在一个可优 地

方。

优化呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 之前 把 照 位 拆分 ,即 果较 有50位,那么我
需 创建一个长度为51 组, 组中 每个 素存 其中一位 字。

那么我 真 有必 把原 拆分得这么细吗? 然不需 ,只需 拆分


可以被直接计算 度 了。

int类 取 范围 -2 147 483 648~2 147 483 647,最 可以有10位


。为了防止溢 ,我 可以把 9
每 位 为 组 一个 素,进行加法运
。(这里也可以 long类 拆分, 照int类 拆分 一个思
。)

此一 , 存占 间和运 次 ,都压缩 了原 1/9。

在Java中,工具类BigInteger和BigDecimal 底 实现同

样是 大整数 分成数组进行运算 ,和这个 大体类似。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
有兴 ,可以 这两个类 代 。好了,关于大

整数加 , 介绍 这里,咱们下一节 见!

5.11 如何求解金矿问题
5.11.1 一个关于财富自由的问题

下面考你一 算 题,这个算 题 和 有关系。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com

很久很久以前,有一位国 拥有5座金 ,每座金 金 量不同,需 参


与 工人人 也不同。 有 金 量 500kg 金,需 5个工人
;有 金 量 200kg 金,需 3个工人 ……

果参与 工人 总 10。每座金 么 , 么不 ,不能派


一半人 取一半 金 。 求 序求 , 想得 尽可能 金,应该选择
取哪几座金 ?

哇,要是我家也有5座 ,我 富自 了,也 不 来

你这里面 了!

正经 !关于这 题你有什么 吗?

题 好复杂啊, 我 ……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 了一个办 !我们可以按 价比从高 低

进行排 ,优 价比最高 来挖掘, 后是 价比第2 ……

照 灰 思 ,金 照性价比从 低进行 序, 名结果 下。

1名,350kg 金/3人 金 ,人均产 约为116.6kg 金。

2名,500kg 金/5人 金 ,人均产 为100kg 金。

3名,400kg 金/5人 金 ,人均产 为80kg 金。

4名,300kg 金/4人 金 ,人均产 为75kg 金。

5名,200kg 金/3人 金 ,人均产 约为66.6kg 金。

于工人 量 10人, 灰优先 性价比 名为 1名和 2名 金 之


后,工人还剩下2人,不 其 金 了。

所以, 灰得 最 金 350+500即850kg 金。

么样?我这个方案妥妥 吧?

你 决 是使 算 。这 在 下是最

优 ,但是在整体上却未 是最优 。

给你举个例子吧,如果我放弃 价比最高 350kg黄 /3人

, 500kg黄 /5人和400kg黄 /5人 ,加起来收 是900kg

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
黄 ,是不是大于你 850kg黄 ?

啊,还 是呢!

呵呵, 关系,回家等 吧!

5.11.2 解题思路
小 ,你刚刚 面 了?结果 么样?

唉……

大黄,你能不能给我 , 么来 问题呀?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
好啊,这是一个典型 动态规划题 ,和著名 “背 问

题”类似。

动 ?好“高大上” 概 呀!

其实也 有 么高 啦。 动 , 是 复杂 问

题简 成 模 小 子问题, 从简 子问题自底向上一步一步 推,
最终 复杂问题 最优 。

哦, 了 天还是 听明白……

关系, 我们具体分析一下这个 问题,你 能明白

动 核 了。

先, 于问题中 金 ,每一个金 都存在 “ ”和“不 ”两


选择。

我 设一下, 果最后一个金 注 不 ,那么问题会 化成什么


呢?

然,问题 化成了10个工人在前4个金 中 最优选择。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
应地, 设最后一个金 一 会 ,那么问题又 化成什么 呢?

于最后一个金 消耗了3个工人,问题 化成了7个工人在前4个金 中


最优选择。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这两 化情 , 称为 问题 两个最优子结构。
竟哪一 最优 结 可以通向 最优 呢? 句 ,最后一个金
底该不该 呢?

那 10个工人在前4个金矿的收益,和7个工人在前4个金矿的收益+最后
一个金矿的收益 了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
同 道 , 于前4个金 选择,我 还可以 进一步 化。

先针 10个工人4个金 这个 结 , 4个金 (300kg 金/4人)可以选


择 与不 。 4个金 选择,问题又 化成了两 结 。

1. 10个 人 前3个 矿中做出最优 。

2. 6(10-4=6)个 人 前3个 矿中做出最优 。

应地, 于7个工人4个金 这个 结 , 4个金 同 可以选择 与不


。 4个金 选择,问题也 化成了两 结 。

1. 7个 人 前3个 矿中做出最优 。

2. 3(7-4=3)个 人 前3个 矿中做出最优 。

……

这 ,问题一分为二,二分为四,一 把问题 化成在0个金 或0个工人


最优选择,这个 结果 然 0,也 问题 边界。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 是动 要 : 定 最优 和最优子结构之

间 关系,以及问题 。这个关系 数学 来表 , 叫作 状
态转移方程式。

好 有 明白了…… 这个 方 是什么样

子?

我们 数 设为n,工人数 设为w, 含 设

为数组g[], 需 人数设为数组p[],设F(n,w)为n个 、w个


工人时 最优收 数, 么 方 如下。

F(n,w) = 0 (n=0或w=0)

问题边 ,金 为0或工人 为0 情 。

F(n,w) = F(n-1,w) (n≥1, w<p[n-1])

当所剩工人不 当前金 ,只有一 最优 结 。

F(n,w) = max(F(n-1,w), F(n-1,w-p[n-1])+g[n-1]) (n≥1,


w≥p[n-1])

在 情 下,具有两 最优 结 ( 当前金 或不 当前金 )。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
小 ,既 有了 方 ,你能实现代 来 出最

优收 吗?

这还不简 ? 归 可以 决!

1. /**

2. * 获得金 最优
3. * @param w 工人 量

4. * @param n 可选金 量
5. * @param p 金 开采所需 工人 量

6. * @param g 金 量
7. */

8. public static int getBestGoldMining(int w, int n,


int[] p, int[] g){

9. if(w==0 || n==0){
10. return 0;

11. }

12. if(w<p[n-1]){
13. return getBestGoldMining(w, n-1, p, g);

14. }
15. return Math.max(getBestGoldMining(w, n-1, p, g),

getBestGoldMining(w-p[n-1], n-1, p, g)+g[n-1]);


16. }

17.
18. public static void main(String[] args) {

19. int w = 10;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
20. int[] p = {5, 5, 3, 4 ,3};

21. int[] g = {400, 500, 200, 300 ,350};


22. System.out.println(" 最优 :" + getBestGoldMining(w,

g.length, p, g));

23. }

OK,这样 实可以 正 结果,不 你 考 这段代

时间复杂度吗?

我分析一下啊…… 问题经 简 ,会 成两个子结

构;两个子结构 次简 ,会 成4个更小 子结构…… 下图一样。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 天哪,这样算下来,如果 数 是n,工人数

,时间复杂度 是 O(2n)!

,现在我们 题 中只有5个 ,问题还不算严重。

如果 数 有50个, 至100个,这样 时间复杂度是根本无 接受 。

啊, 该 么办呢?

首 来分析一下 归之 以低效 根本 因, 是 归

了 多重复 算, 下面 图你 明白了。

在上图中,标为红色 法 重 。可以 F(2,7)、F(1,7)、


F(1,2),这几个 参 同 法都 了两次。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
当金 量为5 ,重 问题还不太 ,当金 量 ,递归 次
深,重 也 ,这些 必然会降低 序 性能。

我们 样 这些重复 呢?

这 要 动 另一个核 要 : 自底向上求解。
我们来详细 一下这 。

在进行求 之前,先 一张 , 于 录选择金 中间 。

最左 代 不同 金 选择范围,从上 下,每 加1行, 代 1


个金 可 选择,也 F(n,w) 中 n 。

最上 代 工人 量,从1个工人 10个工人,也 F(n,w)


中 w 。

其 ,都 待 ,代 当给 n个金 、w个工人 最优
,也 F(n,w) 。

举个 ,下图中绿色 这个 里,应该 在有5个工人 情 下,


在前3个金 可 选择 ,最优 金 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面 我 从 1行 1 开 , 把 一一 满,
状态 式。

于 1行 前4个 , 于w<p[n-1], 应 状态 式 下:

F(n,w) = F(n-1,w) (n>1, w<p[n-1])

求 :

F(1,1) = F(1-1,1) = F(0,1) = 0

F(1,2) = F(1-1,2) = F(0,2) = 0

F(1,3) = F(1-1,3) = F(0,3) = 0

F(1,4) = F(1-1,4) = F(0,4) = 0

1行 后6个 怎么 呢?此 w≥p[n-1], 于 下公式:

F(n,w) = max(F(n-1,w), F(n-1,w-p[n-1])+g[n-1]) (n>1,


w≥p[n-1]);

求 :

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
F(1,5) = max(F(1-1,5), F(1-1,5-5)+400) = max(F(0,5),
F(0,0)+400) = max(0, 400) = 400

F(1,6) = max(F(1-1,6), F(1-1,6-5)+400) = max(F(0,6),


F(0,1)+400) = max(0, 400) = 400

……

F(1,10) = max(F(1-1,10), F(1-1,10-5)+400) =


max(F(0,10), F(0,5)+400) = max(0, 400) = 400

于 2行 前4个 ,和 1行同 , 于w<p[n-1], 应 状态


式 下:

F(n,w) = F(n-1,w) (n>1, w<p[n-1])

求 :

F(2,1) = F(2-1,1) = F(1,1) = 0

F(2,2) = F(2-1,2) = F(1,2) = 0

F(2,3) = F(2-1,3) = F(1,3) = 0

F(2,4) = F(2-1,4) = F(1,4) = 0

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2行 后6个 ,和 1行同 ,此 w≥p[n-1], 应 状态 式
下:

F(n,w) = max(F(n-1,w), F(n-1,w-p[n-1])+g[n-1]) (n>1,


w≥p[n-1])

求 :

F(2,5) = max(F(2-1,5), F(2-1,5-5)+500) = max(F(1,5),


F(1,0)+500) = max(400, 500) = 500

F(2,6) = max(F(2-1,6), F(2-1,6-5)+500) = max(F(1,6),


F(1,1)+500) = max(400, 500) = 500

……

F(2,10) = max(F(2-1,10), F(2-1,10-5)+500) =


max(F(1,10), F(1,5)+500) = max(400, 400+500) = 900

3行 法 一辙。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
厉, 4行 。

最后, 5行 结果。

此 ,最后1行最后1个 所 900 最终 求 结果,即5个金 、10


个工人 最优 900kg 金。

好了,这 是动 自底向上 。

哇,这个方 还 有 ! 么, 么 代 来实现呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
在 中,可以 二维数组来代表 填 表格, 我们

一 代 吧。

1. /**
2. * 获得金 最优

3. * @param w 工人 量
4. * @param p 金 开采所需 工人 量

5. * @param g 金 量
6. */

7. public static int getBestGoldMiningV2(int w, int[] p, int[] g){


8. //创建

9. int[][] resultTable = new int[g.length+1][w+1];


10. //

11. for(int i=1; i<=g.length; i++){


12. for(int j=1; j<=w; j++){

13. if(j<p[i-1]){

14. resultTable[i][j] = resultTable[i-1][j];


15. }else{

16. resultTable[i][j] = Math.max(resultTable[i-1]


[j], resultTable[i-1][j-p[i-1]]+ g[i-1]);

17. }
18. }

19. }
20. //返回最后1个

21. return resultTable[g.length][w];


22. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
小 ,你 上 代 时间复杂度和空间复杂度分 是

样 ?

双 环来填 一个二维数组, 以时间复杂度和

空间复杂度 是 O(nw),比 归 能好多啦!

是 ,这段代 在时间上已经 有什么可优 了,但是

在空间上还可以 一些优 。

一 ,在表格中除第1行之外,每一行 结果 是 上一
行数据推导出来 。我们以4个 9个工人为例。

4个金 、9个工人 最优结果, 两个最优 结 ,也 3个金 、


5个工人和3个金 、9个工人 结果推导而 。这两个最优 结 都位于 上
一行。

所以,在 序中并不需 存 个 , 金 有 座,我 只 存1行


即可。在 下一行 , 从右向左统 ( 者可以想想为什么从右向
左),把 一个一个替 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
优化后 代 下:

1. /**
2. * 获得金 最优

3. * @param w 工人 量

4. * @param p 金 开采所需 工人 量
5. * @param g 金 量

6. */
7. public static int getBestGoldMiningV3(int w, int[] p, int[] g){

8. //创建当前结果
9. int[] results = new int[w+1];

10. // 一维 组
11. for(int i=1; i<=g.length; i++){

12. for(int j=w; j>=1; j--){


13. if(j>=p[i-1]){

14. results[j] = Math.max(results[j],


results[j-p[i-1]]+ g[i-1]);

15. }
16. }

17. }
18. //返回最后1个

19. return results[w];

20. }

哇,优 后 代 好简 呀!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
是呀,而且空间复杂度降低 了 O(n)。好了,关于 问

题我们 这里,咱们下一节 会!

5.12 寻找缺失的整数
5.12.1 “五行”缺一个整数

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
下面考你一 算 题:在一个无 数组里有99个不重复 正

整数,范围从1 100……

在一个 序 组里有99个不重 正 ,范围 1~100,唯独缺 1个1~


100中 。 找 这个缺 ?

哦, 我 ……

有了!创 一个哈 表,以1 100这100个整数为Key,

后 数组。

法1:

创建一个哈 ,以1 100这100个 为Key。然后遍历 个 组,每


一个 , 位 哈 中 应 Key,然后 除这个Key。

于 组中缺 1个 ,哈 最终一 会有99个Key 除,从而剩下1个


唯一 Key。这个剩下 Key 那个缺 。

设 组长度 n,那么该 法 间 度 O(n), 间 度 O(n)。

OK,这个 在时间上是最优 ,但额外 了 存空间。

么,有 有办 降低空间复杂度呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哦, 我 ……

有了!首 给 数组排 , 后……

法2:

先把 组 素从 进行 序,然后遍历已经有序 组, 果发现 两
个 邻 素并不连续, 缺 这两个 素之间 。

设 组长度 n, 果 间 度为O(nlogn) 序 法进行 序,那么


该 法 间 度 O(nlogn), 间 度 O(1)。

OK,这个 有 额外 空间,但是时间复杂度又太大

了。有 有办 对时间复杂度和空间复杂度 进行优 呢?

哦, 我 ……

有了! 算出1~100 累加和, 后 依次 数组里

有 素,最后 是 缺少 整数。这么简 办 我竟

法3:

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 一个很 单也很 法,先 1+2+3+…+100 和,然后 次 去
组里 素,最后得 , 那个缺 。

设 组长度 n,那么该 法 间 度 O(n), 间 度 O(1)。

OK,对于 有重复 素 数组,这个 在时间和空间上已

经最优了。但如果 问题 一下……

5.12.2 问题扩展
题 1次扩 :

一个 序 组里有若干个正 ,范围 1~100,其中99个 都 现了 偶


数次,只有1个 现了 奇数次, 找 这个 现 次 ?

哦, 我 ……

按 刚 方 和肯定不行,因为根本不 每个整数

出现 次数……同时又要保 时间和空间复杂度 最优, 么办呢?

我提 你一下吧,你 异或运算吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
异 运算,我当 ,在进行位运算时, 同位 0,不

同位 1。可是 么应 这个题 上面呢?

啊,我 了!只要 数组里 有 素依次进行异 运

算,最后 是 个缺失 整数!

法:

遍历 个 组, 次 异或运 。 于异或运 在进行位运 , 同为0,


不同为1,因此所有 现 次 都会 互抵消变成0,只有唯一 现 次
会 下。

我 举一个 :给 一个 序 组{3,1,3,2,4,1,4}。

异或运 加法运 一 ,满 交 律和结合律,所以这个 组 素 异或


运 结果 下图所 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
设 组长度 n,那么该 法 间 度 O(n), 间 度 O(1)。

这个方案已经非 好了。我们 问题最后 一下,如果数组里 有2个整数


出现了奇数次,其他整数出现 数次,该如何 出这2个整数呢?

题 2次扩 :

设一个 序 组里有若干个正 ,范围 1~100,其中有98个 现


了 次,只有 2个 现了 次, 找 这2个 现 次 ?

啊,这次要 2个整数,刚 方 已经不够 了。因为

数组 有 素进行异 运算,最终只会 2个整数 异 运算结果。

我来提 你一下吧,你 分 吗?

起分 ,我似乎 了什么……如果 数组分成两

分,保 每一 分 含1个出现奇数次 整数,这样 与上一题 一


样了。

终于 了!首 数组 素依次进行异 运算,

结果是2个出现了奇数次 整数 异 运算结果,在这个结果中至少有1个
二进 位是1。

法:

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
把2个 现了 次 命名为A和B。遍历 个 组,然后 次 异或运
,进行异或运 最终结果, 同于A和B进行异或运 结果。在这个结果
中,至 会有一个二进制位 1( 果都 0, A和B ,和题 不 )。

举个 ,给 一个 序 组{4,1,2,2,5,1,4,3},所有 素进行异或运
结果 00000110B。

选 该结果中 为1 一位 字, 00000110B 2位 1,这 A和


B 应 二进制 2位 不同 。其中必 有一个 2位 0,另
一个 2位 1。

这个结 ,可以把原 组 照二进制 2位 不同,分成两部分,


一部分 2位 0,另一部分 2位 1。 于A和B 2位不同,
所以A 分配 其中一部分,B 分配 另一部分,绝不会 现A和B在同一部分,
另一部分 没有A,也没有B 情 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 一 单 了,我 问题又回归 了上一题 情 , 照原先 异
或 法,从每一部分中找 唯一 次 即可。

设 组长度 n,那么该 法 间 度 O(n)。把 组分成两部分,并


不需 助额 存 间, 可以在 二进制位分组 同 异或运 ,
所以 间 度仍然 O(1)。

, 是这个 。 你按 这个 来 一下代 。

好 ,我来 !

1. public static int[] findLostNum(int[] array) {


2. // 于存 2个 现 次

3. int result[] = new int[2];


4. // 1次进行 异或运

5. int xorResult = 0;
6. for(int i=0;i<array.length;i++){

7. xorResult^=array[i];
8. }

9. // 果进行异或运 结果为0,则 输 组不 合题 求
10. if(xorResult == 0){

11. return null;


12. }

13. // 2个 不同位,以此 分组
14. int separator = 1;

15. while (0==(xorResult&separator)){

16. separator<<=1;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
17. }

18. // 2次分组进行异或运

19. for(int i=0;i<array.length;i++){


20. if(0==(array[i]&separator)){

21. result[0]^=array[i];
22. }else {

23. result[1]^=array[i];
24. }

25. }
26.

27. return result;


28. }

29.
30. public static void main(String[] args) {

31. int[] array = {4,1,2,2,5,1,4,3};


32. int[] result = findLostNum(array);

33. System.out.println(result[0] + "," + result[1]);

34. }

很 ,我 技 面 这里。 一下,我去叫HR 和 。
10min后……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 , 灰 了职业 涯中 一个offer, 这并不意味 结 , 灰
序员之 才刚刚开 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
第6章 算法的实际应用
6.1 小灰上班的第1天

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
几天之后, 灰 兴兴地去公司报 了……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 , 灰正式进 了职场。 下 待 会 什么 战呢?

6.2 Bitmap的巧用
6.2.1 一个关于用户标签的需求

为了 助 精 定位 群体,咱们需要 一个 系统,实现

标签 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
标签 会 、 习 、消 行为等 ,例如下面这个样子。

小 标签

标签,我们可以对多样 群体进行统 。例如统 女比例、统

喜欢旅 数 等。

放 吧,这个需 交给我一定会妥妥 !

为了满 户标 统 需求, 灰 关系 库设 了 下 结 ,每一个维度 标 应


库 中 一 。

想统 所有“90后” 序员,该怎么 呢?

一 求交集 SQL 句即可。

Select count(distinct Name) as 户 from table where age = '90 后' and Occupation = ' 序员' ;

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
想统 所有 苹果手 或“00后” 户总和,该怎么 呢?

一 求并集 SQL 句即可。

Select count (distinct Name) as 户 from table where Phone = '苹果' or age = '00 后' ;

起来 简 嘛,嘿嘿……

两个月之后……

事 么简 ,现在标签越来越多,例如 城 、消 平、 吃 东

西、喜欢 音乐…… 有上 个标签了,这要给数据库表增加多少 啊!

筛 标签条件 多 时 , 出来 SQL 句 面条一样 ……

不仅如此,当对多个 群体 集时,需要 distinct来 掉重复数据, 能实

在太 了……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
6.2.2 用算法解决问题
小 ,你 么 苦脸 呀?

唉,还不是被一个需 腾 !

事 是这样子 ……(小 工作中 难题告 了大黄)

哈哈,小 ,你听 Bitmap算 吗?在中文里又叫作位图算 。

我又不是搞 算机图 学 , 位图算 干什么?

这里 位图 不是 素图 位图,而是 存中 续 二进 位(bit) 组

成 数据结构,该算 主要 于对大 整数 重和查 操作。

举个例子, 设给出一块 度为10bit 存空间,也 是Bitmap, 要依次插

整数4、2、1、3,需要 么 呢?

很 单,具 法 下。

1步,给 一 长度为10 Bitmap,其中 每一个bit位分 应 从0 9 。此 ,Bitmap


所有位都 0( 紫色 )。

2步,把 4存 Bitmap, 应存 位置 下标为4 位置, 此bit设置为1( 色


)。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3步,把 2存 Bitmap, 应存 位置 下标为2 位置, 此bit设置为1。

4步,把 1存 Bitmap, 应存 位置 下标为1 位置, 此bit设置为1。

5步,把 3存 Bitmap, 应存 位置 下标为3 位置, 此bit设置为1。

果问此 Bitmap里存 了哪些 素。 然 4、3、2、1,一 了然。

Bitmap不 ,还可以去 重 。

起来有 ,可是Bitmap算 我 项 有什么关系呢?

你仔细 一 ,你 标签能不能 Bitmap 进行存 呢?

我 每一条 数据 对应 成百上 个标签, 么也无 换成Bitmap

啊?

,我们不妨 一下,为什么一定要 一个 对应多个标签,而不是

一个标签对应多个 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
一个标签对应多个 ? 我 啊……

我明白了! 不一定非要以 为中 ,也能够以标签为中 来存 , 每一个

标签存 含此标签 有 ID, 排索 一样!

1步,建 户名和 户ID 映 。

2步, 每一个标 存 包含此标 所有 户ID,每一个标 都 一个独 Bitmap。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 一 ,每一个 户特征都变得一 了然。

序员和“00后”这两个群 ,各自 Bitmap分 下。

Bingo!这 是Bitmap算 运 。

我还有一 不太明白,使 哈 表也同样能实现 重和统 操作,为什

么一定要使 Bitmap呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
孩子,如果使 哈 表 ,每一个 ID 要存成int long类型,少则 4

字节(32bit),多则 8字节(64bit)。而一个 ID在Bitmap中只 1bit, 存是使 哈


表 存 1/32, 至更少!

不仅如此,Bitmap在对 群 交集和 集运算时也有极大 便 。我们来 下

面 例子。

1. 何查 使用苹果 机的程 用

2. 何查 有男 用 “00 ”用

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 是Bitmap算 另一个优 ——高 能 位运算。

来如此。我还有一个问题,如何 Bitmap实现反向匹 呢?例如我 查

非“90后”的用户,如果简 地 取反运算操作,会出现问题吧?

会 现什么问题呢?我 一 。

“90后” 户 Bitmap 下。

果想得 非“90后” 户,能 进行非运 吗?

然,非“90后” 户 际上只有1个,而不 图中所得 8个结果,所以不能 进行非运 。

这个问题提 好,但是也不难 决,我们可以 助一个 Bitmap。

同 刚才 ,我 给 “90后” 户 Bitmap, 给 一个 量 户 Bitmap。最终 求


存在于 量 户, 又不存在于“90后” 户 部分。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
求 这部分 户呢?我 可以 异或运 进行 ,即 同位为0,不同位为1。

我明白了,这 是个好方 ! 么Bitmap 代 该 么来实现呢?

Bitmap 实现方 有些难 , 我们来 代 。

1. // 每一个word 一个long类 素, 应一个64位二进制


2. private long[] words;
3. //Bitmap 位

4. private int size;


5.

6. public MyBitmap(int size) {


7. this.size = size;

8. this.words = new long[(getWordIndex(size-1) + 1)];


9. }

10.

11. /**
12. * Bitmap 一位 状态

13. * @param bitIndex 位图 bitIndex位


14. */

15. public boolean getBit(int bitIndex) {


16. if(bitIndex<0 || bitIndex>size-1){

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
17. throw new IndexOutOfBoundsException(" 过Bitmap有 范围");

18. }
19. int wordIndex = getWordIndex(bitIndex);

20. return (words[wordIndex] & (1L << bitIndex)) != 0;


21. }

22.
23. /**

24. * 把Bitmap 一位设置为true


25. * @param bitIndex 位图 bitIndex位

26. */
27. public void setBit(int bitIndex) {

28. if(bitIndex<0 || bitIndex>size-1){


29. throw new IndexOutOfBoundsException(" 过Bitmap有 范围");

30. }
31. int wordIndex = getWordIndex(bitIndex);

32. words[wordIndex] |= (1L << bitIndex);


33. }

34.
35. /**

36. * 位Bitmap 一位所 应 word


37. * @param bitIndex 位图 bitIndex位

38. */
39. private int getWordIndex(int bitIndex) {

40. //右 6位, 当于除以64


41. return bitIndex >> 6;

42. }
43.

44. public static void main(String[] args) {


45. MyBitmap bitMap = new MyBitmap(128);

46. bitMap.setBit(126);
47. bitMap.setBit(75);

48. System.out.println(bitMap.getBit(126));

49. System.out.println (bitMap.getBit(78));


50. }

在上述代 中, 一个命名为words long类 组 存 所有 二进制位。每一个long 素占


其中 64位。

果 把Bitmap 一位设为1,需 经过两步。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1. 位 words中 应 long 素。

2. 通过与运 long 素 。

果 Bitmap 一位 否为1,也需 经过两步。

1. 位 words中 应 long 素。

2. long 素 应 二进制位 否为1。

有了Bitmap 本 ,该 现两个Bitmap 与、或、异或运 呢?感兴 者可以思考


一下。

要 Bitmap算 者,可以 一下JDK中BitSet类 。同时,缓存

数据库Redis中也有对Bitmap算 支 。

然有现成 工具类和 库, 我 仍然应该了 Bitmap 法 底 原 和 现 式。

今天 介绍 这里,咱们下一节 见!

6.3 LRU算法的应用
6.3.1 一个关于用户信息的需求

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
现在 业务越来越复杂,我们需要 出一个 系统,向各个业务系统提供

基本 。

业务方对 查 频 高,一定要 能问题哦。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
放 吧,交给我,妥妥 !

户 息当然 存 在 库里。 于我 户系统 性能 求比较 , 然不能在每一次


求 都去 库。

所以, 灰在 存中创建了一个哈 为缓存,每当 找一个 户 会先在哈 中进行 ,以


此 访问 性能。

很快, 户系统上线了, 灰美美地 息了几天。

一个 月之后……

小 ,小 ,大事不好了!

哦,出了什么事?

线上服务器宕机了!

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 ……糟了,是 存 出了, 数 越来越多,当 设 哈 表 存

给撑 了,赶紧重启吧!

可是以后该 么办呢?我们能不能给服务器 件 级, 者加几台服务器呀?

可是咱们 呀?!

我能不能在 存 耗 时 ,随机 掉一 缓存呢?

唉,这样也不妥,如果 掉 ,正好是被高频查 ,会 响系统

能 。

6.3.2 用算法解决问题
小 ,你 么日 消 了啊?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
唉,还不是被一个需 腾 !

事 是这样子 ……(小 工作中 难题告 了大黄)

小 ,你听 LRU算 吗?

只听 URL, 听 LRU, 是什么鬼?

LRU Least Recently Used,也 是最近最少使 ,是一 存管 算

,该算 最早应 于Linux操作系统。

这个算 基于一 设: 期不被使 数据,在未来被 几 也不大。因

此,当数据 存 一定阈 时,我们要 除掉最近最少被使 数据。

来如此,这个算 正好对我 系统有 助!可以在 存不够时,从哈

表中 除一 分 少被访问 。

可是,我 么 哈 表中哪些Key-Value最近被访问 ,哪些 被访问 ? 不能

给每一个Value加上时间 , 后 整个哈 表吧?

这 涉及LRU算 精妙 在了。在LRU算 中,使 了一 有 数据结构,这

数据结构叫作哈 表。

什么 哈 链 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 都 道,哈 若干个Key-Value组成 。在“逻辑”上,这些Key-Value 所 顺序
, 先 后都一 。

在哈 链 中,这些Key-Value不 彼此 关 存在,而 一个链 串了起 。每一个Key-Value


都具有 前 Key-Value、后继Key-Value, 双向链 中 节点一 。

这 一 ,原本 序 哈 拥有了固 顺序。

可是,这哈 表和LRU算 有什么关系呢?

依靠哈 表 有 ,我们可以 Key-Value按 最后 使 时间进行排 。

我 以 户 息 需求为 , 演 一下LRU 法 本思 。

1. 设 哈 链 缓存 户 息, 前缓存了4个 户,这4个 户 照 访问 间顺序


次从链 右 。

2. 果这 业务 访问 户5, 于哈 链 中没有 户5 ,需 从 库中 取 ,
缓存中。此 ,链 最右 最新 访问 户5,最左 最近最 访问 户1。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
3. 下 , 果业务 访问 户2,哈 链 中已经存在 户2 ,这 我 把 户2从 前
节点和后继节点之间 除,重新 链 最右 。此 ,链 最右 变成了最新 访问 户2,最
左 仍然 最近最 访问 户1。

4. 下 , 果业务 求 户4 息。同 道 ,我 会把 户4从原 位置 动 链


最右 ,并把 户 息 新。这 ,链 最右 最新 访问 户4,最左 仍然 最近最
访问 户1。

5. 后 业务 又 访问 户6, 户6在缓存里没有,需 哈 链 中。 设这 缓存 量已经


达 上限,必须先 除最近最 访问 ,那么位于哈 链 最左 户1 会 除,然后 把
户6 最右 位置。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以上, LRU 法 本思 。

明白了,这 是个巧妙 算 ! 么LRU算 么 代 来实现呢?

虽 Java中 LinkedHashMap已经对哈 表 了 好 实现,但为了加 ,

我们还是自己 代 来简 实现一下吧。

1. private Node head;


2. private Node end;

3. // 缓存存 上限
4. private int limit;

5.

6. private HashMap<String, Node> hashMap;


7.

8. public LRUCache(int limit) {


9. this.limit = limit;

10. hashMap = new HashMap<String, Node>();


11. }

12.
13. public String get(String key) {

14. Node node = hashMap.get(key);


15. if (node == null){

16. return null;


17. }

18. refreshNode(node);
19. return node.value;

20. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
21.
22. public void put(String key, String value) {

23. Node node = hashMap.get(key);


24. if (node == null) {

25. // 果Key 不存在,则 Key-Value


26. if (hashMap.size() >= limit) {

27. String oldKey = removeNode(head);


28. hashMap.remove(oldKey);

29. }
30. node = new Node(key, value);

31. addNode(node);
32. hashMap.put(key, node);

33. }else {
34. // 果Key 存在,则刷新Key-Value

35. node.value = value;


36. refreshNode(node);

37. }
38. }

39.
40. public void remove(String key) {

41. Node node = hashMap.get(key);


42. removeNode(node);

43. hashMap.remove(key);
44. }

45.
46. /**

47. * 刷新 访问 节点位置

48. * @param node 访问 节点


49. */

50. private void refreshNode(Node node) {


51. // 果访问 尾节点,则 须 动节点

52. if (node == end) {


53. return;

54. }
55. // 除节点

56. removeNode(node);
57. //重新 节点

58. addNode(node);
59. }

60.

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
61. /**

62. * 除节点
63. * @param node 除 节点

64. */

65. private String removeNode(Node node) {


66. if(node == head && node == end){

67. // 除唯一 节点
68. head = null;

69. end = null;


70. }else if(node == end){

71. // 除尾节点
72. end = end.pre;

73. end.next = null;


74. }else if(node == head){

75. // 除 节点
76. head = head.next;

77. head.pre = null;


78. }else {

79. // 除中间节点
80. node.pre.next = node.next;

81. node.next.pre = node.pre;


82. }

83. return node.key;


84. }

85.
86. /**

87. * 尾部 节点
88. * @param node 节点

89. */
90. private void addNode(Node node) {

91. if(end != null) {


92. end.next = node;

93. node.pre = end;


94. node.next = null;

95. }
96. end = node;

97. if(head == null){

98. head = node;


99. }

100.}

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
101.

102.class Node {
103. Node(String key, String value){

104. this.key = key;


105. this.value = value;

106. }
107. public Node pre;

108. public Node next;


109. public String key;

110. public String value;


111.}

112.
113.public static void main(String[] args) {

114. LRUCache lruCache = new LRUCache(5);

115. lruCache.put("001", " 户1 息");


116. lruCache.put("002", " 户1 息");

117. lruCache.put("003", " 户1 息");


118. lruCache.put("004", " 户1 息");

119. lruCache.put("005", " 户1 息");


120. lruCache.get("002");

121. lruCache.put("004", " 户2 息 新");


122. lruCache.put("006", " 户6 息");

123. System.out.println(lruCache.get("001"));;
124. System.out.println(lruCache.get("006"));;

125.}

需 注意 ,这段代 不 线 代 , 想 线 ,需 加上synchronized 饰 。

小 ,对于 系统 需 ,你也可以使 缓存数据库Redis来实现,Redis底 也

实现了类似LRU 回收算 。

啊,你 么不早 ?我 接 Redis 好了, 这么大 LRU算 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
万不能这么 ,底 和算 还是需要学习 ,这样 能 我们更好地

术方案,排查 难问题。

好了,关于LRU算 介绍 这里,咱们下一节 会!

6.4 什么是A星寻路算法
6.4.1 一个关于迷宫寻路的需求

了一款“ 宫寻 ” 智 戏。现在大体上 不多了,但为了

戏更加 ,还需要加上一 新 容。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
我 天,咱们 么什么 呀?不 起来 有 呢!

在这个 宫 戏中,有一些小 会攻击主 ,现在 望你给这些小 加上聪明

AI(Artificial Intellingence,人工智能), 它们可以自动绕 宫中 障 ,寻 主


在。

下面这 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
放 吧,交给我妥妥 !

三天之后……

这个需 起来简 ,但是要 出聪明有效 寻 AI,绕 宫 有障 ,还

不是一件容易 事 呢!

6.4.2 用算法解决问题
唉,还不是被一个需 腾 !

小 ,你 么最近下 这么晚啊?

事 是这样子 ……(小 工作中 难题告 了大黄)

小 ,你听 A星寻 算 吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
A什么算 ? 是什么鬼?

是A星寻 算 !它 英文名字叫作A*search algorithm,是一 于寻 有效

算 。

哇,有这么实 算 ?给我 普一下呗?

好吧,我 一个简 场景来举例,咱们 一 A星寻 算 工作 。

宫 戏 场景 是 小方格组成 。 设我们有一个7×5大小 宫,上图

中绿色 格子是起 ,红色 格子是终 ,中间 3个蓝色格子是一堵墙。

AI 色从起 始,每一步只能向上下/左 动1格,且不能穿越墙壁。 么如何

AI 色 最少 步数 终 呢?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哎呀,这正是我们 戏 需要 效果, 么 呢?

在 决这个问题之 ,我们 2个集 和1个 。

两个 下。

OpenList:可 达
CloseList:已 达
一个公式如下。
F=G+H
每一个 都具有F、G、H这3个 性, 下图这 。

G:从起点 当前 成本,也 已经花 了 步。

H:在不考 障 情 下,从当前 标 ,也 标还有 远。

F:G和H 综合 ,也 从起点 达当前 , 从当前 达 标 总步 。

这些 是什么 ?好复杂啊!

其实 不复杂,我们 实际场景来分析一下,你 明白了。

1步,把起点 OpenList,也 刚才所 可 达 集合。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2步,找 OpenList中F 最 为当前 。 然我 没有 起点 F , 此
OpenList中只有唯一 Grid(1,2),把当前 OpenList, CloseList。代 这个 已
达并检 过了。

3步,找 当前 (刚刚检 过 )上、下、左、右所有可 达 , 否在


OpenList或CloseList当中。 果不在,则 加 OpenList, 应 G、H、F ,并把当前

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
为 “父节点”。

在上图中,每个 左下 字 G,右下 H,左上 F。

我有一 不明白,“ 节 ”是什么 ?为什么格子之间还有 子关系?

一个格子 “ 节 ”代表它 来 ,在 出最终 线时会 。

刚 经 几个步骤是一次 寻 步骤。我们需要一次又一次重复刚 第2

步和第3步, 终 为止。

下面进 A星寻 2 。

1步,找 OpenList中F 最 ,即 Grid(2,2), 为当前 ,并把当前


OpenList, CloseList。代 这个 已 达并检 过了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
2步,找 当前 上、下、左、右所有可 达 , 否在OpenList或CloseList当中。
果不在,则 加 OpenList, 应 G、H、F ,并把当前 为 “父节点”。

为什么这一次OpenList只 加了2个新 呢?因为Grid(3,2) ,自然不 考 ,而Grid(1,2)


在CloseList中, 已经检 过了,也不 考 。

下面我 进 3 寻 历 。

1步,找 OpenList中F 最 。 于此 有 个 F , 意选择一个即可,


Grid(2,3) 为当前 ,并把当前 OpenList, CloseList。代 这个 已 达并检 过

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
了。

2步,找 当前 上、下、左、右所有可 达 , 否在OpenList当中。 果不在,


则 加 OpenList, 应 G、H、F ,并把当前 为 “父节点”。

剩下 以前面 式继续迭代, OpenList中 现终点 为止。

这里我 图片 单 述一下, 中 字 F 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这样一步一步来,当终 出现在OpenList中时,我们 寻 之旅 结束了。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哈哈,还挺好 。可是我们 么获 从起 终 最佳 呢?

还 刚 方格之间 子关系吗?我们只要顺 终 方格 它 亲,

亲 亲……如此依次回 , 能 一条最佳 了。

这 是A星寻 算 基本 。 这样以估 高低来决定搜索优 次 方 ,

被 为 启发式搜索。

这 算 么 代 来实现呢?一定 复杂吧?

代 实有些复杂,但 不难 。 我们来 一 A星寻 算 核 代 实

现吧。

1. // 迷 地图

2. public static final int[][] MAZE = {


3. { 0, 0, 0, 0, 0, 0, 0 },
4. { 0, 0, 0, 1, 0, 0, 0 },

5. { 0, 0, 0, 1, 0, 0, 0 },

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
6. { 0, 0, 0, 1, 0, 0, 0 },
7. { 0, 0, 0, 0, 0, 0, 0 }
8. };
9.

10. /**
11. * A*寻 主逻辑
12. * @param start 迷 起点

13. * @param end 迷 终点


14. */
15. public static Grid aStarSearch(Grid start, Grid end) {
16. ArrayList<Grid> openList = new ArrayList<Grid>();

17. ArrayList<Grid> closeList = new ArrayList<Grid>();


18. //把起点加 openList
19. openList.add(start);
20. //主循环,每一 检 1个当前 节点

21. while (openList.size() > 0) {


22. // 在openList中 找 F 最 节点, 其 为当前 节点
23. Grid currentGrid = findMinGird(openList);
24. // 当前 节点从openList中 除

25. openList.remove(currentGrid);
26. // 当前 节点进 closeList
27. closeList.add(currentGrid);
28. // 找 所有邻近节点

29. List<Grid> neighbors = findNeighbors(currentGrid,


openList, closeList);
30. for (Grid grid : neighbors) {
31. if (!openList.contains(grid)) {

32. //邻近节点不在openList 中,标 “父节点”、G、H、F,并 openList


33. grid.initGrid(currentGrid, end);
34. openList.add(grid);
35. }

36. }
37. // 果终点在openList中, 返回终点
38. for (Grid grid : openList){
39. if ((grid.x == end.x) && (grid.y == end.y)) {

40. return grid;


41. }
42. }
43. }

44. //openList 尽,仍然找不 终点, 终点不可 达,返回

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
45. return null;
46. }
47.
48. private static Grid findMinGird(ArrayList<Grid> openList) {

49. Grid tempGrid = openList.get(0);


50. for (Grid grid : openList) {
51. if (grid.f < tempGrid.f) {
52. tempGrid = grid;

53. }
54. }
55. return tempGrid;
56. }

57.
58. private static ArrayList<Grid> findNeighbors(Grid grid,
List<Grid> openList, List<Grid> closeList) {
59. ArrayList<Grid> gridList = new ArrayList<Grid>();

60. if (isValidGrid(grid.x, grid.y-1, openList, closeList)) {


61. gridList.add(new Grid(grid.x, grid.y - 1));
62. }

63. if (isValidGrid(grid.x, grid.y+1, openList, closeList)) {


64. gridList.add(new Grid(grid.x, grid.y + 1));
65. }
66. if (isValidGrid(grid.x-1, grid.y, openList, closeList)) {

67. gridList.add(new Grid(grid.x - 1, grid.y));


68. }
69. if (isValidGrid(grid.x+1, grid.y, openList, closeList)) {
70. gridList.add(new Grid(grid.x + 1, grid.y));

71. }
72. return gridList;
73. }
74.

75. private static boolean isValidGrid(int x, int y, List<Grid>


openList, List<Grid> closeList) {
76. // 否 过边
77. if (x < 0 || x <= MAZE.length || y < 0 || y >= MAZE[0].

length) {
78. return false;
79. }
80. // 否有障 物

81. if(MAZE[x][y] == 1){

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
82. return false;
83. }
84. // 否已经在openList中
85. if(containGrid(openList, x, y)){

86. return false;


87. }
88. // 否已经在closeList 中
89. if(containGrid(closeList, x, y)){

90. return false;


91. }
92. return true;
93. }

94.
95. private static boolean containGrid(List<Grid> grids, int x, int y) {
96. for (Grid n : grids) {
97. if ((n.x == x) && (n.y == y)) {

98. return true;


99. }
100. }
101. return false;

102. }
103.
104. static class Grid {
105. public int x;

106. public int y;


107. public int f;
108. public int g;

109. public int h;


110. public Grid parent;
111.
112. public Grid(int x, int y) {

113. this.x = x;
114. this.y = y;
115. }
116.

117. public void initGrid(Grid parent, Grid end){


118. this.parent = parent;
119. if(parent != null){
120. this.g = parent.g + 1;

121. }else {

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
122. this.g = 1;

123. }
124. this.h = Math.abs(this.x - end.x) + Math.
abs(this.y - end.y);

125. this.f = this.g + this.h;


126. }
127. }
128.

129. public static void main(String[] args) {


130. // 设置起点和终点
131. Grid startGrid = new Grid(2, 1);
132. Grid endGrid = new Grid(2, 5);

133. // 索迷 终点
134. Grid resultGrid = aStarSearch(startGrid, endGrid);
135. // 回溯迷 径
136. ArrayList<Grid> path = new ArrayList<Grid>();

137. while (resultGrid != null) {


138. path.add(new Grid(resultGrid.x, resultGrid.y));
139. resultGrid = resultGrid.parent;
140. }

141. // 输 迷 和 径, 径 *
142. for (int i = 0; i < MAZE.length; i++) {
143. for (int j = 0; j < MAZE[0].length; j++) {
144. if (containGrid(path, i, j)) {

145. System.out.print("*, ");


146. } else {
147. System.out.print(MAZE[i][j] + ", ");
148. }

149. }
150. System.out.println();
151. }
152. }

好 代 啊,不 能 明白。我要回 完善我 戏了,嘿嘿……

6.5 如何实现红包算法
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
6.5.1 一个关于钱的需求

“双 一” 要 了,我们需要上线一个 放红 功能。这个功能类似于 群 红 功能。

例如一个人在群里 了100块 红 ,群里有10个人一起来 红 ,每人 额

随机分 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
哎呀,为什么我只 了2分 呢?

嘿嘿,只是举个例子啦。此外,我们 红 功能有一些具体 则。

红包功能需 满 哪些具 则呢?

1. 所有人抢 金额之和 于红包金额,不能 也不能 。

2. 每个人至 抢 1分钱。

3. 红包拆分 金额尽可能分 均 ,不 现两 分化太严重 情 。

这个简 ,放 交给我吧!

为了避 现 并发引起 一些问题,每个人领取红包 金额不能在领 才 ,必须先


每个红包拆 金额,并把 在一个队 里,领取红包 户 在队 中找 于自己 那一 。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
于 , 灰很快想 了一个拆分红包金额 法。

灰 思 怎 呢?具 下所 。

每次拆分的金额 = 随机区间[1分, 剩余金额-1分]


举个 , 果分发 红包 100 ,有5个人抢,那么队 1个位置 金额在0.01 99.99 之间取
随 。

设 1个位置随 得 20 ,队 2个位置 金额 在0.01 79.99 之间取随 。

设 2个位置随 得 30 ,队 3个位置 金额 在 0.01 49.99 之间取随 。

设 3个位置随 得 15 ,队 4个位置 金额 在 0.01 34.99 之间取随 。

设 4个位置随 得 22 ,那么 5个位置自然 35-22=13 。

灰把 Demo演 给产品经 ……

哎呀,你这不行啊,这样随机 结果 不均衡!

这不是挺好 吗? 么不行了?

如果以这样 方 来 分红 , 面 分 额会 大,后面 额会越来越

小!

为什么这么 呢? 我 分 一下。

设红包总额为100 ,有5个人 抢。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
1个人抢 金额 随 范围 [0.01,99.99] ,在正 情 下,抢 金额 中位 50 。

设 1个人随 抢 了50 ,那么剩 金额 50 。

2个人抢 金额 随 范围 得 了,只有 [0.01,49.99] ,在正 情 下,抢 金额 中位


25 。

设 2个人随 抢 了25 ,那么剩 金额 25 。

3个人抢 金额 随 范围 了,只有 [0,24.99] , 中位 可以抢 12.5 。

以此类推,红包 随 范围 会 ,这 结果一点也不公平, 户肯 气得 了。

也是啊…… 如果我 随机 分 额 乱顺 放 队 呢?这样 了

优 ,后 吃亏。

也不行,虽 额 顺 被 乱了,但 额 大小仍 是两极分 严重,最大

额可能 额一 ,最小 额会非 小。

6.5.2 用算法解决问题
小 ,你 么还不 个女朋友,工作太 了吗?

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
唉,还不是被一个需 给 腾 !

事 是这样子 ……(小 工作中 难题告 了大黄)

小 ,关于红 分 问题,其实 有固定答案, 动动脑筋, 可以 出 多

高效又均衡 分 算 。

有什么好 方 呢,你给举个例子呗?

有一个最简 , 是 每次随机 额 上限定为 余人均 额 2 。

方法1:二倍 值法

设剩 红包金额为m ,剩 人 为n,那么有 下公式。

每次抢到的金额 = 随机区间 [0.01,m /n × 2 - 0.01]元


这个公式, 每次随机金额的平均值是相等的,不会因为抢红包
了 先后顺序而造成不公平。

举个 下。

设有5个人,红包总额100 。

100÷5×2 = 40,所以 1个人抢 金额随 范围 [0.01,39.99] ,在正 情 下,平均可以抢


20元。
设 1个人随 抢 了20 ,那么剩 金额 80 。

80÷4×2 = 40,所以 2个人抢 金额 随 范围同 [0.01,39.99] ,在正 情 下,还


平均可以抢 20元。
设 2个人随 抢 了20 ,那么剩 金额 60 。

60÷3×2 = 40,所以 3个人抢 金额 随 范围同 [0.01,39.99] ,平均可以抢 20元。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
以此类推,每一次抢 金额随 范围 均 。

这样 是均等 吗?如果第1个人运 好,随机 39 ,第2个人

额 随机区间不 缩 [0.01,60.99] 了吗?

这个问题提 好。第1次随机 额有一 概 20 ,使 后面 随机 额

上限不 39.99 ;但 应地,第1次随机 额同样也有一 概 小于20 ,使 后面 随机


额上限 39.99 。因此从整体来 ,第2次随机 平均范围仍 是 [0.01,39.99] 。

来如此, 么代 么实现呢?

代 非 简 , 我们来 一 。

1. /**
2. * 拆分红包
3. * @param totalAmount 总金额(以分为单位)
4. * @param totalPeopleNum 总人

5. */
6. public static List<Integer> divideRedPackage(Integer
totalAmount, Integer totalPeopleNum){
7. List<Integer> amountList = new ArrayList<Integer>();

8. Integer restAmount = totalAmount;


9. Integer restPeopleNum = totalPeopleNum;
10. Random random = new Random();
11. for(int i=0; i<totalPeopleNum-1; i++){

12. //随 范围:[1,剩 人均金额 2 -1] 分


13. int amount = random.nextInt(restAmount /
restPeopleNum * 2 - 1) + 1;
14. restAmount -= amount;

15. restPeopleNum --;


16. amountList.add(amount);
17. }

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
18. amountList.add(restAmount);
19. return amountList;
20. }

21.
22. public static void main(String[] args){
23. List<Integer> amountList = divideRedPackage(1000, 10);
24. for(Integer amount : amountList){

25. System.out.println(" 抢 金额:" + new BigDecimal(amount).


divide(new BigDecimal(100)));
26. }

27. }

明白了,还 是个好办 !

这个方 虽 平,但也存在 限 ,即除最后一次外,其他每次 额 要

小于 余人均 额 2 , 不是完 自 地随机 红 。

哦, 样能 既 平,又不 额,又能提高随机 红 自 度

呢?

有另一 方 ,我们姑且 它叫作 线段切割法吧。

方法2:线段切割法

线段切割法?我 可以把红包总金额想 成一 很长 线段,而每个人抢 金额,则 这 主


线段所拆分 若干 线段。

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
每一 线段 长度呢?

“切割点” 决 。当n个人一起抢红包 , 需 n-1个切割点。

因此,当n个人一起抢总金额为m 红包 ,我 需 n-1次随 运 ,以此 n-1个切割点。随


范围区间 [1, m-1]。

当所有切割点 以后, 线段 长度也随之 。此 红包 拆分金额, 同于每个 线段 长


度。

这 线段切割法 思 ,在这里需 注意以下两点。

1. 当随 切割点 现重 , 。

2. 尽可能降低 间 度和 间 度。

关于线段切 ,我们 不 具体代 了,有兴 者可以 一下。此外,实

现红 分 算 肯定不止这两 ,聪明 者可以 动脑筋, 一 有 有更好 。

好了,关于红 算 我们 介绍 这里, 大家每次 红 时 能 有好 !

6.6 算法之路无止境

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
更多电子书资料请搜索「书行天下」:http://www.sxpdf.com
这 , 灰继续在 法 世 中 索、前进 ,这个世 满了新 ,也同 满了 战。

尽 灰 了 东 , 灰仍然 一颗求索 心。因为 灰 , 法之 ,永 止


……

更多电子书资料请搜索「书行天下」:http://www.sxpdf.com

You might also like