一、树
1、基本概念:
【定义:n(n>=0)个节点的有限集合。】
【当n为0时,称为空树。任意一棵非空树有且仅有一个特定的点成为根】
【当n>1时,除根节点外的其余节点被分成m个互不相交的有限集合T1,T2....Tm,其中每个集合又是一棵树,并称为这个根结点的子树。】
对于树的理解需要理解递归方法(树的定义就是利用递归定义),即理解树的子树的概念。让我们一般化一点,对于一棵二叉树:如果根节点存在左右子树,而且左子树的节点又延伸出左子树和右子树,对于右子树也是同样如此,如此重复下去。这也是遍历操作的实现基础,要把一棵二叉树访问完,就应当访问完该树的所有左子树右子树,访问左子树也是在访问该左子树的左右子树。访问每一棵子树都是同样的方法。二叉树是这样,树也是这样的。
【节点的度:节点所拥有的子树的个数】
【树的度:树中各节点度的最大值】
【叶子节点:度为0的节点】
【分支节点:度不为0的节点】
【孩子:分支】【兄弟:具有同一个双亲的孩子节点】【路径:路径上经过的边的个数为路径长度】
【祖先,子孙:至上到下有一条路径从x到y,x称为y的祖先,y是x的子孙】
树的深度:树中所有结点的最大层数,也称高度
有序树、无序树:如果一棵树中结点的各子树从左到右是有次序的,称这棵树为有序树;反之,称为无序树。
森林:m (m≥0)棵互不相交的树的集合。
2、遍历:
【前序遍历】:先访问根节点,再访问左子树,再访问右子树,DFS
【中序遍历】:先访问左子树,再访问根节点,再访问右子树,DFS
【后序遍历】:先访问左子树,再访问右子树,最后访问根,DFS
【层序遍历】:一层一层访问,BFS
除了层序遍历以外的遍历操作的实质:不断地向一个方向访问,比如说用前序遍历访问左子树就是按照前序遍历的规则访问左子树的左子树...
3、树的存储:
树的存储同样分为顺序存储和链式存储
顺序存储:
1、双亲表示法:
搞个结构体,两个成员,一个就是节点的数据,另一个就是存储该节点的双亲在数组中的下标
下图是改造后的双亲孩子表示法(存储结构是可以改造的):
链式存储
2、孩子表示法-多重链表表示法
【指针域的个数等于树的度】
如图所示,浪费空间
【指针域的个数等于节点的度】
2、孩子表示法-孩子链表表示法
就是把每个节点的所有孩子节点构成一个链表
【孩子兄弟表示法】
在上一个的基础上增加了一个指针域记录兄弟
二、二叉树
写在前面:非递归算法同样重要,写好非递归是代码能力的体现!
为什么研究这个结构?
结构简单,适合于计算机处理。可以将复杂的树结构转化为二叉树(原则是儿子变右兄弟,反之就是倒过来),从而解决相关问题。
1、基本概念
二叉树是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成
二叉树每个节点最多有两棵子树,并且二叉树是有序的,其次序不能颠倒,二叉树和树是两种不同的树结构。
斜树的节点个数与其深度相同,斜树的概念很简单(就是一个字:斜)
【满二叉树】【叶子只出现在最后一层,只有度为0和度为2的节点,满二叉树在同样深度的二叉树中节点数最多,叶子节点最多】
【完全二叉树】【在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树。】【叶子节点只能出现在最后两层,如果有度为1的节点只可能有一个,且该节点只有左孩子,深度为k的完全二叉树在k-1层上是满二叉树】
【二叉树的基本性质】:
1、二叉树第i层上最多有2的(i-1)个节点
2、一棵深度为k的二叉树中,最多有2的k次方-1个节点
3、在一棵二叉树中,如果叶子节点为n0,度为2的节点数为n2,则有n0=n2+1
4、树枝为0*n0+1*n1+2*n2+3*n3+....k*nk,又为节点数减一(n-1),n又等于n0+n1+n2+n3+......
5、具有n个节点的完全二叉树的深度为 【向下取整(log2n)】+1
6、对一棵具有n个节点的完全二叉树中从1开始按照层序编号,则对于任意的序号为i的节点,有
【1】如果i>.1,如果i=1,则节点i的双亲节点的序号为i/2,如果i=1,则i是根节点,无双亲节点
【2】如果2*i<=n,左孩子序号为2i
【3】2i+1<=n 则右孩子序号为2i+1
l 结点 i 的双亲结点为 i /2;l 结点 i 的左孩子为2 i ;l 结点 i 的右孩子为2 i +1 。
2、遍历
这一块非常重要,遍历是许多算法的基础,比如建立一棵二叉树就使用遍历的思想
前序遍历:先访问完根再是左子树再访问右子树
中序遍历:先左子树再根再右子树
后序遍历:先左子树再右子树再根
层序遍历:按层遍历,与左子树右子树无关
一个非常重要的一点是:遍历是基于递归定义的,比如前序遍历,我们要访问左子树,就要不断访问左子树,直到没有左子树。每一个子树还要使用前序遍历的顺序遍历。
3、存储
二叉树的顺序结构一般仅存储完全二叉树
链表结构:二叉链表
具有n个结点的二叉链表中,有n+1个空指针。
class BiTree
{
public:
BiTree();
~BiTree( );
void PreOrder(){PreOrder(root);}
void InOrder() {InOrder(root);}
void PostOrder() {PostOrder(root);}
void LeverOrder(){LeverOrder(root)};
private:
BiNode<T> *root;
BiNode<T> * Creat( );
void Release(BiNode<T> *root);
void PreOrder(BiNode<T> *root);
void InOrder(BiNode<T> *root);
void PostOrder(BiNode<T> *root);
void LeverOrder(BiNode<T> *root);
};
前序遍历(递归)
void BiTree::PreOrder(BiNode<T> *root)
{
if (root ==NULL) return;
else {
cout<<root->data;
PreOrder( );
PreOrder( );
}
}
前序建立二叉树(递归)
BiTree ::BiTree(){
root=creat();
}
BiNode<T> * BiTree ::Creat(){
BiNode<T> *root; char ch;
cin>>ch;
if (ch=='# ') root=NULL;
else {
root=new BiNode<T>;
root->data=ch;
root->lchild=creat();
root->rchild= creat();
}
return root
}
/*
void BiTree<T>::Creat(BiNode<T> * &root )
{
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch=="#") root = NULL;
else{
root = new BiNode<T>; //生成一个结点
root->data=ch;
Creat(root->lchild ); //递归建立左子树
Creat(root->rchild); //递归建立右子树
}
}*/
中序遍历(递归)
void BiTree::InOrder (BiNode<T> *root)
{
if (root==NULL) return;
else {
InOrder(root->lchild);
cout<<root->data;
InOrder(root->rchild);
}
}
后序遍历(递归)
void BiTree::InOrder (BiNode<T> *root)
{
if (root==NULL) return;
else {
InOrder(root->lchild);
InOrder(root->rchild);
cout<<root->data;
}
}
非递归算法。。。有空再总结吧
4、算法设计
【1】求二叉树的节点个数:
1、遍历思想:
void Count(BiNode *root)
{
if (root) {
Count(root->lchild);
number+ +; //number为数据成员
Count(root->rchild);
}
}
2、左子树+右子树+1
int BiTree<T>::count(BiNode<T>* root)
{
int number=0;
if (root==NULL)
number=0;
else
number=count(root->lchild)+count(root->rchild)+1;
return number;
}
【2】求叶子节点
遍历
template<typename T>
void BiTree<T>:: countleaf(BiTreeNode<T> * root){
if (root) {
if (root->lchild==0 && root->rchild==0)
leafcount=leafcount+1;
else
{
countleaf(root->lchild);
countleaf(root->rchild);
}
}
return;
}
左右子树
int BiTree<T>::leafcount(BiNode<T>* 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;
}
【3】树的高度
递归定义,高度加一
如果root==NULL, 高度为0;
否则,分别计算左子树的高度;右子树的高度;返回max(左子树高度,右子树高度)+1
int BiTree<T>::cal_height(BiTreeNode<T> * root){
int lheight=0,rheight=0;
if (root==0) return 0;
lheight=cal_height(root->lchild);
rheight=cal_height(root->rchild);
if (lheight>rheight) return lheight+1;
else return rheight+1;
}
【4】输出中缀表达式
void BiTree<T>::In_Expression(BiNode<T>* root){
if(root)
{
if(root!=this->root&&root->lchild!=0&&root->rchild!=0)
cout<<"(";
In_Expression(root->lchild);
cout<<root->data;
In_Expression(root->rchild);
if(root!=this->root&&root->lchild!=0&&root->rchild!=0)
cout<<")";
}
}
【5】二叉树的旋转
void BiTree<T>::Left_Rotate(BiNode<T>* root,int level)
{
if(root)
{
Left_Rotate(root->rchild,level+1);
for(int i=0;i<level;i++)
cout<<"\t";
cout<<root->data<<endl;
Left_Rotate(root->lchild,level+1);
}
}
5、三叉链表
比起二叉链表就是多了个记录双亲的指针域
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;
}
6、线索链表
线索:将二叉链表中的空指针域指向前驱结点和后继结点的指针被称为线索;
线索化:使二叉链表中结点的空链域存放其前驱或后继信息的过程称为线索化;
线索二叉树:加上线索的二叉树称为线索二叉树
7、树和二叉树和森林的转换
【树或森林转换为二叉树】
【二叉树转换为森林或树】
⑴ 加线——若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来;
⑵ 去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;
⑶ 层次调整——整理由⑴、⑵两步所得到的树或森林,使之层次分明。
遍历森林就是分别遍历森林里的每一棵树
8、哈夫曼树(最优树)
每次从根节点里选取两个权最小的节点组成一个根节点,也就是组成一棵子树,这就是为什么所以哈夫曼树里没有度为1的节点,只有度为2和度为0,
前缀码就是按照左零右一编码的,前缀码中没有作为别的码前缀的码