为什么线索化二叉树?

对于二叉树的遍历,我们知道每个节点的前驱与后继,但是这是建立在遍历的基础上,否则我们只知道后续的左右子树。现在我们充分利用二叉树左右子树的空节点,分别指向当前节点的前驱、后继,便于快速查找树的前驱后继。

不多说,直接上代码:

/// <summary>
/// 线索二叉树 节点
/// </summary>
/// <typeparam name="T"></typeparam>
public class ClueTreeNode<T>
{
/// <summary>
/// 内容
/// </summary>
public T data { get; set; }
/// <summary>
/// 左树
/// </summary>
public ClueTreeNode<T> leftNode { get; set; }
/// <summary>
/// 右树
/// </summary>
public ClueTreeNode<T> rightNode { get; set; }
/// <summary>
/// 0 标识左树 1 标识 当前节点的前驱
/// </summary>
public int leftTag { get; set; }
/// <summary>
/// 0标识右树 1 标识 当前节点的后继
/// </summary>
public int rightTag { get; set; } public ClueTreeNode()
{
data = default(T);
leftNode = null;
rightNode = null;
} public ClueTreeNode(T item)
{
data = item;
leftNode = null;
rightNode = null;
}
}
/// <summary>
/// 线索化 二叉树
///
/// 为什么线索化二叉树?
/// 第一:对于二叉树,如果有n个节点,每个节点有指向左右孩子的两个指针域,所以一共有2n个指针域。
/// 而n个节点的二叉树一共有n-1条分支线数,也就是说,其实是有 2n-(n-1) = n+1个空指针。
/// 这些空间不存储任何事物,白白浪费内存的资源。
/// 第二:对于二叉树的遍历,我们知道每个节点的前驱与后继,但是这是建立在遍历的基础上。
/// 否则我们只知道后续的左右子树。
/// 第三:对于二叉树来说,从结构上来说是单向链表,引入前驱后继后,线索化二叉树可以认为是双向链表。
/// </summary>
/// <typeparam name="T"></typeparam>
public class ClueBinaryTree<T>
{
/// <summary>
/// 树根节
/// </summary>
private ClueTreeNode<T> head { get; set; }
/// <summary>
/// 线索化时作为前驱转存
/// </summary>
private ClueTreeNode<T> preNode { get; set; } public ClueBinaryTree(){
head = new ClueTreeNode<T>();
}
public ClueBinaryTree(T val){
head = new ClueTreeNode<T>(val);
} public ClueTreeNode<T> GetRoot(){
return head;
} /// <summary>
/// 插入左节点
/// </summary>
/// <param name="val"></param>
/// <param name="node"></param>
/// <returns></returns>
public ClueTreeNode<T> AddLeftNode(T val, ClueTreeNode<T> node){
if (node == null)
throw new ArgumentNullException("参数错误");
ClueTreeNode<T> treeNode = new ClueTreeNode<T>(val);
ClueTreeNode<T> childNode = node.leftNode;
treeNode.leftNode = childNode;
node.leftNode = treeNode;
return treeNode;
} /// <summary>
/// 插入右节点
/// </summary>
/// <param name="val"></param>
/// <param name="node"></param>
/// <returns></returns>
public ClueTreeNode<T> AddRightNode(T val, ClueTreeNode<T> node){
if (node == null)
throw new ArgumentNullException("参数错误");
ClueTreeNode<T> treeNode = new ClueTreeNode<T>(val);
ClueTreeNode<T> childNode = node.rightNode;
treeNode.rightNode = childNode;
node.rightNode = treeNode;
return treeNode;
}
/// <summary>
/// 删除当前节点的 左节点
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public ClueTreeNode<T> DeleteLeftNode(ClueTreeNode<T> node){
if (node == null || node.leftNode == null)
throw new ArgumentNullException("参数错误");
ClueTreeNode<T> leftChild = node.leftNode;
node.leftNode = null;
return leftChild;
} /// <summary>
/// 删除当前节点的 右节点
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public ClueTreeNode<T> DeleteRightNode(ClueTreeNode<T> node){
if (node == null || node.rightNode == null)
throw new ArgumentNullException("参数错误");
ClueTreeNode<T> rightChild = node.rightNode;
node.rightNode = null;
return rightChild;
} /// <summary>
/// 中序遍历线索化二叉树
/// </summary>
public void MiddlePrefaceTraversal(){
ClueTreeNode<T> node = head;
while (node != null)
{
//判断是否是
while (node.leftTag == 0)
{
node = node.leftNode;
}
Console.Write($" {node.data}");
while (node.rightTag == 1)
{
node = node.rightNode;
Console.Write($" {node.data}");
}
node = node.rightNode;
}
}
/// <summary>
/// 线索化二叉树
/// </summary>
/// <param name="node"></param>
public void MiddleClueNodes(ClueTreeNode<T> node){
if (node == null)
{
return;
}
//线索化左子树
MiddleClueNodes(node.leftNode);
//当左树为空时,指向前驱,标识为 1
if (node.leftNode == null)
{
node.leftNode = preNode;
node.leftTag = 1;
}
//如果 前驱的右树不为空
if (preNode != null && preNode.rightNode == null)
{
preNode.rightNode = node;
preNode.rightTag = 1;
}
preNode = node;
//线索化右子树
MiddleClueNodes(node.rightNode);
}
}

现在我们测试:

