数据结构与算法学习笔记 树

Posted 临风而眠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法学习笔记 树相关的知识,希望对你有一定的参考价值。

数据结构与算法学习笔记(7) 树

前情回顾

文章目录

一.树和二叉树的定义

1.树

树的定义

递归嵌套定义

  • 树的其他表示方式

就有点像这个markdown语法:

  • A
  • B
    • E
      • K
      • L
    • F
  • C
    • G
  • D
    • H
      • M
    • I
    • J

树的基本术语

结点的度,也是分支个数,也是后继个数

    • 有序树:树中结点的各子树从左至右有次序(最左边为第一个孩子)

    • 无序树:树中结点的各子树无次序

    • 森林:是m(m≥0)棵互不相交的树的集合

      树变森林:

树结构与线性结构比较

2.二叉树

二叉树的定义

二叉树与树的差别

二叉树与树是两个概念,二叉树不是树的特殊情况

  • 虽然是两个概念,但是有关树的基本术语对二叉树都适用

二叉树基本形态

3.树和二叉树的抽象数据类型定义

树:

  • 主要学习二叉树的抽象数据类型定义

    • 基本操作很多,下为几个主要的:

二.二叉树的性质与存储结构

1.二叉树的性质

①二叉树性质1、2、3

  • 每层结点个数

    • 第i层上至少有1个结点
  • 总共结点个数

  • 叶子数与度为2的结点数的关系

    n表示结点总个数

    n 1 n_1 n1表示度为1的结点

    • 🤩从上往下然后从下往上的思想

②两种特殊形式的二叉树:满二叉树和完全二叉树

定义与特点
  • 满二叉树

  • 完全二叉树

    • 判断技巧

    • 特点

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTpIvBoR-1637761025234)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20210928093038.png)]

两者关系

③二叉树的性质4、5(完全二叉树的两个性质)

  • 性质4:完全二叉树的深度

  • 性质5:结点编号之间的关系

    • 性质5表明了完全二叉树中双亲结点编号与孩子结点编号之间的关系

      证明:数学归纳法

判断完全二叉树:借助队列

2.二叉树的存储结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LWSp7IJN-1637761025237)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20211005081221.png)]

①顺序存储

  • 实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素

  • 如:

    先从上到下,再从左到右

  • 类型定义

    #define MAXTSIZE 100
    typedef TElemType SqBiTree[MAXTSIZE];
    SqBiTree bt;
    

    上面SqBiTree是一个数据类型, 定义 的变量类型就是TElemType[MAXTSIZE]

    即上面的bt就是一个TElemType类型的有MAXTSIZE个元素的数组

  • 看个例子

  • 二叉树顺序存储的特点

②链式存储

二叉链表
  • 二叉树结点的特点

  • 存储结构定义

    typedef struct BiNode
    	TElemType	data;
    	struct BiNode	*lchild,*rchild;	//左右孩子指针
    BiNode,*BiTree;
    
    • 看个例子

  • 空指针域的个数

    • 每个结点都有一个和其双亲的连线,除了根节点
      • 所以只会有n-1个结点的链域存放指针,指向非空子女节点
三叉链表
  • 有时候需要找前驱

三.遍历二叉树和线索二叉树

1.遍历方法概述

  • 重点研究DLR、LDR、LRD三种

    由二叉树的递归定义可知,遍历左子树和遍历右子树可如同遍历二叉树一样"递归"进行

先序遍历二叉树的操作定义

中序遍历二叉树的操作定义

后序遍历二叉树的操作定义

  • 先序:ABDGCEHF
  • 中序:DGBAEHCF
  • 后序:GDBHEFCA

  • 先序:-+a×b-cd/ef

  • 中序:a+b×c-d-e/f

  • 后序:abcd-×+ef/-

