二叉树存储实现

二叉树

01.概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

1.1 二叉树的特点

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。(排序树)

1.2 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

02.二叉树的性质

2.1 性质1

在二叉树的第i层上最多有2^(i-1)个结点(i≥1)。
第一层是根结点,只有一个,所以2(1-1)=20=1。 第二层有两个,2(2-1)=21=2。 第三层有四个,2(3-1)=22=4。 第四层有八个,2(4-1)=2^3=8。

2.2 性质2

深度为k的二叉树至多有2^k-1个结点(k≥1)。
注意这里一定要看清楚,是2k后再减去1,而不是2(k-1)。以前很多同学不能完全理解,这样去记忆,就容易把性质2与性质1给弄混淆了。 深度为k意思就是有k层的二叉树,我们先来看看简单的。 如果有一层,至多1=21-1个结点。 如果有二层,至多1+2=3=22-1个结点。 如果有三层,至多1+2+4=7=23-1个结点。 如果有四层,至多1+2+4+8=15=2^4-1个结点。

2.3 性质3

对任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为1或2的结点数了,我们设n1为度是1的结点数。则树T结点总数n=n0+n1+n2
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为1或2的结点数了,我们设n1为度是1的结点数。则树T结点总数n=n0+n1+n2 。

2.4 性质4

具有n个结点的完全二叉树的深度为|log(2^n)+1| (向下取整)。
由满二叉树的定义我们可以知道,深度为k的满二叉树的结点数n一定是2k-1。因为这是最多的结点个数。那么对于n=2k-1倒推得到满二叉树的深度为k=log2(n+1),比如结点数为15的满二叉树,深度为4。

2.5 性质5

如果对一棵有n个结点的完全二叉树(其深度为|log(2^n)+1|)的结点按层序编号(从第一层到第层,每层从左到右),对任一结点i(1<=i<=n),有

1.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点。
2.如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
3.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1

03.二叉树的存储结构

二叉树一般可以使用两种存储结构,一种顺序结构,一种链式结构

3.1 顺序存储结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

3.1.1 代码
#define MAXSIZE 100
using namespace std;
typedef struct BiTNode{
	int data;
    bool isEmpty;
}BiTNode;
BiTNode t[MAXSIZE];
3.1.2 性质

将树中结点按照层次并从左到右依次标号(从数组下标1开始),若结点 i 有左右孩子,则其左孩子结点为 2i,右孩子结点为 2i+1,此性质可用于还原数组中存储的完全二叉树。

3.2 链式存储结构

3.2.1 代码
#include <iostream>
using namespace std;
typedef struct BiTNode {
    int data;
    BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

04. 二叉树的遍历

二叉树的遍历是指 从根结点出发按照某种次序一次访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次

二叉树的遍历方式有很多,如果我们限制了从左到右的方式,那么主要分为以下四种:

  1. 前序遍历
  2. 中序遍历
  3. 后序遍历
  4. 层次遍历

4.1 前序遍历

4.1.1 规则

先遍历节点**(前)**,再遍历左子树,最后遍历右子树——根左右

若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。

使用递归来实现遍历操作是非常的简洁。

4.1.2 代码
//先序遍历
void preOrder(BiTree &t){
    if (t!= nullptr){
        cout<<t->data<<endl;
        preOrder(t->lchild);
        preOrder(t->rchild);
    }
}

4.2 中序遍历

4.2.1 规则

先遍历左子树,再遍历节点**(中)**,最后遍历右子树——左根右

4.2.2 代码
//中序遍历
void inOrder(BiTree &t){
    if (t!= nullptr){
        inOrder(t->lchild);
        cout<<t->data<<endl;
        inOrder(t->rchild);
    }
}

4.3 后序遍历

4.3.1 规则

先遍历左子树,再遍历右子树,最后遍历节点**(中)**——左右根

4.3.2 代码
//后序遍历
void postOrder(BiTree &t){
    if (t!= nullptr){
        postOrder(t->lchild);
        postOrder(t->rchild);
        cout<<t->data<<endl;
    }
}

4.4 层次遍历

4.4.1 规则

通过对树中各层的节点从左到右依次遍历,即可实现对正棵二叉树的遍历,此种方式称为层次遍历。

4.4.2 代码
void levelOrder(BiTree t) {
    queue<BiTNode *> q;
    q.push(t);
    while (!q.empty()) {
        cout << q.front()->data << endl;
        if (q.front()->lchild != nullptr) {
            q.push(q.front()->lchild);
        }
        if (q.front()->rchild != nullptr) {
            q.push(q.front()->rchild);
        }
        q.pop();
    }
}

4.5 遍历的运用—求二叉树的深度

4.5.1 代码
int treeDepth(BiTree t)
{
    if(t==nullptr)
    {
        return 0;
    }
    int l = treeDepth(t->lchild);
    int r = treeDepth(t->rchild);
    return l>r ? l+1:r+1;
}

4.6 由遍历序列构造二叉树

通过遍历结果来反推二叉树的形态,最重要的是理解每种遍历之间和二叉树的联系

注意: 若只给出一个二叉树的遍历序列的一种不能唯一确定二叉树的结构

4.6.1 方法

前序、后序、层序序列与中序两两组合

4.6.2 前序+中序

**前序遍历序列:根结点 左子树的前序遍历序列 右子树的前序遍历序列 **

中序遍历序列:左子树的中序遍历序列 根结点 右子树的中序遍历序列

4.6.3 后序+中序

后序遍历序列:左子树的中序遍历序列 右子树的中序遍历序列 根结点

中序遍历序列:左子树的中序遍历序列 根结点 右子树的中序遍历序列

4.6.4 层次+中序

层序遍历序列:根结点 左子树的根 右子树的根

中序遍历序列:左子树的中序遍历序列 根结点 右子树的中序遍历序列

05.线索二叉树

5.1 定义

线索二叉树:指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。

线索二叉树也分为前序线索二叉树、中序线索二叉树以及后序线索二叉树,即它们都是对二叉树中的所有结点的空指针进行某种遍历方式加线索,指向结点前驱和后继的指针称为线索

5.2 代码实现

struct ThreadNode{
    int data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;//左右线索标志,0表示指向孩子,1表示指向前驱或后继
};

5.3 中序线索化二叉树

5.3.1 思路

中序遍历二叉树,一边遍历一遍线索化

5.3.2 代码实现
#include "iostream"

typedef struct ThreadNode {
    int data;
    struct ThreadNode *lchild, *rchild;
    int ltag=0, rtag=0;//左右线索标志,0表示指向孩子,1表示指向前驱或后继
} ThreadNode, *ThreadTree;

ThreadNode *pre = nullptr;

//线索化
void visit(ThreadNode *t) {
    if (t->lchild == nullptr) {
        t->lchild == pre;
        t->ltag=1;
    }
    if (pre != nullptr && pre->rchild == nullptr) {
        pre->rchild = t;
        pre->rtag=1;
    }
    pre = t;
}

//中序遍历
void inOrder(ThreadTree t) {
    if (t != nullptr) {
        inOrder(t->lchild);
        visit(t);
        inOrder(t->rchild);
    }
}

//中序线索化二叉树
void createInThread(ThreadTree t){
    pre= nullptr;
    if (t== nullptr){
        return;
    }
    inOrder(t);
    if (pre->rchild== nullptr){
        pre->rtag=1;
    }
    return;
}


int main() {
    return 0;
}
  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值