//创建树
ClueBinaryTree<string> clueBinaryTree = new ClueBinaryTree<string>("A");
ClueTreeNode<string> tree1 = clueBinaryTree.AddLeftNode("B", clueBinaryTree.GetRoot());
ClueTreeNode<string> tree2 = clueBinaryTree.AddRightNode("C", clueBinaryTree.GetRoot());
ClueTreeNode<string> tree3 = clueBinaryTree.AddLeftNode("D", tree1);
clueBinaryTree.AddRightNode("E", tree1);
clueBinaryTree.AddLeftNode("F", tree2);
clueBinaryTree.AddRightNode("G", tree2); clueBinaryTree.MiddleClueNodes(clueBinaryTree.GetRoot()); Console.Write("中序遍历");
clueBinaryTree.MiddlePrefaceTraversal();

打印结果:

中序遍历 D B E A F C G

C#数据结构-线索化二叉树的更多相关文章

  1. 线索化二叉树的构建与先序,中序遍历(C++版)

    贴出学习C++数据结构线索化二叉树的过程, 方便和我一样的新手进行测试和学习 同时欢迎各位大神纠正. 不同与普通二叉树的地方会用背景色填充 //BinTreeNode_Thr.h enum Point ...

  2. 数据结构与算法---线索化二叉树(Threaded BinaryTree)

    先看一个问题 将数列 {1, 3, 6, 8, 10, 14  } 构建成一颗二叉树 问题分析: 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 } 但是 6, 8 ...

  3. 后序线索化二叉树(Java版)

    前面介绍了前序线索化二叉树.中序线索化二叉树,本文将介绍后序线索化二叉树.之所以用单独的一篇文章来分析后序线索化二叉树,是因为后序线索化二叉树比前序.中序要复杂一些:另外在复习线索化二叉树的过程中,大 ...

  4. JAVA递归实现线索化二叉树

    JAVA递归实现线索化二叉树 基础理论 首先,二叉树递归遍历分为先序遍历.中序遍历和后序遍历. 先序遍历为:根节点+左子树+右子树 中序遍历为:左子树+根节点+右子树 后序遍历为:左子树+右子树+根节 ...

  5. YTU 3026: 中序线索化二叉树

    原文链接:https://www.dreamwings.cn/ytu3026/2896.html 3026: 中序线索化二叉树 时间限制: 1 Sec  内存限制: 128 MB 提交: 9  解决: ...

  6. 图解前序遍历线索化二叉树,前序线索二叉树遍历,C\C++描述

    body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...

  7. 图解中序遍历线索化二叉树,中序线索二叉树遍历,C\C++描述

    body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...

  8. 数据结构丨N叉树

    遍历 N叉树的遍历 树的遍历 一棵二叉树可以按照前序.中序.后序或者层序来进行遍历.在这些遍历方法中,前序遍历.后序遍历和层序遍历同样可以运用到N叉树中. 回顾 - 二叉树的遍历 前序遍历 - 首先访 ...

  9. js:数据结构笔记9--二叉树

    树:以分层的方式存储数据:节点:根节点,子节点,父节点,叶子节点(没有任何子节点的节点):层:根节点开始0层: 二叉树:每个节点子节点不超过两个:查找快(比链表),添加,删除快(比数组): BST:二 ...

随机推荐

  1. Mysql数据安全备份

    数据安全备份的意义 在出现意外的时候(硬盘损坏.断点.黑客攻击),以便数据的恢复 导出生产的数据以便研发人员或者测试人员测试学习 高权限的人员那操作失误导致数据丢失,以便恢复 备份类型 完全备份:对整 ...

  2. SQL注入学习-Dnslog盲注

    1.基础知识 1.DNS DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 ...

  3. tp5 上传视频方法

    控制器调用 /** * 视频上传 */ public function video_add(){ if (request()->isPost()){ $video = $_FILES['vide ...

  4. windows下mysql的远程访问和权限设置

    如果想要用户root可以远程登录,则可通过修改user表中root用户对应的host字段值为"%"即可.我们用以下语句进行修改: update user set host = '% ...

  5. FL Studio 插件使用技巧——Fruity Reeverb 2 (上)

    许多学习FL的用户会发现,自己在听大师的电子音乐作品时都能感受到他们的音乐有一股强大的空间感,有时还能感知到深邃的意境.不少人会因此而疑惑:为什么出自我们之手的音乐就没有这样的效果呢?我们的音乐里到底 ...

  6. 思维导图软件iMindMap怎么使用

    人人都说,思维导图记忆法实用.可是,我们应该如何使用思维导图呢?又该如何从思维小白摇身一变成为逻辑大神呢?俗话说,心急吃不了热豆腐,让我们一步一步来,慢慢接触使用思维导图吧. 小编作为"过来 ...

  7. influxdb的基本使用

    influxDB名词 database:数据库: measurement:数据库中的表: points:表里面的一行数据. influxDB中独有的一些概念 Point由时间戳(time).数据(fi ...

  8. kubelet CPU 使用率过高问题排查

    kubelet CPU 使用率过高问题排查 问题背景 客户的k8s集群环境,发现所有的worker节点的kubelet进程的CPU使用率长时间占用过高,通过pidstat可以看到CPU使用率高达100 ...

  9. sentinel整合dubbo

    <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-dubbo-a ...

  10. uni-app 封装接口request请求

    我们知道一个项目中对于前期架构的搭建工作对于后期的制作有多么重要,所以不管做什么项目我们拿到需求后一定要认真的分析一下,要和产品以及后台沟通好,其中尤为重要的一个环节莫过于封装接口请求了.因为前期封装 ...