KEY:(不敢相信没有堆…)
参考教材
电子工业出版社 数据结构算法与分析 c++版 第三版 Clifford A.Shaffer
部分参考了清华大学严版教材c语言版
二叉树由结点的有限集合组成,这个集合或者为空, 或者由一个根结点及两棵不相交的,分别称作这个根的 左子树和右子树的二叉树组成。
如果一棵二叉树的任何结点,要么是叶子节点,要么是刚还有两个非空子节点的分支节点。
1. 非空满二叉树叶子结点数等于其分支节点数+1
首先二叉树的结点数n=叶结点数l+分支结点数b
因为每个分支结点,恰好有两个子结点,故一棵满二叉树有2*b条边,换一个角度看,每个结点都刚好有一条边连接其父结点,故有n-1条边
n − 1 = 2 ∗ b = l + b − 1 ⇒ l = b + 1 n-1=2*b=l+b-1\\ \Rightarrow l=b+1 n−1=2∗b=l+b−1⇒l=b+1
2. 一棵非空二叉树 空子树的数目等于其结点数目加1
由性质1可知空子树,每个叶子结点有两棵空子树,所以,空子树的数目既叶子结点数*2
若一棵二叉树最多只有最下面的两层结点度数可以小于2,并且最下面一层的结点都集中在该 层最左边的若干位置上,则称此二叉树为完全二 叉树。
形状要求:自根结点起每一层从左至右地填充。一棵高度为d的完全二叉除了d-1层外,每一层都是满的。底层叶结点集中在左边的若干位置 上。
h e i g h t = ⌊ l o g 2 n ⌋ + 1 height=\lfloor log_2n\rfloor +1 height=⌊log2n⌋+1
若对含 n 个结点的完全二叉树从上到下且从左至右进 行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点:
若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
这个性质写出来只是为了方便编程把。。。。。
二叉树的第i层(根为第0层)最多有2i个结点
高度为k(深度为k-1。只有一个根结点的二叉 树的高度为1,深度为0)的二叉树至多有 2 k − 1 2^k-1 2k−1个 结点
任何一棵二叉树,度为0的结点比度为2的结点多 一个。
推导过程
假设二叉树度为0的结点有$ n_0 , 度 为 1 的 结 点 有 ,度为1的结点有 ,度为1的结点有n_1 度 为 2 的 有 度为2的有 度为2的有n_2 , 总 数 量 为 n , 则 ,总数量为n,则 ,总数量为n,则n=n_0+n_1+n_2$
则边数为: n 1 + 2 ∗ n 2 \\ n1 + 2*n2 n1+2∗n2
又 : 每个结点都与其父节点有唯一的一条边 除了根节点
所以 :边数又为: n-1
⇒ 2 ∗ n 2 = n 0 + n 2 − 1 ⇒ n 2 + 1 = n 0 \Rightarrow 2*n_2=n_0+n_2-1\\ \Rightarrow n_2+1=n_0 ⇒2∗n2=n0+n2−1⇒n2+1=n0
template
//注意这只是二叉树的结点的ADT
class BinNode
{
public:
BinNode() {}
virtual ~BinNode() {}
virtual E &val()=0;//返回元素的值
virtual void setVal(const E&)=0;//设置该结点的元素值
virtual BinNode * left()const=0;//返回该节点左子树的指针
virtual BinNode * reight()const =0;
virtual void setLeft(BinNode *)=0;//设置其左子树的值
virtual void setRight(BinNode *)=0;
virtual bool isLeaf()=0;//判断本结点是否为叶子结点
};
遍历:系统地访问二叉树中的结点。每个结点都 正好被访问到一次。
方法:
前序遍历(preorder traversal):DLR
中序遍历(inorder traversal):(LDR)
后序遍历(postorder traversal):(LRD)
层次遍历:从上到下,从左至右
层次遍历算法思想
- 首先创建一个队列;若二叉树非空, 将根放入队列; 2. 从队列取出一个元素并访问,如果该 元素有左孩子就将它放入队列,如果 该元素有右孩子就将它放入队列; 3. 若队列非空,继续第2步,直至队列为 空,则二叉树的层次遍历过程结束。
一、给树写遍历
二、给定中序+任意遍历可以确定还原唯一的一棵树
中序+先序:
先序:abcdefg 中序cbdaegf
先序是根左右,中序是左根右.
根据先序所以a是根节点,将中序遍历分为
第0层:a
第一层:左:cbd 右:egf
然后是b
b左结点为 c 右为d
同理e把e左子树分为空 右子树分为gf
然后是f 把f右子树分为左子树g
复杂一点的:已知一棵二叉树的先序序列:ABDGJEHCFIKL;中序序列:DJGBEHACKILF。画出二叉树的形态。
中序+后序
已知某二叉树的后序遍历序列是dabec,中序遍历序列 是debac
后序是左右根 所以根节点是c 所以中序遍历被分为
deba <- c-> /
然后是e d<-e->ba
然后是b b->a
中序+层次
层次: A B C D E F G
中序: B D A E G C F
第一轮:
一、统计二叉树中叶子结点的个数(先序遍历)
算法思想:
二叉树的叶子结点数=左子树的叶子结点数+右子树的叶子结点数.所以只需要递归的调用函数,不断分解其根节点,左右子树.当遍历到空子树/叶子节点,子问题结束.
算法伪代码
template
int BTNode::leafNum(BinNode *root)
{
if(root==NULL) return 0;
if(root->isLeaf()) return 1;
else return leafNum(root->left())+leafNum(root->right());
}
二、 求二叉树的高度(后序遍历)
算法思想
从二叉树高度的定义可知,二叉树的 高度应为其左、右子树高度的最大值加 1。 由此,需先分别求得左、右子树的高度, 算法中“访问结点”的操作为:求得左、 右子树高度的最大值,然后加 1 。最小子问题为当前节点为空
算法实现
int Depth(BinNode *T)
{
int dep=0;
if(!T) return 0;
else
{
int depthLeft=Depth(T->left());
int depthRight=Depth(T->right());
dep = 1+(depthLeft>depthRight?depthLeft:depthRight);
}
return dep;
}
三、 复制二叉树(后序遍历)
虽然老师上课没怎么说这里,但是为什么要用后序遍历呢?因为一般来说都是二叉链表实现二叉树,所以一般都需要先 生成好左右孩子,然后再建这个结点.
一般来说 顺序存储结构只用于完全二叉树.因为在最差情况下,普通的二叉树,如果为单支树,需要 2 k − 1 2^k-1 2k−1大小的一维数组却只有k个结点.当然如果不在意这个也是可以存储的, 非完全二叉树在置空值而转换为完全二叉树存储
例如下图,存储结果是 CEDJFX//K/G/I/L
关于结点的计算公式:
公式中r表示结点的索引, n表示二叉树结点总数。
这个比较常用,一般有两种,一种是含有两个指针域的结点结构(左、右孩子) ,另一种在此基础上还包括了指针。
以第一种为例,因为在含有n个结点的二叉链表中,除了根节点外都有其父节点,所以有n-1条边,故有n+1个空链域 。这些空链域常用来存储其他有用的信息,这也就可以得到一种新的存储结构--线索链表。 不过回到正题,二叉链表的定义和实现(简易版,无关键字)
#include "BTNode.h"
#include
using namespace std;
template
BTNode::BTNode()
{
lc=rc=NULL;
//ctor
}
template
BTNode::BTNode(E it,BTNode *left,BTNode *right)
{
lc=left;
rc=right;
item=it;
//ctor
}
template
BTNode::~BTNode()
{
//dtor
}
template
E& BTNode:: val()//返回元素的值
{
return item;
}
template
void BTNode::setVal(const E& it)//设置该结点的元素值
{
item=it;
}
template
BTNode* BTNode::left()const
{
return left;
}
template
BTNode* BTNode::right()const
{
return right;
}
template
void BTNode::setLeft( BinNode* b)
{
lc=(BTNode*)b;
}
template
void BTNode::setRight( BinNode* b)
{
rc=(BTNode*)b;
}
template
bool BTNode::isLeaf()
{
return (lc==NULL)&&(rc==NULL);
}
Ⅰ、定义
二叉检索树或者为空, 或者是满足下列条件 的非空二叉树:
Ⅱ、性质
按照中序遍历将各结点打印出来,将得到按 照由小到大的排列。
Ⅲ、作用
二叉检索树,树如其名,很方便检索呀!二叉检索树的效率就在于只需检索二个子树之一。
ADT:
数据对象:一组可比较大小的数据
数据关系:
BST树或者是一棵空树,或者是具有BST 特 性的二叉树基本操作
- 构建空树
- 插入一个新数据
- 删除树中的一个数据
- 查找数据
- 输出树中所有数据
首先结点的类型在前面已经写过,也即是BTNode(简易版,无关键字)
#ifndef BST_H
#define BST_H
#include
template
class BST
{
public:
BST();
virtual ~BST();
void clear();
void insert();
E remoeve(const E&);
E removeRoot();
E find(const E&) const;
int size();
void print()const;
private:
int nodeCount;
BTNode *root;
//辅助的内部私有函数
void clearhelp(BTNode);
BTNode* inserthelp(BTNode *,const E &);
BTNode* deletemin(BTNode*);
BTNode* getmin(BTNode*);
BTNode* removehelp(BTNode*, const E&);
//假定没有重复元素,若有那只好删除高度低的了
E findhelp(BTNode*, const E&) const;
void printhelp(BTNode*, int) const;
};
#endif // BST_H
是不是惊了!!定义都有这么多!!
问题描述
对于给定的一棵BST和待查元素值e,若BST中存在某个结点的元素值 等于e,那么返回查找成功; 否则返回查找不成功。
算法思想
递归
若BST树为空 查找失败
否则:
然而怎么表示你找到了这个结点呢?书上对结点的存储其实有关键字和值,也就是每个结点不仅有本身的值比如abcde,还有一个可以标识它的关键字用于排序。因而可以传一个可改变的地址,然后更改改地址的值表示你找到的那个结点的值是什么。后面的代码也一律默认存在关键字和值。
非递归
算法描述
递归
template
Elem BST :: findhelp( BTNode* subroot,const Key& k) const
{
if (subroot == NULL) return NULL; // Empty tree
if (k < subroot->key())
return findhelp(subroot->left(), k); // Check left
else if (k > subroot->key())
return findhelp(subroot->right(), k);// Check right
else return subroot->val(); // Found it }
}
非递归(很明显不递归更省空间)
template
bool BST::findhelp(const Key& k)const
{
BTNode*temp=root;
while(temp)
{
if(temp->key()==k) return true;
if(temp->key()right();
else if(temp->key()>k) temp=temp->left();//不要掉了这个else,这是老师ppt里重大bug,找了我好久啊
}
return false;
}
算法分析
时间复杂度 平均情况为 O ( l o g n ) O(log_n) O(logn) 最差情况为 O(n)
空间复杂度 递归最差O(n) 非递归O(1)
插入需要注意的是插入后不能改变BST的特性。插入的基本想法是要作为叶子结点插入最合适。
算法思想
递归
若(当前)BST树为空,直接插入该新建结点,并返回新结点的地址。
否则:
非递归
新建一个结点r 数据域存储新的元素值
若BST为空,将当前根节点设置为r。插入结束,返回成功。
定义两个临时树结点指针变量,cur和nxt。把bst树的根节点的值赋给cur。
若cur非空 将cur的值赋给nxt
重复执行第三步,直至cur为空 结束查找
若当前元素值小于nxt的关键字,则将r作为nxt的左孩子,否则将r作为nxt的右孩子
结束插入,返回成功。
算法描述
递归
template
BTNode *BST::inserthelp(BTNode* subRoot,const Elem &e,const Key& k)const
{
if(!subRoot) return new BTNode(e,k,NULL,NULL);
if(k<(subRoot->key()))
subRoot->setLeft(inserthelp(subRoot->left(),e,k));
else
subRoot->setRight(inserthelp(subRoot->right(),e,k));
return subRoot;
}
非递归
template
bool BST::inserthelp(const Elem &e,const Key& k)
{
if(!root) {root=new BTNode(e,k,NULL,NULL);return true;}
BTNode *cur,*nxt;
cur=root;
while(cur)
{
nxt=cur;
if(kkey())
cur=cur->left();
else cur=cur->right();
}
if(kkey()) nxt->setLeft(new BTNode(e,k,NULL,NULL));
else nxt->setRight(new BTNode(e,k,NULL,NULL));
return true;
}
算法分析
根据bst树的特点,最小值的结点一定是最左的结点。因此我们只需要遍历bst树的左子树,找到一个结点左子树为空,则该结点为最小结点。但是要注意删除后要保持bst特性。要考虑的只有两种情况,
第一种是删除的结点不仅没有左孩子,右孩子也没有。 此时直接把这个结点删除即可(即设置7的左孩子为空)
第二种是待删除结点有右孩子的情况,那么此时就需要把右孩子代替待删除结点的位置。比如下图删除最小的24需要把32变为37的左孩子
算法思想
递归
若BST的当前根结点的左孩子为空,则该结点为最小值结点, 返回最小值结点右孩子结点;
否则:
继续在左子树上进行删除bst树最小值的操作,并将返回值设置为根节点的左孩子
返回当前根节点。
非递归
算法描述
递归:
template
BTNode* BST::deletemin(BTNode*subroot,BTNode* &dele)
{
if(!subroot->left())
{
dele=subroot;
return subroot->right();
}
else
{
subroot->setLeft(deletemin(subroot->left(),dele));
return subroot;
}
}
//这是一个私有函数 调用之前要判断嗷,入下:
Elem removemin()
{
if(!root)
{
cout<<"empty tree"< *temp=new BTNode();
root= deletemin(root,temp);
nodeCount--;
Elem a=temp->val();
delete temp;
return a;
}
}
非递归
bool BST::deletemin()
{
if(!root) return false;
BTNode *cur,*nxt;
cur=root;
nxt=cur->left();
while(nxt)
{
if(!(nxt->left()))
{
cur->setLeft(nxt->right());
delete nxt;
return true;
}
else cur=nxt;
nxt=nxt->left();
}
root=root->right();
return true;
}
算法分析
BST的最小值结点,从根结点的左孩子开始, 第一个左孩子为空的结点。
算法的步骤分为查找和接入。
BST删除最小值的时间复杂度等于查找时间 复杂度。
如果二叉树是平衡的,则有n个结点的二叉树 的高度约为logn,但是,如果二叉树完全不平 衡(如成一个链表的形状),则其高度可以 达到n。
在BST树中找到保存R值的结点
删除该结点。
删除算法保持树的BST特性,如果待删除的结点为度为2的结点,那么需要把右子树中最小的作为根节点。
算法思想
递归
若BST树为空,返回空。
否则
非递归
首先在BST树中查找要删除的结点(find),看是否在BST树中,若不在则不做任何操作;
否则,假设要删除的结点为cur,结点cur的父结点为parent结点。下面分两 种情况讨论:
算法实现
累了累了,先写一个,以后有空再写了
template
BTNode* BST::removehelp(BTNode*subroot, const Key&k )
{
if(subroot==NULL) return NULL;
else
{
if(k<(subroot->key())) subroot->setLeft(removehelp(subroot->left(),k));
else if(k>subroot->key()) subroot->setRight(removehelp(subroot->right(),k));
else
{
if(subroot->left()==NULL)
{
subroot=subroot->right();
}//01 00
else if(subroot->right()==NULL)
{
subroot=subroot->left();
}
//10
else
{
BTNode *temp=new BTNode();
subroot->setRight(deletemin(subroot->right(),temp));
subroot->setVal(temp->val());
subroot->setKey(temp->key());
delete temp;
}
}
return subroot;
}
}
算法分析
貌似,不考代码的样子,简单复习一下吧,知道一下堆的siftdown和建树。
堆树或者是一棵空树,或者是具有下列性质的 一棵完全二叉树(堆的局部有序特性)
最大值堆(MaxHeap)ADT 设计
数据对象:一组可比较大小的数据
数据关系:堆或者是一棵空树,或者是具有局部有序性(任意一 个结 点的值都大于或等于其任意一个子结点存储的值) 的完全二叉树
基本操作:构建空树 插入一个新数据 删除树中的最大值 构建一个最大值堆
堆的查找算法的算法分析
1)若查找最大值堆的最大值,只需访问 堆树的根结点,物理上访问数组的首地址 的元素,时间复杂度为O(1)。
2)若查找其他值,则必须用二叉树的遍 历算法进行查找,堆结构特征并不能加快 查找的性能,时间复杂度为O(n)。
要插入一个新值
它应该成为树的一个叶结点是最合适,新建一个结点,存储新值; 然后把新结点接(插)入堆树中 调整新树,维持堆的性质
算法思想
算法分析
堆的插入操作,插入结点都是作为一个叶子 结点插入到堆中。
算法的步骤分为插(接)入和交换值。
接入过程不需要移动结点,也不会整体改动 树,所以时间开销为常数。
堆的插入时间复杂度取决于交换的次数。
堆是完全二叉树,则有n个结点的堆的高度为 logn。插入新元素时,最佳情况时不用交换, 最差情况交换logn,平均情况为logn。
下拉(siftdown)操作的步骤:
假设根的左、右子树都已经是堆,并且根的元素为R。在这种情 况下,有两种可能:
交换法构建最大值堆的方法:
void maxheap::siftdown(int pos)
{
while (!isLeaf(pos))
{
int j = leftchild(pos);
int rc = rightchild(pos);
if ((rcn,则该结点无左孩子
//从大号王小号
void heap::buildheap()
{
for (int i=n/2-1; i>=0; i--)
siftdown(i);
}
复杂度
初始化堆:O(n)
template <class Elem, class Comp>
bool maxheap<Elem, Comp>::remove(int pos,
Elem & it) {
if ((pos < 0) || (pos >= n)) return false;
swap(Heap, pos, --n);
while ((pos != 0) && (Comp::gt(Heap[pos],
Heap[parent(pos)])))
swap(Heap, pos, parent(pos));
siftdown(pos);
it = Heap[n];
return true;
}
堆的插入删除平均和最差时间代价都是 O ( l o g n ) O(log_n) O(logn)
宝藏网站——数据结构各种动图演示
AVL树是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于"二叉查找树",它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。
AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。
如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理。学AVL树,重点的地方也就是它的旋转算法;以下参考
平衡二叉树(AVL)图解与实现
AVL树(一)之 图文解析 和 C语言的实现
typedef int Type;
typedef struct AVLTreeNode{
Type key; // 关键字(键值)是用来对AVL树的节点进行排序的。
int height; //高度
struct AVLTreeNode *left; // 左孩子
struct AVLTreeNode *right; // 右孩子
}Node, *AVLTree;
LeftLeft,也称为"左左"。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
这次失衡结点是16,11是其左孩子,9为其失衡结点的左孩子的左孩子,所以是LL型,以失衡结点的左孩子为旋转中心进行一次右旋转即可。将k1变成根节点,k2变成k1的右子树,“k1的右子树"变成"k2的左子树”。
LeftRight,也称为"左右"。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
如:最开始插入数据16,3,7后的结构如上图所示,结点16失去了平衡,3为16的左孩子,7为失衡结点的左孩子的右孩子,所以为LR型。
接下来通过两次旋转操作复衡,先通过以3为旋转中心,进行左旋转,结果如图所示,然后再以7为旋转中心进行右旋转,旋转后恢复平衡了。
称为"右右"。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
称为"右左"。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
总结 LL/RR都只需要进行一次右旋/左旋,但是LR/RL需要进行两次旋转,以LR为例,抓着失衡结点的下一个结点左旋,然后抓着失衡节点根节点右旋。
数据对象:一组可比较大小的数据(频率值)
数据关系:哈夫曼树是一棵满二叉树,数据集中的数据存储在叶结点中
基本操作:构建哈夫曼树 获取结点的路径(根结点到某个叶结点的路径) 解码 编码
删除一个节点后,根节点的右子树的左子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
[外链图片转存中…(img-C2b1hiDw-1603279150816)]
总结 LL/RR都只需要进行一次右旋/左旋,但是LR/RL需要进行两次旋转,以LR为例,抓着失衡结点的下一个结点左旋,然后抓着失衡节点根节点右旋。
哈夫曼树,又称最优树,是一类带权路径长度最短的树。用于数据压缩。可以利用字母出现频率来编码,经常出现的字母的编码较短,这样的处理既能节省磁盘空间,又能提高运算速度。
从树中一个结点到另一个结点之间的分支构成了两结点之间的路径,路径上的分支个数称为路径长度。二叉树的路径长度是指由根结点到所有叶子结点的路径长度之和。如果二叉树中的叶子结点都有一定的权值,则可将这一概念拓展:设二叉树具有n个带权值的叶子结点,则从根结点到每一个叶子结点的路径长度与该叶子结点权值的乘积之和称为二叉树路径长度,记为: W P L = ∑ k = 1 n w k l k WPL=\sum_{k=1}^{n}w_kl_k WPL=k=1∑nwklk
哈夫曼算法:
(1)根据给定n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn};其中,每棵二叉树Ti(1<=i<=n)只有一个带权值wi的根结点,其左、右子树均为空。
(2)在F中选取两棵根结点权值最小的二叉树作为左、右子树来构造一棵新的二叉树,且置新的二叉树根结点权值为其左右子树根结点的权值之和。
(3)在F中删除这两棵树,同时将生成新的二叉树加入到F中。
(4)重复(2)(3),直到F中只剩下一棵二叉树加入到F中。
数据对象:一组可比较大小的数据(频率值)
数据关系:哈夫曼树是一棵满二叉树,数据集中的数据存储在叶结点中
基本操作:构建哈夫曼树 获取结点的路径(根结点到某个叶结点的路径) 解码 编码
//结点类 又分为两类结点,一个是叶子节点LeafNode一个是中间节点IntNode,由huffman特性可以知道他只存在这两种结点,具体实现就不写了。就知道一下这几个函数作用就行
template
class HuffNode
{
//Node abstract base class public:
HuffNode();
virtual int weight() = 0;
virtual bool isLeaf() = 0;
virtual HuffNode* left() const = 0;
virtual void setLeft(HuffNode*) = 0;
virtual HuffNode* right() const = 0;
virtual void setRight(HuffNode*) = 0;
};
贪心算法
由于每次都需要选出F中权值最小的两棵二叉树,故可以用最小值堆来存储这个二叉树集合
HuffTree* buildHuff(HuffTree** TreeArray, int count)
{
heap* forest = new heap*, minTreeComp>(TreeArray, count, count);
HuffTree *temp1, *temp2, *temp3 = NULL;
while (forest->size() > 1)
{
temp1 = forest->removefirst(); // Pull first two trees
temp2 = forest->removefirst(); // off the list
temp3 = new HuffTree(temp1, temp2);
forest->insert(temp3); // Put the new tree back on list
delete temp1; // Must delete the remnants
delete temp2; // of the trees we created
}
return temp3;
}
哈夫曼树 的构建算法基于贪心算法设计。
算法的步骤分为查找权值最小的两个子树,合并子树。
构建树的过程时间复杂度取决于查找。
如果用堆来实现查找最小值,初始时构建最小值堆的开销为O(n),每次的查找开销为2logn。
构建整个树的时间复杂度为O(nlogn) 。
首先,回顾一下遍历算法
先序:ABCDEFGHK
中序:BDCAHGKFE
后序:DCBHKGFEA
如何适应多次遍历的应用需求?
指向该线性序列中的“前驱”和 “后继” 的指针,称作==“线索”==
与其相应的二叉树,称作 “线索二叉树”
包含 “线索”的存储 结构,称作“线索链表”
需要注意的是 这个前驱/后继的意思是指某种次序遍历所得到的序列中的前驱后继
如:下图是一棵中序线索树和中序线索链表,虚线表示线索。因为a是最最左的,f是最右的,因此a前驱为NULL,f后驱为NULL
易知中序遍历结果为a+b*c-d-e/f
在二叉链表的结点中增加两个标志域, 并作如下规定:
若该结点的左子树不空,则Lchild域的指针指向其左子树,且左标志域的值为“指针 Link”;
否则,Lchild域的指针指向其“前驱”,且左标志的值为“线索 Thread” 。
若该结点的右子树不空, 则rchild域的指针指向其右子树, 且右标志域的值为 “指针 Link”;
否则,rchild域的指针指向其“后继”, 且右标志的值为“线索 Thread”。
如此定义的二叉树的存储结构称作“线索链表”。
在中序遍历过程中修改结点的 左、右指针域,以保存当前访问结 点的“前驱”和“后继”信息。
遍历过程中,附设指针pre,并始终保持 指针pre指向当前访问的、指针p所指 结点的前驱。
( )1、存在这样的二叉树,对它采用任何次序的遍历,结果相同。
( )2、中序遍历一棵二叉排序树的结点,可得到排好序的结点序列。
( )3、对于任意非空二叉树,要设计其后序遍历的非递归算法而不使用堆栈结构,最适合的方法 是对该二叉树采用三叉链表。
( )4、在哈夫曼编码中,当两个字符出现的频率相同时,其编码也相同,对于这种情况应做特殊 处理。
若以 { 4 , 5 , 6 , 7 , 8 } 作为权值构造哈夫曼树,则该树的带权路径长度为( C )。
A. 67
B. 68
C. 69
D. 70
解析: 画树过程见Huffman,带权路径长为 W P L = ∑ k = 1 n w k l k WPL=\sum_{k=1}^{n}w_kl_k WPL=∑k=1nwklk
故 W P L = 3 ∗ 4 + 3 ∗ 5 + 2 ∗ 8 + 2 ∗ 6 + 2 ∗ 7 = 69 WPL=3*4+3*5+2*8+2*6+2*7=69 WPL=3∗4+3∗5+2∗8+2∗6+2∗7=69