数据结构与算法学习⑫
数据结构与算法学习⑫
索引利器B+树
B+树:是B树的变形,也是一种平衡的多路搜索 树形数据结构,多用于文件系统,数据库的实现
B+树
相同点
不同点
B+树中数据都存储在叶子节点中,非叶子节点只存储关键字(索引),故非叶子节点也被称为索引节点(内部节点); 而B树中,所有节点都存储数据
总结
B+树中叶子结点都存有相邻叶子结点的指针,且依关键字的大小自小而大顺序链接
B+树中父节点存储的关键字来自右孩子中的第一个元素(最小元素或最大元素均可)
查找
B+树的查找一定是在叶子节点命中,而B树可能会在非叶子节点命中
1、从根节点开始通过与关键字比较决定去左边,中间,还是右边的子树,依次同理直到叶子节点
2、在节点内部可以使用的典型查找算法如:二分查找
添加
如何解决上溢
叶子节点分裂:从中间位置分裂成左右两个叶子节点(中间位置的元素归右边)
选取分裂后右边节点内最小元素的关键字等信息向上合并,作为父节点索引存储
删除
被删除的肯定都是叶子节点中的元素
如果非叶子节点中存在被删除元素的关键字索引需要重新选取新的关键字索引
如何解决下溢
如果临近的兄弟节点能借元素:下溢节点跟兄弟节点借一个元素
如果临近的兄弟节点无法借元素:下溢节点和临近的兄弟节点合并,父节点中的关键字索引要下来
MYSQL索引利器
MYSQL架构
核心组件
存储引擎对比
索引的本质
**索引(Index)**是帮助MySQL高效获取数据的数据结构
索引系统如何设计?
读取索引可能需要返回什么样的数据?
mysql索引是否可以采用这种返回数据格式?
索引的数据格式应该是怎样的?
k:v 结构
索引可以采取什么样的数据结构?
官方文档
结论:使用什么样的数据结构跟存储引擎有关系,比如InnoDB,MyISAM本质上使用B+树(InnoDB也支持自适应hash), Memory使用hash
索引选择HASH的场景分析
选什么树作为mysql的索引数据结构?
1、计算机局部性原理(空间/时间):可参考 局部性原理 如果一个信息项正在被访问,那么在近期它很可能还会被再次访问; 在最近的将来将用到的信息很可能与正在使用的信息在空间地址上是临近的。
2、磁盘预读:操作系统跟磁盘交互时有一个最小的逻辑单元,称之为:页,无论是将磁盘中的数据加载到内存中, 还是将内存中的数据写回磁盘,操作系统都会以页为单位进行操作,哪怕我们只向磁盘中写入一个字节的数据 ,我们也需要将整个页中的全部数据刷入磁盘中,页大小一般是4KB或8KB,在进行数据操作时也可以是页的整数倍, Innodb引擎默认读取16KB,可通过: SHOW GLOBAL VARIABLES LIKE ‘%innodb_page_size%’; 查询
B树和B+树的对比分析
总结
真正在mysql中 B+树的高度也 就在3~4层
skiplist-跳跃表
概念
是由William Pugh发明的,最早出现于他在1990年发表的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》
增加了向前指针的链表叫作跳表。跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
普通有序链表:
如何提升效率?
空间换时间,升维
skiplist如何用空间换时间
假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,会发生什么?
新增加的指针连成了一个新的链表,包含的节点个数只有原来的一半(7, 19, 26)
查找/插入23如何操作?
先沿着这个新链表进行查找,当碰到比待查数据大的节点时,再回到原来的链表中进行查找
如果想继续提高效率 ?
继续为每相邻的两个节点增加一个指针,从而产生第三层链表
查找/插入23如何操作? 可以一次性跳过19前面的元素
当链表足够长的时候, 这种多层链表的查找 方式能让我们跳过很 多下层节点,大大加快查找的速度
查找复杂度分析
按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半, 这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)
弊端:插入/删除元素后,打乱了上下相邻两层链表上节点个数严格的比例关系。为保证这种严格的关系就需要调整,而重新调整链表会让复杂度蜕化成O(n)
如何避免插入/删除复杂度退化?
不要求上下相邻两层链表之间的节点个数有严格的对应关系
每个节点随机出一个层数(level)
1、节点的层数(level)是随机 出来的
2、新插入节点不会影响其它节点的层数,只需要修改插入节点 前后的指针
跳表,指的就是除了最下面第1层 链表之外,会产生若干层稀疏的 链表,这些链表里面的指针跳过 了一些节点(而且越高层的链表 跳过的节点越多)。这就使得我 们在查找数据的时候能够先在高 层的链表中进行查找,然后逐层 降低,最终降到第1层链表来精确 地确定数据位置。在这个过程中, 我们跳过了一些节点,从而也就 加快了查找速度
skiplist性能分析
节点插入时随机出一个层数,依靠一个简单的随机数操作而构建出来的多层链表结构,能保证良好的查找性能吗?
1、首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。
2、如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那 么它有第(i+1)层指针的概率为p。
3、节点最大的层数不允许超过一个最大值,记为MaxLevel。
经概率统计分析:
1、一个节点的平均层数(也即包含的平均指针数目)为:1/1-p
2、从第1层到最高层,各层链表的平均节点数是一个指数递减的等比数列
查找一个元素的平均时间复杂度为:O( log n )
skiplist,平衡树,哈希表对比
1206. 设计跳表
class Skiplist {
private float p=1/4;
private int Max_Level=32;
private Node head;//头结点
private int levelCount;//最高层数
private Random random;
public Skiplist() {
this.head=new Node(-1,Max_Level);
random=new Random();
}
public boolean search(int target) {
Node p=head;
//从最高层开始查找
for(int i=levelCount-1;i>=0;i--){//往下移动一鞥
while(p.next[i]!=null&&p.next[i].key<target){
p=p.next[i];
}
}
//targer都在1层,判断是否找到
if(p.next[0]!=null&&p.next[0].key==target){
return true;
}
return false;
}
//需要一个函数产生随机层数
private int randomLevel(){
int level=1;//第一层是必须的
while(random.nextInt()<p&&level<Max_Level){
level++;
}
return level;
}
public void add(int num) {
//定义新元素应该占几层
int level=head.next[0]==null?1:randomLevel();
//如果随机level超过目前最大层数,意味着要上涨很多层,我们选择每次只上涨一层
if(level>levelCount){
level=++levelCount;
}
//创建新节点
Node newNode=new Node(num,level);
//从最高层开始查找,找到新节点要插入的位置
Node p=head;
for(int i=levelCount-1;i>=0;i--){
while(p.next[i]!=null&&p.next[i].key<num){
p=p.next[i];
}
if(i<level){//从这层往下到最底层;某些节点后需要添加一个新节点newNode
if(p.next[i]==null){
//直接在后面街上新节点
p.next[i]=newNode;
}else{
//在当前节点和当前节点后面节点的中间插入新节点
Node next=p.next[i];
p.next[i]=newNode;
newNode.next[i]=next;
}
}
}
}
public boolean erase(int num) {
boolean exist=false;
Node p=head;
for(int i=levelCount-1;i>=0;i--){
while(p.next[i]!=null&&p.next[i].key<num){
p=p.next[i];
}
if(p.next[i]!=null&&p.next[i].key==num){
exist=true;
p.next[i]=p.next[i].next[i];
}
}
return exist;
}
class Node{
int key;
Node[]next;
public Node(int key,int level){
this.key=key;
next=new Node[level];
}
}
}
/**
* Your Skiplist object will be instantiated and called as such:
* Skiplist obj = new Skiplist();
* boolean param_1 = obj.search(target);
* obj.add(num);
* boolean param_3 = obj.erase(num);
*/