数据结构与算法(六)——二叉树

一、排序二叉树(二叉查找树、二叉搜索树)

1、介绍

  排序二叉树(Binary Sort Tree),性质:左孩子 < 根结点 < 右孩子。在一般情况下,查询效率比链表要高。
  按照中序遍历可以得到一个从小到大的有序序列。

2、创建、添加、删除

  创建和添加简单,删除情况比较复杂,有三种情况考虑:
  ①删除叶子结点(度为0),比如:2,5,9,12。
  ②删除只有左(右)孩子的结点(度为1),比如:1。
  ③删除有左(右)孩子的结点(度为2),比如:7,3,10。
  删除度为2的结点,方法:取右子树的最小值替换它,且删除这个最小值。比如:删除7,用 7 的右子树最小值 9 替换7。且删除9。

  代码示例:排序二叉树创建、添加、删除

 1 // 二叉树
 2 public class BinaryTree {
 3     // 根结点
 4     protected TreeNode root;
 5 
 6     /**
 7      * 获取树的高度
 8      *
 9      * @return
10      */
11     public int getHeight() {
12         return this.getHeight(root);
13     }
14 
15     protected int getHeight(TreeNode root) {
16         if (root == null) {
17             return 0;
18         }
19 
20         final int left = this.getHeight(root.left);
21         final int right = this.getHeight(root.right);
22 
23         return Math.max(left, right) + 1;
24     }
25 
26     /**
27      * 获取树的结点数
28      *
29      * @return
30      */
31     public int getSize() {
32         return this.getSize(root);
33     }
34 
35     private int getSize(TreeNode root) {
36         if (root == null) {
37             return 0;
38         }
39 
40         final int left = this.getSize(root.left);
41         final int right = this.getSize(root.right);
42 
43         return left + right + 1;
44     }
45 
46     // 中序遍历
47     public void infixOrder() {
48         this.infixOrder(root);
49     }
50 
51     private void infixOrder(TreeNode root) {
52         if (root != null) {
53             this.infixOrder(root.left);
54             System.out.print("-->" + root.value);
55             this.infixOrder(root.right);
56         }
57     }
58 
59     /**
60      * 树结点结构
61      */
62     protected static class TreeNode {
63         public int value;
64         public TreeNode left;
65         public TreeNode right;
66 
67         public TreeNode(int value) {
68             this.value = value;
69         }
70 
71         // 返回结点的度
72         public int getDegree() {
73             if (this.left == null && this.right == null) {
74                 return 0;
75             }
76 
77             if ((this.left != null && this.right == null) || (this.left == null && this.right != null)) {
78                 return 1;
79             }
80 
81             return 2;
82         }
83 
84         @Override
85         public String toString() {
86             return "TreeNode{" +
87                     "value=" + value +
88                     '}';
89         }
90     }
91 
92 }
二叉树
  1 public class BinarySortTree extends BinaryTree {
  2 
  3     public BinarySortTree() {
  4 
  5     }
  6 
  7     // arr = {7, 3, 10, 12, 5, 1, 9};
  8     public BinarySortTree(int[] arr) {
  9         if (arr == null || arr.length == 0) {
 10             return;
 11         }
 12 
 13         for (int i : arr) {
 14             this.add(i);
 15         }
 16     }
 17 
 18     /**
 19      * 添加结点
 20      *
 21      * @param value
 22      */
 23     public void add(int value) {
 24         TreeNode node = new TreeNode(value);
 25 
 26         if (root == null) {
 27             root = node;
 28             return;
 29         }
 30 
 31         this.add(root, node);
 32     }
 33 
 34     private void add(TreeNode root, TreeNode node) {
 35         if (node.value <= root.value) {
 36             if (root.left == null) {
 37                 root.left = node;
 38             } else {
 39                 this.add(root.left, node);
 40             }
 41         } else {
 42             if (root.right == null) {
 43                 root.right = node;
 44             } else {
 45                 this.add(root.right, node);
 46             }
 47         }
 48     }
 49 
 50     /**
 51      * 删除结点
 52      *
 53      * @param value
 54      */
 55     public void delNode(int value) {
 56         if (this.checkRoot(value)) {
 57             return;
 58         }
 59 
 60         // 1.查找要删除的结点
 61         final TreeNode targetNode = this.search(value);
 62         if (targetNode == null) {
 63             return;
 64         }
 65 
 66         this.delNode(targetNode);
 67     }
 68 
 69     private void delNode(TreeNode targetNode) {
 70         // 2.查找 targetNode 的父结点
 71         final int value = targetNode.value;
 72         final TreeNode parent = this.searchParent(value);
 73 
 74         // 度为0
 75         if (targetNode.getDegree() == 0) {
 76             if (parent.left != null && parent.left.value == value) {
 77                 parent.left = null;
 78             } else if (parent.right != null && parent.right.value == value) {
 79                 parent.right = null;
 80             }
 81         }
 82         // 度为1
 83         else if (targetNode.getDegree() == 1) {
 84             // 是左子结点
 85             if (targetNode.left != null) {
 86                 if (parent.left.value == value) {
 87                     parent.left = targetNode.left;
 88                 } else {
 89                     parent.right = targetNode.left;
 90                 }
 91             } else {
 92                 if (parent.left.value == value) {
 93                     parent.left = targetNode.right;
 94                 } else {
 95                     parent.right = targetNode.right;
 96                 }
 97             }
 98         }
 99         // 度为2
100         else {
101             targetNode.value = this.delRightTreeMin(targetNode.right);
102         }
103 
104     }
105 
106     // 单独处理根结点
107     private boolean checkRoot(int value) {
108         if (root == null) {
109             return true;
110         }
111 
112         if (root.value == value) {
113             if (root.getDegree() == 0) {
114                 root = null;
115                 return true;
116             }
117 
118             if (root.getDegree() == 1) {
119                 if (root.left != null) {
120                     root = root.left;
121                 } else {
122                     root = root.right;
123                 }
124                 return true;
125             }
126         }
127 
128         return false;
129     }
130 
131     private int delRightTreeMin(TreeNode node) {
132         TreeNode temp = node;
133 
134         while (temp.left != null) {
135             temp = temp.left;
136         }
137 
138         this.delNode(temp);
139 
140         return temp.value;
141     }
142 
143     /**
144      * 查找指定结点 value
145      *
146      * @param value
147      * @return
148      */
149     private TreeNode search(int value) {
150         return this.search(root, value);
151     }
152 
153     private TreeNode search(TreeNode root, int value) {
154         if (root != null) {
155             if (value == root.value) {
156                 return root;
157             }
158 
159             // 递归左孩子
160             if (value < root.value) {
161                 return this.search(root.left, value);
162             } else {
163                 // 递归右孩子
164                 return this.search(root.right, value);
165             }
166         }
167 
168         return null;
169     }
170 
171     /**
172      * 查找指定结点 value 的父结点
173      *
174      * @param value
175      * @return
176      */
177     private TreeNode searchParent(int value) {
178         return this.searchParent(root, value);
179     }
180 
181     private TreeNode searchParent(TreeNode root, int value) {
182         if (root != null) {
183             if ((root.left != null && root.left.value == value) ||
184                     (root.right != null && root.right.value == value)) {
185                 return root;
186             }
187 
188             // 递归左子树
189             if (value < root.value) {
190                 return this.searchParent(root.left, value);
191             } else {
192                 // 递归右子树
193                 return this.searchParent(root.right, value);
194             }
195         }
196 
197         return null;
198     }
199 
200 }
排序二叉树

  代码示例:测试类

 1 public class Main {
 2 
 3     public static void main(String[] args) {
 4         // 1.创建
 5         int[] arr = {7, 3, 10, 12, 5, 1, 9};
 6         BinarySortTree sortTree = new BinarySortTree(arr);
 7 
 8         // 2.中序遍历
 9         sortTree.infixOrder();
10         System.out.println("");
11 
12         // 3.添加
13         sortTree.add(2);
14         sortTree.infixOrder();
15         System.out.println("");
16 
17         // 4.删除
18         sortTree.delNode(10);
19         sortTree.delNode(3);
20         sortTree.delNode(9);
21         sortTree.delNode(12);
22         sortTree.delNode(1);
23         sortTree.delNode(2);
24         sortTree.delNode(5);
25         //sortTree.delNode(7);
26         sortTree.infixOrder();
27     }
28 }
29 
30 // 结果
31 -->1-->3-->5-->7-->9-->10-->12
32 -->1-->2-->3-->5-->7-->9-->10-->12
33 -->7
测试类

