二叉树学习笔记之二叉查找树(BSTree)

二叉查找树即搜索二叉树,或者二叉排序树(BSTree),学习回顾一下有关的知识。

>>关于二叉查找树

二叉查找树(Binary Search Tree)是指一棵空树或者具有下列性质的二叉树:
1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
3. 任意节点的左、右子树也分别为二叉查找树。
4. 没有键值相等的节点,这个特征很重要,可以帮助理解二叉排序树的很多操作。
二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构。

>>基本性质

(1)二叉查找树是一个递归的数据结构,对二叉查找树进行中序遍历,可以得到一个递增的有序序列。
(2)二叉查找树上基本操作的执行时间和树的高度成正比。

对一棵n个节点的完全二叉树来说,树的高度为lgn,这些操作的最坏情况运行时间为O(lg n),而如果是线性链表结构,这些操作的最坏运行时间是O(n)。
一棵随机构造的二叉查找树的期望高度为O(lg n),但实际中并不能总是保证二叉查找树是随机构造的,
有些二叉查找树的变形能保证各种基本操作的最坏情况性能,比如红黑树的高度为O(lg n),而B树对维护随机访问的二级存储器上的数据库特别有效。

注意对复杂度的理解,所谓的O(lg n)就是指复杂度是对数级别,是数量级的比较,和对数的底数其实没关系,

只要底数是大于1的,就是相同的数量级,有些书上说二叉查找树的复杂度是O(log2-n),指的是相同的时间复杂度。

>>前驱和后继节点

一个节点的后继是该节点的后一个,即比该节点键值稍大的节点。
给定一个二叉查找树中的节点,找出在中序遍历顺序下某个节点的前驱和后继。
如果树中所有关键字都不相同,则某一节点x的前驱就是小于key[x]的所有关键字中最大的那个节点,后继即是大于key[x]中的所有关键字中最小的那个节点。根据二叉查找树的结构和性质,不用对关键字做任何比较,就可以找到某个节点的前驱和后继。

>>查找、插入与删除

(1)查找
利用二叉查找树左小右大的性质,可以很容易实现查找任意值和最大/小值。

在二叉查找树中查找一个给定的关键字k的过程与二分查找很类似

首先是关键字k与树根的关键字进行比较,如果k比根的关键字大,则在根的右子树中查找,否则在根的左子树中查找,重复此过程,直到找到与遇到空节点为止。

在二叉查找树中查找x的过程如下:
1.若二叉树是空树,则查找失败。
2.若x等于根节点的数据,则查找成功,否则。
3.若x小于根节点的数据,则递归查找其左子树,否则。
4.递归查找其右子树。

(2)插入
二叉树查找树b插入操作x的过程如下:
1.若b是空树,则直接将插入的节点作为根节点插入。
2.x等于b的根节点的数据的值,则直接返回,否则。
3.若x小于b的根节点的数据的值,则将x要插入的节点的位置改变为b的左子树,否则。
4.将x要出入的节点的位置改变为b的右子树。

(3)删除


假设从二叉查找树中删除给定的结点z,分三种情况讨论:
1.节点z为叶子节点,没有孩子节点,那么直接删除z,修改父节点的指针即可。
2.节点z只有一个子节点或者子树,将节点z删除,根据二叉查找树的性质,将z的父节点与子节点关联就可以了。
3.节点Z有两个子节点,删除Z该怎样将Z的父结点与这两个孩子结点关联呢?
在删去节点Z之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整。
这种情况下可以用Z的后继节点来替代Z。
实现方法就是将后继从二叉树中删除,将后继的数据覆盖到Z中。

>>代码实现

主要参考《数据结构与算法分析—Java语言实现》:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

public class BinarySearchTree <T extends Comparable<? super T>>{

 

    //节点数据结构 静态内部类

    static class BinaryNode<T>{

        T data;

        BinaryNode<T> left;

        BinaryNode<T> right;

        public BinaryNode(){

            data=null;

        }

        public BinaryNode(T data) {

            this(data,null,null);

        }

        public BinaryNode(T data,BinaryNode<T> left,BinaryNode<T> right){

            this.data=data;

            this.left=left;

            this.right=right;

        }

    }

     

    //私有的头结点

    private BinaryNode<T> root;

     

    //构造一棵空二叉树

    public BinarySearchTree(){

        root=null;

    }

    //二叉树判空

    public boolean isEmpty(){

        return root==null;

    }

    //清空二叉树

    public void clear(){

        root=null;

    }

     

    //检查某个元素是否存在

