第五章 树与二叉树

目录

前言

ac8e1b05676322dcc46382f2f5b8dd47.png


一、5.2 树的定义与结构

术语:

7001e21811ddaded3eeeb04ec6f3f8ab.png

结点高度的定义为:从结点x向下到某个叶结点最长简单路径边的条数

节点深度:从根节点往下的层数,根节点深度为多少 看定义 一般为0。

对于整棵树来说,最深的叶结点的深度就是树的深度;树根的高度就是树的高度。这样树的高度和深度是相等的。

 对于树中相同深度的每个结点来说,它们的高度不一定相同,这取决于每个结点下面的叶结点的深度。

树的深度 = 层数(从上至下)

树的高度 = 层数 + 1(从下至上)

基操:

f8f2e22c3366eff13850ea6011738e2d.png

 

 a98353e6e6e54a7296357f2d4630a164.jpg

 

二、5-3二叉树

1.定义

二叉树:每个节点有且仅有两个子树的有序树。

 满二叉树:仅有0度和2度的二叉树。(哈夫曼树,表达式树)

完全二叉树:按层序顺序放结点的二叉树。前d-2层度为2,最后一层都为叶节点度为0。倒数第二层,各节点度从左向右单调非递增,度为1的结点要么没有,要么其左子树非空。(堆)

编号从上到下,从左到右层序编号。

完美二叉树:倒数第二层结点度数全为2.

 扩充二叉树:将二叉树结点的空子树位置都添加空树叶,构成满二叉树。原所有节点的度数都变为2。

2.基本性质

3个节点的二叉树有5个;

4个节点的二叉树有14个。

非空二叉树:n0=n2+1

证明:子节点=n-1=2*n2+n1(子节点的父节点唯一,中间结点乘相应度数的和即为所有子节点的个数)=n0+n1+n2-1

满二叉树:子节点=中间节点+1(满二叉树没有度为1的结点)

第i层最多有2^(i-1)个结点;

深度(高度)为d的二叉树最多有2^d - 1个结点(累加各层);

完美二叉树:深度(高度)为d的二叉树有2^d - 1个结点

完全二叉树(ver:n):

对编号k的结点:

左子节点2k;

右子节点2k+1(所有二度结点*2+根节点);

父节点[k/2]向下取整。

