二叉树的存储结构

二叉树的存储结构有两种:顺序存储结构和链式存储结构。

顺序存储结构

对于满二叉树和完全二叉树来说,可以将其数据元素逐层存放到一组连续的存储单元中,如图6-3 所示。用一维数组来实现顺序存储结构时,将二叉树中编号为i 的结点存放到数组中的第i 个分量中。如此根据性质6.7,可以得到结点i 的父结点、左右孩子结点分别存放在、2i 以及2i+1 ?i / 2? 分量中。

图6-3 顺序存储结构

这种存储方式对于满二叉树和完全二叉树是非常合适也是高效方便的。因为满二叉树和完全二叉树采用顺序存储结构既不浪费空间,也可以根据公式很快的确定结点之间的关系。

但是对于一般的二叉树而言,必须用“虚结点”将一棵二叉树补成一棵完全二叉树来存储,否则无法确定结点之间的前驱后续关系,但是这样一来就会造成空间的浪费。一种极端的情况是,为了存储k个结点,需要2k-1 个存储单元,图6- 4 说明了这一情况。此时存储空间浪费巨大,这是顺序存储结构的一个缺点。

图6-4 单支二叉树的顺序存储结构

链式存储结构

设计不同的结点结构可构成不同的链式存储结构。在二叉树中每个结点都有两个孩子,则可以设计每个结点至少包括3 个域:数据域、左孩子域和右孩子域。数据域存放数据元素,

左孩子域存放指向左孩子结点的指针,右孩子域存放指向右孩子结点的指针。如图6-5(a)所示。利用此结点结构得到的二叉树存储结构称为二叉链表。容易证明在具有n 个结点的二叉链表中有n+1 个空链域。

图6-5 二叉树的链式存储结构

为了方便找到父结点,可以在上述结点结构中增加一个指针域,指向结点的父结点。如图6-5(b)所示。采用此结点结构得到的二叉树存储结构称为三叉链表。在具有n 个结点的三叉链表中也有n+1 个空链域。

不同的存储结构实现二叉树操作的方法也不同。例如要找某个结点的父结点,在三叉链表中很容易实现;在二叉链表中则需从根结点出发一一查找。在实际应用中,要根据二叉树的主要操作来选择存储结构。

为了方便的找到父结点,我们以三叉链表作为二叉树的存储结构,并且在6.3 节中,二叉树的基本操作的实现也是基于三叉链表来实现的。下面我们首先给出具有四个域的结点结构的定义。

代码6-1 二叉树存储结构结点定义

public class BinTreeNode implements Node {

private Object data; //数据域

private BinTreeNode parent; //父结点

private BinTreeNode lChild; //左孩子

private BinTreeNode rChild; //右孩子

private int height; //以该结点为根的子树的高度

private int size; //该结点子孙数(包括结点本身)

102

public BinTreeNode() { this(null); }

public BinTreeNode(Object e) {

data = e; height = 0; size = 1;

parent = lChild = rChild = null;

}

/******Node 接口方法******/

public Object getData() { return data; }

public void setData(Object obj) { data = obj;}

/******辅助方法,判断当前结点位置情况******/

//判断是否有父亲

public boolean hasParent(){ return parent!=null;}

//判断是否有左孩子

public boolean hasLChild(){ return lChild!=null;}

//判断是否有右孩子

public boolean hasRChild(){ return rChild!=null;}

//判断是否为叶子结点

public boolean isLeaf(){ return !hasLChild()&&!hasRChild();}

//判断是否为某结点的左孩子

public boolean isLChild(){ return (hasParent()&&this==parent.lChild);}

//判断是否为某结点的右孩子

public boolean isRChild(){ return (hasParent()&&this==parent.rChild);}

/******与height 相关的方法******/

//取结点的高度,即以该结点为根的树的高度

public int getHeight() { return height; }

//更新当前结点及其祖先的高度

public void updateHeight(){

int newH = 0;//新高度初始化为0,高度等于左右子树高度加1 中的大者

if (hasLChild()) newH = Math.max(newH,1+getLChild().getHeight());

if (hasRChild()) newH = Math.max(newH,1+getRChild().getHeight());

if (newH==height) return; //高度没有发生变化则直接返回

height = newH; //否则更新高度

if (hasParent()) getParent().updateHeight(); //递归更新祖先的高度

}

/******与size 相关的方法******/

//取以该结点为根的树的结点数

public int getSize() { return size; }

//更新当前结点及其祖先的子孙数

public void updateSize(){

size = 1; //初始化为1,结点本身

if (hasLChild()) size += getLChild().getSize(); //加上左子树规模

if (hasRChild()) size += getRChild().getSize(); //加上右子树规模

if (hasParent()) getParent().updateSize(); //递归更新祖先的规模

}

/******与parent 相关的方法******/

//取父结点

public BinTreeNode getParent() { return parent; }

//断开与父亲的关系

public void sever(){

if (!hasParent()) return;

if (isLChild()) parent.lChild = null;

else parent.rChild = null;

parent.updateHeight(); //更新父结点及其祖先高度

parent.updateSize(); //更新父结点及其祖先规模

parent = null;

}

/******与lChild 相关的方法******/

//取左孩子

public BinTreeNode getLChild() { return lChild; }

//设置当前结点的左孩子,返回原左孩子

public BinTreeNode setLChild(BinTreeNode lc){

BinTreeNode oldLC = this.lChild;

if (hasLChild()) { lChild.sever();} //断开当前左孩子与结点的关系

if (lc!=null){

lc.sever(); //断开lc 与其父结点的关系

this.lChild = lc; //确定父子关系

lc.parent = this;

this.updateHeight(); //更新当前结点及其祖先高度

this.updateSize(); //更新当前结点及其祖先规模

}

return oldLC; //返回原左孩子

}

/******与rChild 相关的方法******/

//取右孩子

public BinTreeNode getRChild() { return rChild; }

//设置当前结点的右孩子,返回原右孩子

public BinTreeNode setRChild(BinTreeNode rc){

BinTreeNode oldRC = this.rChild;

if (hasRChild()) { rChild.sever();} //断开当前右孩子与结点的关系

if (rc!=null){

rc.sever(); //断开lc 与其父结点的关系

this.rChild = rc; //确定父子关系

104

rc.parent = this;

this.updateHeight(); //更新当前结点及其祖先高度

this.updateSize(); //更新当前结点及其祖先规模

}

return oldRC; //返回原右孩子

}

}

