这几种树都属于数据结构中较为复杂的,在平时面试中,经常会问理解用法,但一般不会问具体的实现,所以今天来梳理一下这几种树之间的区别与联系,感谢知乎用户@Cailiang,这篇文章参考了他的专栏。

二叉查找树

是一棵空树,或是具有下列性质的二叉树:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。

插入数据:

1 如果根节点为空,则将插入的节点作为根节点

2 否则和根节点比较(我们是通过key来比较,所以 K 必须是实现了Comparable接口的对象)如果比根节点小,则新节点会插入到左子树中,如果比根节点大,则新节点会插入到右子树中,如果和根节点相等,则更新根节点的值

通过key查询:

1 如果根节点为空,返回 null

2 如果,key 和根节点的key相同,则返回根节点

3 如果,key比根节点的key小,则递归查找根节点的左节点

4 如果,key比根节点的key大,则递归查找根节点的右节点

删除节点:

1 将要删除的节点没有子节点 ----> 直接删除

2 将要删除的节点下有一个子节点 -----> 将要被删除的节点的子节点挂靠到将要被删除的节点的父节点上即可

3 将要删除的节点下有两个子节点 ----> 在将要被删除的节点的右子树中找到一个最小节点,然后用找到的最小节点与需要删除的节点替换。最后再将最小节点进行删除。(这里你可能有其它的方案,我们这里这么做的原因是一,右子树的最小节点一定没有左节点,处理起来会简单一些,二,这样可以保持树的高度不变,甚至是降低树的高度,树的高度越低意味着操作的时间复杂度越低。所以实现删除一个节点的原则是,用最小的代价实现删除的功能,并且保持树的高度不变甚至降低树的高度)

搜索二叉树的增删改查的性能都不错,但是在一些特殊情况下,搜索二叉树的性能可能由对数型变成线性,性能大大降低,比如插入的一组数据是有序的,那么二叉搜索树的结构将变成一个链表时间复杂度变为 O(N),也就是说插入一组有序或局部有序的数据将会导致二叉搜索树不平衡,树的高度会很大,时间复杂度会趋近于 O(N)。

为了解决这个问题,引出了平衡二叉树(AVL)。

平衡二叉树,首先是一棵二叉查找树,但是它满足一点重要的特性:每一个结点的左子树和右子树的高度差最多为1。这个高度差限制就完全规避了上述的最坏情况,因此查找、插入和删除的时间复杂度都变成了O(lg n)。

AVL树虽然是一个完美平衡的二叉排序树,并且保证插入,删除,查找的时间复杂度为 lg n, 但是AVL树的应用却不广泛,原因就是维护一颗AVL树操作太复杂,成本太高。当我们对一颗AVL树进行插入或删除的操作时,我们需要不断的回朔来修改平衡因子(左子树的高度减去右字树的高度),需要通过左旋转,右旋转来保证每个节点的左右子树的高度差不超过1。这些复杂的操作甚至抵消了AVL树给我们带来的性能上的提升,所以我们一般不会使用AVL树。

红黑树

吸取了AVL树和2-3树的思想,但放弃了AVL树完美平衡的特性,改为局部平衡或完美黑色平衡;放弃了2-3树3节点,改为通过颜色(红色和黑色)来区分不同的节点类型,这样就降低了维护平衡的成本和实现的复杂度,可以直接使用二叉排序树的查询方法来查询无需任何修改。

恢复红黑属性需要少量(O(log n))的颜色变更(这在实践中是非常快速的)并且不超过三次树旋转(对于插入是两次)。插入和删除为 O(log n) 次,但是这导致了非常复杂的操作。

红黑树的性质:

1 所有节点都是红色或者黑色

2 根节点为黑色

3 所有的 NULL 叶子节点都是黑色

4 如果该节点是红色的,那么该节点的子节点一定都是黑色

5 所有的 NULL 节点到根节点的路径上的黑色节点数量一定是相同的

B-Tree

前面介绍了AVL树,红黑树,它们都属于二叉树,即每个节点最多只能拥有2个子节点,而B-tree(B树)的每个节点可以拥有2个以上的子节点,所以简单概括一下:B-tree就是一颗多路平衡查找树,它广泛应用于数据库索引和文件系统中。

首先我们介绍下 m 阶B-tree的特性,那么这个 m 阶是怎么定义的呢?这里我们以一个节点能拥有的最大子节点数来表示这颗树的阶数。举个例子,如果一个节点最多有 n 个key,那么这个节点最多就会有 n+1 个子节点,这棵树就叫做 n+1(m=n+1)阶树。一颗 m 阶B-tree包括以下5条特性:

1 每个节点最多有 m 个子节点

2 除根节点和叶子节点,其它每个节点至少有 [m/2] (向上取整的意思)个子节点

3 若根节点不是叶子节点,则其至少有2个子节点

4 所有NULL节点到根节点的高度都一样

5 除根节点外,其它节点都包含 n 个key,其中 [m/2] -1 <= n <= m-1

B+ Tree是B Tree的一个变种,不同之处在于:

第一:在 B-Tree中一个含有n个子树的节点有n-1个关键字(key)。而在 B+Tree中一个含有n个子树的节点有n个关键字(key)。

为什么在拥有同样子树的情况下B+Tree的节点多需要一个key呢?那是因为 B+Tree的节点会存储该节点的子树中最小的key。

第二:B-Tree的每个节点都包含了关键字(key)以及指向包含这些关键字记录的指针。而 B+Tree非叶子节点仅用来索引,数据都保存在叶子节点中。

在叶子节点中存储了所有的关键字信息,以及指向包含这些关键字记录的指针。而且这些叶子节点构成一个有序链表,即每个叶子节点会有一个指针指向其兄弟节点。在非叶子节点中只存储了关键字信息。

下面这张图画的非常好:

在数据库索引的实现中,大部分采用的是B+Tree而不是B-Tree,这又是为什么呢?

原因有二,其一是由于B+Tree 在非叶子节点中只存储了关键字信息,而没有存储指向包含这些关键字记录的指针,所以在树的高度相同时,B+Tree往往能比B-Tree存储更多的关键字信息。更最要的原因是因为 B+Tree在叶子节点中存储了所有的关键字信息,以及指向包含这些关键字记录的指针。而且这些叶子节点构成一个有序链表,这样B+Tree在实现范围查询的时候就比较容易,只需要遍历这个有序链表就行。而B-tree要实现范围查询则比较困难,但范围查询又是数据库中比较常用的功能,所以数据库中大部分采用的是B+Tree而不是B-Tree。当然B-Tree也有强于B+tree的地方,例如在随机查询中,由于B-Tree的每个节点都包含了关键字(key)以及指向包含这些关键字记录的指针,所以B-Tree可能中途就查询到需要的数据,不需要遍历到叶子节点。而B+Tree由于只在叶子节点中存储了所有的关键字信息,以及指向包含这些关键字记录的指针。在非叶子节点中只存储了关键字信息,没有存储指向包含这些关键字记录的指针,所以B+Tree一定要遍历到叶子节点才能获取到包含这些关键字记录的指针。所以B-Tree的随机查询性能会高于B+Tree。

