数据结构笔记整理第5章:树和二叉树

第5章 树和二叉树

本章内容

本章主要介绍树、二叉树的概念,遍历方法以及应用等,本章在考研中是重点内容。

5.1 树相关的基本概念

树是一种非线性的数据结构,是若干结点的集合,有唯一的根结点和若干棵互不相交的子树构成。其中每一棵子树又是一棵树,也是由唯一的根结点和若干棵互不相交的子树组成的,由此可知:树的定义是递归的。树的结点数目可以为0,为0的时候是一棵空树。

结点:结点不仅包含数据元素,而且包含指向子树的分支。

结点的度:结点拥有子树的个数或者分支的个数。

树的度:树中各结点度的最大值。

叶子结点:终端结点,度为0的结点。

非终端结点:分支结点,内部结点,度不为0的结点。

以根结点为第一层,以此类推。

树的高度(深度):树中结点的最大层次。

结点的深度:从根结点开始计算,根结点深度为1。

结点的高度:从底层叶子结点开始计算,叶子结点高度为1。

有序树:树中结点子树从左到右有序,不能交换。

丰满树:出最底层外,其他层都是满的。

森林:若干棵互不相交的树的集合。

5.2 树的存储结构

双亲存储结构:一种顺序存储结构,知道了i,tree[i]为其双亲结点。

孩子存储结构:同图中的邻接表存储。

5.3 二叉树的相关的基本概念

一般的树加上如下的两个限制条件,就得到了二叉树:每个结点最多只有两棵子树,即二叉树中结点的度只能为0,1以及2;子树有左右之分,不能颠倒。

满二叉树:所有的分支结点都有左孩子和右孩子结点,并且叶子结点都集中在二叉树的最下层,这样的二叉树称为满二叉树。对满二叉树编号:编号从1开始,从上到下、从左到右进行。

完全二叉树:一棵深度为K有n个结点的二叉树进行编号后,各结点的编号与深度为K的满二叉树上相同位置的结点的编号相同。一棵完全二叉树一定是由一棵满二叉树从右至左、从下至上挨个删除结点所得到的。

5.4 二叉树的主要性质

1.非空二叉树上叶子结点数等于双分支结点数加1,即总分支数 = 总结点数 - 1

2.二叉树的第i层上最多有(i >= 1)个结点

3.高度(或深度)为K的二叉树最多有个结点,换句话说:满二叉树中前K层结点个数为:

4.有n个结点的完全二叉树,对各结点从上到下,从左到右依次编号(范围1~n),则结点之间的关系,若i为某结点编号,则:

若i≠1,双亲编号?i/2?;

若2i≤n,左孩子为2i,若2i>n,无左孩子;

若2i+1≤n,右孩子为2i+1,若2i+1>n,无右孩子;

5.n个结点,构成h(n)种不同的二叉树,则:

6.n个结点的完全二叉树高度(深度)为:

5.5 二叉树的存储结构

顺序存储:最适合于完全二叉树,使用顺序存储结构要从数组下标为1开始。这里要注意区分树的顺序存储结构和二叉树的顺序存储结构:

树:数组下标代表结点的编号,内容表示结点之间的关系;

二叉树:数组下标既指示了编号又指示了关系。

链式存储:

typedef struct BTNode {
    char data;
    struct BTNode *lchild;
    struct BTnode *rchild;
}BTNode;

5.6 树、森林与二叉树的转换

将树转换为二叉树:同一结点的各孩子结点用线串起来,将每个结点的分支从左往右除了第一个以外,其余全部剪掉,擦掉虚线,连成实线,得到二叉树。

【例子1】树转换为二叉树

将森林转换为二叉树:根据孩子兄弟的法则,根结点没有右兄弟,所以转换为二叉树没有右孩子。将森林中第二棵树转换成的二叉树,当作第一棵树的右子树,依此类推。

【例子2】森林转换为二叉树

5.7 二叉树的遍历算法

包括:先序遍历、中序遍历、后序遍历和层次遍历。

先序遍历:

如果二叉树为空数,什么都不做。

否则,访问根节点->先序遍历左子树->先序遍历右子树