二、平衡二叉树(Self-balancing binary search tree ,AVL树)

1、介绍

  给定一个数列,{1,2,3,4,5},构建排序二叉树为:

  排序二叉树存在的问题:左子树都为空,从形式上看,像一个单链表;插入速度没有影响,查询速度明显降低(因为每次需要比较左子树),比单链表还慢。
  解决:引入平衡二叉树。
  定义:自平衡的排序二叉树。性质:左右两个子树的高度差绝对值不超过1,且左右子树也是平衡二叉树。
  在平衡二叉树中插入结点要随时保证插入后整棵二叉树是平衡的,所以可能需要通过一次或多次旋转来重新平衡这个树。可以保证查询效率较高。
  平衡二叉树是在排序二叉树的基础上定义的,所以,平衡二叉树是一颗排序二叉树。

2、左旋  

  右高,左旋

  代码示例:对 root 左旋

 1 // 左旋
 2 private void leftRotate(TreeNode root) {
 3     TreeNode newNode = new TreeNode(root.value);
 4     newNode.left = root.left;
 5     newNode.right = root.right.left;
 6 
 7     root.value = root.right.value;
 8     root.left = newNode;
 9     root.right = root.right.right;
10 }

  说明:root 的右子树高度 - 左子树高度 > 1,那么进行左旋。图示:X 结点,可能是 7 的左孩子或者右孩子。按上述方法左旋,都可以达到平衡。但是,如果 X 结点是 5 的左孩子或者右孩子,按上述方法就不能平衡,后面会介绍。
  旋转后,原来的结点 6(灰色)其实还在,只是没有引用指向它,它会JVM视为垃圾回收掉。