在B+树的基础上又演变出B*树,B*Tree在非叶子结点中也增加了指向兄弟节点的指针,并且它将非叶子节点上存储的关键字个数的最小值提高到 (2/3) * m,这样的话就提高了空间利用率。

 public class BinarySerachTree<K extends Comparable<K>, V> {

     private class Node {
private K key; // 存储的key
private V value; // 存储的值
private Node leftNode; // 左节点
private Node rightNode; // 右节点 public Node(K key, V value, Node leftNode, Node rightNode) {
super();
this.key = key;
this.value = value;
this.leftNode = leftNode;
this.rightNode = rightNode;
} @Override
public String toString() {
return "{\"key\":" + this.key + ", \"value\":" + "\"" + this.value + "\"" + ", \"leftNode\":"
+ this.leftNode + ", \"rightNode\":" + this.rightNode + "}";
}
} private Node root; // 根节点 public void put(K key, V value) {
Node newNode = new Node(key, value, null, null);
if (null == root) {
root = newNode;
} else {
upsert(null, root, newNode);
}
} private void upsert(Node parent, Node current, Node newNode) {
if (null == current) {
if (newNode.key.compareTo(parent.key) > 0) {
parent.rightNode = newNode;
} else {
parent.leftNode = newNode;
}
} else {
int result = newNode.key.compareTo(current.key);
if (result == 0) {
current.value = newNode.value;
}
parent = current;
if (result > 0) {
upsert(parent, parent.rightNode, newNode);
}
if (result < 0) {
upsert(parent, parent.leftNode, newNode);
}
}
} public Node get(K key) {
if (null != key) {
return find(key, root); // 从根节点开始找
}
return null;
} private Node find(K key, Node root) {
if (null != root) {
int result = key.compareTo(root.key);
if (result == 0) {
return root;
}
if (result > 0) {
return find(key, root.rightNode);
}
if (result < 0) {
return find(key, root.leftNode);
}
}
return null;
} public boolean delete(K key) {
if (null != key) {
if (null != root) {
return deleteNode(key, root, null);
}
}
return false;
} private boolean deleteNode(K key, Node current, Node parent) {
if (null != current) {
if (key.compareTo(current.key) > 0) {
return deleteNode(key, current.rightNode, current);
}
if (key.compareTo(current.key) < 0) {
return deleteNode(key, current.leftNode, current);
}
if (key.compareTo(current.key) == 0) {
if ((null != current.leftNode) && (null != current.rightNode)) { // 将要删除的节点下有两个子节点
dleTwoChildrenNode(current);
return true;
} else {
if ((null == current.leftNode) && (null == current.rightNode)) { // 将要删除的节点没有子节点
if (current.key.compareTo(parent.key) > 0) {
parent.rightNode = null;
} else {
parent.leftNode = null;
}
return true;
} else { // 将要被删除的节点的子节点挂靠到将要被删除的节点的父节点上即可
Node childNode = (null == current.leftNode) ? current.rightNode : current.leftNode;
if (current.key.compareTo(parent.key) > 0) {
parent.rightNode = childNode;
} else {
parent.leftNode = childNode;
}
return true;
}
}
}
}
return false;
} /**
* 处理被删除节点有两个子节点的情况
* @param parent
* 将要被删除的节点
*/
private void dleTwoChildrenNode(Node parent) {
Node parentRight = parent.rightNode;
Node tmp = parentRight.leftNode;
if (null == tmp) {
parent.value = parentRight.value;
parent.key = parentRight.key;
parent.rightNode = parentRight.rightNode;
} else {
Node tmpParent = parentRight;
while (null != tmp.leftNode) {
tmpParent = tmp;
tmp = tmp.leftNode;
}
parent.value = tmp.value;
parent.key = tmp.key;
tmpParent.leftNode = tmp.rightNode;
}
} public static void main(String[] args) { BinarySerachTree<Integer, String> bst = new BinarySerachTree<Integer, String>(); bst.put(100, "v100");
bst.put(50, "v50");
bst.put(150, "v150");
bst.put(20, "v20");
bst.put(85, "v85");
bst.put(10, "v10");
bst.put(15, "a15");
bst.put(75, "v75");
bst.put(95, "v95");
bst.put(65, "v65");
bst.put(76, "v76");
bst.put(60, "v60");
bst.put(66, "v66");
bst.put(61, "v61"); System.out.println(bst.get(100));// 打印根节点
}
}