    public boolean contains(T t){

        return contains(t,root);

    }

     

    /**

     * 从某个节点开始查找某个元素是否存在

     * 在二叉查找树中查找x的过程如下:

     * 1、若二叉树是空树,则查找失败。

     * 2、若x等于根结点的数据,则查找成功,否则。

     * 3、若x小于根结点的数据,则递归查找其左子树,否则。

     * 4、递归查找其右子树。

     */

    public boolean contains(T t,BinaryNode<T> node){

        if(node==null){

            return false;

        }

        /**

         * 这就是为什么使用Comparable的泛型

         * compareTo的对象也必须是实现了Comparable接口的泛型,

         * 所以参数必须是BinaryNode<T> node格式

         */

        int result=t.compareTo(node.data);

        if(result>0){//去右子树查找

            return contains(t,node.right);

        }else if(result<0){//去左子树查找

            return contains(t,node.left);

        }else{

            return false;          

        }

    }

     

    //插入元素

    public void insert(T t){

        root=insert(t,root);

    }

     

    /**

     * 将节点插入到以某个节点为头的二叉树中

     * 这个插入其实也是一个递归的过程

     * 递归最深层的返回结果一个包含要插入的节点子树的头节点

     */

    public BinaryNode insert(T t,BinaryNode<T> node){

        //如果是空树,直接构造一棵新的二叉树

        if(node==null){

            return new BinaryNode<T>(t);

        }

         

        int result=t.compareTo(node.data);

         

        if(result<0){

            node.left=insert(t,node.left);

        }else if(result>0){

            node.right=insert(t,node.right);

        }else{

            ;//即要插入的元素和头节点值相等,直接返回即可

        }

        return node;

    }

     

     

    /**

     * 删除元素

     * 返回调整后的二叉树头结点

     */

    public BinaryNode delete(T t){

        return delete(t,root);

         

    }

     

    /**

     * 在以某个节点为头结点的树结构中删除元素

     * 首先需要找到该关键字所在的节点p,然后具体的删除过程可以分为几种情况:

     * p没有子女,直接删除p

     * p有一个子女,直接删除p

     * p有两个子女,删除p的后继q(q至多只有一个子女)

     * 确定了要删除的节点q之后,就要修正q的父亲和子女的链接关系,

     * 然后把q的值替换掉原先p的值,最后把q删除掉

     */

    public BinaryNode delete(T t,BinaryNode<T> node){

        if(node==null){//节点为空还要啥自行车

            return node;

        }

        /**

         * 首先要找到这个节点,所以还是得比较

         */

        int result=t.compareTo(node.data);

         

        /**

         * 去左半部分找这个节点,

         * 找到节点result==0,这个递归就停止

         */

        if(result<0){

            node.left=delete(t,node.left);

        }else if(result>0){//去右半部分找这个节点

            node.right=delete(t,node.right);

        }

        /**

         * 如果这个节点的左右孩子都不为空,那么找到当前节点的后继节点,

         *

         */

        if(node.left!=null && node.right!=null){

            /**

             * node节点的右子树部分的最小节点,实际上就是它的后继节点

             * 得到后继节点的值

             */

            node.data = findMin(node.right).data; 

            /**

             * 这个过程并不是删除后继节点,是一步一步的把所有的节点都替换上来

             */

            node.right = delete(node.data,node.right); 

        }else{

            /**

             * 如果二叉搜索树中一个节点是完全节点,

             * 那么它的前驱和后继节点一定在以它为头结点的子树中,应该是这样的

             * 来到了只有一个头节点和一个子节点的情况

             */

            node = (node.left!=null)?node.left:node.right;

        }

        //此处的node,是经过调整后的传入的root节点

        return node;

         

    }

     

    /**

     * 返回二叉树中的最小值节点

     * 此时无比想念大根堆和小根堆

     */

    public BinaryNode<T> findMin(BinaryNode node){

        if(node==null)

            return null;   

        /**

         * 如果node不为空,就递归的去左边找

         * 最小值节点肯定是左孩子为空的节点

         */

        if(node.left!=null)

            node=findMin(node.left);

        return node;

    }

     

}

  

时间: 2024-09-18 16:19:01

二叉树学习笔记之二叉查找树(BSTree)的相关文章

二叉树学习笔记之经典平衡二叉树(AVL树)

