前面介绍了基础类型,和上一章的复杂类型
本章的重点在于更加真实的数据结构, 包括二叉树、链表、hash表、栈、队列
本章主题主要包括以下几个:
- graph 和 node
- 计算算法复杂度
- 二叉树
- 哈希表
- 链表
- 双向链表
- 队列
- 栈
- container标准包中的数据结构
- 随机树的产生
- 难破解的密码
图,一种数据结构, 图G = 顶点V(node) + 叶子E(edge)
图分两种:
- 循环图 全部或部分node组成一个闭合的链
- 非循环图 没有闭合的链
有向图:叶子有方向的。有向非循环图没有闭合的链
算法的评价依据是计算复杂度, O(n) 比 O(n2) 好
二叉树,一个node下最多有两个子node
二叉树的应用场景是:要表达分层数据时, 好处是:树自带排序,还出是删除尽量少做
树的深度,也就是叶子的最大层数, 平衡二叉树,最大叶子深度和最小叶子深度相差不超过1
平衡一个已有二叉树很困难,最容易的是创建的时候就创建一个平衡二叉树。 平衡二叉树的复杂度是log(n),深度是log2(n),
- 10000, 高度是14
- 10w,高度是17
- 100w,高度是20,意思是100w的数据中查找一个值,20次就ok了, 这就是为啥二叉树效率那么高,应用那么广。平衡二叉树的效率非常高,非平衡的效率未知
平衡二叉树在搜索时灵活性非常高,非常高效,她的形状取决于数据加入的顺序, 所以key如果很长很复杂,那么搜索的效率也会很低。
当然某些链表和数组的读取速度比二叉树高,但搜索的时候还是二叉树高。
hash 是一种数据结构,也是一个很大的概念,和graph一样。 graph其中的一种叫树,树中有一种叫二叉树,二叉树有种叫平衡树, 平衡树中有一种叫avl高度平衡树,上一小结认识了avl。 这一节也只认识hash中的一小类:链式hash。
hash里存储的是kv键值对,链式hash有一个数组,还有个hash函数, 通过这个hash函数,key可以计算出一个唯一的索引, 这个索引也叫bucket(翻译是桶,哈希表也叫哈希桶),bucket就是数组元素
多个key计算的bucket可能重复,这叫哈希冲突,解决方式就是链式。 也就是说特征相同(计算结果相同)的放到同一个桶里,而桶里是用链表存的
一个优秀的hash表,她的值是均匀分布的, 最特殊(差)的情况:所有的值都在一个桶里,这时就退化为纯链表了。
hash的优势在于搜索速度的降低,如果bucket有20个, 那么搜索复杂度会降低很多,空间换时间的典型例子, 一般用于字典搜索,或大数据搜索。
链表 双向链表
队列
是链表的一种特殊情况
限制了数据结构的操作,只能 pop 和push, 还要一些其他的约束 fifo
栈 类似队列,不过约束是 filo
我们可以自己写二叉树、hash、链表、队列、栈,标准库也帮我们做了这些
container包括了3个数据结构:
- heap
- list
- ring
ring是一个环链
heap 堆,一种数据结构,可以看成一颗树,go中底层存储可以使用数组或链表
这课树要满足什么条件才能称为heap呢:
- 完全二叉树(除了最底层,其他层节点都是满的,如果最底层也满了,就是满二叉树)
- 任意一节点都大于或小于她的后裔节点
那么堆就分两种:
- 大根堆 - 最大值在根节点上
- 小根堆 - 最小值在根节点上
heap适用于一些 优先级问题
go中提供的container/heap 是小根堆,常用于优先队列
类型断言:x是一个interface,T是一个类型, x.(T) 表达式就是类型断言,判断x里的值是不是类型T
heap的源码是50行左右,实现的功能是"小根堆"
heap的Pop()是根节点,最小值。复杂一点的优先需要自定义
双向链表
实现了一个基本的双向链表
环链,内部是一个双向链表,只是头尾连接在一起了
heap可以做优先队列,list可以补充一下日常需求,ring真心没想到合适的场景, 可能只是go为了充数(炫耀)写的一些玩具
math/rand包提供了产生随机数的功能,不过需要先有一个seed种子, 这个seed在使用之前需要先初始化,这个seed会影响到生成的随机数, 如果seed不变,那么多次产生的随机数是一样的