BinarySerachTree

 public class RedBlackTree<K extends Comparable<K>, V> {

     private static final byte RED = 1;
private static final byte BLACK = 0; private class Node {
private byte color;
private K key; // 存储的key
private V value; // 存储的值
private Node leftNode; // 左节点
private Node rightNode; // 右节点
private Node parentNode; // 父节点 public Node(K key, V value, Node leftNode, Node rightNode, byte color, Node parentNode) {
super();
this.key = key;
this.value = value;
this.leftNode = leftNode;
this.rightNode = rightNode;
this.color = color;
this.parentNode = parentNode;
} @Override
public String toString() {
return "{" + "\"key\":" + this.key + ", " + "\"value\":" + "\"" + this.value + "\"" + ", " + "\"color\":"
+ this.color + ", " + "\"leftNode\":" + this.leftNode + "," + "\"rightNode\":" + this.rightNode
+ "}";
}
} private Node root; // 根节点 public Node getRoot() {
return this.root;
} private void leftRotation(Node h) {
Node m = h.rightNode;
// 1. 将 k 节点设置为 h 的右节点
h.rightNode = m.leftNode;
if (null != m.leftNode) {
m.leftNode.parentNode = h;
}
// 2. 将 h 的父节点 赋给 m 的父节点,之后分 3 种情况讨论
m.parentNode = h.parentNode;
if (null == m.parentNode) { // I: 说明 h 原来是根节点,现在将 m 设置为新的根节点
root = m;
} else {
if (h.key.compareTo(h.parentNode.key) < 0) { // II: 说明 h 原来是它父节点的左孩子,现在将 m 设置为新的左孩子
h.parentNode.leftNode = m;
} else {
h.parentNode.rightNode = m; // III: 说明 h 原来是它父节点的右孩子,现在将 m 设置为新的右孩子
}
}
// 3. 将 h 挂靠在 m 的左孩子上
m.leftNode = h;
h.parentNode = m;
} private void rightRotation(Node m) {
Node h = m.leftNode;
// 1. 将 k 设置为 m 的左节点
m.leftNode = h.rightNode;
if (null != h.rightNode) {
h.rightNode.parentNode = m;
}
// 2. 将 m 的父节点 赋给 h 的父节点,之后分 3 种情况讨论
h.parentNode = m.parentNode;
if (null == m.parentNode) { // I: 说明 m 原来是根节点,现在将 h 设置为新的根节点
root = h;
} else {
if (m.key.compareTo(m.parentNode.key) < 0) { // II: 说明 m 原来是它父节点的左孩子,现在将 h 设置为新的左孩子
m.parentNode.leftNode = h;
} else {
m.parentNode.rightNode = h; // III: 说明 m 原来是它父节点的右孩子,现在将 h 设置为新的右孩子
}
}
// 3. 将 m 挂靠在 h 的右孩子上
h.rightNode = m;
m.parentNode = h;
} /**
* 插入新的节点,如果指定的key已经存在,则更新原来的值
*
* @param key
* @param value
*/
public void put(K key, V value) {
Node newNode = new Node(key, value, null, null, RED, null);
if (null == root) {
root = newNode;
root.color = BLACK;
} else {
upsert(null, root, newNode);
}
} private void upsert(Node parent, Node current, Node newNode) {
if (null == current) {
if (newNode.key.compareTo(parent.key) > 0) {
parent.rightNode = newNode;
} else {
parent.leftNode = newNode;
}
newNode.parentNode = parent;
upsertFix(newNode); // 插入新节点后 对红黑树进行修复
} else {
int result = newNode.key.compareTo(current.key);
if (result == 0) {
current.value = newNode.value;
}
parent = current;
if (result > 0) {
upsert(parent, parent.rightNode, newNode);
}
if (result < 0) {
upsert(parent, parent.leftNode, newNode);
}
}
} private void upsertFix(Node newNode) {
Node parent = newNode.parentNode;
if (RED == parent.color) { // 父节点如果是黑节点 则不需要处理
Node grandfather = parent.parentNode;
if (parent == grandfather.leftNode) { // 1. 父节点原来是 左节点
Node uncle = grandfather.rightNode;
if ((null != uncle) && (RED == uncle.color)) { // case 3: 叔叔节点是红色
uncleRedFix(newNode);
} else { // 叔叔节点为 NULL 或者 是黑色节点
if (newNode.key.compareTo(parent.key) < 0) { // case 1: 叔叔节点是黑色,插入到左子树中
leftNodeFix(grandfather, parent);
} else { // case 2: 叔叔节点是黑色,插入到右子树中
leftRotation(parent);
leftNodeFix(grandfather, newNode); // 我们将 parent 节点作为“新插入的节点”,这样 真正新插入的节点 newNode 就是父节点
}
}
} else { // 1. 父节点原来是 右节点
Node uncle = grandfather.leftNode;
if ((null != uncle) && (RED == uncle.color)) { // case 3: 叔叔节点是红色
uncleRedFix(newNode);
} else { // 叔叔节点为 NULL 或者 是黑色节点
if (newNode.key.compareTo(parent.key) > 0) { // case 1: 叔叔节点是黑色,插入到右子树中
rightNodeFix(grandfather, parent);
} else { // case 2: 叔叔节点是黑色,插入到左子树中
rightRotation(parent);
rightNodeFix(grandfather, newNode); // 我们将 parent 节点作为“新插入的节点”,这样 真正新插入的节点 newNode 就是父节点
}
}
}
}
} /**
* 处理 父节点原来是 左节点 的 case 1 的情况: 叔叔节点是黑色,插入到左子树中
*
* @param grandfather
* @param parent
*/
private void leftNodeFix(Node grandfather, Node parent) {
parent.color = BLACK;
grandfather.color = RED;
rightRotation(grandfather);
} /**
* 处理 父节点原来是 右节点 的 case 1 的情况: 叔叔节点是黑色,插入到右子树中
*
* @param grandfather
* @param parent
*/
private void rightNodeFix(Node grandfather, Node parent) {
parent.color = BLACK;
grandfather.color = RED;
leftRotation(grandfather);
} /**
* 处理 case 3: 叔叔节点是红色
*
* @param newNode
*/
private void uncleRedFix(Node newNode) {
Node parent = newNode.parentNode;
if ((null != parent) && (RED == parent.color)) {
Node grandfather = parent.parentNode;
Node uncle = grandfather.leftNode;
if (parent == grandfather.leftNode) {
uncle = grandfather.rightNode;
}
parent.color = BLACK;
uncle.color = BLACK;
if (root != grandfather) {
grandfather.color = RED;
upsertFix(grandfather);
}
}
} /**
* 删除 叶子节点 后的修复过程
*
* @param deletedNode
* 被删除的节点
* @param deletedNodeParent
* 被删除节点的父节点
*/
private void deleteLeafFix(Node deletedNode) {
while ((deletedNode != root) && (BLACK == deletedNode.color)) {
Node parent = deletedNode.parentNode;
Node brother = getBrother(deletedNode);
if (deletedNode.key.compareTo(parent.key) > 0) { // 删除的是右叶子节点
if (RED == brother.color) { // case5: 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
brother.color = BLACK;
brother.rightNode.color = RED;
rightRotation(parent);
break;
} else {
if ((null == brother.leftNode) && (null == brother.rightNode)) { // case4: 兄弟节点是黑色的,且没有子节点
brother.color = RED; // 将兄弟节点设为红色,将父节点设为当前节点递归, 直到根节点,或遇到红色节点,
deletedNode = parent;
} else {
if ((null != brother.leftNode) && (RED == brother.leftNode.color)) {// case1:
// 兄弟节点是黑色的,且有一个左节点(可以断定
// 左节点是红色的)
// case3: 兄弟节点是黑色的,且有两个节点(可以断定 左右节点都是红色的) 这个和情况 1 是一样的
brother.color = parent.color;
parent.color = BLACK;
brother.leftNode.color = BLACK;
rightRotation(parent);
break;
} else {// case2: 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
brother.rightNode.color = BLACK;
brother.color = RED;
leftRotation(brother);
}
}
}
} else {// 删除的是左叶子节点
if (RED == brother.color) { // case5 : 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
brother.color = BLACK;
brother.leftNode.color = RED;
leftRotation(parent);
break;
} else {
if ((null == brother.leftNode) && (null == brother.rightNode)) { // case4: 兄弟节点是黑色的,且没有子节点
brother.color = RED; // 将兄弟节点设为红色,将父节点设为当前节点递归, 直到根节点,或遇到红色节点,
deletedNode = parent;
} else {
if ((null != brother.rightNode) && (RED == brother.rightNode.color)) { // case1 :
// 兄弟节点是黑色的,且有一个右节点(可以断定
// 右节点是红色的)
// case3 : 兄弟节点是黑色的,且有两个节点(可以断定 左右节点都是红色的) 这个和情况 1 是一样的
brother.color = parent.color;
parent.color = BLACK;
brother.rightNode.color = BLACK;
leftRotation(parent);
break;
} else { // case2: 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的)
brother.leftNode.color = BLACK;
brother.color = RED;
rightRotation(brother);
}
}
}
}
} deletedNode.color = BLACK;
} private Node getBrother(Node node) {
if (null == node) {
return null;
}
Node parent = node.parentNode;
if (null == parent) {
return null;
}
if (node.key.compareTo(parent.key) > 0) {
return parent.leftNode;
} else {
return parent.rightNode;
}
} public boolean delete(K key) {
if (null != key) {
if (null != root) {
return deleteNode(key, root, null);
}
}
return false;
} private boolean deleteNode(K key, Node current, Node parent) {
if (null != current) {
if (key.compareTo(current.key) > 0) {
return deleteNode(key, current.rightNode, current);
}
if (key.compareTo(current.key) < 0) {
return deleteNode(key, current.leftNode, current);
}
if (key.compareTo(current.key) == 0) {
if ((null != current.leftNode) && (null != current.rightNode)) { // 将要删除的节点下有两个子节点
dleTwoChildrenNode(current);
return true;
} else {
if ((null == current.leftNode) && (null == current.rightNode)) { // 将要删除的节点没有子节点
deleteLeafFix(current);
if (current.key.compareTo(parent.key) > 0) {
parent.rightNode = null;
} else {
parent.leftNode = null;
}
return true;
} else { // 将要删除的节点下有一个子节点,
dleOneChildNode(current);
return true;
}
}
}
}
return false;
} private void dleOneChildNode(Node delNode) {
Node replaceNode = (null == delNode.leftNode) ? delNode.rightNode : delNode.leftNode;
deltetLeafNode(delNode, replaceNode);
} /**
* 处理被删除节点有两个子节点的情况
*
* @param target
* 将要被删除的节点
*/
private void dleTwoChildrenNode(Node target) {
Node replaceNode = successor(target);
if ((null == replaceNode.rightNode) && (null == replaceNode.leftNode)) {
deltetLeafNode(target, replaceNode);
} else {
target.key = replaceNode.key;
target.value = replaceNode.value;
dleOneChildNode(replaceNode);
}
} private void deltetLeafNode(Node target, Node replaceNode) {
target.key = replaceNode.key;
target.value = replaceNode.value;
deleteLeafFix(replaceNode);
if (replaceNode == replaceNode.parentNode.rightNode) {
replaceNode.parentNode.rightNode = null;
} else {
replaceNode.parentNode.leftNode = null;
}
} // 找后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"
private Node successor(Node node) {
if (node == null) {
return null;
}
if (null != node.rightNode) { // 获取 后继节点
Node p = node.rightNode;
while (null != p.leftNode) {
p = p.leftNode;
}
return p;
} else {
Node p = node.parentNode;
Node ch = node;
while (p != null && ch == p.rightNode) {
ch = p;
p = p.parentNode;
}
return p;
}
} public static void main(String[] args) { RedBlackTree<Integer, String> bst = new RedBlackTree<Integer, String>(); bst.put(100, "v100");
bst.put(50, "v50");
bst.put(150, "v150");
bst.put(20, "v20");
bst.put(85, "v85");
bst.put(10, "v10");
bst.put(15, "a15");
bst.put(75, "v75");
bst.put(95, "v95");
bst.put(65, "v65");
bst.put(76, "v76");
bst.put(60, "v60");
bst.put(66, "v66");
bst.put(61, "v61"); // 当前节点是左节点 的 5中情况
// bst.delete(15); // 1. 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的) // 2. 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的
// bst.put(140, "v140");
// bst.delete(95); // 4. 兄弟节点是黑色的,且没有子节点
// bst.delete(66); // 5. 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
// bst.delete(95);
// bst.delete(15); System.out.println(bst.getRoot());
}
}

