数据结构详细笔记——二叉树

文章目录

  • 二叉树的定义和基本术语
  • 特殊的二叉树
    • 满二叉树
    • 完全二叉树
    • 二叉排序树
    • 平衡二叉树
  • 二叉树的常考性质
  • 完全二叉树的常考性质
  • 二叉树的存储结构
    • 顺序存储
    • 链式存储
  • 二叉树的先中后序遍历
    • 先序遍历(空间复杂度:O(h))
    • 中序遍历
    • 后序遍历
    • 应用
  • 二叉树的层序遍历
  • 由遍历序列构造二叉树
  • 线索二叉树
    • 线索二叉树的存储结构
    • 二叉树的线索化
    • 二叉树的线索化


二叉树的定义和基本术语

二叉树的基本概念

二叉树是n(n>=0)个结点的有限集合
①或者为空的二叉树,即n=0
②或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成,左子树和右子树又分别是一颗二叉树

特点:
1、每一个结点至多只有两棵子树
2、左右子树不能颠倒(二叉树是有序树)

注意区别:度为2的有序树与二叉树的区别
度为2的树:肯定是非空树,所有结点的度<=2,至少有一个结点的度为2
二叉树:可以为空树,所有的结点只要<=2就可

特殊的二叉树

满二叉树

定义:一颗高度为n,且有 2ⁿ-1 个结点的二叉树
在这里插入图片描述

特点:
1、只有最后一层有叶子结点
2、不存在度为1的结点(只存在度为0或者2的结点)
3、按层序从1开始编号,结点 i 的左孩子为 2i,右孩子为 2i+1,结点 i 的父节点为 ⌊ i/2 ⌋;

完全二叉树

定义:当且仅当其每个结点都与高度为 h 的满二叉树中的编号为 1~n 的结点一一对应时,称为完全二叉树
在这里插入图片描述
特点:
1、只有最后两层有叶子结点
2、最多只有一个度为1的结点
3、按层序从1开始编号,结点 i 的左孩子为 2i,右孩子为 2i+1,结点 i 的父节点为 ⌊ i/2 ⌋;(如果有的话)
4、i <= ⌊ n/2 ⌋ 为分支结点,i > ⌊ n/2 ⌋ 为叶子结点

如果某结点只有一个孩子,那么这个孩子一定是左孩子

二叉排序树

定义:一颗二叉树或者空二叉树具有如下性质的称为二叉排序树
1、左子树上所有结点的关键字均小于根结点的关键字
2、右子树上所有结点的关键字均大于根结点的关键字
3、左子树和右子树又各是一棵二叉排序树

二叉排序树可用于元素的排序和搜索

平衡二叉树

定义:树上任一结点左子树右子树深度之差不超过1

平衡二叉树能有更高的搜索效率

二叉树的常考性质

考点一:设非空二叉树中度为0、1、2的结点个数分别为n0、n1、n2,则 n0 = n2 + 1; ( 叶子结点比二分支结点多一个 )

假设树中结点总数为n,则
① n = n0 + n1 +n2
② n = n1 + 2*n2 + 1 (树的结点数 = 总度数 + 1)
②-①得 n0 = n2 + 1

考点二:二叉树第 i 层至多有 2ⁱ⁻¹ 个结点(i>=1)
源于: m 叉树第 i 层至多有 mⁱ⁻¹ 个结点(i>=1)

考点三:高度为 n 的二叉树至多有 2ⁿ - 1 个结点(满二叉树)
源于: 高度为 n 的 m 叉树至多有 mⁿ - 1/m-1 个结点

完全二叉树的常考性质

在这里插入图片描述

常考考点2:对于完全二叉树,可以由结点数 n 推出度为0、1、2的结点个数为n0、n1、n2
完全二叉树最多只有一个度为1的结点,所以
n1 = 0或1
n0 = n2 + 1 ----> n0 + n2 = 2*n2 + 1 一定是奇数

