树论

符号表:描述一张抽象的表格,我们将信息(值)存储在其中,然后按照指定的键来搜索获取这些信息。符号表的实现方式:

1,BST二叉查找树(binary search tree):

数据结构:节点。每个节点只有一个父节点(除了根节点没有父节点),并且每个节点都只有左右两个链接,分别指向自己的左子节点和右子节点。还包含一个键和一个值。键是有顺序的。

特性:一个二叉查找树是一棵二叉树。其中每个节点都含有一个可以比较的键,且每个节点的键都大于其左子树中的任意节点的键,并且小于右子树的任意节点的键。

构建一棵二叉查找树,我们第一步想到的就是首先写一个插入方法,在插入的时候,先调用一个search方法(search方法就是被查找的键等于当前节点,命中返回,如果大于则找右边,否则找左边,递归),看看有没有找到,如果找到的话,直接更新此节点的值,如果没找到,用一个hot指针,指向最后一个没找到节点的父节点。这时候,插入就很简单了。这里要注意的地方在于指针。一次遍历可以找到许多有用的信息,这时候需要合理的使用遍历,多使用指针,去维护可能会用到的信息。同时删除操作也是一样,先调用查找方法,看看是否存在,如果不存在,直接返回,如果存在的话,使用此节点的右子树的最小值或者此节点的左子树的最大值替换当前节点。假设此节点为x,具体步骤:1,将指向被删除节点的值保存为t。2,将x指向x右子树中最小的节点(x=min(t.right))。3,将x的右链接指向删除了最小节点的右子树(x.right=delete(t.right))。4,将x的左连接指向t.left。

致命缺点:树的性能取决于树的形状(矮矮的,扁扁的最好),而树的形状取决于插入节点的顺序

 

2,AVL树(也就是BBST,即: balance binary search tree)

基于上面的二叉查找树,约定,树的高度等于从根节点到当前节点做走的路径+1。引入一个平衡因子的概念,平衡因子是指对于任意的包含于这棵树的节点n,有左右子树的高度差不超过1。这样的二叉树,已经变成了一个扁扁的二叉树了,这里引入一个新的概念,旋转,旋转是指我们可以改变树的形状。比如删除了一个节点后(或者插入了一个节点),这个被删除节点的父节点hot节点的负载因子大于1了,这时候,我们需要通过旋转,使得平衡因子不大于一。如何旋转呢,找到当前节点的孩子中最深的,最深中最深的,从两次最深的那个节点,向上的父亲的父亲(如果你的二叉树结构中没有包含父节点的引用,如何找到父节点?--在找到最深的过程中,维护一个指针hot),这三个节点,我们需要将旋转之前的三个节点的中序遍历顺序标记为节点a,b和c,以及这三个节点对应的四棵子树的中序遍历顺序标记为t1,t2,t3,t4,然后将b作为a,c的父节点,a的左右分别指向t1和t2,c的左右节点分别指向t3,t4。这样就完成了一次旋转。也就是我们可以调整树的上下位置,但是不可以调整数的左右位置,这个左右位置是指映射在一条直线上的顺序,也就是中序遍历的顺序,这也是二叉查找树的特点,那就是小中大。这还没完,

缺点:这里虽然会重新平衡,但是依旧和插入顺序有关系,貌似??不大确定

3,2-3树

这个就厉害了。标准二叉树中的节点为2-节点,即一个键和两个链接。这里引入3-节点,它包含两个键和三个链接,分别对应小中大,类似于三向切分排序。构造一个2-3树,还是挺简单的,只需要在查找的时候,如果失配,就插入在失配的节点上,不断的添加,最终形成一个4-节点,即三个键a,b,c,四条链接t1,t2,t3,t4,这时候再分解这个4-节点。但是分解的时候会有点儿麻烦,那是因为如果这个四节点如果是根节点的话,那么可以直接将树的高度加1,并且拆成三个2-节点,但是如果这个四节点的父节点不为空,还要分情况。要在脑海中构造模型。

反正就是一路分解,一直会延续到根。这时候,节点的插入顺序影响不了树了,因为树永远是完美平衡的。这里需要,这里还有个逆操作,那就是合成3-节点或者是4-节点。todo

