版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q__y__L/article/details/70229911
二叉树是一种非常常见的数据结构,它结合了有序数组与链表的优点:在二叉树中查找数据与在数组中查找一样快,在二叉树添加、删除数据的速度也和在链表中一样,所以二叉树的相关技术一直是程序员面试笔试中必考的知识点。
- 问题的思考
这里引用一个例子:
二叉树,本质上,是对链表和数组的一个折中。。比如,我有一个任务,需要输入 10万个数据(32位整数),然后有两个操作:
1.添加(删除)一个整数。
2.询问第x大的数据。
比如,我给你 1, 8, 13, 10(等等一堆数据)…….然后我询问第3大的数据,然后我插入 18然后我询问第4大的数据我再插入 9我再询问第2大的数据不停的重复1,2重复10万次。。应该如何实现。
你会发现,用有序链表,不行,查找(包括1需要找到对应位置,以及2查找)成本大O(N),但具体这个插入操作成本小O(1)。用有序数组,查找(2的查找)成本小O(1)。但1的插入操作成本很大O(N)。
所以,我们折中使用排序二叉树(二叉树仅仅作为排序二叉树的基础),查找(包括1需要找到对应位置,以及2查找)成本挺小O(logN)。具体这个插入操作成本也挺小O(logN)。
- 基本概念
二叉树的样子
- 节点的度:节点所拥有的子树的个数成为该节点的度,B的度为2,D的度为0.
- 叶节点:度为0的节点成为叶节点,或称终端节点。如D、E、F、G都是。
- 分支节点:度不为0的节点成为分支节点,或者成为非终端节点。
- 左孩子、右孩子、双亲。树种一个节点的子树的根节点称为这个节点的孩子,这个节点成为它孩子节点的双亲。
- 路径、路径长度:如果一棵树的一串节点由n1,n2,……nk有如下关系:节点ni是ni+1的父节点
(1≤i≤k) ,就把n1,n2,……nk称为一条由n1->nk的路径,这条路径的长度为k-1.如A->B->D,路径长度为2. - 节点的层数:规定根节点的层数为1,其与节点的层数为它的双亲节点的层数加1。
- 树的深度:树中所有节点的最大层数成为树的深度。
- 满二叉树:树中所有的分支节点都存在左子树和右子树,且所有的叶节点都在同一层上。上图就是。
- 完全二叉树:一颗深度为K的又n个节点的二叉树,树中的节点从上至下,从左到右的顺序进行编号,如果编号为i的节点与满二叉树编号为i的节点在二叉树中的位置相同,则该树为完全二叉树。完全二叉树的特点是叶子节点只能出现在最下层或者次下层。上图去掉G,仍然是完全二叉树,但去掉E就不是了。
#- 基本性质
- 一颗非空二叉树第i层上最多有
2i−1 个节点. - 一颗深度为
k 的二叉树中,最多具有2k−1个节点,最少为k个。 - 对于一颗非空完全二叉树,度为0的节点重视比度为2的节点多一个,即若叶节点数为
n0 ,度为2的节点数为n2 ,则有n0=n2+1 .
证明:n0,n1,n2 分别表示叶子节点有0、1、2的节点个数,n 为节点总数,则n=n1+n2+n0 ,由树和二叉树的性质,n=2×n2+n1 (所有节点的度之和+1=节点总数)
联立2式子可得n2=n0−1 . - 具有n个节点的完全二叉树的深度为
⌊log2n⌋+1
证明:由前面的性质,深度为k的二叉树最多只有zk−1 个节点,切完全二叉树的定义是与同深度二叉树前面的编号相同,即它的总节点数N位于k 层和k−1 层满二叉树容量之间,即2k−1−1<n≤2k−1 或者2k−1<n≤2k ,取对数有k−1≤log2n<k ,因为k是整数,所以需要取整。 - 对于具有n个节点的完全二叉树,如果按照从上至下,从左至右的顺序对二叉树中的所有节点从1开始编号,则对于任意序号为i的节点有:
- 如果
i>1 ,则序号为i 的节点的双亲节点序号为i/2 ,(“/”表示整除) - 如果
2i≤n ,则序号为i的节点的左孩子结点序号为2i ;如果2i>n ,则序号i的节点无左孩子。
- 如果
- 一棵树有N个节点,则边的数目为N-1。
二叉树的构造
二叉树的数据结构类似于链表,不同的是每个节点保存了指向左右孩子的指针
typedef struct BitNode
{
char data;
struct BitNode* lchild,*rchild;
BitNode(char c):data(c),lchild(nullptr),rchild(nullptr){}
}BitNode,*BitTree;
有了数据结构接下来就要构造二叉树了,假设二叉树是这样的:
a
b c
d e f
那么我们需要加上终止符(比如“#”)来告诉计算机哪些是叶节点。所以加入终止符后的二叉树为:
a
b c
# d e f
# # # # # #
我们在构造的时候,一般按照先序遍历的顺序构造,所以上面的二叉树对于的输入为:ab#d##ce##f## .
//二叉树的建立
BitTree CreateBiTree()
{
char ch;
BitTree T;
cin>>ch;
if(ch=='#')
T=nullptr;
else
{
T=new BitNode(ch) ;
T->lchild=CreateBiTree();
T->rchild=CreateBiTree();
}
return T;
}
二叉树的遍历
二叉树的遍历有3种方式
- 先序遍历:访问根节点,先序遍历根节点的的左子树,先序遍历根节点的右子树。对应上面的例子为abdcef.
- 中序遍历:中序遍历根结点的左子树,访问根节点,中序遍历根节点的右子树。对应上面的例子为bdaecf.
- 后续遍历:后序遍历根节点的左子树,后续遍历根节点的右子树,访问根节点。对应上面的例子为dbefca.
对应的代码为:
//先序遍历
void PreOdrderTraverse(BitTree T)
{
if(T)
{
cout<<T->data;
PreOdrderTraverse(T->lchild);
PreOdrderTraverse(T->rchild);
}
}
//中序遍历
void InOrderTraverse(BitTree T)
{
if(T)
{
InOrderTraverse(T->lchild);
cout<<T->data;
InOrderTraverse(T->rchild);
}
}
//后序遍历
void PostOrderTraverse(BitTree T)
{
if(T)
{
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout<<T->data;
}
}
未完待续……