RedBlackTree

从二叉搜索树到AVL树再到红黑树 B树的更多相关文章

  1. 数据结构中很常见的各种树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    数据结构中常见的树(BST二叉搜索树.AVL平衡二叉树.RBT红黑树.B-树.B+树.B*树) 二叉排序树.平衡树.红黑树 红黑树----第四篇:一步一图一代码,一定要让你真正彻底明白红黑树 --- ...

  2. 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: BST树 ...

  3. 算法二叉搜索树之AVL树

    最近学习了二叉搜索树中的AVL树,特在此写一篇博客小结. 1.引言 对于二叉搜索树而言,其插入查找删除等性能直接和树的高度有关,因此我们发明了平衡二叉搜索树.在计算机科学中,AVL树是最先发明的自平衡 ...

  4. 算法进阶面试题04——平衡二叉搜索树、AVL/红黑/SB树、删除和调整平衡的方法、输出大楼轮廓、累加和等于num的最长数组、滴滴Xor

    接着第三课的内容和讲了第四课的部分内容 1.介绍二叉搜索树 在二叉树上,何为一个节点的后继节点? 何为搜索二叉树? 如何实现搜索二叉树的查找?插入?删除? 二叉树的概念上衍生出的. 任何一个节点,左比 ...

  5. 二叉搜索树、AVL平衡二叉搜索树、红黑树、多路查找树

    1.二叉搜索树 1.1定义 是一棵二叉树,每个节点一定大于等于其左子树中每一个节点,小于等于其右子树每一个节点 1.2插入节点 从根节点开始向下找到合适的位置插入成为叶子结点即可:在向下遍历时,如果要 ...

  6. 数据结构中的树(二叉树、二叉搜索树、AVL树)

    数据结构动图展示网站 树的概念 树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合.它是由n(n>=1)个有限节点组成一个具有 ...

  7. 【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)

    定义 能够在key插入时一直保持平衡的二叉查找树: AVL树 利用AVL树实现ADT Map, 基本上与BST的实现相同,不同之处仅在于二叉树的生成与维护过程 平衡因子 AVL树的实现中, 需要对每个 ...

  8. 数据结构之二叉搜索树、AVL自平衡树

    前言 最近在帮公司校招~~ 所以来整理一些数据结构方面的知识,这些知识呢,光看一遍理解还是很浅的,看过跟动手做过一遍的同学还是很容易分辨的哟~ 一直觉得数据结构跟算法,就好比金庸小说里的<九阳神 ...

  9. 【数据结构05】红-黑树基础----二叉搜索树(Binary Search Tree)

    目录 1.二分法引言 2.二叉搜索树定义 3.二叉搜索树的CRUD 4.二叉搜索树的两种极端情况 5.二叉搜索树总结 前言 在[算法04]树与二叉树中,已经介绍过了关于树的一些基本概念以及二叉树的前中 ...

随机推荐

  1. 对python里的装饰器

    内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效. 再回到我们的主 ...

  2. VMware Workstation 12 PRo密钥

    注意:手动输入(起初复制输入,一个都没成功,不要偷懒哦) VMware Workstation 12序列号: 5A02H-AU243-TZJ49-GTC7K-3C61N

  3. CDH6.2安装配置第二篇:CDH安装的前期配置

    本篇介绍cdh安装之前需要的一些必要配置,当然这些配置也可以用shell脚本来配置.在安装之前请先配置好yum源,在文中用的统一都是阿里源.在安装的时候,要确保主机的内存是4G以上,要不然会无限重启c ...

  4. Delphi7 流操作_压缩

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  5. findbugs报OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE的修改实例

    先看出问题的一段代码 public void encode(String xxxPath, String thumbTmpPath, String imageType) { LOGGER.info(& ...

  6. 直击JDD | 共建智能新城 京东云让城市生活变得简单美好

    技术快速革新,创新持续激发.在"智能+"时代,云计算.大数据.5G等新技术,已成为社会生产方式变革.创新人类生活空间的重要力量--11月19日,JDD-2019京东全球科技探索者大 ...

  7. 72)MFC测试动态共享库

    动态共享库: 首先我建立一个新的动态库: 然后不选择空项目了,因为我们普通的cpp文件 入口是main  win32入口是winmain  那么这个动态库的入口在哪里  我们就是为了看一看: 出来这样 ...

  8. 寒假day15

    今天完成了毕设的人才动态模块,同时刷了计算机网络的相关面试题

  9. 提高js性能的方法

    1.文档瘦身 (1)删除注释(版权及法律声明部分应保留),运行时不需要注释. (2)删除制表符.空格和换行符,这些只是为了便于程序的维护,但是与执行无关. (3)替换长的变量名为短的变量名. (4)使 ...

  10. CSS中的 position与Grid Layout

    [1]CSS之Position(定位布局): 现css常用的属性有5种: 1.static 2.absolute 3.fixed 4.relative 5.sticky. 1.static 表示元素的 ...