3、右旋

  左高,右旋

  代码示例:对 root 右旋

 1 // 右旋
 2 private void rightRotate(TreeNode root) {
 3     TreeNode newNode = new TreeNode(root.value);
 4     newNode.left = root.left.right;
 5     newNode.right = root.right;
 6 
 7     root.value = root.left.value;
 8     root.left = root.left.left;
 9     root.right = newNode;
10 }

  说明:与左旋同理。

4、双旋

  左旋问题:
  上面提到,"如果 X 结点是 5 的左孩子或者右孩子,按上述方法就不能平衡。",如图所示:

  经过左旋后,发现树并没有平衡。解决办法:
  ①先 root.right 右旋(因为对于root.right是左子树高度 > 右子树)。
  ②再对 root 左旋。

  右旋问题:
  同理:

  经过右旋后,发现树并没有平衡。解决办法:
  ①先 root.left 左旋(因为对于root.left是右子树高度 > 左子树)。
  ②再对 root 右旋。

5、代码实现

  代码示例:平衡二叉树

 1 public class AVLTree extends BinarySortTree {
 2 
 3     public AVLTree() {
 4     }
 5 
 6     public AVLTree(int[] arr) {
 7         super(arr);
 8     }
 9 
10     /**
11      * 复写add方法,在排序二叉树性质上,需要调整树的平衡
12      *
13      * @param value
14      */
15     @Override
16     public void add(int value) {
17         // 1.添加结点
18         super.add(value);
19 
20         // 2.检查是否平衡.如果(右子树高度 - 左子树 > 1),左旋
21         if (this.getHeight(root.right) - this.getHeight(root.left) > 1) {
22             // 右.左子树高度 > 右.右子树
23             if (this.getHeight(root.right.left) > this.getHeight(root.right.right)) {
24                 // 右子树->右旋
25                 rightRotate(root.right);
26             }
27 
28             leftRotate(root);
29         }
30 
31         // 2.检查是否平衡.如果(左子树高度 - 右子树 > 1),右旋
32         if (this.getHeight(root.left) - this.getHeight(root.right) > 1) {
33             // 左.右子树高度 > 左.左子树
34             if (this.getHeight(root.left.right) > this.getHeight(root.left.left)) {
35                 // 左子树->左旋
36                 leftRotate(root.left);
37             }
38 
39             rightRotate(root);
40         }
41     }
42 
43     // 左旋
44     private void leftRotate(TreeNode root) {
45         TreeNode newNode = new TreeNode(root.value);
46         newNode.left = root.left;
47         newNode.right = root.right.left;
48 
49         root.value = root.right.value;
50         root.left = newNode;
51         root.right = root.right.right;
52     }
53 
54     // 右旋
55     private void rightRotate(TreeNode root) {
56         TreeNode newNode = new TreeNode(root.value);
57         newNode.left = root.left.right;
58         newNode.right = root.right;
59 
60         root.value = root.left.value;
61         root.left = root.left.left;
62         root.right = newNode;
63     }
64 
65 }
平衡二叉树

  代码示例:测试类

 1 public class Main {
 2     public static void main(String[] args) {
 3         // int[] arr = {4, 3, 6, 5, 7, 8};
 4         // int[] arr = {10, 12, 8, 9, 7, 6};
 5         // int[] arr = {4, 3, 7, 8, 6, 5};
 6         int[] arr = {10, 12, 7, 6, 8, 9};
 7 
 8         AVLTree avlTree = new AVLTree(arr);
 9 
10         avlTree.infixOrder();
11     }
12 }
测试类

