二叉树的定义
二叉树是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一
个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
二叉树的特点:
①每个结点最多有两棵子树;
②二叉树是有序的,其次序不能任意颠倒。
注意:二叉树和树是两种树结构。
特殊的二叉树
一、斜树
1.所有结点都只有左子树的二叉树称为左斜树;
2.所有结点都只有右子树的二叉树称为右斜树;
3.左斜树和右斜树统称为斜树。
斜树的特点:
1.在斜树中,每一层只有一个结点;
2.斜树的结点个数与其深度相同。
二、满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上。
满二叉树的特点:
1.叶子只能出现在最下一层;
2.只有度为0和度为2的结点。
满二叉树在同样深度的二叉树中结点个数最多
满二叉树在同样深度的二叉树中叶子结点个数最多
三、完全二叉树
对一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满
二叉树中编号为i的结点在二叉树中的位置完全相同。
在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树。
完全二叉树的特点
1.叶子结点只能出现在最下两层,且最下层的叶子结点都集中在二叉树的左部;
2.完全二叉树中如果有度为1的结点,只可能有一个,且该结点只有左孩子。
3.深度为k的完全二叉树在k-1层上一定是满二叉树。
二叉树性质:①二叉树的第i层上最多有2i-1个结点(i≥1)
②一棵深度为k的二叉树中,最多有2k-1个结点,最少有k个结点。
③在一棵二叉树中,如果叶子结点数为n0,度为2的结点数为n2,则有: n0=n2+1。
④具有n个结点的完全二叉树的深度为 log2n +1。
⑤对一棵具有n个结点的完全二叉树中从1开始按层序编号,则对于任意的序号为
i(1≤i≤n)的结点(简称为结点i),有:
(1)如果i>1,
则结点i的双亲结点的序号为 i/2;如果i=1,
则结点i是根结点,无双亲结点。
(2)如果2i≤n,
则结点i的左孩子的序号为2i;
如果2i>n,则结点i无左孩子。
(3)如果2i+1≤n,
则结点i的右孩子的序号为2i+1;如果2i+1>n,则结点 i无右孩子。
二叉树的遍历操作
二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中的所有结点,使得每个结
点被访问一次且仅被访问一次。
前序(根)遍历
若二叉树为空,则空操作返回;否则:
①访问根结点;
②前序遍历根结点的左子树;
③前序遍历根结点的右子树。
中序(根)遍历
若二叉树为空,则空操作返回;否则:
①中序遍历根结点的左子树;
②访问根结点;
③中序遍历根结点的右子树。
后序(根)遍历
若二叉树为空,则空操作返回;否则:
①后序遍历根结点的左子树;
②后序遍历根结点的右子树。
③访问根结点。
层序遍历
二叉树的层次遍历是指从二叉树的第一层(即根结点)开始,从上至下逐层遍历,在同
一层中,则按从左到右的顺序对结点逐个访问。
二叉树的存储结构及实现
顺序存储结构
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置
(下标)应能体现结点之间的逻辑关系——父子关系。
#include<bits/stdc++.h>
#include<cstring>
using namespace std;
void Preorder(int root, char data[])
{
if(data[root]!='\0')
{
cout<<data[root];
Preorder(2*root,data);
Preorder(2*root+1,data);
}
return;
}
void InOrder(int root, char data[])
{
if(data[root]!='\0')
{
InOrder(2*root,data);
cout<<data[root];
InOrder(2*root+1,data);
}
return;
}
void PostOrder(int root, char data[])
{
if(data[root]!='\0')
{
PostOrder(2*root,data);
PostOrder(2*root+1,data);
cout<<data[root] ;
}
return;
}
void create(char Preorder[],char Inorder[],int start_p, int end_p,
int start_i,int end_i, char data[],int root)
{
if(start_p>end_p)
return;
else{
int k;
for(int i=start_i;i<=end_i;i++)
{
if(Inorder[i]==Preorder[start_p])
{
k=i;
break;
}
}
data[root]=Preorder[start_p];
create(Preorder,Inorder,start_p+1,start_p+k-start_i,start_i,k-1,data, 2*root);
create(Preorder,Inorder,start_p+k-start_i+1,end_p,k+1,end_i,data,2*root+1);
}
return;
}
int main()
{
int total=1;
char preorder[100],inorder[100];
cin>>preorder>>inorder;
int length=0;
while(preorder[length]!='\0')
length++;
char *data=new char[(int)pow(2,length+1)];
memset(data,'\0',pow(2,length+1));
create(preorder,inorder,0,length-1,0,length-1,data,1);
PostOrder(1,data);
return 0;
}
二叉链表
基本思想:令二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树结点有
关的数据信息外,还要设置指示左右孩子的指针。
结点结构: lchild data rchild
data:数据域,存放该结点的数据信息;
lchild:左指针域,存放指向左孩子的指针;
rchild:右指针域,存放指向右孩子的指针。
#include<iostream>
#include<cstring>
using namespace std;
struct Node{
char data;
Node *lchild,*rchild;
};
class Tree{
Node *root;
Node *creat();
void release(Node *root);
void qian(Node *root);
void zhong(Node *root);
void hou(Node *root);
public:
Tree(){root=creat();}
~Tree(){release(root);}
void qian(){qian(root);}
void zhong(){zhong(root);}
void hou(){hou(root);}
};
Node *Tree::creat(){
Node *root;
char c;
cin>>c;
if(c=='#') root=NULL;
else{
root=new Node;
root->data=c;
root->lchild=creat();
root->rchild=creat();
}
return root;
}
void Tree::release(Node *root){
if(root==NULL) return;
else{
release(root->lchild);
release(root->rchild);
delete root;
}
}
void Tree::qian(Node *root){
if(root==NULL) return;
else{
cout<<root->data;
qian(root->lchild);
qian(root->rchild);
}
}
void Tree::zhong(Node *root){
if(root==NULL) return;
else{
zhong(root->lchild);
cout<<root->data;
zhong(root->rchild);
}
}
void Tree::hou(Node *root){
if(root==NULL) return;
else{
hou(root->lchild);
hou(root->rchild);
cout<<root->data;
}
}
前序遍历——非递归算法(伪代码)
1.栈s初始化(空栈);
2.循环直到root为空且栈s为空
2.1 当root不空时循环
2.1.1 输出root->data;
2.1.2 将指针root的值保存到栈中;
2.1.3 继续遍历root的左子树(root=root->lchild)
2.2 如果栈s不空,则
2.2.1 将栈顶元素弹出至root(root=s.pop());
2.2.2 准备遍历root的右子树(root=root->rchild);
template <class T>
void BiTree::PreOrder(BiNode<T> *root) {
SeqStack<BiNode<T> *> s;
while (root!=NULL||!s.empty()) {
while (root!= NULL){
cout<<root->data;
s.push(root);
root=root->lchild;
}
if(!s.empty()){
root=s.pop();
root=root->rchild;
}
}
}
中序遍历——非递归算法(伪代码)
1.栈s初始化(空栈);
2.循环直到root为空且栈s为空
2.1 当root不空时循环
2.1.1 将指针root的值保存到栈中;
2.1.2 继续遍历root的左子树(root=root->lchild)
2.2 如果栈s不空,则
2.2.1 将栈顶元素弹出至root(root=s.pop());
2.2.2 输出root->data;
2.2.3 准备遍历root的右子树(root=root->rchild);
template <class T>
void BiTree::InOrderwithoutD (BiNode<T> *root)
{
stack< BiNode<T> * > aStack;
while(!aStack.empty()||root) {
while(root){
aStack.push(root);
root=root->lchild;
}
if(!aStack.empty()){
root=aStack.top();
aStack.pop();
cout<<root->data;
root=root->rchild;
}
}
}
层序遍历
1.队列Q初始化;
2. 如果二叉树非空,将根指针入队;
3. 循环直到队列Q为空
3.1 q=队列Q的队头元素出队;
3.2 访问结点q的数据域;
3.3 若结点q存在左孩子,则将左孩子指针入队;
3.4 若结点q存在右孩子,则将右孩子指针入队;
template<class T>
void BiTree<T>::LevelOrder(BinaryTreeNode<T>* root){
queue<BiTreeNode<T>*> aQueue;
if(root)
aQueue.push(root);
while(!aQueue.empty())
{
root=aQueue.front(); //取队列首结点
aQueue.pop();
cout<<pointer->data;//访问当前结点
if(root->lchild) //左子树进队列
aQueue.push(root->lchild);
if(root->rchild) //右子树进队列
aQueue.push(root->rchild);
}//end while
}
求二叉树的结点个数
int Tree::count(Node *root){
int number=0;
if(root==NULL) number=0;
else
number=count(root->lchild)+count(root->rchild)+1;
return number;
}
统计叶子节点的数目
int Tree::leafcount(Node *root){
int number=0;
if(root==NULL)
number=0;
else if(root->lchild==NULL&&root->rchild==NULL)
number=1;
else
number=leafcount(root->lchild)+leafcount(root->rchild);
return number;
}
计算树的高度
int Tree::cal_height(Node *root){
int lheight=0,rheight=0;
if(root==NULL) return 0;
lheight=cal_height(root->lchild);
rheight=cal_height(root->rchild);
if(lheight>rheight) return lheight+1;
else return rheight+1;
}
交换左右子树
void Tree::change(Node *root){
if(root==NULL) return;
else{
swap(root->lchild,root->rchild);
change(root->lchild);
change(root->rchild);
}
}
三叉链表
结点结构:lchild data parent rchild
data、lchild和rchild三个域的含义同二叉链表的结点结构;
parent域为指向该结点的双亲结点的指针。
template<class T>
struct Node
{
T data;
Node<T> * lchild, *rchild,*parent;
};
template <class T>
BiNode<T> * BiTree<T>::Creat(BiNode<T> * &root ,BiNode<T> *parent){
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch=="#") root = NULL;
else{
root = new BiNode<T>; //生成一个结点
root->data=ch;
root->parent=parent;
Creat(root->lchild,root ); //递归建立左子树
Creat(root->rchild,root); //递归建立右子树
}
return root;
}
template<class T>
BiTree<T>::BiTree( int i)
{
number=0;
Creat(root,NULL);
}
森林转换为二叉树
⑴ 将森林中的每棵树转换成二叉树;
⑵ 从第二棵二叉树开始,
依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,
当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
二叉树转换为树或森林
⑴ 加线——若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来;
⑵ 去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;
⑶ 层次调整——整理由⑴、⑵两步所得到的树或森林,使之层次分明。
森林的遍历
森林有两种遍历方法:
⑴前序(根)遍历:前序遍历森林即为前序遍历森林中的每一棵树。
⑵后序(根)遍历:后序遍历森林即为后序遍历森林中的每一棵树。
最优二叉树-哈夫曼树及哈夫曼编码
相关概念
叶子结点的权值:对叶子结点赋予的一个有意义的数值量。
二叉树的带权路径长度:设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结
点的路径长度与相应叶子结点权值的乘积之和。
哈夫曼树:给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
哈夫曼树的特点:
1. 权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。
2. 只有度为0(叶子结点)和度为2(分支结点)的结点,不存在度为1的结点.
哈夫曼算法的存储结构
weight lchild rchild parent
weight:权值域,保存该结点的权值;
lchild:指针域,结点的左孩子结点在数组中的下标;
rchild:指针域,结点的右孩子结点在数组中的下标;
parent:指针域,该结点的双亲结点在数组中的下标。
struct element
{ int weight;
int lchild, rchild, parent;
};
1.数组huffTree初始化,所有元素结点的双亲、左右孩子都置为-1;
2. 数组huffTree的前n个元素的权值置给定值w[n];
3. 进行n-1次合并
3.1 在二叉树集合中选取两个权值最小的根结点,
其下标分别为i1, i2;
3.2 将二叉树i1、i2合并为一棵新的二叉树k(初值为n;依次递增);
void HuffmanTree(element huffTree[ ], int w[ ], int n ) {
for (i=0; i<2*n-1; i++) {
huffTree [i].parent= -1;
huffTree [i].lchild= -1;
huffTree [i].rchild= -1;
}
for (i=0; i<n; i++)
huffTree [i].weight=w[i];
for (k=n; k<2*n-1; k++) {
Select(huffTree, &i1, &i2);
huffTree[k].weight=huffTree[i1].weight+huffTree[i2].weight;
huffTree[i1].parent=k;
huffTree[i2].parent=k;
huffTree[k].lchild=i1;
huffTree[k].rchild=i2;
}
}
哈夫曼树应用——哈夫曼编码
编码:给每一个对象标记一个二进制位串来表示一组对象。
例:ASCII,指令系统
等长编码:表示一组对象的二进制位串的长度相等。
不等长编码:表示一组对象的二进制位串的长度不相等。
前缀编码:一组编码中任一编码都不是其它任何一个编码的前缀 。
前缀编码保证了在解码时不会有多种可能。
线索二叉树
线索链表
线索:将二叉链表中的空指针域指向前驱结点和后继结点的指针被称为线索;
线索化:使二叉链表中结点的空链域存放其前驱或后继信息的过程称为线索化;
线索二叉树:加上线索的二叉树称为线索二叉树。
结点结构:ltag data child rtag
ltag={
0: lchild指向该结点的左孩子
1: lchild指向该结点的前驱结点
}
rtag={
0: rchild指向该结点的右孩子
1: rchild指向该结点的后继结点
}
enum flag {Child, Thread};
template <class T>
struct ThrNode
{
T data;
ThrNode<T> *lchild, *rchild;
flag ltag, rtag;
};
template <class T>
class InThrBiTree{
public:
InThrBiTree();
~ InThrBiTree( );
ThrNode *Next(ThrNode<T> *p);
void InOrder(ThrNode<T> *root);
private:
ThrNode<T> *root;
ThrNode<T> * Creat();
void ThrBiTree(ThrNode<T> *root);
};
template <class T>ThrNode<T>* InThrBiTree<T>::Creat( ){
ThrNode<T> *root;
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch=="#") root = NULL;
else{
root=new ThrNode<T>;
root->data = ch;
root->ltag = Child; root->rtag = Child;
root->lchild = Creat( );
root->rchild = Creat( );
}
return root;
}
中序线索化二叉树:递归实现
基本思想:
在遍历的过程中完成线索化
可以采用前序、中序、后序遍历建立前序线索二叉树、中序线索二叉树和后序线索二叉树。
中序线索二叉树的构造方法
中序线索化根结点的左子树;
对根结点进行线索化;
中序线索化根结点的右子树;
template <class T> void ThrBiTree<T>::ThrBiTree (ThrNode<T>*root) {
if (root==NULL) return; //递归结束条件
ThrBiTree(root->lchild);
if (!root->lchild){ //对root的左指针进行处理
root->ltag = Thread;
root->lchild = pre; //设置pre的前驱线索
}
if (!root->rchild) root->rtag = Thread;
if(pre != NULL){
if (pre->rtag==Thread) pre->rchild = root;
}
pre = root;
ThrBiTree(root->rchild);
}
线索二叉树的建立
template <class T>
InThrBiTree<T>::InThrBiTree( )
{
//ThrNode<T>* pre = NULL;
this->root = Creat( );
ThrBiTree(root);
}
在中序线索树中查找结点的中序遍历的后继
template <class T> ThrNode<T>* InThrBiTree<T>::Next(ThrNode<T>* p)
{
ThrNode<T>* q; //要查找的p的后继
if (p->rtag==Thread) q = p->rchild;
else{
q = p->rchild;
while (q->ltag==Child) {
q = q->lchild;
}
}
return q;
}
线索链表的遍历算法:中序遍历中序线索树
template <class T>
void InThrBiTree<T>::InOrder(ThrNode<T> *root){
ThrNode<T>* p = root;
if (root==NULL) return;
while (p->ltag==Child) { p = p->lchild; }
cout<<p->data<<" ";
while (p->rchild!=NULL) {
p = Next(p);
cout<<p->data<<" ";
}
cout<<endl;
}