2-3树

定义

一棵 2-3 树是一棵查找树,该查找树要么为空要么满足以下性质(令 leftmiddleright 为 2-3 树结点的孩子指针;dl, dr为 2-3 树结点元素):

  1. 每个内部结点或者是一个2结点,或者是一个3结点。一个2结点存放一个元素,而一个3结点存放两个元素。
  2. 每个结点的 dl 值大于 left 指向子树所有结点内元素的值 且 小于 middle 指向子树所有结点的元素值。
  3. 每个结点的 dr 值大于 middle 指向子树所有结点的元素值 且 小于 right 指向子树的所有结点的元素值。
  4. 所有外部结点都在同一层。

根据定义,可以看出,2-3 树本身就是一个自平衡树。对于高度为 h 的一棵 2-3 树,其上的元素个数介于 2- 1 到 3- 1 之间。一棵包含 n 个结点的 2-3 树,其高度在 log2(n+1) 和 log3(n + 1) 之间。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

类型定义

结点Java 定义

1 public class TwoThreeNode<T extends Comparable<T>> {
2     private T dataLeft;
3     private T dataRight;
4     private TwoThreeNode left;
5     private TwoThreeNode middle;
6     private TwoThreeNode right;
7     private TwoThreeNode parent;
8 }

 树Java定义

 1 public class TwoThreeTree<T extends Comparable<T>> implements Tree<TwoThreeNode<T>, T> {
 2 
 3     private static final int LEFT_TREE = 1;
 4     private static final int MIDDLE_TREE = 2;
 5     private static final int RIGHT_TREE = 3;
 6     private static final int EQ_LEFT = 4;
 7     private static final int EQ_RIGHT = 8;
 8 
 9     /** 2 结点 */
10     private static final int TWO_NODE = 1;
11     /** 3 结点 */
12     private static final int THREE_NODE = 2;
13 }

 

比较函数

结点有2个元素,定义一个比较函数,返回值含义:

  • 1 为左子树方向
  • 2 为中子树方向
  • 3 为右子树方向
  • 4 为等于左元素
  • 8 为等于右元素

查找函数

Java 定义

 1     public TwoThreeNode<T> search(T data) {
 2         TwoThreeNode<T> node = head;
 3         while (node != null) {
 4             int r = compare(node, data);
 5             switch (r) {
 6                 case 1: node = node.getLeft(); break;
 7                 case 2: node = node.getMiddle(); break;
 8                 case 3: node = node.getRight(); break;
 9                 case 4:
10                 case 8: return node;
11             }
12         }
13         return null;
14     }

插入

插入新元素,必然是插入到叶子结点中,如果是 2 结点的话,直接插入就结束了,但如果是3结点的话,如果向下溢出,则破坏了 2-3 树的平衡,因此需要拆分当前的结点然后向上合并,直到树根,以此保持 2-3 树自身的平衡。

  • 如果是空树,则插入一个结点,设置结点为树根。
  • 查找插入的结点位置,如果发现元素在树中,则插入失败,否则 定位插入的叶子结点 n
  • n 结点是 2 结点时,直接插入 e 即可。
  • 结点是 3 结点时,由于插入 e 后,结点变为 4 结点,这时需要对结点 n 进行分裂,定义一个结点变量 q,初始化值为空,具体过程如下:
    • 将 4 结点的三个元素的最小值存入 n 的左元素位置
    • 以 4 结点的三个元素的最大值构建一个新的 2 结点 qq
    • 根据 e 的位置设置 qq 的 left 和 middle 指针:
      • 如果 e < n.dlqq.left = n.middle | qq.middle = n.right |  n.middle = q
      • 如果 n.dl < e < n.drqq.left = q | qq.middle = n.right 
      • 如果 n.dr < eqq.left = n.right | qq.middle = q 
    • qq 赋值给 q,将 n 的父结点 赋值给 n,将 4 结点中中间值赋给 e 继续向上回溯

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

自底向上 拆分3结点

 1     private void mergeNode(TwoThreeNode<T> n, T e) {
 2         TwoThreeNode<T> q = null;
 3         while (n != null) {
 4             int dc = dataType(n);
 5             if (dc == TWO_NODE) {
 6                 T dl = n.getDataLeft();
 7                 if (e.compareTo(dl) < 0) {
 8                     n.setDataLeft(e);
 9                     n.setDataRight(dl);
10                     n.setRight(n.getMiddle());
11                     n.setMiddle(q);
12                 } else {
13                     n.setDataRight(e);
14                     n.setRight(q);
15                 }
16                 break;
17             } else if (dc == THREE_NODE) {
18                 TwoThreeNode<T> qq = null;
19                 TwoThreeNode<T> temp = null;
20                 if (e.compareTo(n.getDataLeft()) < 0) {
21                     qq = new TwoThreeNode<>(n.getDataRight());
22                     T tmp = n.getDataLeft();
23                     n.setDataLeft(e);
24                     setChild(qq, n.getMiddle(), LEFT_TREE);
25                     setChild(qq, n.getRight(), MIDDLE_TREE);
26                     setChild(n, q, MIDDLE_TREE);
27                     e = tmp;
28                 } else if (e.compareTo(n.getDataRight()) > 0) {
29                     qq = new TwoThreeNode<>(e);
30                     e = n.getDataRight();
31                     setChild(qq, n.getRight(), LEFT_TREE);
32                     setChild(qq, q, MIDDLE_TREE);
33                 } else {
34                     qq = new TwoThreeNode<>(n.getDataRight());
35                     setChild(qq, q, LEFT_TREE);
36                     setChild(qq, n.getRight(), MIDDLE_TREE);
37                 }
38                 n.setRight(null);
39                 n.setDataRight(null);
40                 q = qq;
41                 if (n == head) {
42                     head = new TwoThreeNode<>(e);
43                     setChild(head, n, LEFT_TREE);
44                     setChild(head, q, MIDDLE_TREE);
45                     break;
46                 }
47             }
48             n = n.getParent();
49         }
50     }

 

插入函数

 1 private boolean insert1(T data) {
 2     if (head == null) {
 3         head = new TwoThreeNode<>(data);
 4         return true;
 5     }
 6     /* 找到待插入元素的叶子结点 */
 7     TwoThreeNode<T> n = head;
 8     while (n != null) {
 9         int r = compare(n, data);
10         TwoThreeNode<T> next = null;
11         switch (r) {
12             case LEFT_TREE: next = n.getLeft();break;
13             case MIDDLE_TREE: next = n.getMiddle(); break;
14             case RIGHT_TREE: next = n.getRight(); break;
15             default: return false; //find true
16         }
17         if (next == null) break;
18         n = next;
19     }
20     mergeNode(n, data);
21     return true;
22 }

 

删除

找到待删除元素所在结点 n,如果 n 不是叶子结点,用它的直接前驱(左子树的最大元素)或者直接后继(右子树的最小元素)替换,进而转换为对叶子结点的删除。
  • 如果被删除的是 3结点,直接删除即可。
  • 如果被删除的是 2结点,删除后,结点元素为空(零元素)。如果删除结点,则2-3树自身平衡被破坏,因此需要从父节点 parent 和 兄弟节点 sibling 进行借元素进行合并:
    • 如果 兄弟结点 sibling 为 3结点时,需要从父结点借一个元素过来,填充当前结点,而父结点的被借的元素位置需要从 sibling 的借一个元素过来。
    • 如果 兄弟节点 sibling 为 2结点时,需要从父节点借一个元素过来 和 兄弟结点的的元素合并,组成一个3结点,兄弟结点得到释放。
    • 如果 父节点处理后,结点元素为空(零元素),则继续向上回溯;否则退出。

 

 

自底向上删除 

 

 1     public void remove(T data) {
 2         if (head == null) return;
 3         TwoThreeNode<T> n = head;
 4         int cr = -1;
 5         // STEP 1. 找到待删除元素所在结点 n
 6         while (n != null) {
 7             cr = compare(n, data);
 8             if (cr == EQ_LEFT || cr == EQ_RIGHT) break;
 9             TwoThreeNode<T> next = null;
10             switch (cr) {
11                 case LEFT_TREE: next = n.getLeft();break;
12                 case MIDDLE_TREE: next = n.getMiddle();break;
13                 case RIGHT_TREE: next = n.getRight(); break;
14             }
15             n = next;
16         }
17         /* 没找到 */
18         if (n == null) return;
19         // STEP 2. 交换元素,使叶子结点成为删除元素的结点。一般用待删除元素所在结点的
20         //         左子树最大元素 或者 右子树的最小元素与待删除元素进行交换后,删除叶
21         //         子的待删除元素。如果不为空则结束删除操作,如果为空要进行后续的旋转
22         //         或者 合并操作。
23         if ((n = exchange(n, cr)) == null) return;
24         // STEP 3. 叶子结点(n) 零元素时 进行旋转 或者 合并操作
25         //         (a) n 结点的兄弟结点为3结点时旋转->结束
26         //         (b) n 结点的兄弟结点为2结点时组合->回溯
27         while (n.getDataLeft() == null) {
28             TwoThreeNode<T> p = n.getParent();
29             TwoThreeNode<T> s = null;
30             if (p.getLeft() == n) {
31                 s = p.getMiddle();
32                 cr = LEFT_TREE;
33             } else if (p.getMiddle() == n) {
34                 s = p.getLeft();
35                 cr = MIDDLE_TREE;
36             } else if (p.getRight() == n) {
37                 s = p.getMiddle();
38                 cr = RIGHT_TREE;
39             }
40             if (dataType(s) == THREE_NODE) {
41                 rotate(p, n, s, cr);
42                 break;
43             } else {
44                 combine(p, n, s, cr);
45             }
46             if (p == head) {
47                 head = p.getLeft();
48                 head.setParent(null);
49                 break;
50             } else {
51                 n = p;
52             }
53         }
54     }

 

