树
概念:树表示的是1对多的关系。
定义(逻辑结构):树(Tree)是n( n>=0 )个结点的有限集合,没有结点的树称为空树,在任意一颗非空树中:
有且仅有一个特定的称为根(root)的结点
当n>1的时,其余结点可分为 m( m>0 ) 个互不相交的有限集T1,T2,…, Tm,其中每一个集合 Ti 本身又是一棵树,并且称之为根的子树。
注意:树的定义是一个递归定义,即在树的定义中又用到树的概念。
- 结点的层级从根开始定义,根为第一层,根的孩子为第二层。若某结点在第i层,则其孩子就在i+1层。
- 对任意结点ni,ni的深度为从根到ni的唯一路径的长。因此,根的深度为0。
- 结点ni 的高是从ni 到一片树叶的最长路径的长。因此,所有树叶的高都为0。
- 一颗树的高等于它根的高。一颗树的深度等于它最深的树叶的深度; 该深度总是等于这棵树的高。
性质:E = N - 1
二叉树
概念:二叉树是一棵树,它的特点是每个结点至多有两棵子树。并且,子树有左右之分,其次序不能颠倒(有序)。
特殊的二叉树:完全二叉树、满二叉树和完美二叉树。
二叉树的性质:
- 二叉树在第 i 层至多有 2^(i-1) 个节点
- 层次为 k 的二叉树至多有 2^k - 1 个节点(完美二叉树)
- 对任何一颗二叉树 T,如果其叶子节点数为 n0 , 度为2的节点数为 n2,则n0 = n2 + 1
- 具有 n 个节点的完全二叉树,树的高度为 log2n (向下取整)。
- 如果对一颗有 n 个结点的完全二叉树的结点按层序从1开始编号,则对任意一结点有:(堆排序)
如果编号 i 为 1,则该结点是二叉树的根;
如果编号 i > 1,则其双亲结点编号为 parent(i) = i / 2,
若 2i > n 则该结点没有左孩子,否则其左孩子的编号为 2i,
若 2i + 1 > n 则该结点没有右孩子,否则其右孩子的编号为 2i + 1。
二叉树的存储结构
顺序映像 | 非顺序映像(推荐) |
---|---|
按照完全二叉树进行编号,将编号为i的元素存储到数组索引为i的地方。 | 以链表的形式来存储数据元素以及数据元素之间的关系。 |
*:顺序映像的缺点:1. 代码可读性差!(索引表示)
- 当树比较稀疏的时候,将极大的浪费内存空间。在最坏的情况下,一棵只有 k 个结点的单支树,却需要一个长度为 2k 的数组来存储。
二叉树的遍历
遍历对线性结构来说,是一个容易解决的问题,但是对于二叉树则不然。因为二叉树是一种非线性结构,每个结点都有可能有两颗子树,因而需要寻找一种规律,以便使二叉树上的结点能排列在一个线性队列上,从而便于遍历。
深度优先遍历
我们再来回顾一下二叉树的递归定义,可知,二叉树由3部分组成:根,左子树和右子树,因此如果能遍历这三个部分,便是遍历了整个二叉树。
假如以L, D, R分别表示左子树,根结点和右子树,则可能有DLR, LDR, LRD, DRL, RDL, RLD这六种遍历方案。
若限定先左后右,则只有DLR, LDR, LRD 这3种情况,分别称之为先 (根) 序遍历,中(根)序遍历,和后 (根) 序遍历。三者的区别主要在于访问根结点的先后顺序。(时间复杂度 0(N)、实现用栈)
广度优先遍历
广度优先遍历又叫层级遍历。简单来说,就是从上到下,从左到右依次遍历。(实现用队列)
二叉树的建树
有些情况下,我们已知的是二叉树的某个或某些遍历序列。我们需要根据这些遍历序列,构建出一棵”相等”的二叉树,这个过程就叫做二叉树的建树。
只知道前序、中序或中序、后序,就可以构建出一棵二叉树。
二叉搜索树
又叫二叉排序树。要求树中的结点可以按照某个规则进行比较。(中序遍历有序)
- 左子树中所有结点的key比根结点的key小,并且左子树也是二叉搜索树。
- 右子树中所有结点的key比根结点的key大,并且右子树也是二叉搜索树。
在BST中存储key相同的对象的方法:
-
可以在结点添加一个count属性。
-
拉链法, 在结点添加一个next指针域。
-
左子树 <= (右子树 >=)
API:
增:
boolean add(E e)
删:
void clear()
boolean remove(Object obj)
查:
boolean contains(Object obj)
E min()
E max()
遍历:
List<E> preOrder()
List<E> inOrder()
List<E> postOrder()
List<E> levelOrder()
获取集合属性:
int size()
boolean isEmpty()
建树:
static BinarySearchTree buildTree(List preOrder, List inOrder)
问题1:在Java中什么样的对象可以进行比较?
实现Comparable接口的对象。
问题2:我们实现的BST,添加,删除和查找的时间复杂度是多少呢?
O(h) h是树的高度
问题3:我们不能够保证树的平衡。因为我们删除度为2的结点,都是在右子树中删除。
动态添加和删除元素时,会导致树往左倾斜,最坏情况下会退化成链表。
自平衡二叉搜索树:
AVL树:对任意一个结点而言, 左子树和右子树的高度不超过1.树的高度 h = O(log(n))
红黑树:通过红黑规则, 保证树的高度也时O(log(n))
AVL VS 红黑树:
查找:AVL
添加:红黑树
删除:红黑树