/* pre order by recursion method */
void preOrder(BTNode *p) {
    if (p != null) {
        visit(p->data);
        preOrder(p->lchild);
        preOrder(p->rchild);
    }
}

/* pre order by no recursion method */
void preOrder(BTNode *p) {
    Stack <BTNode *> s;
    BTNode *q;
    q = p;
    s.initial();
    while(q != null || !s.empty()) {
        if (q != null) {
            visit(q->data);
            s.push(q);
            q = q->lchild;
        }
        else {
            s.pop(q);
            q = q->rchild;
        }
    }
}

中序遍历:

如果二叉树为空数,什么都不做。

否则,中序遍历左子树->访问根结点->中序遍历右子树

/* in order by recursion method */
void inOrder(BTNode *p) {
    if (p != null) {
        inorder(p->lchild);
        visit(p->data);
        inorder(p->rchild);
    }
}

/* in order by no recursion method */
void inOrder(BTNode *p) {
    Stack <BTNode *> s;
    BTNode *q = p;
    s.initial();
    while(q != null || !s.empty()) {
        if (q != null) {
            s.push(q);
            q = q->lchild;
        }
        else {
            s.pop(q);
            visit(q->data);
            q = q->rchild;
        }
    }
}

后序遍历:

如果二叉树为空数,什么都不做。

否则,后序遍历左子树->后序遍历右子树->访问根结点

/* post order by recursion method */
void postOrder(BTNode *p) {
    if (p != null) {
        postOrder(p->lchild);
        postOrder(p->rchild);
        visit(p->data);
    }
}

/* post order by no recursion method */
void postOrder(BTNode *p) {
    Stack <BTNode *> s;
    Stack <int> tag;
    BTNode *q = p;
    int f;
    s.initial();
    tag.initial();
    while(q != null || !s.empty()) {
        if (q != null) {
            s.push(q);
            q = q->lchild;
            tag.push(1);
        }
        else {
            s.pop(q);
            tag.pop(f);
            if (f == 1) {
                s.push(q);
                tag.push(2);
                q = q->rchild;
            }
            else {
                visit(q->data);
                q = null;
            }
        }
    }
}

层次遍历:

按照一定的方向(如从左至右,从上至下)每一层次对二叉树各个结点进行访问。要进行层次遍历,需要建立一个循环队列,先将二叉树头结点入队列,然后出队列,访问该结点,如果有左子树,则左子树根结点入队;如果有右子树,则右子树根结点入队。出队列,访问出队列结点,如此反复直到队列空为止。

void levelOrder(BTNode *p) {
    Queue <BTNode *> q;
    BTNode *r;
    q.initial();
    if (p != null) {
        q.push(p);
        while(!q.empty()) {
            q.pop(r);
            visit(r->data);
            if (r->lchild != null) {
                q.push(r->lchild);
            }
            if (r->rchild != null) {
                q.push(r->rchild);
            }
        }
    }
}

【例子3】二叉树的遍历结果

先序遍历:A->B->C->D->E->F->G->H

中序遍历:C->B->E->D->F->A->H->G

后序遍历:C->E->F->D->B->H->G->A

层次遍历:A->B->G->C->D->H->E->F

5.8 树和森林的遍历算法

树的遍历:

先根遍历:先访问根结点、再访问子树。

后根遍历:先访问子树,再访问根结点。

树的先根遍历对应二叉树的先序遍历。

树的后根遍历对应二叉树的中序遍历。

森林的遍历:

先序遍历:对应二叉树的先序遍历,先访问第一棵树的根结点,先序遍历第一棵树中根结点的子树,先序遍历森林中除去第一棵树的其他树。

中序遍历:对应二叉树的中序遍历,中序遍历第一棵树根结点的子树,访问第一棵树的根结点,中序遍历森林中除去第一棵树的其他树。

5.9 线索二叉树

ltag=0:lchild为指针,指向结点左孩子;ltag=1,表示lchild为线索,指向结点直接前驱。

rtag=0:rchild为指针,指向结点右孩子;rtag=1,表示rchild为线索,指向结点直接后驱。

