您的位置:首页 > 理论基础 > 数据结构算法

数据结构之二叉树

2015-10-20 08:30 218 查看
昨天看了一个视频,讲解Facebook、Amazon、Google啊这类名企的面试题目,始终离不开数据结构这个基础课程,围绕数据结构能出的考题实在太多。虽然我不是软件开发工程师,但作为嵌入式工程师,数据结构和算法也是必修课,一个高效的嵌入式代码离不开巧妙的数据结构和优秀的算法。

介绍二叉树之前先说说什么是树。树作为非线性数据结构,课本上的定义太抽象:树是包括n个节点的有限非空集合T,其中一个特定的节点r称为根,其余节点(T-{r})划分成m(m>=0)个互不相交的子集T1、T2、。。。Tm,其中每个子集都是树,被称为根r的子树。是不是很拗口,其实树就是一种逻辑上的数据表示方式,值得注意的一点是树不可为空,这与二叉树是有区别的。

树的一些基本定义和术语: 节点:树中的元素。

边:从根节点到它的子树根之间形成的一条边。

路径:从某个节点沿树中的边可到达另一个节点,则称这两个节点之间存在一条路径。

节点的度(degree):节点拥有的子树个数

叶子节点(leaf):度为0的节点

分支结点(branch):度不为0 的节点

树的度:最大节点的度

树的高度:树的层次

森林:树的集合。0个或多个不相交的树组成的集合

二叉树。二叉树是一类非常重要的数据类型,许多实际的问题都可以转化到二叉树进行处理。

二叉树的定义:二叉树(binary tree)是结点的有限集合,该集合或者为空集,或者是由一个根和两个互不相交的、称为该根的左子树和右子树的二叉树组成。因此一个二叉树可能存在以下5种形态


注意,二叉树可以为空!光看定义来分析下树与二叉树的区别:可否为空集的区别;每个节点的最大子树的区别;有序与无序的区别。

下面来分析下二叉树的常用性质:

1、二叉树的第i(i≥1)层上至多有2^(i-1) 个节点。本性质可用归纳法证明,当i=1时,二叉树只有一个节点,结论成立。设当i=k时至多有2^(k-1)个节点,则i=k+1时,因为每个节点最多有两个子树,所以第k+1层最多有2*2^(k-1)=2^(k)个节点。结论成立。

2、高度为h的二叉树上至多有2^h–1个结点。证明:当h=0时,二叉树为空二叉树。当h>0时,利用性质1,高度为h的二叉树中结点的总数最多为2^0+2^1+2^2+...+2^(h-1)=2^h-1等比数列求和问题。

3、包含n个元素的二叉树的高度至少为[log2 (n+1)]。这个可以由性质2推导出来:n<=2^h-1,所以h>=log2(n+1)

4、任意一棵二叉树中,若叶子节点的个数为n0,度为2的节点的个数为n2,则必有n0=n2+1。证明:由于二叉树的度只有0、1、2三种,则设度为1的节点个数为n1,则总节点n=n0+n1+n2,因为除了根节点每个节点都有一个分支进来,所以分支B=n-1,同时B=2*n2+n1,两式联立可得叶子节点的个数比度为2的节点数多1.

5、满二叉树:高度为h的二叉树恰好有2^h
–1个节点时称为满二叉树。

6、完全二叉树:一棵二叉树中,只有最下面两层节点的度可以小于2,并且最下一层的叶子节点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。



下面主要介绍完全二叉树的性质

1、具有n个节点的完全二叉树的高度为log2(n+1),也就是说完全二叉树是二叉树中最矮的

2、假定对一棵有n个节点的完全二叉树中的节点,按从上到下、从左到右的顺序,从0到n-1编号,设树中一个节点的序号为i,0£i<n,则有以下关系成立:

(1)当i=0时,该节点为二叉树的根。

(2)若i>0,则该节点的双亲的序号为ë(i-1)/2û

(3)若2i+1<n,则该节点的左子树的序号为2i+1,否则该结点无左子树。

(4)若2i+2<n,则该节点的右子树的序号为2i+2,否则该结点无右子树。

二叉树的C++描述

ADT BinaryTree{
Data: 二叉树是结点的有限集合,该集合或者为空集,或者是由一个根和两个互不相交的称为该根的左子树和右子树的二叉树组成。
Operations:
Create(): 创建一棵空二叉树。
Destroy(): 撤销一棵二叉树。
IsEmpty():若二叉树空,则返回true,否则返回false。
Clear():   移去所有结点,成为空二叉树。
Root(x):取x为根结点;若操作成功,则返回true,否则返回false
MakeTree(x,left,right):创建一棵二叉树,x为根结点,left为左子树,right为右子树。
BreakTree(x ,left, right):拆分二叉树为三部分,x为根的值,left和right分别为原树的左右子树
PreOrder: 前序遍历二叉树。
InOrder:  中序遍历二叉树。
PostOrder:后序遍历二叉树。
} 

与堆栈类似,二叉树也存在顺序表示和链式表示。我们所有的数据结构都是一种逻辑上的描述,但是数据最终都是要存到存储介质中的,不外乎两种存储方式:一是像数组一样按顺序存储在一块连续的内存单元,二是像链表那样可以存放在不同的内存单元,每块链表结点都有指向下一节点的指针。





一般的二叉树不适合顺序存储的方式,所以多采用链式表示。在链式表示中,每个节点有两个指针域,分别指向左子树的数据单元和右子树的数据单元。二叉树的链表结构有利于父节点到子节点的访问。

设置根节点
template<class T>
bool BinaryTree<T>::Root(T& x)const {
if(root) {
x = root->element;
return true;
}
else return false;
}
创建一颗二叉树,本函数仅创建根节点和分支,并没指向实际的子树节点
template<class T>
void BinaryTree<T>::MakeTree(const T& x,BinaryTree<T>& left,BinaryTree<T>& right) {
if(root||&left==&right)    return;
root = new BTNode<T>(x, left.root, right.root);
left.root = right.root = NULL;
}
上式用到的节点构造函数
BTNode(const T& x, BTNode<T>* l,BTNode<T>* r) {
element = x;        lChild = l;         rChild = r;
}
删除一个二叉树,将它拆分为根节点的值、左子树、右子树
template<class T>
void BinaryTree<T>::BreakTree(T& x,
BinaryTree<T>&left,BinaryTree<T>&right) {
if(!root||&left==&right||left.root||right.root) //不可将已经为节点的left/right作为参数传递进来
return;
x = root->element;
left.root = root->lChild;
right.root = root->rChild;
delete root;
root = NULL;
}


二叉树的遍历:•遍历(traverse)一个有限节点的集合,意味着对该集合中的每个节点访问且仅访问一次。

二叉树的遍历算法有两种依据规则,一种是先左后右即左子树优先遍历,一种是先右后左即右子树优先遍历。我们一般采用左子树优先的原则。遍历算法有:

前序遍历:先访问每棵树的根节点,然后是左子树,最后是右子树

中序遍历:先访问每棵树的左子树,在访问根节点,最后访问右子树

后序遍历:先访问每棵树的左子树,再访问右子树,最后是中间根节点

由遍历可得:给出二叉树的序遍历序列和中序遍历序列可以唯一确定一棵二叉树。
给出二叉树的后序遍历序列和中序遍历序列可以唯一确定一棵二叉树。
但是:当n>1时,给出二叉树的序遍历序列和后序遍历序列可以唯一确定一棵二叉树。

例:前序遍历得AB,后序遍历得BA,但是可以对应两种二叉树


二叉树的遍历算法多采用递归算法,可以高效的解决很多实际问题。本人能力有限,这里不展开了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: