双向链接的二叉查找树(一):基本概念

本文介绍了双向链接的二叉查找树在处理动态键值对场景中的关键作用,包括为何引入双向链接、结构定义、直接前后继操作,并提到了与普通BST的区别。重点讲解了如何在动态环境中实现插入、删除和查找直接前驱/后继,以及免责声明。
摘要由CSDN通过智能技术生成

1. 背景介绍

从这章开始,我们将会深入讲解一下Voronoi图(Voronoi Diagrams)相关的内容,但是在进入到Voronoi图讲解之前,我们需要先来讲解一下特殊的数据结构——双向链接的红黑树(Doubly-linked red black tree),它的实现和理解对于我们高效实现Voronoi图算法至关重要,所以整个系列文章的结构如下:

  1. 双向链接的二叉查找树(Doubly-linked binary search tree);
  2. 双向链接的红黑树(Doubly-linked red black tree);
  3. 点定位(Point Location);
  4. Voronoi图(Voronoi Diagrams);

为了清晰地理解前两个数据结构,你需要理解原版的二叉查找树和红黑树,不太清楚的童鞋可以参考我写的系列文章:超详细 | 红黑树详解文章汇总(含代码)

2. 基本概念

2.1 为什们需要双向链接?

之所以要在BST(二叉查找树)和BBST(平衡二叉查找树,这里特指红黑树)中引入双向链接的概念,是因为在某些特殊的算法(如Voronoi图)中,我们在插入或删除某个节点时,我们不能使用key的比较来进行查找删除操作,必须依赖树本身的空间结构进行插入和删除,这是什么意思呢?比如下面这个场景:

在这里插入图片描述

再上图的场景中,我们是无法使用之前的插入方式,即自顶向下,通过比较key的大小来确定节点插入到某个节点的左还是右子树之中。但是通过观察,我们也发现了我们可以通过BST的空间结构进行插入,即明确知道插入位置的时候,我们不必依赖key的比较,直接进行插入即可。对于删除操作,也会有同样的问题,大家可以想象一下,某一时刻BST中出现了数个key相同,但是我们明确知道删除的是哪个节点,所以也无需依赖key的比较,直接在树进行删除即可。

相似地,BBST中也会有同样的场景应用,只是会更加复杂,因为BBST中会涉及旋转自平衡,所以接下来我们先来讲解如何在BST中实现上述的需求,在理解BST的基础上,在深入讨论BBST的实现。

除了插入和删除,我们在查找直接前继(Predecessor)或直接后继(Successor)时,也无法通过key的比较来查找,但是通过BST的空间结构关系,我们可以快速知道某个节点的直接前继或后继,比如M的前继就是L,后继是N,我们可以通过某一机制来实现这个快速查找过程,无需依赖key的比较。总之,双向链接的主要目的为:

使BST和BBST无需依赖key的比较,也能快速进行查找,删除等操作。

或许大家也能注意到,出现这些问题的关键在于,我们的BST或BBST的key不再是静态的,而是随时某种状态的改变而改变,即key为动态的,这种状态或是时间,也或是算法的状态。这种BST或BBST,笔者一般称之为动态BST或BBST( Dynamic BST or BBST ),这样的数据结构在计算几何中非常的常见,因为树中存储在Value中的空间几何结构会随着算法的变化,而其对应的key也会产生相应的改变,并非静态一成不变的。

2.2 结构和定义

那双向链接到底是什么意思呢?首先,双向是指:

每个父节点有指针指向其子节点,每个子节点有指针指向其父结点。

其次,链接是指:

BST或BBST都会维护一个有序双向链表用来表示树中的数据。

即2.1中的例子,我们把树的节点投影到数值上,而我们用双向链表(Doubly-linked list)来存储这样的数轴。这样,我们就能在O(1)时间内查找某个节点的直接前继或后继,下图将展示一个完整的双向链接BST:

在这里插入图片描述
经过这样的处理,大家也需要注意到,这样的BST已经不是一棵树了,严格意义上来说,它是一个连接图(Connected Graph)。

3. 直接前后继

在理解双向链接的概念之后,我们就来看看如何实现上面的需求。首先,我们先来看看直接前后继的实现。实现这个操作,我们需要维护一个双向链表。双向链表是一个非常基础的数据结构,不是很懂的童鞋可以网上查查其相关的概念和基础操作,这里不再赘述。

与普通的双向链表不同,我们这里需要它有直接插入和删除节点的能力,因为它的结构会根据BST的改变而改变,所以Java内置的LinkedList就不能满足我们的需求,因为内置的链表不会暴露节点给外部,所以插入和删除操作都是O(n),如果用它去实现双向链接BST,会使BST的性能大幅退化,所以需要一个能允许外部直接插入和删除节点的双向链表。

以删除为例:

/**
  * remove the given node in this linked list and return its value.
  * return null if this list is empty.
  * Do nothing if the node is not in this list.
  * */

public E remove( DoublyLinkedNode<E> node ) {
    if ( node == null ) return null;
    assert contains( node );

    if ( node.prev != null ) node.prev.next = node.next;
    if ( node.next != null ) node.next.prev = node.prev;

    if ( front == node ) front = node.next;
    if ( end == node ) end = node.prev;

    node.prev = null;
    node.next = null;

    size--;
    assert check();
    return node.data;
}

传入的参数就是一个节点,而不是下标或者节点里面的值,具体的删除操作和普通的链表基本一致,大家可以结合代码自己画图理解一下,应该不难。在实现这样的链表之后,我们就能快速查找某个树节点的直接前后继了,方法非常简单,我们只需要找到某个树节点在链表中的节点,再查找其上一个(下一个)节点即可。所以除了链表节点需要存储树节点,这个树节点也需要存储它所在的链表节点(树节点 <=> 链表节点),之后我们可以查看链表节点的直接前继或后继。以查找前继为例:

/**
  * find node's predecessor. Assume that the node is already in this BST.
  * Note that this method doesn't compare keys to find the predecessor,
  * so is different from lower();
  *
  * Ordered linked list is the key to implementing this method.
  * */

default MapTreeNode<K, V> predecessor( MapTreeNode<K, V> node ) {
    if ( node == null ) return null;

    return node.node.getPrev() == null ? null : node.node.getPrev().getData();
}

我们查找给定树节点中的链表节点,看看它是否有前继节点。另外,如果想实现BST或BBST能保留插入顺序,可以使用这里相似的思路:维护另一个双向链表,用来存储插入顺序下的数据,而非树本身的空间结构。

接下来,我们将讲解一下实现双向链接功能的插入和删除操作,之后我们会进入到双向链接BBST的讲解之中。

上一节:终章:二叉查找树和红黑树的性能比较
下一节:双向链接的二叉查找树(二):直接插入和删除
系列汇总:塞尔达和计算几何 | Voronoi图详解文章汇总(含代码)

4. 免责声明

※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值