二叉查找树(BSTree)中进行查找.插入和删除操作的时间复杂度都是O(h),其中h为树的高度.BST的高度直接影响到操作实现的性能,最坏情况下,二叉查找树会退化成一个单链表,比如插入的节点序列本身就有序,这时候性能会下降到O(n).可见在树的规模固定的前提下,BST的高度越低越好. 平衡二叉树 平衡二叉树是计算机科学中的一类改进的二叉查找树. 平衡二叉树具有以下性质: (1)一棵空树是平衡二叉树 (2)如果树不为空,它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.

二叉树学习笔记之树的旋转

树旋转(Tree rotation)是二叉树中的一种子树调整操作,每一次旋转并不影响对该二叉树进行中序遍历的结果. 树旋转通常应用于需要调整树的局部平衡性的场合. 左旋和右旋 树的旋转有两种基本的操作,即左旋(逆时针方向旋转)和右旋(顺时针方向旋转). 树旋转包括两个不同的方式,分别是左旋转(以P为转轴)和右旋转(以Q为转轴).两种旋转呈镜像,而且互为逆操作. 下图示意了两种树旋转过程中, 子树的初态和终态 +---+ +---+ | Q | | P | +---+ +---+ / \ righ

二叉树学习笔记之B树、B+树、B*树

动态查找树主要有二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree), 红黑树 (Red-Black Tree ), 都是典型的二叉查找树结构,查找的时间复杂度 O(log2-N) 与树的深度相关,降低树的深度会提高查找效率,于是有了多路的B-tree/B+-tree/ B*-tree (B~Tree). 关于这B树以及B树的两种变体,其实很好区分, 相比B树,B+树不维护关键字具体信息,不考虑value的存储,所有的我们需

作为一个新手的Oracle(DBA)学习笔记

Oracle数据库笔记 Jack Chaing 作者QQ595696297 交流群 127591054 祝大家学习进步. 如果大家想看Word版本的可以去下载:Word排版比较清晰一些. http://download.csdn.net/detail/jack__chiang/9810532 此笔记是作者本人去年开始从一个DBA新人的学习笔记,积累至今,希望拿出来给那些对DBA有兴趣的童孩学习,大家一起努力嘛. 此笔记记录了作者工作学习中从零基础的学习的记录,和从中遇见的问题与问题的解决!很高兴

简单入门——深度学习笔记(Part II)

更多深度文章,请关注:https://yq.aliyun.com/cloud 作者介绍:Deepak Shah Deepak Shah毕业于德克萨斯奥斯汀分校,徒步旅行爱好者,目前是深度学习\数据科学实习生,研究领域专注于深度学习.编程.金融等方面. 个人主页:http://www.deepakvshah.com/ Medium论坛:https://medium.com/@dvshah13 Github论坛:https://github.com/Dvshah13  笔记分为两个部分,本文是笔记P

JetSpeed学习笔记(一)

笔记 JetSpeed学习笔记(一) fuweilin 2005-4-7 前言 参加了公司的portal的兴趣小组,今天对portal进行学习.首先上网看了看一些portal的资料,对portal.portlet.portlet container以及JSR168等概念有个基本的了解.决定进一步实战的方式感受portal,于是学习JetSpeed.     1.  JetSpeed介绍JetSpeed是Apache组织开发的一个采用Java和XML的开放源代码的企业信息门户的实现.门户可以让终端

PHP输入输出流学习笔记

  这篇文章主要介绍了PHP输入输出流学习笔记,PHP输入和输出流是通过php://来访问的,它允许访问 PHP 的输入输出流.标准输入输出和错误描述符,内存中.磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器,需要的朋友可以参考下 PHP输入和输出流是通过php://来访问的,它允许访问 PHP 的输入输出流.标准输入输出和错误描述符, 内存中.磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器. php://stdin, php://stdout 和 php://std

PHP学习笔记 (1) 环境配置与代码调试

一配置PHP环境 1.了解什么是PHP PHP("PHP: Hypertext Preprocessor",超文本预处理器的字母缩写) PHP,是英文超级文本预处理语言Hypertext Preprocessor的缩写.PHP 是一种 HTML 内嵌式的语言,是一种在服务器端执行的嵌入HTML文档的脚本语言,语言的风格有类似于C语言,被广泛的运用 2.PHP的背景和优势 PHP的发展背景 1).1994年由Rasmus Lerdorf创建,开始是一个简单的Perl语言编写的程序,用统计

Node.js 学习笔记之简介、安装及配置

 本文是Node.js学习笔记系列文章的第一篇,主要给大家讲解的是在Windows和Linux上安装Node.js的方法.软件安装版本以0.12.0为例.希望大家能够喜欢.     简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好. 谁适合阅