深度:d=[log2(n+1)]向上取整;(4ea017cfa268876dc7268e4f0e6ca643.png

log2n向下取整再加1

727667ade24a41de94594011542dfeb9.jpg

 

3.顺序表储存

顺序 or 链式

完全二叉树:顺序表

非完全二叉树:顺序——占空间,ver:n,max空间=2^n(中间结点左子树都为空数)

                        链接:二叉,三叉

d9628fa6f25a4983952bbce9f1116512.jpg

 顺序:n个点,n-1条边,2n-(n-1)=n+1个空指针域

4.基操

ce9ec3d4296c949c2ebb814e50024fa2.png

 

构造二叉树:(通过引用直接改树,根据前序序列,若空则停,若数据则建点,递归先左子树后右子树建立树)

typedef char Elemtype;  // 数据类型
 
/*二叉树的链式存储结构*/
typedef struct BiTNode
{
	Elemtype data; // 数据域
	struct BiTNode* lchild, * rchild; // 左右孩子指针
}BiTNode, *BiTree;
void create(BiTree &Tree){//通过引用直接将传入的树根进行改变
    char ch;
    scanf("%c",&ch);
    if(ch=='#') Tree = NULL;
    else {
        Tree = new BiTNode;
        Tree->data=ch;    
        create(Tree->lchild);
        create(Tree->rchild);
    }   
}

叶节点个数:左右孩子为空则返回1,其他则返回左子树叶节点加右子树叶节点,递归 做法

二叉树的高度(深度):若为空则返回零 从根往下走,每次递归返回取左右子树的高的最大值+1

int Depth( BiTree Tree )   
{
    int L,R;      
    if(Tree==NULL)  return 0;   
    else
    {
    L=Depth(Tree->lchild);    
    R=Depth(Tree->rchild);   
    return max(L,R)+1;              
    }
}

 二叉树的遍历:

前序:r->L->R

中序:L->r->R(每次都找最左的子树,然后按LrR顺序遍历)

后序:L->R->r(每次都找最左的子树,左子树为空再找右子树)

b14e60177d2c7c83bbc166d78fa20431.png

前序遍历和中序遍历若相同,则说明二叉树所有结点没有左子节点

 前序

/*先序遍历*/
void PreOrder(BiTree T)
{
	if (T != NULL)
	{ 
		visit(T);              // 访问结点              
		PreOrder(T->lchild);   // 遍历结点左子树
		PreOrder(T->rchild);   // 遍历结点右子树
	}
}
 
/*输出树结点*/
void visit(BiTree T)
{
	printf("树结点的值:%c\n", T->data);
}

 中序

/*中序遍历*/
void InOrder(BiTree T)
{
	if (T != NULL)
	{
		InOrder(T->lchild);    // 遍历结点左子树
		visit(T);              // 访问结点
		InOrder(T->rchild);    // 遍历结点右子树
	}
}
 
/*输出树结点*/
void visit(BiTree T)
{
	printf("树结点的值:%c\n", T->data);
}

 后序

/*后序遍历*/
void PostOrder(BiTree T)
{
	if (T != NULL)
	{
		PostOrder(T->lchild);	// 遍历结点左子树
		PostOrder(T->rchild);	// 遍历结点右子树
		visit(T);				// 访问结点
	}
}
 
/*输出树结点*/
void visit(BiTree T)
{
	printf("树结点的值:%c\n", T->data);
}

 层序遍历(BST广度优先遍历 O(n))

(设置队列,挨个入队孩子(非空),每轮出队一个)

void LayerOrder(BiTree root) {
    queue<BiTree> q;   //注意队列里面存的是地址
    q.push(root);           //根结点地址入队
    while (!q.empty()) {
        BiTree now = q.front(); //取出队首元素
        q.pop();
        cout << now->data << "\t";  //访问队首元素
        if (now->lchild != NULL) q.push(now->lchild);   //左子树非空
        if (now->rchild != NULL) q.push(now->rchild);   //右子树非空
    }
}

非递归算法遍历:

 使用栈结构模拟函数调用中系统栈的工作原理,防止栈溢出。

前序遍历:当tree指针不为空时,tree指针沿左子树一直遍历入栈同时入序列,到底后将指针移动到栈顶元素的右子树,将栈顶元素弹出。循环继续遍历右子树。

6e66eaf9ec2e5c6e336145173e78acc4.png

中序遍历:沿左子树一直遍历入栈但不入序列,到底后,指针移到栈顶元素,并访问栈顶元素,弹出栈顶元素,将栈移动到右子树进行遍历。

04bc3322028b69be8651980d3b085ddf.png

 后序遍历:从左分支遍历入栈,到底后,访问栈顶结点

38dfabba8da0328da071eff1351e3317.png

 

void PostOrder(BiTree){  
	InitStack(s);
	BiTree *p=T,*r=NULL; // r标记最近访问过的结点
	while(p!=NULL || !IsEmpty(s)){//p不为空或者栈不为空就可以一直循环
		if(p!=NULL){
			push(s,p);  // 一直向左走,左孩子入栈,直至左孩子不能入栈,p为空才有可能进行其他步骤
			p=p->lchild;
		}
		else{//左孩子已经无路可走,只能从栈顶元素开始考虑
			GetTop(s,p);
			// 获取s的栈顶元素地址赋值给p
			// GetTop(s,p)意思就是判断栈顶元素的情况
			//❤case one❤
			if(p->rchild && p->rchild!=r){
			// 若右孩子存在且未被访问
				p=p->rchild;  // 就让右孩子成为新的p指针,再入栈后重复第一个过程
				push(s,p)  // 入栈
				p=p->lchild;  // 让右孩子向左
				//上面三句意思就是让右孩子的左孩子一直入栈,一直向左走
			}
			// ❤case two❤
			else{
				pop(s,p);  // 右孩子为空或未被访问过,就出栈
				visit(p->data);
				r=p;  // r标记最近访问结点
				p=NULL;  // r置空
				// 置空原因:因为这个结点已经出栈了
				//继续指向就没必要了,置空后r不标记任何结点
			}
		}
	}
}

 

 

三.表达式树

 
表达式树:表达式不断拆分为(左运算区域)运算符(右运算区域)。
根节点是表达式中优先级最低的运算符;表达树是满树,叶节点对应常数,中间结点放运算符。

表达式树转中缀表达式:

1.若左子树非空:

“(”中序遍历左子树“)”

2.输出中间值

3.若右子树非空:

“(”中序遍历右子树“)”

表达树的构建:

1.由中缀表达式查找优先级低的生成子树根节点,然后递归转化。难度大。

2.将中缀转为后缀,用后缀建树。建树过程类似后缀转中缀。时间效率为O(n)

后缀建树过程:

aff30135c25330fa4f5b038292ffae59.png

 


void expr_tree::parse_expr(std::string str)
{
	std::stack<base_node *> stack_node;//建立树节点指针类型的栈
	for (auto c : str)
	{
		base_node * node = new base_node(c);//初始化该字符对应的结点
		if (c >= '0' && c <= '9' || c >= 'a' && c <= 'z')
			stack_node.push(node);//若结点为操作数则直接入栈
		else if (c == '+' || c == '-' || c == '*' || c == '/')
		{
			if (!stack_node.empty())
			{
				node->rc = stack_node.top();//栈非空则右子树为栈顶元素对应的结点地址
				stack_node.pop();//栈顶元素出栈
			}
			if (!stack_node.empty())
			{
				node->lc = stack_node.top();//栈非空则右子树为栈顶元素对应结点地址
				stack_node.pop();
			}
			stack_node.push(node);//将结合的新结点地址入栈
		}
		else
		{
			std::cout << "表达式有误!" << std::endl;//若都出现其他符号则异常退出
			exit(1);
		}
	}
	root = stack_node.top();//根节点为栈顶元素
	while (!stack_node.empty())//若栈不为空则持续出栈,清空栈
		stack_node.pop();
}

中缀转后缀 参见第三章 栈与队列 表达式相关内容  

四、奇偶树

设二叉树所有结点的元素都是正整数,如果在奇数层的结点元素都是奇数,而在偶数
层的结点元素都是偶数,则该二叉树称作奇偶树。
 
 

层序遍历:

结点队列和层数队列。层序遍历的同时,将左右结点对应的层数+1入队,即可得到层数队列。当元素出队时检验结点数据%2是否等于层数%2即可。

前序遍历:

递归算法-》把结点所在层数作为参数传递给子节点。判空模块->判奇偶模块->递归左右孩子模块

如果还需要奇偶树的定义要求每层都递增,则:

层序遍历:记录前驱结点的层数和数值,如果同层需要数值递增

前序遍历:添加辅助空间顺序表,记录各层前一个访问节点pre_nodes[level],比较数据大小。

 五、二叉树的序列化和反序列化

序列化:二叉树->线性序列

反序列化:线性序列->二叉树。

根据前序遍历序列只能确定根节点,需要借助特殊符号#来表示空结点,才能反序列化

 

序列化例子

1acfcd9e2dbd30373363faa0eb23dd7e.png

反序列化例子:根据r->L->R的顺序 建立二叉树结点并导入数据

628b8e6e8a84173c29745a22801179fe.png

用中序遍历和后序遍历结果重构二叉树:

typedef struct node{
    int val;
    node* lc,*rc;
}*Tree,node;
Tree build(int in[],int po[],int n)
{
    if(n==0) return NULL;//需要设置叶节点结束标志否则报错
    int r=0;
    while(in[r]!=po[n-1]) r++;
    Tree root=new node;
    root->val=in[r];
    root->lc=build(in,po,r);//in: 0~r-1(r)r+1~n-1
    root->rc=build(in+r+1,po+r,n-r-1);//po: 0~r-1 r~n-2 n-1(r)
    return root;
}
void Layorder(Tree root)
{
    queue<Tree>q;
    q.push(root);
    bool flag=false;
    while(!q.empty())
    {
        Tree now=q.front();
        q.pop();
        if(!flag) {cout<<now->val;flag=true;}
        else{cout<<" "<<now->val;}
        if(now->lc!=NULL) q.push(now->lc);
        if(now->rc!=NULL) q.push(now->rc);
    }
}

用前序遍历和中序遍历结果重构二叉树:

前序能得到根节点,中序能将序列划分为左右子树,由此得到前序序列中左右子树的划分界,从而找到子树的根节点。

node* create(int preL,int preR,int inL,int inR){
	if(preL > preR){
		return NULL;
	}//停止遍历的条件
	node* root = new node;
	root->data = pre[preL];//将pre的preL值放入到结构体中
//结点数值是当前根节点数值,也即前序序列preL对应点的数值
	int i ;
	for(i = inL; i <= inR;i++) {
		if(in[i] == pre[preL]) break;//用i存放中序序列中根结点的位置
	}
	int numLeft = i - inL;//看左边是否还有节点 //左段元素数量=i-inL
	//注意这里没有对numLeft的数目进行判断!! 
	root->lchild = create(preL+1,preL+numLeft,inL,i-1);
	root->rchild = create(preL+numLeft+1,preR,i+1,inR);	
	return root;
}

 六、哈夫曼树(最优二叉树)

概念:

带权二叉树

叶节点的带权路径长度 wili

WPL树的带权路径长度:所有叶节点的带权路径长度之和

哈夫曼树:给定一组叶节点权重,构建带权二叉树使得WPL最小

中间结点权重:定义为左右孩子权重之和

哈夫曼树性质:

1.一定是满树(若结点度数为1,则可去掉该节点降低WPL)

2.权重小的在较高层,最小和次小的在最下层且为兄弟节点

哈夫曼算法:从下而上构建huffman树,不断合并权重次小和最小的两个带权二叉树。

顺序查找权重最小的二叉树耗时O(n^2),使用堆可以优化。

791c9dc349b2259f5657a8d5f97cf23b.png

typedef struct huffNode {
    double weight;                 //权重
    int lchild, rchild, parent;    //左右子节点和父节点
} HTNode, *HuffTree;
void select(const HuffTree &HT, int n, int &s1, int &s2) {
    s1 = s2 = 0;
    double min1 = INF; //最小值
    double min2 = INF; //次小值

    for (int i = 1; i <= n; i++) {
        //筛选没有父节点的最小和次小权值下标(还没有合并过)
        if (HT[i].parent == 0) {
            //如果比最小值小
            if (HT[i].weight < min1) {
                //更新次小值
                min2 = min1;
                s2 = s1;
                //更新最小值
                min1 = HT[i].weight;
                s1 = i;
            }
            //如果大于等于最小值,且小于次小值
            else if ((HT[i].weight >= min1) && (HT[i].weight < min2)) {
                //只更新次小值
                min2 = HT[i].weight;
                s2 = i;
            }
        }
    }
}
//HT:哈夫曼树,w:构造哈夫曼树节点的权值,n:构造哈夫曼树节点的个数
void createHT(HuffTree &HT, double *w, int n) {
    int s1, s2, m = 2 * n - 1;                  //m: n个节点构造的哈夫曼树是2n-1个节点
                                                //m=n(节点数)+n-1(边数 两节点合并类似边)
    HT = new HTNode[m+1];                       //0单元未使用

    for (int i = 1; i <= n; i++) {
        //初始化前n个节点 (构造哈夫曼树的原始节点)
        HT[i] = {w[i], 0, 0, 0};
    }

    for (int i = n+1; i <= m; i++) {
        //初始化后n-1个节点 (创建最小两节点的父节点)
        HT[i] = {0, 0, 0, 0};
    }

    //构建哈夫曼树
    for (int i = n+1; i <= m; i++) {
        //找出前i-1个节点中权值最小的节点下标,合并后的根节点存在i处
        select(HT, i - 1, s1, s2);
        HT[s1].parent = i;
        HT[s2].parent = i;
        HT[i].lchild = s1;
        HT[i].rchild = s2;
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }

}

不定长码:传输信息使用不定长码,信息频率高的用短编码,频率低的用长编码。

前缀码:每个字母的编码都不是其它字母编码的前缀,可以防止出现歧义。可以用二叉树表示前缀码,每个叶节点记录唯一的一个字母。

前缀码树:最优前缀码可用哈夫曼算法求解

WPL=ΣWiLi=中间结点的权重之和e82c3890baca358f9b587a8161486f64.png

利用前缀码树解码二进制字符串decoding:

遍历字符串,若0左移,若1右移,到达叶节点输出,返回树根。

 

哈夫曼树的应用:

多路归并:

4e319ade98bb61fcd72f8f0019ac75c0.png

相当于求最短WPL,将蓝圈部分数值相加即可。

 f0ec91152822038299f04b6484cc6222.png

 

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
//若只需要得到带权路径长度 可以计算所有中间结点的权重之和即可
priority_queue<int,vector<int>,greater<int>> h;
int main()
{    
    int n;cin>>n;int t=0;
    for(int i=0;i<n;i++) {
        cin>>t;h.push(t);
    }
    int x=0,y=0,cnt=0,wpl=0;
    while(h.size()!=1)//结束条件是堆中只剩堆顶元素
    {
        x=h.top();h.pop();
        y=h.top();h.pop();
        h.push(x+y);
        cnt++;
        wpl+=x+y;
    }
    cout<<wpl<<endl;

    return 0;
}

七、其他表示法

1.父亲表示法

用顺序表存储每个节点的父节点位置,根节点的父节点记为-1

可通过循环迭代找到树的根节点findroot,O(H)

方便每个节点找到祖先节点,可用于实现并查集,但是不方便查找结点的子节点或兄弟节点

//结点结构
typedef struct Snode  
{
    //树中结点的数据类型
    ElemType data;
    //结点的父结点在数组中的位置下标
    int parent;
}PNode;
//树结构
typedef struct  
{
    //存放树中所有结点
    PNode tnode[MAX_SIZE];
    //结点个数
    int n;                
}PTree;

最近公共祖先LCA

若不在同一层,存临时结点tx,ty,将tx,ty同步上移,有一方小于0时停止循环,取小者为x,tx,当tx<0时,y和ty之间的距离即为x,y的层数差,y与ty同步上移,直至ty<0,此时xy在同一层。二者同步移动至x=y,即可得到LCA.

299b913ad4c56ac298e46503244388f4.png

2.孩子表示法

用顺序表存储树,每个节点包含数据域,父节点位置域和子节点链表。链表中各个子节点按照从左到右的顺序进行排列。

第一个孩子是最左边的子节点,下一个兄弟为结点右侧且相邻的兄弟节点。

各节点第一个孩子就是子节点链表的第一个结点,查找仅需O(1),查找各节点的下一个兄弟O(d)d树的度.

1.孩子链表的孩子结构体(自身位置,下一个兄弟节点位置)

2.结点结构体(自身数据,孩子链表的头结点指针)

3.树结构体(结点结构体数组,结点数量,树根位置)

 

typedef struct CTNode{
    //链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标!!
    int child;
    struct CTNode * next;
}ChildPtr;
typedef struct {
    //结点的数据类型
    TElemType data;
    //孩子链表的头指针
    ChildPtr* firstchild;
}CTBox;
typedef struct{
    //存储结点的数组
    CTBox nodes[MAX_SIZE];
    //结点数量和树根的位置
    int n,r;
}CTree;

3.孩子兄弟表示法

fd7a014eeecba2e2c5dc1f2769170610.png

typedef struct CSNode{
    ElemType data;
    struct CSNode * firstchild,*nextsibling;
}CSNode,*CSTree;

树通过孩子兄弟表示法可转换为二叉链表结构,有唯一二叉树与它相对应,并且该二叉树的根节点右子树为空(根无兄弟),利用右子树的链条将树串联起来可构成森林。

树的前序遍历与二叉树的前序遍历结果相同

!!!树的后序遍历与对应二叉树的中序遍历结果相同。

树无中序遍历

要实现树(非二叉)的遍历,需要利用孩子兄弟表示法构成的二叉链表结构

森林的遍历:

前序:从第一个树开始,按序对每个树前序遍历。

后序:从第一个树开始,按序对每个树后序遍历。

  树    r                                对应二叉树        r

    L       R                                                L

                                                                  R                (兄弟结点成为自己的右子树)

森林的前序遍历同对应二叉树的前序遍历。后序遍历同对应二叉树的中序遍历。

八、其他树

1.前缀树(Trie树)

词频统计,存储并查找大量字符串

dad7e4ee44fdda769946daa2238e8b7e.png

 

3dfae99abacf77ab41cdc26088135150.png

 

int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
void insert(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';//存当前字符的标号
        if (!son[p][u]) son[p][u] = ++ idx;
    //第p个创建的结点的u字符分支孩子为空,则创建该节点,命名为第++dix个新节点
        p = son[p][u];//将指针移动到u字符分支的孩子结点上
    }
    cnt[p] ++ ;//第p个创建的结点的使用次数++
}

