zoukankan      html  css  js  c++  java
  • C++ 数据结构 3:树和二叉树

    1 树

    1.1 定义

    由一个或多个(n ≥ 0)结点组成的有限集合 T,有且仅有一个结点称为根(root),当 n > 1 时,其余的结点分为 m (m ≥ 0)个互不相交的有限集合T1,T2,…,Tm。每个集合本身又是棵树,被称作这个根的子树 。

    1.2 结构特点

    • 非线性结构,有一个直接前驱,但可能有多个直接后继(1:n)

    • 树的定义具有递归性,树中还有树。

    • 树可以为空,即节点个数为0。

    1.3 术语

    • 根:即根结点(没有前驱)

    • 叶子:即终端结点(没有后继)

    • 森林:指m棵不相交的树的集合(例如删除A后的子树个数)

    • 有序树:结点各子树从左至右有序,不能互换(左为第一)

    • 无序树:结点各子树可互换位置。

    • 双亲:即上层的那个结点(直接前驱) parent

    • 孩子:即下层结点的子树 (直接后继) child

    • 兄弟:同一双亲下的同层结点(孩子之间互称兄弟)sibling

    • 堂兄弟:即双亲位于同一层的结点(但并非同一双亲)cousin

    • 祖先:即从根到该结点所经分支的所有结点

    • 子孙:即该结点下层子树中的任一结点

    • 结点:即树的数据元素

    • 结点的度:结点挂接的子树数(有几个直接后继就是几度)

    • 结点的层次:从根到该结点的层数(根结点算第一层)

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

    • 分支结点:除树根以外的结点(也称为内部结点)

    • 树的度:所有结点度中的最大值(Max{各结点的度})

    • 树的深度(或高度):指所有结点中最大的层数(Max{各结点的层次})

    上图中的结点数= 13,树的度= 3,树的深度= 4

    1.4 树的表示法

    1.4.1 广义表表示法

    用广义表表示法表示上图:

    中国(河北(保定,石家庄),广东(广州,东莞),山东(青岛,济南))

    根作为由子树森林组成的表的名字写在表的左边

    1.4.2 左孩子右兄弟表示法

    左孩子右兄弟表示法可以将一颗多叉树转化为一颗二叉树

    节点的结构

    节点有两个指针域,其中一个指针指向子节点,另一个指针指向其兄弟节点。

    1.5 树的结构

    1.5.1 逻辑结构

    一对多(1:n),有多个直接后继(如家谱树、目录树等等),但只有一个根结点,且子树之间互不相交。

    1.5.2 存储结构

    树的存储仍然有两种方式:

    • 顺序存储

    可规定为:从上至下、从左至右将树的结点依次存入内存。

    重大缺陷:复原困难(不能唯一复原就没有实用价值)。

    • 链式存储

    可用多重链表:一个前趋指针,n个后继指针。

    细节问题:树中结点的结构类型样式该如何设计?

    即应该设计成“等长”还是“不等长”?

    缺点:等长结构太浪费(每个结点的度不一定相同);

    不等长结构太复杂(要定义好多种结构类型)。

    以上两种存储方式都存在重大缺陷,应该如何解决呢?

    计算机实现各种不同进制的运算是通过先研究最简单、最有规律的二进制运算规律,然后设法把各种不同进制的运算转化二进制运算。树的存储也可以通过先研究最简单、最有规律的树,然后设法把一般的树转化为这种简单的树,这种树就是 二叉树

    2 二叉树

    2.1 定义

    n(n ≥ 0)个结点的有限集合,由 一个根结点 以及 两棵互不相交的、分别称为左子树和右子树的 二叉树 组成 。

    2.2 逻辑结构

    一对二(1:2)

    2.3 基本特征

    • 每个结点最多只有两棵子树(不存在度大于2的结点)

    • 左子树和右子树次序不能颠倒(有序树)

    2.4 基本形态

    2.5 二叉树的性质

    • 性质1: 在二叉树的第 i 层上至多有 ({2^{i-1}}) 个结点(i > 0)

    • 性质2: 深度为 k 的二叉树至多有 ({2^{k-1}}) 个结点(k > 0)

    • 性质3: 对于任何一棵二叉树,若 2 度的结点数有 n2 个,则叶子数(n0)必定为 n2+1 (即 n0 = n2 + 1)

    满二叉树:一棵深度为k 且有 ({2^{k-1}}) 个结点的二叉树。

    特点:每层都“充满”了结点

    完全二叉树:深度为 k 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应。

    • 性质4: 具有 n 个结点的完全二叉树的深度必为 ({log_{2}n} +{1})

    • 性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为 2i,其右孩子编号必为 2i+1。其双亲的编号必为i/2(i=1 时为根,除外)

    使用此性质可以使用完全二叉树实现树的顺序存储。

    2.2 二叉树的表示

    2.2.1 二叉链表 表示法

    一般从根结点开始存储。相应地,访问树中结点时也只能从根开始。

    存储结构

    结点数据类型定义

    typedef struct BiTNode
    {
    	int		data;
    	struct BiTNode *lchild, *rchild;
    }BiTNode, *BiTree;
    

    2.2.2 三叉链表 表示法

    存储结构

    每个节点有三个指针域,其中两个分别指向子节点(左孩子,右孩子),还有一共指针指向该节点的父节点。

    结点数据类型定义

    //三叉链表
    typedef struct TriTNode 
    {
    	int data;
    	//左右孩子指针
    	struct TriTNode *lchild, *rchild;
    	struct TriTNode *parent;
    }TriTNode, *TriTree;
    

    2.2.3 双亲 表示法

    存储结构

    每个节点都由一个数据结构组成,每个节点顺序排放在数组中。

    结点数据类型定义

    //双亲链表
    #define MAX_TREE_SIZE 100
    typedef struct BPTNode
    {
    	int data;	// 数据
    	int parentPosition; //指向双亲的指针,数组下标
    	char LRTag; //左右孩子标志域
    }BPTNode;
    
    typedef struct BPTree
    {
    //因为节点之间是分散的,需要把节点存储到数组中
    	BPTNode nodes[100]; 
    	int num_node;  //节点数目
    //根结点的位置,注意此域存储的是父亲节点在数组的下标
    	int root; 
    }BPTree;
    

    2.3 二叉树的遍历

    定义:指按某条搜索路线 遍访每个结点且不重复

    遍历方法

    牢记一种约定,对每个结点的查看都是“先左后右” 。

    限定先左后右,树的遍历有三种实现方案:

     DLR                 LDR                LRD
    

    先 (根)序遍历 中 (根)序遍历 后(根)序遍历

    • DLR:先序遍历,即先根再左再右

    • LDR:中序遍历,即先左再根再右

    • LRD: 后序遍历,即先左再右再根

    注:“先、中、后”的意思是指访问的结点 D 是先于子树出现还是后于子树出现。

    2.3.1 先序遍历

    PreOrder(NODE *root )
    {  
        if (root) //非空二叉树
        {
    printf(“%d”,root->data); //访问D
    PreOrder(root->lchild); //递归遍历左子树
    PreOrder(root->rchild); //递归遍历右子树
        }
    }
    

    2.3.2 中序遍历

    InOrder(NODE *root)
    { 
    if(root !=NULL)
      {  
    InOrder(root->lchild);
          printf(“%d”,root->data);
          InOrder(root->rchild); 
      } 
    }
    

    2.3.3 后序遍历

    PostOrder(NODE *root)
    {
    if(root !=NULL) 
       {
    PostOrder(root->lchild);
           PostOrder(root->rchild);
           printf(“%d”,root->data); 
       } 
    }
    

    2.3.4 三种遍历的本质

    从前面的三种遍历算法可以知道:如果将printf语句抹去,除去printf的遍历算法:

    XXX (NODE *root)
    {  
    if(root) 
       {
    XXX(root->lchild);
           XXX(root->rchild);
       }
    }
    

    从递归的角度看,这三种算法是完全相同的,或者说这三种遍历算法的访问路径是相同的,只是访问结点的时机不同。

    从虚线的出发点到终点的路径上,每个结点经过 3 次。

    • 第 1 次经过时访问=先序遍历

    • 第 2 次经过时访问=中序遍历

    • 第 3 次经过时访问=后序遍历

    2.4 二叉树的创建

    2.4.1 先序和中序 创建树

    算法

    • 通过先序遍历找到根结点A,再通过A在中序遍历的位置找出左子树,右子树

    • 在A的左子树中,找左子树的根结点(在先序中找),转步骤1

    • 在A的右子树中,找右子树的根结点(在先序中找),转步骤1

    练习1

    先序遍历结果:A D E B C F

    中序遍历结果:D E A C F B

    解:

    练习2

    先序遍历结果:A B D H K E C F I G J

    中序遍历结果:H K D B E A I F C G J

    解:

    2.4.2 # 号法 创建树

    什么是 # 号法创建树:# 创建树,让树的每一个节点都变成度数为 2 的树

    例子1: 124###3##

    解:

    例子2:先序遍历:A B D H # K # # # E # # C F I # # # G # J # #,请画出树的形状

    解:

    # 号法编程实践

    利用前序遍历来建树

    Bintree createBTpre( )
    {      Bintree T; char ch;
            scanf(“%c”,&ch);
            if(ch==’#’) T=NULL; 
            else
            {   T=( Bintree )malloc(sizeof(BinTNode));
                T->data=ch;
                T->lchild=createBTpre(); 
                T->rchild=createBTpre();
            }        
            return T;
    }
    

    使用后序遍历的方式销毁一棵树, 先释放叶子节点,在释放根节点:

    void  BiTree_Free(BiTNode* T)
    {	
    	BiTNode *tmp = NULL;
    	if (T!= NULL)
    	{
    		if (T->rchild != NULL) BiTree_Free(T->rchild);
    		if (T->lchild != NULL) BiTree_Free(T->lchild);
    		if (T != NULL)
    		{
    			free(T); 
    			T = NULL;
    		}
    	}
    }
    

    3 霍夫曼树

    3.1 概念

    组建一个网络,耗费最小 WPL(树的带权路径长度)最小,称为 霍夫曼树。

    从树中一个节点到另一个节点之间的分支构成两个节点之间的路径,路径上的分支数目称作 路径长度

    例子

    如下图的二叉树 a 中,根节点到节点 D 的路径长度为 4,二叉树 b 中根节点到节点 D 的路径长度为 2。树的路径长度就是从树根到每一节点的路径长度之和。二叉树 a 的树路径长度就为 1+1+2+2+3+3+4+4=20。二叉树 b 的树路径长度为 1+2+3+3+2+1+2+2=16。

    3.2 霍夫曼树的构造

    对于文本”BADCADFEED”的传输而言,因为重复出现的只有“ABCDEF”这6个字符,因此可以用下面的方式编码:

    A B C D E F
    000 001 010 011 100 101

    B A D C A D F E E D 001 000 011 010 000 011 101 100 100 011

    接收方可以根据每 3 个 bit 进行一次字符解码的方式还原文本信息。这样的编码方式需要 30 个 bit 位才能表示 10 个字符那么当传输一篇 500 个字符的情报时,需要 15000 个 bit 位,在战争年代,这种编码方式对于情报的发送和接受是很低效且容易出错的。如何提高收发效率?

    A B C D E F
    01 1001 101 00 11 1000

    B A D C A D F E E D 1001 01 00 101 01 00 1000 11 11 00

    准则:任一字符的编码都不是另一个字符编码的前缀!

    也就是说:每一个字符的编码路径,都不包含另外一个字符的路径。

    3.2.1 构建规则

    • 给定 n 个数值{ v1, v2, …, vn}。

    • 根据这 n 个数值构造二叉树集合 F = { T1, T2, …, Tn},Ti 的数据域为 vi,左右子树为空。

    • 在 F 中选取两棵根结点的值最小的树作为左右子树构造一棵新的二叉树,这棵二叉树的根结点中的值为左右子树根结点中的值之和

    • 在 F 中删除这两棵子树,并将构造的新二叉树根节点加入 F 中

    • 重复 3 和4,直到 F 中只剩下一个树为止。

    这棵树即 霍夫曼树

    3.2.2 特点

    • 所用的字符都作为叶子节点出现。

    • 根到每个字符的路径都不重复,也不存在重叠的现象。

    3.2.3 特点

    • 霍夫曼树是一种特殊的二叉树。

    • 霍夫曼树应用于信息编码和数据压缩领域。

    • 霍夫曼树是现代压缩算法的基础。

  • 相关阅读:
    【HTML5 绘图与动画】使用canvas
    【H5新增元素和文档结构】新的全局属性 1. contentEditable 可编辑内容 2. contextmenu 快捷菜单 3. data 自定义属性 4. draggable 可拖动 5. dropzone 拖动数据 6. hidden 隐藏 7. spellcheck 语法检查 8. translate 可翻译
    【H5新增元素和文档结构】完善旧元素 1. a 超链接 2. ol 有序列表 3. dl 定义列表 4. cite 引用文本 5. small 小号字体 6. iframe 浮动框架 7. script 脚本
    【H5新增元素和文档结构】新的语义信息 1. address 2. time 3. figure 跟 figcaption 4. details 和 summary 5. mark 6. progress 7. meter 8. dialog 9.bdi 10. wbr 11. ruby、rt、rp 12. command
    【H5新增元素跟文档结构】新的文档结构 1. article 文章块 2. section 区块 3. nav 导航条 4. aside 辅助栏 5. main 主要区域 6. header 标题栏 7. hgroup 标题组 8. footer 页脚栏
    5_PHP数组_3_数组处理函数及其应用_9_数组集合运算函数
    【华为云技术分享】鲲鹏弹性云服务器GCC交叉编译环境搭建指南
    【华为云技术分享】7 分钟全面了解位运算
    【华为云技术分享】Linux内核编程环境 (1)
    【华为云技术分享】华为云MySQL 8.0正式商用,全新增强版开源利器强势来袭
  • 原文地址:https://www.cnblogs.com/PikapBai/p/13387075.html
Copyright © 2011-2022 走看看