若完全二叉树有 2k 个(偶数)个结点,则 n = n1 + n2 + n3,由上面得 n0 + n2 一定是奇数,所以只有 n1 = 1 时, n 才会是偶数
所以 n1 = 1,n0 = k,n2 = k-1

若完全二叉树有 2k-1 个(奇数)个结点,则 n = n1 + n2 + n3,由上面得 n0 + n2 一定是奇数,所以只有 n1 = 0 时, n 才会是奇数
所以 n1 = 0,n0 = k,n2 = k-1

二叉树的存储结构

顺序存储

定义一个长度为 MaxSize 的数组t,按照从上至下,从左至右的顺序依次存储完全二叉树中的各个结点

#define MaxSize 100
struct TreeNode{
	ElemType value;   // 结点中的数据元素
	bool isEmpty;     // 结点是否为空
}

// 初始化时所有结点标记为空
for(int i = 0; i < MaxSize; i++){
	t[i].isEmpty = true;
}

TreeNode t[MaxSize]

几个重要的常考的基本操作
i 的左孩子 ———— 2i
i 的右孩子 ———— 2i+1
i 的父节点 ———— ⌊ i/2 ⌋
i 所在的层次 ———— 在这里插入图片描述

若完全二叉树中共有n个结点,则
判断 i 是否有左孩子? ———— 2i <= n
判断 i 是否有右孩子? ———— 2i+1 <= n
判断 i 是否是叶子/分支结点? ———— i > ⌊ n/2 ⌋

注意:如果不是完全二叉树,依然按照层序将各个结点顺序存储,那么无法从结点编号反映出以上这些逻辑关系,所以一定要把二叉树的结点编号与完全二叉树对应起来,但是会导致出现很多空值的存储单元

最坏情况:高度为 n 且只有 n 个结点的单支树(所有结点只有右孩子),也至少需要 2ⁿ - 1 个存储单元

结论:二叉树的顺序存储结构,只适合存储完全二叉树

链式存储

typedef struct BiTNode{
	ElemType data;
	struct BiTNode *lchild,*rchild;  // 左右孩子指针
}BiTNode,*BiTree;

假设有n个结点,则有2n个指针域,除了根结点,每一个结点头上都会连接一个指针域,那么就有n-1个有值的指针域,得出就会有n+1个空指针域
n个结点的二叉链表共有n+1个空链域

根据实际需求决定要不要加父结点指针(三叉链表——方便找父结点)

typedef struct BiTNode{
	ElemType data;
	struct BiTNode *lchild,*rchild;  // 左右孩子指针
	struct BiTNode *parent;          // 父结点指针
}BiTNode,*BiTree;

二叉树的先中后序遍历

  1. 先序遍历:根左右
  2. 中序遍历:左根右
  3. 后序遍历:左右根

其中这三种遍历方式与前中后缀表达式的关系
先序遍历——>前缀表达式
中序遍历——>中缀表达式(需要加界限符)
后序遍历——>后缀表达式

先序遍历(空间复杂度:O(h))

void PreOrder(BiTree T){
	if(T!=NULL){
		visit(T);     //访问根结点
		PreOrder(T->lchild);   // 递归遍历左子树
		PreOrder(T->rchild);   // 递归遍历右子树
	}
}

中序遍历

void PreOrder(BiTree T){
	if(T!=NULL){
		PreOrder(T->lchild);   // 递归遍历左子树
		visit(T);     //访问根结点
		PreOrder(T->rchild);   // 递归遍历右子树
	}
}

后序遍历

void PreOrder(BiTree T){
	if(T!=NULL){
		PreOrder(T->lchild);   // 递归遍历左子树
		PreOrder(T->rchild);   // 递归遍历右子树
		visit(T);     //访问根结点
	}
}

应用

求树的深度

int treeDepth(BiTree T){
	if(T == NULL)
		return 0;
	else{
		int l = treeDepth(T->lchild);
		int r = treeDepth(T->rchild);
		// 树的深度=Max(左子树深度,右子树深度) + 1
		return l>r ? l+1 : r+1;
	}
}