// 查询字符串出现的次数
int query(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;//若该分支孩子为空则返回0
        p = son[p][u];//指针移动到孩子结点上,返回孩子结点出现标号
    }
    return cnt[p];/遍历完字符后,返回最后一个字符对应的出现次数
}

 2.后缀树

查找最长公共子串、最长重复子串、字符精确匹配

先利用trie树,存入所有字符串的后缀,再进行压缩compressed,合并独立节点。

隐式后缀树:后缀可能隐藏于其他后缀中

显式后缀树:在每个后缀后加入#等字符,以叶节点形式区分后缀

4bc431fc3fde347c28cfc4121174e256.png

3.决策树:

d1c244c6b9bfbb3691ae13b1f11cd612.png

7971046c8255677727aa4db2c5d06a0d.png

 按优异与否得到总信息熵H(s)。 优异人数/总人数

根据指标,用指标的情况与优异与否结合,得到各组指标不同分类下的优异信息熵。 a指标的第一种情况下,优秀/总人数。H(Ti)

各指标情况下优异信息熵和优秀比例结合,得到条件信息熵H(S,T)  a指标的第一种情况总人数/总人数 *H(Ti)

总信息熵-a指标的H(S,T)=信息增益

879e7f710d547b16d2ff9128b622ee1a.png

