树型结构
- 树的定义
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:- 每个节点有零个或多个子节点
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树。
- 树的基本术语
若一个结点有子树,那么该结点称为子树根的"双亲",子树的根是该结点的"孩子"。有相同双亲的结点互为"兄弟"。一个结点的所有子树上的任何结点都是该结点的后裔。从根结点到某个结点的路径上的所有结点都是该结点的祖先。
结点的度:结点拥有的子树的数目。
叶子:度为零的结点。
分支结点:度不为零的结点。
树的度:树中结点的最大的度。
层次:根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1。
树的高度:树中结点的最大层次。
无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。
有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。
森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。 - 树的性质
- 树中的结点数等于所有结点度数加1;
- 度为m的树中第i层上至多有m^(i-1)个结点(i>=1);
- 高度为h的m叉树至多有(m^h-1)/(m-1)个节点;
- 具有n个结点的m叉树的最小高度为logm(n(m-1)+1)的上界;
- 完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
- 满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
- 平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

二叉树
- 二叉树的定义
二叉树是每个节点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。 - 二叉树的性质
二叉树有以下几个性质:TODO(上标和下标)
性质1:二叉树第i层上的结点数目最多为 2^(i-1) (i≥1)。
性质2:深度为k的二叉树至多有2^k-1个结点(k≥1)。
性质3:包含n个结点的二叉树的高度至少为log2(n+1)。
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。 - 二叉树存储结构
- 顺序存储:采用数组的方式进行存储
- 链式存储:包括三个域:数据域data、左指针域lchild和右指针域rchild;
typedef struct node
{
ElemType data;
struct node *lchild,*rchild;
}BTNode;
- 二叉树的遍历
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。- 前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
void preScanf(TreeNode* t)
{
if (t == NULL)
{
return;
}
cout << t->val <<" ";
preScanf(t->left);
preScanf(t->right);
} - 中序遍历:若二叉树为空,则空操作返回,否则从根结点开始,先中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
//中序遍历二叉树:左—根---右
void middleScanf(TreeNode* t)
{
if (t == NULL)
{
return;
}
middleScanf(t->left);
cout << t->val << " ";
middleScanf(t->right);
} - 后序遍历:若二叉树为空,则空操作返回,否则从根结点开始,先后序遍历根结点的左子树,再后序遍历根结点的右子树,最后访问根结点。
//后序遍历二叉树:左—右---根
void backScanf(TreeNode* t)
{
if (t == NULL)
{
return;
}
backScanf(t->left);
backScanf(t->right);
cout << t->val <<" ";
}
-层次遍历: 若二叉树为空,则空操作返回,否则从二叉树的第一层也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。代码思路讲解:若要实现按层遍历,那么应该先访问根结点,然后再访问遍历结点的两个子节点,放入容器,按照从左到右的顺序遍历到左子节点时就把其的两个子节点放到容器,最后输出的时候,就是从容器中按照顺序输出,先进先出,所以想到可以用队列做为数据容器。从上到下打印二叉树的规律:每一次打印一个节点的时候,如果该结点有子结点,则把该结点的子结点放到一个队列的末尾,接下来从队列的头部取出最早进入队列的结点,重复输出,直至队列中所有结点都被打印出来。
//层序遍历二叉树:从上到下,从左到右
void fromTopToBottomScanf(TreeNode* t)
{
if (t==NULL)
{
return;
}
deque<TreeNode*> dequeTreeNode;
dequeTreeNode.push_back(t);
while (dequeTreeNode.size())
{
TreeNode* pNode = dequeTreeNode.front(); //依次取出队列中的头部元素进行打印
dequeTreeNode.pop_front(); //
cout << pNode->val << " ";
if (pNode->left)
{
dequeTreeNode.push_back(pNode->left);
}
if (pNode->right)
{
dequeTreeNode.push_back(pNode->right);
}
}
}
- 前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
- 线索二叉树
在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
线索二叉树中的线索能记录每个结点前驱和后继信息。为了区别线索指针和孩子指针,在每个结点中设置两个标志ltag和rtag。
当tag和rtag为0时,leftChild和rightChild分别是指向左孩子和右孩子的指针;否则,leftChild是指向结点前驱的线索(pre),rightChild是指向结点的后继线索(suc)。由于标志只占用一个二进位,每个结点所需要的存储空间节省很多。
现将二叉树的结点结构重新定义如下:lchild、ltag、data、rtag、rchild;其中:ltag=0 时lchild指向左儿子;ltag=1 时lchild指向前驱;rtag=0 时rchild指向右儿子;rtag=1 时rchild指向后继。
本文深入探讨树型数据结构的概念,包括树的定义、基本术语、性质及不同类型的树,如二叉树、满二叉树、平衡二叉树等。同时,详细介绍了二叉树的存储结构、遍历方式及线索二叉树的概念。

6076

被折叠的 条评论
为什么被折叠?



