1.二叉树的概念
树是一些节点的集合,节点之间用边链接,节点之间不能有环路。上层的节点称为父节点,下层节点称为子节点。最上层的节点称为根节点。
二叉树是特殊的树。对于每个节点而言,与之直接相连的子节点不能超过两个(可以为0)。左边的子节点称为左子树,右边的子节点称为右子树。如下图就是一颗二叉树:
与树相关的一些概念:
没有任何子节点的节点称为树叶,或叶子节点。
深度:对于任意节点N,其深度指的是从根节点到N的唯一路径的长。根的深度为0。深度最深的叶子节点的深度为树的深度。可以理解为:树根是一个入口,离树根越远,就越深。如上图:A、B、C的深度为1,D、E的深度为2。
高: 对于任意节点N,从N到一片树叶的最远路径的长为N的高度。(只可以从从上到下不能经过从下到上的节点。)树叶的高为0。树的高为根的高。如上图,根的高度为2。A的高度为1,其他节点高度为0。
2.二叉树的应用和时间复杂度
二叉树是一种常见的数据结构,常常用于查找,也运用于unix等常见操作系统的文件系统中。c++STL(标准模板库)中的set和map也使用二叉树中的红黑树实现。
二叉树的查找思想基于:在二叉树中,对于任意节点N,左子树中的所有项的值不大于节点N中存储的值,左子树中的所有项的值不小于节点N中存储的值。如下图:
这样,在查找时,只需要不断比较需要查找的x与N的大小,若小于N中的值,只需要搜索左子树,若大于N中的值,只需要搜索右子树。这样每次就能缩小搜索的范围。经过证明,普通二叉树的平均时间复杂度是O(LogN)。
看到这里,我们发现其实二叉树的搜索思想和二分查找一致,每次不断的减少搜索范围。但是二者之间还是有区别的。
对于二分查找而言,每次的时间复杂度不会超过O(LogN)。但是对于二叉树,搜索时间的复杂度取决于树的形状。在最坏情况下可能达到O(N)。如下图,如果要找到10,则要查找5次。
那我们为什么还要使用二叉树而不直接使用二分查找来代替?
这是因为,二分查找一般基于数组,如果需要插入或删除数据,则会带来很大的开销。因为每次插入或者删除数据需要将改变节点之后的数据往后挪或者往前挪。但是对于二叉树而言,只需要改变一下指向下一个节点的指针就可以很方便的实现插入或者删除。而且一些特殊的二叉树如红黑树可以保证查找的最坏复杂度不超过O(LogN)。
所以,如果是对于静态数据,不需要改变的数据而言,采用数组存储,使用二分查找比较好。而对于动态数据,需要频繁插入或者删除数据的,采取二叉树存储是较好的。
3.c++实现二叉树的插入、查询、遍历、删除(递归实现)
3.1二叉树的插入
思路:插入数据x,从根节点开始,不断比较节点与x的大小。若x小于节点,下一次比较x与节点的左子树,反之,比较x与节点的右子树。直到遇到一个空的节点,插入数据。(我们不考虑插入重复数据) 。如下图:
过程:比较4与7,4<7,再比较4与7的左子树6,4<6,比较4与6的左子树3,4>3,比较4与3的右子树,为空,插入4。
代码:
template< typename T> void BinaryTree<T>::insert(const T &theElement, BinaryNode * &t ) { if ( nullptr == t ){ t