一维数组存储树的结点(一般层序存储),数组一个元素对应一个结点,每个数组元素记录结点数据信息和该结点双亲在数组的下标。
struct PNode
{
char data;
int parent;
};
该方法能方便的找到双亲,但是不好找孩子,可以在结构体中再加一个信息,即第一个孩子的下标。
多重链表表示法。
法1:指针域的个数等于树的度。
缺点:会造成空间的浪费
法2:指针域个数等于结点的度
缺点:结点结构不一致,不好实现
法3:结点所有孩子构成一个单链表
如下图:
n个单链表共有n个头指针,这n个头指针又组成了一个线性表。
struct CTNode//孩子结点
{
int child;
CTNode *next;
};
struct CBNode//表头结点
{
char data;
CTNode *firstChild;
};
缺点:孩子好找,双亲不好找
二叉链表表示法。
每个结点除数据域外,还设置了两个指针分别指向该结点的第一个孩子和右兄弟。
顺序存储:双亲表示法,双亲、孩子表示法
链式存储:多重链表表示法,孩子链表表示法,孩子兄弟表示法。
二叉树及二叉搜索树BST
Treap树
Splay树
线段树(一)
线段树(二)
答: 最少2h-1 和 最多2h-1
图片来自《设高为h的二叉树(规定叶子结点的高度为1)只有度为0和2的结点,则此类二叉树的最少结点数和最多结点数分别为:》
答案:至少2k-1 至多2k-1 序号2k-2+1
用一维数组存储二叉树中的结点,并且结点的存储位置即下标应能体现结点之间的逻辑关系即父子关系。
二叉树的顺序存储结构是按层序存储的,一般适合存储完全二叉树。
void PreOrder(int root,char data[])//前序遍历
{
if(data[root]!='\0')
{
cout<<data[root];
PreOrder(2*root,data);
PreOrder(2*root+1,data);
}
}
void InOrder(int root,char data[])//中序遍历
{
if(data[root]!='\0')
{
InOrder(2*root,data);
cout<<data[root];
InOrder(2*root+1,data);
}
}
void PostOrder(int root,char data[])//后序遍历
{
if(data[root]='\0')
{
PostOrder(2*root,data);
PostOrder(2*root+1,data);
cout<<data[root];
}
}
二叉树的顺序存储结构一般仅存储完全二叉树。
二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树结点有关的数据信息外,还要设置指示左右的孩子指针。
结点:
struct BiNode
{
char data;
BiNode *lchild,*rchild;
};
void BiTree::PreOrder(BiNode *bt)
{
if(bt==NULL)
return;
else {
cout<<bt->data<<"\t";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
非递归算法:用栈实现,栈是实现递归最常用的结构。即遇到一个结点,就访问该结点并把该结点推入栈中,然后遍历它的左子树。遍历完它的左子树后,从栈顶托出该结点,遍历右子树。
void BiTree::PreOrder(BiNode *root)
{
Stack<BiNode*>s;
while(root!=NULL||s.empty())
{
while(root!=NULL)
{
cout<<root->data;
s.push(root);
root=root->lchild;
}
if(!s.empty())
{
root=s.top();
s.pop();
root=root->rchild;
}
}
}
中序遍历:(递归)
void BiTree::InOrder(BiNode *bt)
{
if(bt==NULL)
return;
else {
InOrder(bt->lchild);
cout<<bt->data<<"\t";
InOrder(bt->rchild);
}
}
非递归:用栈实现,遇到一个结点,把该结点推入栈中,然后遍历它的左子树。遍历完它的左子树后,从栈顶托出该结点并访问该结点,遍历右子树。
void BiTree::InOrder(BiNode *root)
{
Stack<BiNode*>s;
while(root!=NULL||s.empty())
{
while(root!=NULL)
{
s.push(root);
root=root->lchild;
}
if(!s.empty())
{
root=s.top();
s.pop();
cout<<root->data;
root=root->rchild;
}
}
}
后序遍历:(递归)
void BiTree::PostOrder(BiNode *bt)
{
if(bt==NULL)
return;
else {
PostOrder(bt->lchild);
PostOrder(bt->rchild);
cout<<bt->data<<"\t";
}
}
非递归:用栈实现,遇到一个结点,把该结点推入栈中,然后遍历它的左子树。遍历完它的左子树后,遍历右子树,最后从栈顶托出该结点并访问该结点。
这里的每个结点进栈两次,第二次出栈时访问结点。而前序和中序只进栈一次。
这里比较复杂,需要给栈中每个元素加一个特征位,区别是从栈顶元素左边回来还是右边回来的,如果从左边回来,需要继续遍历右子树,如果从右边回来的,说明左右子树均已遍历。
法1:
enum Tags//特征标识符定义
{
Left,Right;
};
class StackElement//栈元素的定义
{
public:
BiNode *pointer;//指向二叉树结点的指针
Tags tag;//特征标识符申明
};
void BiTree::PostOrder(BiNode *root)
{
StackElement element;
Stack<StackElement>s;
BiNode *pointer;
if(root==NULL)
return;
else pointer=root;
while(1)
{
while(pointer!=NULL)
{
element.pointer=pointer;
element.tag=Left;
s.push(element);
pointer=pointer->lchild;
}
element=s.pop();
pointer=element.pointer;
while(element.tag==Right)
{
cout<<pointer->data;
if(s.empty())
return;
else{
element=s.pop();
pointer=element.pointer;
}
}
element.tag=Right;
s.push(element);
pointer=pointer->rchild;
}
}
法2:依次将根结点的右儿子,左儿子入栈,当结点出栈时再进行访问。
void tree::T_print(BiNode *bt)
{
stack<BiNode*>s;
BiNode *cur,*pre=NULL;//cur指向栈顶,pre记录刚刚访问的结点。
if(root==NULL)
return;
s.push(bt);
while(!s.empty())
{
cur=s.top();
if((cur->lchild==NULL&&cur->rchild==NULL)||(pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
//如果栈顶是叶子结点或刚出栈元素和栈顶元素之间有“儿子-双亲”关系,出栈
{
cout<<cur->data;
s.pop();
pre=cur;
}
else {
if(cur->rchild!=NULL)
s.push(cur->rchild);
if(cur->lchild!=NULL)
s.push(cur->lchild);
}
}
}
层序遍历:队列
法1:顺序队列
void BiTree::LeverOrder( )
{
BiNode *Q[100],*q=NULL;//顺序队列最多100个结点
int front=-1,rear=-1; //队列初始化
if(root==NULL)
return;
Q[++rear]=root;//根指针入队
while(front!=rear)//当队列非空时
{
q=Q[++front];//出队
cout<<q->data;
if(q->lchild!=NULL)
Q[++rear]=q->lchild;
if(q->rchild!=NULL)
Q[++rear]=q->rchild;
}
}
法2:队列
void BiTree::LevelOrder(BiNode *root)
{
queue<BiNode*>a;
if(root)
a.push(root);
while(!a.empty())
{
root=a.front();
a.pop();
cout<<root->data;
if(root->lchild)
a.push(root->lchild);
if(root->rchild)
a.push(root->rchild);
}
}
二叉树的建立
原二叉树->扩展二叉树,如下图:
这里的二叉树的建立是按扩展前序遍历序列输入每个结点的值。如果输入#,建一棵空的子树,否则申请空间建新的结点。
BiNode* BiTree::creat(BiNode *bt)
{
BiNode *bt;
char ch;
cin>>ch;
if(ch=='#')
bt=NULL;
else
{
bt=new BiNode;
bt->data=ch;
bt->lchild=creat(bt->lchild);
bt->rchild=creat(bt->rchild);
}
return bt;
}
销毁二叉树
防止根结点先删导致找不到孩子结点,考虑后序的方法:
void BiTree::Release(BiNode *bt)
{
if(bt==NULL)
return;
else
{
Release(bt->lchild);
Release(bt->rchild);
delete bt;
}
}
#include
using namespace std;
struct BiNode
{
char data;
BiNode *lchild, *rchild;
};
class BiTree
{
public:
BiTree( )
{
root=Creat(root); //构造函数,建立一棵二叉树
}
~BiTree( )
{
Release(root); //析构函数,释放各结点的存储空间
}
void PreOrder( )
{
PreOrder(root); //前序遍历二叉树
}
void InOrder( )
{
InOrder(root); //中序遍历二叉树
}
void PostOrder( )
{
PostOrder(root); //后序遍历二叉树
}
void LeverOrder( ); //层序遍历二叉树
private:
BiNode *Creat(BiNode *bt); //构造函数调用
void Release(BiNode *bt); //析构函数调用
void PreOrder(BiNode *bt); //前序遍历函数调用
void InOrder(BiNode *bt); //中序遍历函数调用
void PostOrder(BiNode *bt); //后序遍历函数调用
BiNode *root; //指向根结点的头指针
};
void BiTree::PreOrder(BiNode *bt)
{
if(bt==NULL)
return;
else
{
cout<<bt->data<<"\t";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
void BiTree::InOrder(BiNode *bt)
{
if(bt==NULL)
return;
else
{
InOrder(bt->lchild);
cout<<bt->data<<"\t";
InOrder(bt->rchild);
}
}
void BiTree::PostOrder(BiNode *bt)
{
if(bt==NULL)
return;
else
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
cout<<bt->data<<"\t";
}
}
void BiTree::LeverOrder( )
{
BiNode *Q[100],*q=NULL;
int front=-1,rear=-1;
if(root==NULL)
return;
Q[++rear]=root;
while(front!=rear)
{
q=Q[++front];
cout<<q->data<<'\t';
if(q->lchild!=NULL)
Q[++rear]=q->lchild;
if(q->rchild!=NULL)
Q[++rear]=q->rchild;
}
}
BiNode *BiTree::Creat(BiNode *bt)
{
char ch;
cout<<"请输入扩展二叉树的前序遍历序列,每次输入一个字符:";
cin>>ch;
if(ch=='#') bt=NULL;
else
{
bt=new BiNode;
bt->data=ch;
bt->lchild=Creat(bt->lchild);
bt->rchild=Creat(bt->rchild);
}
return bt;
}
void BiTree::Release(BiNode *bt)
{
if(bt==NULL) return;
else
{
Release(bt->lchild);
Release(bt->rchild);
delete bt;
}
}
int main( )
{
BiTree T;
cout << "该二叉树的前序遍历序列是:";
T.PreOrder( );
cout << "\n该二叉树的中序遍历序列是:";
T.InOrder( );
cout << "\n该二叉树的后序遍历序列是:";
T.PostOrder( );
cout << "\n该二叉树的层序遍历序列是:";
T.LeverOrder( );
return 0;
}
求二叉树的结点个数: 前序,中序,后序都可
void Count(BiNode *root)
{
if(root)
{
Count(root->lchild);
number++;
Count(root->rchild);
}
}
求叶子结点个数:
void constleaf(BiNode *root)
{
int leaf=0;
if(root)
{
if(root->lchild==NULL&&root->rchild==NULL)
leaf++;
else
{
constleaf(root->lchild);
constleaf(root->rchild);
}
}
}
求树的高度
int BiTree::cal_height(BiNode *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;
}
叶子结点的权值: 叶子结点的数值
二叉树的带权路径长度: 从根结点到各个叶子结点的路径长度与相应叶子结点权值的乘积之和
哈夫曼树: 给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
哈夫曼树的特点: 权值越大的叶子结点越靠近根结点,权值越小的叶子结点越远离根结点。
只有度为0或2的结点,不存在度为1的结点。
哈夫曼树的构造: 已知二叉树集合,每次选根结点权值最小的两棵二叉树分别作为左右子树构造一棵新的二叉树,新根结点的权值为其左右子树根结点的权值之和。新根结点替代原来集合里的那两个结点,继续合并。
哈夫曼树共有2n-1个结点。
struct element
{
int weight;
int lchild,rchild,parent;
};
void HuffmanTree(element huffTree[],int w[],int n)
{
int i,k,i1,i2;
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].lchild=i2;
}
}
等长编码: 所有编码都等长,表示n个不同的字符需要[log2n]位。
不等长编码: 让频率高的字符采用尽可能短的编码。
前缀编码: 一组编码中任意一编码都不是其他任何一个编码的前缀,保证在解码时不会有多种可能。
哈弗曼编码: 用于构造最短不等长编码,先构造哈夫曼树,树的左分子为0,右分支为1,从根结点到叶子结点所经过的路径组成的0和1的序列为该叶子结点对应字符的编码,称为哈夫曼编码。(因为叶子结点不在同一个路径上,不会出现一个编码是另一个编码的前缀的情况)。
叶子结点的平均深度即平均编码长度,树的带权路径长度是各个字符的码长与其出现次数的乘积之和,即编码总长度。
在2n个指针域中只有n-1个指针域用来存储孩子结点的地址,存在n+1个空指针,利用这些空指针存某种遍历的前序和后序结点,指向前驱和后继结点的指针称为线索,加上线索的二叉链表称为线索链表,加上线索的二叉树称为线索二叉树。
前序线索二叉树:
若左指针为空,指向前序遍历的前驱;若右指针为空,指向前序遍历的后继。
中序,后序同理。
定义: 森林是m(m>=0)棵互不相交的树的集合。
遍历森林: 用前序或后序的方式遍历森林里的每一棵树。
就相当于孩子兄弟表示法,结点的第一个孩子为其左孩子,结店的右兄弟为其右孩子。
如下图:
树的前序遍历等价于二叉树的前序遍历;树的后序遍历等价于二叉树的中序遍历。
先把森林里的每棵树先转换为二叉树,然后每棵二叉树的根结点依次挂到前一棵二叉树根结点的右孩子上。
转换后的二叉树根结点右分支上的结点个数就是原来森林中树的个数。
二叉树根结点右分支上的每一个结点都是一棵树的根结点,先拆分出每一棵二叉树,再根据孩子兄弟表示法倒推出树。