旋转函数

 1 private void rotate(TwoThreeNode<T> p, TwoThreeNode<T> n, TwoThreeNode<T> s, int cr) {
 2     if (cr == LEFT_TREE /* n 是 p 的左子树 */){
 3         /* 设置 n 结点的左兄弟 以及 中子树 */
 4         n.setDataLeft(p.getDataLeft());
 5         setChild(n, s.getLeft(), MIDDLE_TREE);
 6         /* 设置 p 结点的左元素 */
 7         p.setDataLeft(s.getDataLeft());
 8         /* 移动兄弟结点的右元素到左元素位置 移动 中右子树 到 左中子树 */
 9         s.setDataLeft(s.getDataRight());
10         setChild(s, s.getMiddle(), LEFT_TREE);
11         setChild(s, s.getRight(), MIDDLE_TREE);
12         s.setDataRight(null);
13         setChild(s, null, RIGHT_TREE);
14     } else if (cr == MIDDLE_TREE /* node 是父结点的中子树 */) {
15         /* 设置 n 结点的左元素 以及 左中子树 */
16         n.setDataLeft(p.getDataLeft());
17         setChild(n, n.getLeft(), MIDDLE_TREE);
18         setChild(n, s.getRight(), LEFT_TREE);
19         /* 设置 p 结点 左元素 */
20         p.setDataLeft(s.getDataRight());
21         /* 设置 s 结点的右元素为空 右子树为空 */
22         s.setDataRight(null);
23         setChild(s, null, RIGHT_TREE);
24     } else if (cr == RIGHT_TREE /* node 是父结点的右子树 */) {
25         /* 设置 n 结点的左元素 以及 左中子树 */
26         n.setDataLeft(p.getDataRight());
27         setChild(n, n.getLeft(), MIDDLE_TREE);
28         setChild(n, s.getRight(), LEFT_TREE);
29         /* 设置 p 结点的右元素为 s 结点的右元素 */
30         p.setDataRight(s.getDataRight());
31         /* 设置 s 结点的右元素为空 以及 右子树为空 */
32         s.setDataRight(null);
33         setChild(s, null, RIGHT_TREE);
34     }
35 }

 

组合函数

 1 public void combine(TwoThreeNode<T> p, TwoThreeNode<T> n, TwoThreeNode<T> s, int cr) {
 2     if (cr == LEFT_TREE /* n 为 p 的左儿子 */) {
 3         /* 设置 p 结点 左右元素 以及 中右子树 */
 4         n.setDataLeft(p.getDataLeft());
 5         n.setDataRight(s.getDataLeft());
 6         setChild(n, s.getLeft(), MIDDLE_TREE);
 7         setChild(n, s.getMiddle(), RIGHT_TREE);
 8         /* 设置父结点左元素以及中子树 */
 9         p.setDataLeft(p.getDataRight());
10         setChild(p, p.getRight(), MIDDLE_TREE);
11         p.setDataRight(null);
12         setChild(p, null, RIGHT_TREE);
13     } else if (cr == MIDDLE_TREE /* n 为 p 的中儿子 */) {
14         /* 设置 n 结点 左右元素 和 左中右子树 */
15         n.setDataLeft(s.getDataLeft());
16         n.setDataRight(p.getDataLeft());
17         setChild(n, n.getLeft(), RIGHT_TREE);
18         setChild(n, s.getLeft(), LEFT_TREE);
19         setChild(n, s.getMiddle(), MIDDLE_TREE);
20         /* 设置 p 结点 左元素 和 左中子树 */
21         p.setDataLeft(p.getDataRight());
22         setChild(p, n, LEFT_TREE);
23         setChild(p, p.getRight(), MIDDLE_TREE);
24         p.setDataRight(null);
25         setChild(p, null, RIGHT_TREE);
26     } else if (cr == RIGHT_TREE /* n 为 p 的右儿子 */) {
27         /* 设置 n 结点 左右元素 以及 左中右子树 */
28         n.setDataLeft(s.getDataLeft());
29         n.setDataRight(p.getDataRight());
30         setChild(n, n.getLeft(), RIGHT_TREE);
31         setChild(n, s.getLeft(), LEFT_TREE);
32         setChild(n, s.getMiddle(), MIDDLE_TREE);
33         /* 设置 p 结点 中子树 置 右元素 和 右子树为空 */
34         setChild(p, n, MIDDLE_TREE);
35         p.setDataRight(null);
36         setChild(p, null, RIGHT_TREE);
37     }
38 }

 

 

posted @ 2023-04-01 14:59  茄菲兔  阅读(47)  评论(0编辑  收藏  举报