4,红黑树

鉴于2-3树的优异性能,但是这样表示二三树有很麻烦,这时候,大神出现了。基于上面的2-3树,提出了红黑树。其实呢,红黑树只是把3-节点变了一下,拆成了两个相连的节点a和b。但是为了表示这是个3-节点,就认为规定了一下相连的线(a指向b的或者是b指向a的那条链接)标记为红色。为什么要这样做呢。其实是说,我这里是一个3-节点可以优化的意思。并且,新插入的节点,指向此节点的链接,都是红色的,因为这时候还没有平衡呢,这些节点都是需要优化的,意思是这里都是可以直接表示为3-节点的,如果一个节点的左右子树的链接都是红色的,那就说明这个节点其实是个4-节点链接,需要分解啦。但是这里的分解相对简单一点儿了,直接将颜色传递给上层。也就是左右节点都标记为黑色。但是指向当前节点的链接被标记为红色。其实这和2-3树的分解过程基本一致,都是向上传递。唯一的不同在于,这里处理的情况比较少,只有四种。因为我们只用处理四节点的情况,但是这里的四节点,都是3个二节点表示的。所以需要平衡三个二节点,回忆一下AVL是如何平衡的,一模一样的。但是问题往往不是那么容易的,如果这时候需要删除一个节点,会使得树的平衡性被打破,这时候,需要重新平衡。那怎么删除呢?或者说删除,并且保持平衡呢?答案就是从AVL树中找灵感,那就是不要删除一个2-节点,因为这样需要重新平衡,如果从三节点,或者四节点删除节点,直接删除就好了,但是如果是2-节点,要把这个节点凑成一个三节点或者四节点,然后再删除。

计算机有个局部性原理,也就是说如果一个值被计算机访问,那么这个值周边的值就很可能被接下来访问到。其实这应该是数组导致的。所以在这里,即把图横着放置的话,树顶在左边,利用局部性原理,这里其实应该让树更加接近树顶,这样就会加快访问速度。

 

5,散列表

散列表就更厉害了,前面中性能最好的应该是红黑树了,红黑树的时间复杂度应该是logn,但是散列表可以达到常数级别。其实就是利用散列函数,将键散列成数组的索引,然后在索引的位置存储值。理想情况下是每一个我们需要存储的值散列出来的数组索引,都是不重复的,但这是不大可能的,因为如果要这样的话,这个数组将会非常大,占用太大的内存,但是如果有重复的话,就要解决冲突。常用的方法有拉链法和线性探测两种。拉链法不用多说,也就是如果两个散列出来的值相同,就排个队,数组加链表的实现。线性探测法,名字听起来很高大上,但是实际上是利用了空位存储,空位是如何产生的呢,首先保证的是key的数组长度不小于要存储的数的个数,这样如果没有冲突,就是一人占一格,但是如果有冲突,就会有两人占一格或者多人占一格,这时候就有空位出来了。既然有空位,我们就要好好利用这些空位,当我们在存储数据的时候,如果发现键散列出来的索引位置不为空,那么,这时候,就将索引值加一,也就是去找下一个位置,看看是不是空的,如果是空的,用存在这里,但是如果不是空的,还要接着往下走,直到找到一个空位,然后使用。数组的缺点相比大家都是清楚的,由于数组在计算机存储的时候,是一个整块的内存,顺序存储的,当我们想要从这个散列表中删除一个键值对的话,就糟糕了。因为如果只是简单的将这个位置的键和值都置为空的话,如果此前因为冲突然后找空位的过程中有人越过你,在你的右边存储进行了存储。那么这个数据就不可达了。因为查找的过程是要查找的键所在的索引如果为空的话,就没找到,但是如果不为空,但有不相等,说明这个位置被别人占了,要索引加一,一直往下找,知道遇见一个空位,返回失败。但这时,由于删除操作导致的空位,造成了不正确的情况。这是不允许的。所以简单的将键值都置为空,是不合理的。那么怎样是合理的呢,那就是将要被删除的键的右边的所有数据都重新插入散列表。这里其实很关键的一个点在于,对于单个键而言,先将键值存下来,然后对应位置清空,插入散列表。重复直到被删除键的右边都这样操作过一次。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值