2.根据遍历序列确定二叉树

  • 若二叉树中各结点的值均不同,则二叉树结点的先序序列、中序序列、后序序列都是唯一的

  • 由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一 一颗二叉树

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgeNdYCK-1637761025258)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20211005094048.png)]

3.二叉树的遍历方法实现

先序遍历递归算法

Status PreOrderTraverse(BiTree T)
	if(T==NULL)	return OK;	//空二叉树
	else
		visit(T);	//访问根结点
		PreOrderTraverse(T->lchild);	//递归遍历左子树
		PreOrderTraverse(T->rchild);	//递归遍历右子树
	

  • 先序序列

    ABDC

中序遍历递归算法

Status InOrderTraverse(BiTree T)
	if(T==NULL)	return OK;	//空二叉树
	else
		InOrderTraverse(T->lchild);	//递归遍历左子树
		visit(T);	//访问根结点
		InOrderTraverse(T-rchild);	//递归遍历右子树
	

后序遍历递归算法

Status PostOrderTraverse(BiTree T)
	if(T==NULL)	return OK;	//空二叉树
	else
		PostOrderTraverse(T->lchild);	//递归遍历左子树
		PostOrderTraverse(T-rchild);	//递归遍历右子树
		visit(T);	//访问根结点
		
	

三种递归遍历算法的比较分析

空间用的是栈

中序遍历非递归算法

Status InOrderTraverse(BiTree T)
	BiTree p;
	InitStack(S);
	p=T;	//指向根结点
	while(p||!StackEmpty(S))
		if(p)
			Push(S,p);
			p=p->child;
		
		else
			Pop(S,p);
			printf("%c",q->data);
			p=q->child;
		
		return OK;
	

二叉树的层次遍历算法

  • 算法思路

  • 算法描述

    使用顺序循环队列

    队列类型定义如下:

    typedef struct
    	BTNode data[MaxSize];	//存放队中元素
    	int front, rear;		//队头和队尾指针
    SqQueue;				//顺序循环队列类型
    

    二叉树层次遍历算法:

    void LevelOrder(BTNode *b)
    	BTNode *p;
    	SqQueue *qu;	//初始化队列
    	enQueue(qu,b);	//根结点指针进入队列
    	while(!QueueEmpty(qu))		//队不为空,则循环
    		deQueue(qu,p);	//出队结点p
    		printf("%c",p->data);	//访问结点p
    		if(p->lchild!=NULL)
    			enQueue(qu,p->lchild);	//有左孩子时令其进队
    		if(p->rchild!=NULL)
    			enQueue(qu,p->rchild);	//有右孩子时令其进队
    	
    
    

4.二叉树遍历算法的应用

建立二叉树的算法

按先序遍历序列建立二叉树的二叉链表

根据某个先序序列建立的树不是唯一的,如:

为避免重复的情况,可以增加空结点,用#代替

  • 算法描述

    Status CreateBiTree(BiTree &T)
    	scanf(&ch);
    	if(ch=="#") T=NULL;
    	else
    		if(!(T=(BiTNode*)malloc(sizeof(BiTNode))))
    			//T=new BiTNode;
    			exit(OVERFLOW);	
    		
            T->data = ch;	//生成根结点
            CreateBiTree(T->lchild);	//构造左子树
            CreateBiTree(T->rchild);	//构造右子树
    	
        return OK;
    
    

复制二叉树

  • 算法思路

  • 算法描述

    int Copy(BiTree T, BiTree &NewT)
    	if(T==NULL)	//如果是空树返回0
    		NewT=NULL;
    		return 0;
    	
    	else
    		NewT=new BiTNode;
    		NewT->data=T->data;
    		Copy(T->lChild,NewT->lchild);
    		Copy(T->rchild,NewT->rchild);
    	
    
    

计算二叉树的深度

  • 算法思想

  • 算法描述

    int Depth(BiTree T)
    	if(T==NULL)	return 0;
    	else
    		m=Depth(T->lchild);
    		n=Depth(T->rchild);
    		if(m>n)	
    			return (m+1);
    		else 
    			return (n+1);
    	
    
    