代码6-1 说明:代码中判断当前结点位置情况的辅助方法以及简单的get 方法都在常数时间内可以完成,实现也相应非常简单。下面主要讨论updateHeight ()、updateSize ()、sever()、setLChild(lc)、getRChild(rc)的实现与时间复杂度。

⑴ updateHeight ():若当前结点v 的孩子发生变化,就需要使用updateHeight ()方法更新当前结点及其祖先结点的高度。请注意,由于一个结点的高度发生变化,会影响到其祖先结点的高度,在这里我们允许直接对任何结点执行这一操作。因为在二叉树中任何一个结点的高度,都等于其左右子树的高度中大者加1,而左右子树的高度只需要获取该结点左右孩子的高度即可获得,只需要Θ(1)时间。续而从v 出发沿parent
引用逆行向上,依次更新各祖先结点的高度即可。如果在上述过程中,发现某个结点的高度没有发生变化,算法可以直接终止。综上所述,当对一个结点v 调用updateHeight ()方法时,若v 的层数为level(v),则最多只需要更新level(v)+1 个结点的高度,因此算法的时

间复杂度T(n) = Ο(level(v))。

⑵ updateSize ():同样如果结点v 的孩子发生变化,应该更新当前结点以及其祖先的规模。因为在二叉树中任何一个结点的规模,都等于其左右子树的规模之和加上结点自身,而左右子树的规模只需要获取该结点左右孩子的规模即可获得,只需要Θ(1)时间。因此算法的时间复杂度T(n) = Ο(level(v))。

⑶ sever():切断结点v 与父结点p 之间的关系。该算法需要修改v 与p 的指针域,需要常数时间。除此之外由于p 结点的孩子发生了变化,因此需要调用updateHeight ()和updateSize ()来更新父结点p 及其祖先的高度与规模。其时间复杂度T(n) = Ο(level(v))。

⑷ setLChild(lc)、getRChild(rc):两个算法的功能相对,一个是设置结点v 的左孩子,

一个是设置结点v 的右孩子。两个算法的实现是类似的,以setLChild()为例说明。首先,如果v 有左孩子oldLC,则应当调用oldLC. sever()断开v 与其左孩子的关系。其次,调用lc. sever()断开其与父结点的关系。最后,建立v 与lc 之间的父子关系,并调用v. updateSize ()与v.updateHeight ()更新v 及其祖先的规模与高度。

更多精彩内容请关注:http://bbs.superwu.cn

关注超人学院微信二维码:

关注超人学院java免费学习交流群:

时间: 2024-10-29 19:09:50

二叉树的存储结构的相关文章

数据结构--树(上)-- 二叉树及存储结构