typedef struct TBTNode {
    char data;
    int ltag, rtag;
    struct TBTNode *lchild;
    struct TBTNode *rchild;
} TBTNode;

5.10 二叉排序树(BST)

这部分内容主要应用在“查找或者排序部分”。二叉排序树或者是空树,或者是满足以下性质的二叉树:

若左子树不空,则左子树所有值 < 根

若右子树不空,则右子树所有值 > 根

左右子树各是一棵二叉排序树

输出二叉排序树的中序遍历,则该输出序列为递增序列。

typedef struct BTNode {
    int key;
    struct BTNode *lchild;
    struct BTNode *rchild;
} BTNode;

5.11 平衡二叉树(AVL)

这部分内容主要应用在“查找或者排序部分”。

左右子树都是平衡二叉树,并且左右子树的高度之差的绝对值不超过1(引入了平衡因子的概念)。

一个结点的平衡因子:左子树高度 - 右子树高度。取值:-1,0,1

当失去平衡的最小子树被调整为平衡子树之后,无需调整原有其他所有不平衡子树,整个二叉排序树会成为一棵平衡二叉树。

最小子树:以距离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。

将失去平衡的二叉树进行平衡调整有4种情况:LL型、RR型、LR型和RL型。

LL型:在左子树根结点的右子树上插入结点 -> 右旋

LR型:在左子树根结点的右子树上插入结点 -> 左旋 + 右旋

RR型:在右子树根结点的右子树上插入结点 -> 左旋

RL型:在右子树根结点的左子树上插入结点 -> 右旋 + 左旋

【例子4】平衡二叉树的插入删除和平衡调整

以关键字{16、3、7、11、9、26、18、14、15}构造一棵AVL树,构造完成后依次删除16、15、11

插入部分:

删除部分:

5.12 B-树与B+树

B-树可以看作是二叉排序树的扩展,二叉排序树是二路查找,B-树的多路查找。因为B-树结点内的关键字是有序的,在结点内查找的时候除了顺序查找外,可以用折半查找。B-树需要满足以下条件:

每个结点最多有m个分支(子树);而最少分支棵树要看是否为根结点,根结点最少两个分支,非根结点最少有个分支;

有n个分支的结点有n-1个关键字,从左至右递增顺序排列;

各个底层是叶结点,叶结点下面是失败结点(空指针表示)。

B+树是B-树的一种变形。它们之间的差别主要有:

B+树中,n个关键字的结点含有n个分支,B-树中有n+1个分支;

B+树中叶子结点包含信息,并且包含了全部关键字,B-树的叶子只包含关键字(索引)

由于这里并不是考研重点考察的部分,所以关于B+树和B-树的详细操作会在后面的文章中单独描述。

时间: 2024-08-25 22:27:05

数据结构笔记整理第5章:树和二叉树的相关文章

数据结构学习之第7章 树和二叉树

数据结构学习之第7章 树和二叉树 0x7.1.1 树的基本概念 ?1.树的定义 ? 树是由n(n>=0)个结点(或元素)组成的有限集合(记为T) ? 如果n>0,这n个结点中有且仅有一个结点作为树的根结点,简称为根,其余结点可分为m(m>=0)个互不相交的有限集\[T_{1}T_{2}\cdots T_{m}\],其中每个子集又是一棵符合定义的子树,称为根结点的子树. 知识点:由树的定义我们可以看出来树的结构是递归的 ?2.树的逻辑表示法 ? 1.树形表示法 ? 2.文氏图表示法 ? 3

数据结构期末复习第六章树和二叉树

知识点: 先序遍历二叉树规则:根-左-右 1.访问根结点 2.先序遍历左子树 3.先序遍历右子树 中序遍历二叉树规则:左-根-右 1.先中序遍历左子树 2.再访问根节点 3.最后访问中序遍历右子树 后序遍历二叉树规则:左-右-根 1.后序遍历左子树 2.后序遍历右子树 3.访问根结点 1.  一棵二叉树的先序遍历结果为ABCDEF,中序遍历结果为CBAEDF,则后序遍历结果为(A)A. CBEFDA                       B. FEDCBAC. CBEDFA        