4.线索二叉树

dea3b89e92934490a3867e99436f8e77.jpg


 

总结

第十周习题

1.一棵非空二叉树,若后序遍历与中序遍历的序列相同,则该二叉树所有结点均无右孩子。

后序 LRr 中序 LrR 若需要后序和中序遍历相同,需使所有结点均无右孩子

一棵非空二叉树,若后序遍历与中序遍历的序列相反,则该二叉树 ▁▁A.所有结点均无左孩子▁▁▁ 

2.非空的二叉树一定满足:某结点若有左孩子,则其中序前驱一定没有右孩子。

中序 LrR 根据遍历顺序 一定先遍历左孩子再遍历该结点,前驱一定为左子树上的点

3.If a complete binary tree with 111 nodes is stored in an array (root at position 1), then the node at position 13 precedes the node at position 96 in its preorder traversal sequence.(x)

左孩子为(2n)右孩子为(2n+1)父结点为([n/2]取小)96结点的父节点为48,48的父节点为12 。12是13的左兄弟,二者同父亲。前序遍历rLR,一定先访问该结点的左子树再访问右子树。所以96比48先被访问到。

第十一周习题

1.将一棵树转换成二叉树,则树的先(后)序遍历序列与转换出的二叉树的 ▁先(中▁ 序列相同。

!!森林的前序遍历同对应二叉树的前序遍历。后序遍历同对应二叉树的中序遍历。

             root                                                      root

        L             R                                         L

  Ll        Lr    Rl    Rr                            Ll             R   

                                                                Lr    Rl

                                                                            Rr

森林后序LRr         访问顺序 Ll Lr L  左孩子 右孩子 根节点

二叉树的中序LrR                     (空)左孩子r 右孩子R 左孩子的父节点L

2.二叉树后序遍历为:CBEDA,中序遍历为:BCAED。则元素D的左孩子是E。

后序遍历的最后一个元素为根节点元素,中序遍历可根据根节点将树划分为左右子树。

A为根,ED为右子树。在右子树中,D为根,E为D的左子树(孩子)

3.对M(M≥2)个权值均不相同的字符构造哈夫曼树。下列关于该哈夫曼树的叙述中,错误的是(D )

A.树中一定没有度为1的结点        //一定是满树,但不一定是完全二叉

B.树中两个权值最小的结点一定是兄弟结点

C.树中任一非叶结点的权值一定不小于下一层任一结点的权值

D.该树一定是一棵完全二叉树

4.设哈夫曼树中有1999个结点,那么有1000个叶子节点。

哈夫曼是满树,度全为2(中间结点)或1(叶节点) n0=n2+1;

习题

1.任何一棵二叉树的叶子结点在前序、中序和后序遍历序列中的相对次序   A   。(都是LR)

    A.不发生改变     B.发生改变      C.不能确定       D.以上都不对

2.若已知一棵二叉树的前序遍历序列和后序遍历序列,则可以恢复该二叉树。( × )

3.已知一棵度为3的树有2个度为1的结点,3个度为2的结点,4个度为3的结点,则该树有(  12 )个叶子结点。(总数=1(根)+1*2+3*2+4*3+0度数之和=2+3+4+n)n=12
A. 10
B. 12
C. 11
D. 未知

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值