二叉树及存储结构 二叉树的定义:一个有穷的结点集合.若不为空,则它是由根节点和称为其左子树和右子树的两个不想交的二叉树组成 一般的左右的树是没有左右之分的.二叉树有左右之分. 三种特殊的二叉树 斜二叉树         实质就可以是链表了. 完美二叉树 完全二叉树 二叉树的几个重要的性质 二叉树的抽象数据类型 对二叉树来讲,最重要的就是Traversal() 遍历,讲二叉树基本就讲遍历了. 二叉树的存储结构 顺序存储结构 顺序存储结构 可以存储完全二叉树:从上往下,从左往右,来进行便利.把这种树

二叉树及存储结构

本文的结构: 二叉树的基本形态 二叉树的重要性质 二叉树的抽象数据类型定义 二叉树的存储结构 二叉树T:一个有穷的节点集合.这个集合可以为空,若不为空,则它是由根节点和称为其左子树TL和右子树TR的两个不相交的二叉树组成 二叉树的五种基本形态: (a) 空树 (b) 有一个结点 (c) 右子树为空 (d) 左子树为空 (e) 有左右子树 二叉树的重要性质: 一个二叉树第i层的最大节点数为:2i-1,i >=1; 深度为k的二叉树有最大结点总数为 2k-1,k>=1: 对任何非空二叉树T,若n0

树1-2、二叉树及存储结构

二叉树的定义: 二叉树的重要性质: 二叉树的存储结构:用数组存储完全二叉树 二叉树的存储结构:用链表存储 原文地址:https://www.cnblogs.com/maider/p/11362237.html

数据结构 - 二叉树的存储结构

顺序存储结构 二叉树存储结构的类型定义: #define MAX_SIZE 100 typedef telemtype sqbitree[MAX_SIZE]; 用一组地址连续的存储单元依次"自上而下.自左至右"存储完全二叉树的数据元素. 对于完全二叉树上编号为i的结点元素存储在一维数组的下标值为i-1的分量中,如图6-6(c)所示. 对于一般的二叉树,将其每个结点与完全二叉树上的结点相对照,存储在一维数组中, 链式存储结构 设计不同的结点结构可构成不同的链式存储结构. (1) 结点的类

二叉树的链式存储结构----二叉链表

头文件:head.h #include<string.h> #include<ctype.h> #include<malloc.h> /* malloc()等 */ #include<limits.h> /* INT_MAX等 */ #include<stdio.h> /* EOF(=^Z或F6),NULL */ #include<stdlib.h> /* atoi() */ #include<io.h> /* eof()

二叉树 二叉树的性质 存储结构 遍历二叉树 C实现二叉树的创建和遍历 线索二叉树

定义 二叉树(binary tree)是n(n>=0)个结点的有限集合,该集合为空集合称为空二叉树,或者有一个根结点和两棵互不相交的,分别称为树根结点的左孩子树和右孩子树组成. 二叉树的特点 每个结点最多有两棵子树,所以二叉树总没有度大于2的结点 左子树和右子树是有顺序的,次数不能任意颠倒 即使树中某结点只有一棵子树,也要区分是左子树还是右子树 特殊的二叉树 1. 斜树 所有的结点都只有左子树的二叉树称为左斜树; 所有的结点都只有右子树的二叉树称为右斜树; 这两者统称为斜树 2. 满二叉树 在一

数据结构--二叉树(定义与存储结构)

什么是二叉树 是具有n个节点的有限集合,由一个根节点和两棵互不相交二叉树组成.如图 从名字简单理解,就是具有2个树叉的树形结构,当然这不是绝对的,正如上图所示,我也可以只有一个树叉. 二叉树具有五种基本形态: (1)空二叉树 (2)只有一个根结点的二叉树 (3)只有左子树 (4)只有右子树 (5)既有左子树又有右子树 完全二叉树 这种二叉树,是二叉树中常用的专业术语,不是说一个完整的二叉树就是完全二叉树,这种二叉树叫满二叉树,如图 简单理解就像图片中,完全二叉树中每个节点的编号,都能映射到满二叉

【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算

1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录).一个自然的问题就是,如何实现有效率的查找? 静态查找:集合中记录是固定的,没有插入和删除操作,只有查找 动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除 静态查找--方法一:顺序查找(时间复杂度O(n)) int SequentialSearch(St

存储结构二叉树

二叉树的存储结构有两种:顺序存储结构和链式存储结构. 顺序存储结构 对于满二叉树和全然二叉树来说,能够将其数据元素逐层存放到一组连续的存储单元中,如图6-3 所看到的. 用一维数组来实现顺序存储结构时.将二叉树中编号为i 的结点存放到数组中的第i 个分量中.如此依据性质6.7,能够得到结点i 的父结点.左右孩子结点分别存放在.2i 以及2i+1 ?i / 2? 分量中. 图6-3 顺序存储结构 这样的存储方式对于满二叉树和全然二叉树是非常合适也是高效方便的.由于满二叉树和全然二叉树採用顺序存储结