二叉树的层序遍历

算法思想:
①初始化一个辅助队列
②根结点入队
③若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(如果有的话)
④重复③直至队列为空

// 二叉树的结点(链式存储)
typedef struct BiTNode{
	char data;
	struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

// 链式队列结点
typedef struct LinkNode{
	BiTNode *data;  // 存指针,而不是结点
	struct LinkNode *next;
}LinkNode;

typedef struct{
	LinkNode *front,*rear;  // 队头队尾
}LinkQueue;

// 层序遍历
void LevelOrder(BiTree T){
	LinkQueue(Q);
	InitQueue(Q);  // 初始化辅助队列
	BiTree p;
	EnQueue(Q,T);  // 将根结点入队
	while(!IsEmpty(Q)){
		DeQueue(Q,p);  // 队头结点出队
		visit(p);       // 访问出队结点
		if(p->lchild!=NULL)
			EnQueue(Q,(p->lchild);  // 左孩子入队
		if(p->rchild!=NULL)
			EnQueue(Q,(p->rchild);  // 右孩子入队
	}
}

由遍历序列构造二叉树

若只给出一颗二叉树的前中后序遍历序列中的一种,不能唯一确定一棵二叉树
能构造二叉树的遍历序列组合
1、前序+中序
2、后序+中序
3、层序+中序

线索二叉树

问题1:如何找到指定结点在中序遍历序列中的前驱?
问题2:如何找到结点的中序后继?
找前驱和后继很不方便,遍历操作必须从根开始

还记得之前学到的,n个结点的二叉树,有n+1个空链域,可以用来记录前驱、后继的信息

线索二叉树的存储结构

typedef struct ThreadNode{
	ElemType data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;    // 左右线索标志
}ThreadNode,*ThreadTree;
// tag=0 表示指针指向孩子
// tag=1 表示指针是线索

在这里插入图片描述

在这里插入图片描述

二叉树的线索化

中序线索化

// 线索二叉树结点
typedef struct ThreadNode{
	ElemType data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;    // 左右线索标志
}ThreadNode,*ThreadTree;

void visit(ThreadNode *q){
	if(q->lchild==NULL){ //左子树为空,建立前驱线索
		q->lchild=pre;
		q->ltag=1;
	}
	if(pre!=NULL&&pre->rchild==NULL){ //前驱结点的右孩子为空,建立前驱结点的后驱线索
		pre->rchild=q;
		pre->rtag=1;
	}
	pre=q;
}

//中序遍历二叉树,一边遍历一边线索化
void InThread(ThreadTree T){
	if(T!=NULL){
		InThread(T->lchild);  // 中序遍历左子树
		visit(T);             // 访问根结点
		InThread(T->rchild);  // 中序遍历右子树
	}
}

// 全局变量 pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;

// 中序线索化二叉树T
void CreateInThread(ThreadTree T){
	pre=NULL;   // pre初始化为NULL
	if(T!=NULL){
		InThread(T)  //中序线索化二叉树
		if(pre->rchild==NULL)
			pre->rtag=1;  // 处理遍历的最后一个点
	}
}

先序线索化(与中序遍历有一点不一样:为了防止重复指向,需要判断lchild不是前驱线索)

// 线索二叉树结点
typedef struct ThreadNode{
	ElemType data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;    // 左右线索标志
}ThreadNode,*ThreadTree;

void visit(ThreadNode *q){
	if(q->lchild==NULL){ //左子树为空,建立前驱线索
		q->lchild=pre;
		q->ltag=1;
	}
	if(pre!=NULL&&pre->rchild==NULL){ //前驱结点的右孩子为空,建立前驱结点的后驱线索
		pre->rchild=q;
		pre->rtag=1;
	}
	pre=q;
}

//先序遍历二叉树,一边遍历一边线索化
void PreThread(ThreadTree T){
	if(T!=NULL){
	    visit(T);             // 访问根结点
	    if(T->ltag==0){         // 与中序遍历不一样的地方,为了防止重复指向,需要判断lchild不是前驱线索
			PreThread(T->lchild);  // 与中序遍历不一样的地方,为了防止重复指向,需要判断lchild不是前驱线索
		PreThread(T->rchild);
	}
}

// 全局变量 pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;

// 先序线索化二叉树T
void CreateInThread(ThreadTree T){
	pre=NULL;   // pre初始化为NULL
	if(T!=NULL){
		InThread(T)  //先序线索化二叉树
		if(pre->rchild==NULL)
			pre->rtag=1;  // 处理遍历的最后一个点
	}
}

后序线索化(和中序差不多)

// 线索二叉树结点
typedef struct ThreadNode{
	ElemType data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;    // 左右线索标志
}ThreadNode,*ThreadTree;

void visit(ThreadNode *q){
	if(q->lchild==NULL){ //左子树为空,建立前驱线索
		q->lchild=pre;
		q->ltag=1;
	}
	if(pre!=NULL&&pre->rchild==NULL){ //前驱结点的右孩子为空,建立前驱结点的后驱线索
		pre->rchild=q;
		pre->rtag=1;
	}
	pre=q;
}

//后序遍历二叉树,一边遍历一边线索化
void InThread(ThreadTree T){
	if(T!=NULL){
		InThread(T->lchild);  // 后序遍历左子树
		InThread(T->rchild);  // 后序遍历右子树
		visit(T);             // 访问根结点
	}
}

// 全局变量 pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;

// 后序线索化二叉树T
void CreateInThread(ThreadTree T){
	pre=NULL;   // pre初始化为NULL
	if(T!=NULL){
		InThread(T)  //后序线索化二叉树
		if(pre->rchild==NULL)
			pre->rtag=1;  // 处理遍历的最后一个点
	}
}

二叉树的线索化

中序线索二叉树中找到指定结点*p的中序后继next
① 若 p->rtag=1, 则 next = p->rchild
② 若 p->rtag=0, 则 next = p的右子树中最左下结点

// 找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
	// 循环找到最左下结点(不一定是叶结点)
	while(p->ltag==0) p=p->lchild;
	return p;
}

// 在中序线索二叉树中找到结点p的后继结点
ThreadNode *Nextnode(ThreadNode *p){
	// 右子树下最左的结点
	if(p->rtag==0) return Firstnode(p->rchild);
	esle return p->rchild;   // rtag ==1 直接返回后继线索
}

// 对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)空间复杂O(1)
void Inorder(TreadNode *T){
	for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p)){
		visit(p);
	}
}

中序线索二叉树中找到指定结点*p的中序前驱pre
① 若 p->ltag=1, 则 pre= p->rchild
② 若 p->rlag=0, 则 pre= p的左子树中最右下结点

// 找到以p为根的子树中,最后一个被中序遍历的结点
ThreadNode *Lastnode(ThreadNode *p){
	// 循环找到最左下结点(不一定是叶结点)
	while(p->rtag==0) p=p->rchild;
	return p;
}

// 在中序线索二叉树中找到结点p的前驱结点
ThreadNode *Prenode(ThreadNode *p){
	// 左子树中最右下结点
	if(p->ltag==0) return Lastnode(p->lchild);
	esle return p->lchild;   // ltag ==1 直接返回前驱线索
}

// 对中序线索二叉树进行逆向中序遍历
void RevInorder(TreadNode *T){
	for(ThreadNode *p=Lastnode(T);p!=NULL;p=Prenode(p)){
		visit(p);
	}
}

在先序线索二叉树中找到指定结点*p的先序后继next
① 若 p->rtag=1, 则 next = p->rchild
② 若 p->rtag=0,
1、若p有左孩子,则先序后继为左孩子
2、若p没有左孩子,则先序后继为右孩子

在先序线索二叉树中找到指定结点*p的先序前驱pre
① 若 p->ltag=1, 则 next = p->lchild
② 若 p->ltag=0, 需要从头开始遍历

在后序线索二叉树中找到指定结点*p的后序前驱pre
① 若 p->ltag=1, 则 pre= p->lchild
② 若 p->ltag=0,
1、若p有右孩子,则后序前驱为右孩子
2、若p没有右孩子,则后序前驱为左孩子

在后序线索二叉树中找到指定结点*p的后序后继next
① 若 p->rtag=1, 则 next = p->rchild
② 若 p->rtag=0, 需要从头开始遍历

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/112554.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【k8s】pod进阶

一、资源限制 1、资源限制的概念 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小&#xff0c;以及其他类型的资源。 当为 Pod 中的容器指定了 request 资源时&#xff0c;调度器就使用该信息来决定将 Pod 调度到哪个节点上…

【高光谱与多光谱:空间-光谱双优化模型驱动】

A Spatial–Spectral Dual-Optimization Model-Driven Deep Network for Hyperspectral and Multispectral Image Fusion &#xff08;一种用于高光谱与多光谱图像融合的空间-光谱双优化模型驱动深度网络&#xff09; 深度学习&#xff0c;特别是卷积神经网络&#xff08;CNN…

【Java 进阶篇】Java Response 输出字符数据案例

在Java Web开发中&#xff0c;使用HTTP响应对象&#xff08;Response&#xff09;来向客户端发送数据是一项非常重要的任务。本篇博客将详细介绍如何使用Java中的Response对象来输出字符数据&#xff0c;并提供示例代码以帮助读者更好地理解和应用这一概念。不仅将讨论基础知识…

java 申请堆外内存吗? java如何使用堆外内存?

java 申请堆外内存吗&#xff1f; java如何使用堆外内存&#xff1f; Java堆外内存管理 JVM可以使用的内存分外2种&#xff1a;堆内存和堆外内存&#xff1a; 堆内存完全由JVM负责分配和释放&#xff0c;如果程序没有缺陷代码导致内存泄露&#xff0c;那么就不会遇到java.lan…

【5G PHY】5G SS/PBCH块介绍(二)

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

工业4G路由器桥接多网络,提升工业环境网络覆盖

一款专为工业环境应用所设计的物联网通讯设备“工业4G路由器”&#xff0c;它具有多种功能和特性。其中之一就是桥接功能&#xff0c;在工业领域中被广泛应用并起着重要的通信作用。 桥接功能是指工业4G路由器通过无线网络的方式&#xff0c;为不同的工业设备提供网络并将其连…

【Linux】jdk、tomcat、MySQL环境搭建的配置安装,Linux更改后端端口

一、作用 工具的组合为开发者和系统管理员提供了构建和运行Java应用程序以及存储和管理数据的完整环境。 JDK&#xff08;Java Development Kit&#xff09;&#xff1a;JDK是Java开发工具包&#xff0c;它提供了开发和运行Java应用程序所需的工具和库。通过安装JDK&#xff0c…

git教程(2)---远程仓库操作

git教程---远程仓库 远程操作创建远程仓库克隆远程仓库HTTPSSSH 向远程仓库推送拉取远程仓库.gitignore文件给git指令起别名IssuesPull Requests 标签管理操作标签推送标签 多人协作场景一场景二 开发模型Git分支设计规范使用Gitee的DevOps平台体验项目开发流程 远程操作 创建…

vue+element ui中的el-button自定义icon图标

实现 button的icon属性自定义一个图标名称&#xff0c;这个自定义的图标名称会默认添加到button下i标签的class上&#xff0c;我们只需要设置i标签的样式就可以了 ##3. 按钮上使用自定义的icon 完整代码 <div class"lookBtn"><el-button icon"el-icon-…

Web3时代:探索DAO的未来之路

Web3 的兴起不仅代表着技术进步&#xff0c;更是对人类协作、创新和价值塑造方式的一次重大思考。在 Web3 时代&#xff0c;社区不再仅仅是共同兴趣的聚集点&#xff0c;而变成了一个价值交流和创新的平台。 去中心化&#xff1a;超越技术的革命 去中心化不仅仅是 Web3 的技术…

go 语言介绍

背景 一直有在零散的时间用go写点代码&#xff0c;正好借着最近比较有时间写东西的契机&#xff0c;给这个看着年轻&#xff0c;实际也已经发展10几年&#xff0c;并在当下众多开发领域都有不可忽视作用的语言做个介绍吧 golang 的起点 golang 的诞生可以说是时代造就了它&a…

MolFormer分子预训练模型

Large-scale chemical language representations capture molecular structure and properties&#xff08;2022&#xff0c;NMI&#xff09; 和原本transformer encoder的不同&#xff1a; 采用linear attention mechanismrotary positional embedding 模型 transformer e…

arcgispro中机器学习部分

参考链接 arcgis.learn 模块 |ArcGIS API for Python arcgis包位置 安装路径\GeoScene\Pro\bin\Python\envs\arcgispro-py3\Lib\site-package\arcgis 以automl进行训练工具为例&#xff0c;工具导入模块中涉及机器学习的模块 该模块所在位置 安装路径\GeoScene\Pro\bin\Py…

VCS与XRUN对语法支持的不同点(持续更新...)

静态方法声明位置不同&#xff1a;VCS支持声明在class内/外&#xff08;extern&#xff09;两种方式&#xff0c;XRUN只支持static function声明于类内&#xff0c;不支持类外声明&#xff08;带extern关键字&#xff09;。 字符串转二进制、8进制、十进制、16进制方法&#xf…

回归预测 | Matlab实现SO-CNN-SVM蛇群算法优化卷积神经网络-支持向量机的多输入单输出回归预测

Matlab实现SO-CNN-SVM蛇群算法优化卷积神经网络-支持向量机的多输入单输出回归预测 目录 Matlab实现SO-CNN-SVM蛇群算法优化卷积神经网络-支持向量机的多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.SO-CNN-SVM蛇群算法优化卷积神经网络-支持向量…

javaEE -15( 13000字 JavaScript入门 - 2)

一&#xff1a;JavaScript(WebAPI) JS 分成三个大的部分 ECMAScript: 基础语法部分DOM API: 操作页面结构BOM API: 操作浏览器 WebAPI 就包含了 DOM BOM&#xff0c;这个是 W3C 组织规定的. (和制定 ECMAScript 标准的大佬们不是一伙人). 前面学的 JS 基础语法主要学的是 …

MySQL主从复制---一主一从配置过程

1、mysql版本一致且后台以服务运行 2、主从都配置在[mysqld]结点下&#xff0c;都是小写 3、主机修改my.ini配置文件 配置信息说明&#xff1a; 1、主服务器唯一ID server-id1 2、启用二进制日志 log-bin自己本地的路径/data/mysqlbin log-binD:/devSoft/MySQLServer5.5…

Maven第五章: 搭建maven私服以及如何进行管理?

Maven第五章&#xff1a; 搭建maven私服以及如何进行管理&#xff1f; 前言 nexus是什么&#xff1f; Nexus是Sonatype公司发布的一款仓库&#xff08;Repository&#xff09;管理软件&#xff0c;常用来搭建Maven私服&#xff0c;所以也有人将Nexus称为“Maven仓库管理器”…

高防CDN如何在防护cc上大显神通

高级防御CDN&#xff08;Content Delivery Network&#xff09;在对抗CC&#xff08;HTTP Flood&#xff09;攻击方面扮演着关键的角色&#xff0c;具备以下重要职能和作用&#xff1a; 流量分散&#xff1a;CC攻击的目标是通过大规模的HTTP请求使服务器过载&#xff0c;从而导…

python脚本-读取shadow关键信息并爆破密码

python脚本-读取shadow关键信息并爆破密码 代码 import crypt from colorama import Fore,Styledef crack():# 密码爆破函数定义with open(/root/top1000.txt) as f:# 此处更改密码字典for passwd in f:passwd2crypt.crypt(passwd.strip(),salt)if passwd2 passwd_hash:prin…