第五章 树和二叉树

上章回顾 单链表的基本操作,包括插入.删除以及查找 双向链表和循环链表的区别 [email protected]:Kevin-Dfg/Data-Structures-and-Algorithm-Analysis-in-C.git 第五章 第五章 树和二叉树 树和二叉树 [email protected]:Kevin-Dfg/Data-Structures-and-Algorithm-Analysis-in-C.git 预习检查 什么是二叉树 树的遍历有哪几种方式 树有哪些应用 [email pr

数据结构——第三章树和二叉树:03树和森林

1.树的三种存储结构: (1)双亲表示法: #define MAX_TREE_SIZE 100 结点结构: typedef struct PTNode { Elem data; int parent; //双亲位置域 } PTNode; (2)孩子双亲链表表示法: typedef struct PTNode { Elem data; int parent; //双亲位置域 struct CTNode* nextchild; } *ChildPtr; (3)树的二叉链表(孩子-兄弟)存储表示法:

数据结构-王道2017-第4章 树与二叉树-树、森林

1.树的存储结构有多种,既可以采用顺序存储结构,也可以采用链式存储结构,都要求能唯一地反映出树中各结点之间的逻辑关系,三种常用的存储结构 1)双亲表示法 采用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲节点在数组中的位置,根节点下标为0,其伪指针域为-1. #define MAX_TREE_SIZE 100 //树中最多结点数 typedef struct{ //树的结点定义 ElemType data; //数据元素 int parent; //双亲位置域 }PTNo

数据结构与算法系列研究五——树、二叉树、三叉树、平衡排序二叉树AVL

树.二叉树.三叉树.平衡排序二叉树AVL 一.树的定义 树是计算机算法最重要的非线性结构.树中每个数据元素至多有一个直接前驱,但可以有多个直接后继.树是一种以分支关系定义的层次结构.    a.树是n(≥0)结点组成的有限集合.{N.沃恩}     (树是n(n≥1)个结点组成的有限集合.{D.E.Knuth})      在任意一棵非空树中:        ⑴有且仅有一个没有前驱的结点----根(root).        ⑵当n>1时,其余结点有且仅有一个直接前驱.         ⑶所有结

第5章 树与二叉树学习小结

前几章学习的基本都是线性的数据结构,就有顺序存储结构和链式存储结构,而这一章“树”结构是一类非线性数据结构,跟之前就有不同的点,但是,树的存储结构还是可以通过找到元素之间逻辑关系,采用类似线性表的方式,按照结点之间的逻辑关系放到线性存储中. 这部分主要学习到二叉树的内容,二叉树有好几个性质,我想这些性质很重要,有时候在解决问题,它能够帮助理解这棵树比较抽象的结构层次,这是我在理解代码时候体会到的.二叉树存储结构跟遍历有很大的关系,遍历的结果是将非线性结构的树中结点排成一个线性序列. 这是二叉链表

我的软考之路(四)——数据结构和算法(2)树和二叉树

上鲍恩描述了数据结构的线性结构,我们引入非线性结构本博客-树和二叉树.我想向大家介绍一些基本概念树,树遍历,然后介绍了二叉树的概念和特征.和二叉树遍历.叉树的对照,总结. 树为了描写叙述现实世界的层次结构,树结构中一个数据元素能够有两个或两个以上的直接后继元素. 树的基本概念: 树的概念是学习树的关键所在.掌握了树的基本概念,学会树与二叉树,so easy. 我通过一棵树来了解树的基本概念.例如以下图 1.结点的度 结点的度是子结点的个数.比如:结点1有三个字结点2,3,4,所以结点1的度为3.

数据结构-王道2017-第4章 树与二叉树-二叉树的遍历

typedef int ElemType; typedef struct BitNode { ElemType data; //数据域 struct BitNode *lchild, *rchild; //左右孩子指针 }BitNode,*BitTree; void visit(BitNode *b) { printf("%d ", b->data); } //无论采用哪种遍历方法,时间复杂度都是O(n),因为每个结点都访问一次且仅访问一次,递归工作栈的栈深恰好为树的深度,空间复