三、多路查找树(multi-way search tree)

1、介绍

  二叉树的问题,二叉树需要加载到内存,如果结点少,没有什么问题,但是如果二叉树的结点很多(比如1亿),就存在如下问题:
  问题1:在构建二叉树时,需要多次进行I/O操作(海量数据存在数据库或文件中),结点海量,构建二叉树时,速度有影响。
  问题2:结点海量,也会造成二叉树的高度很大,降低操作速度。

2、2-3树

  在二叉树中,每个结点有数据项,最多有两个子结点。如果允许每个结点可以有更多的数据项和更多的子结点,就是多叉树(multiway tree)。2-3树,2-3-4树就是多叉树,多叉树通过重新组织结点,减少树的高度,能对二叉树进行优化。如图:

  多路查找树中的每一个结点都具有两个孩子或者三个孩子,我们称之为2-3树。是一种平衡的多路查找树。2-3树,2-3-4树是B树的一个特例,属于B树。2-3树,高度平衡。
  一个结点拥有两个孩子和一个元素称之为2结点,它跟二叉排序树类似,左子树包含的元素小于结点的元素,小于右子树包含的元素。
  不同的是,这个2结点要么没有孩子,要么有两个孩子。不能只有一个孩子。
  2-3树所有叶子都在同一层次。

3、2-3-4树

  概念和2-3树类似,也是一种B树。

四、B树(B-树)

1、介绍

  B树通过重新组织结点,降低树的高度,减少I/O读写次数来提升效率。文件系统及数据库系统的设计者利用了磁盘预读原理,将一个结点的大小设为等于一页(页得大小通常为4k),这样每个结点只需要一次I/O就可以完全载入。
  将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可以读取到想要的元素,B树(B+)广泛应用于文件存储系统以及数据库系统中。
  所有叶子结点都在同一层。

2、结构

  B树的阶:节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4
  B树的搜索:从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点。关键字集合分布在整颗树中,即叶子节点和非叶子节点都存放数据。搜索有可能在非叶子结点结束。其搜索性能等价于在关键字全集内做一次二分查找。
  B树的插入算法中,通过结点的向上"分裂",代替了专门的平衡调整。

五、B+树

1、介绍

  B+树是B树的变体,也是一种多路搜索树。

2、结构

  B+树的搜索:与B树基本相同,区别是B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找。所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字恰好是有序的。
  不可能在非叶子结点命中。
  非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层。
  更适合文件索引系统。
  B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然。

六、B*树

1、介绍

  B*树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针。

2、结构

  B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而B+树的块的最低使用率为B+树的1/2。
  B*树分配新结点的概率比B+树要低,空间使用率更高。

posted @ 2021-01-27 14:06  Craftsman-L  阅读(123)  评论(0编辑  收藏  举报