计算二叉树结点总数

  • 算法思想

    加1加的就是根结点

  • 算法描述

    int NodeCount(BiTree T)
    	if(T==NULL)
    		return 0;
    	else
    		return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
    
    

计算二叉树叶子结点数

  • 算法思想

  • 算法描述

    int LeafCount(BiTree T)
    	if(T==NULL)	//如果是空树返回0
    		return 0;
    	if(T->lchild == NULL && T->rchild == NULL)
    		return 1;	//如果是叶子结点返回1
    	else
    		return LeafCount(T->lchild) + LeafCount(T->rchild);
    
    

5.线索二叉树

  • why

    • 可能的解决办法

    • 回顾空指针域

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LoFYc38f-1637761025279)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20211008083920.png)]

    • 利用二叉链表的空指针域

  • what

    • 对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化

  • 结点结构

    typedef struct BiThrNode
    	int data;
    	int ltag,rtag;
    	struct BiThrNode *lchild,*rchild;
    BiThrNode, *BiThrTree;
    

线索二叉树的存储

先序线索二叉树

中序线索二叉树

后序线索二叉树

增设头结点

四.树和森林

1.树的存储结构

双亲表示法

  • C语言类型描述

孩子链表

  • C语言类型描述

  • 再增加数据域

孩子兄弟表示法(二叉树表示法、二叉链表表示法)

  • 例子

2.树与二叉树的转换

树转为二叉树

  • 规律

二叉树转为树

3,森林与二叉树的转换(二叉树与多棵树之间的关系)

森林转二叉树

二叉树转为森林

4.树与森林的遍历

树的遍历(三种方式)

森林的遍历

  • 先序遍历

  • 中序遍历

五.哈夫曼树

1.引入

  • 优化

  • 哈夫曼树

2.基本概念

路径、路径长度、权

结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树

哈夫曼树
哈夫曼树特点
  • 满二叉树不一定是最优二叉树(哈夫曼树)
  • 哈夫曼树中权越大的叶子离根越近
  • 具有相同带权结点的哈夫曼树不唯一

3.哈夫曼树的构造算法

  • C语言实现:

    https://tyrantlucifer.com/44.html

哈夫曼算法(构造哈夫曼树的方法)

哈夫曼算法特点

哈夫曼树构造算法的实现
  • 顺序存储结构

  • 算法实现

    • 代码实现

      void CreatHuffmanTree(HuffmanTree HT,int n)//构造哈夫曼树--哈夫曼算法
      	if(n<=1) return;
      	m=2*n-1;	//数组共2n-1个元素
      	HT = new HTNode[m+1];	//0号单元未用,HT[m]表示根结点
      	for(i=1;i<=m;++i)	//将2n-1个元素的lch、rch、parent置为0
      		HT[i].lch=0;
      		HT[i].rch=0;
      		HT[i].parent=0;
      	
      	for(i=1;i<=n;++i)
      		cin>>HT[i].weight;//输入前n个元素的weight值
          //初始化结束,下面开始建立哈夫曼树
          for(i=n+1;i<=m;i++)	//合并产生n-1个结点--构造Huffman树
              Select(HT,i-1,s1,s2);	//在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
              HT[s1].parent=i;
              HT[s2].parent=i;	//表示从F中删除s1,s2
              HT[i].lch=s1;
              HT[i].rch=s2;	//s1,s2分别作为i的左右孩子
              HT[i].weight=HT[s1].weight+HT[s2].weight;	//i的权值为左右孩子权值之和
              
          
          
      
      
    • 初始化
    • 构造哈夫曼树

哈夫曼树的应用–哈夫曼编码

  • 引入

哈夫曼编码的性质

  • 例54

哈夫曼编码的算法实现
  • 算法描述

4.哈夫曼树的应用

文件的编码和解码