死磕二叉树

  • 近一年都比较关注算法相关的知识,也刷了不少题,之前的文章中大多也是算法相关的文章,但是感觉每次遇到树相关的题型都不能应对自如,因此还是有必要在相关知识上下功夫,因此有此次总结,以下是所有树相关的文章

数据结构与算法–面试必问AVL树原理及实现

数据结构与算法–二叉树的深度问题

数据结构与算法–二叉堆(最大堆,最小堆)实现及原理

数据结构与算法–二叉查找树转顺序排列双向链表

数据结构与算法-- 二叉树中和为某一值的路径

数据结构与算法-- 二叉树后续遍历序列校验

数据结构与算法-- 广度优先打印二叉树

数据结构与算法–解决问题的方法- 二叉树的的镜像

数据结构与算法–重建二叉树

数据结构与算法–二叉查找树实现原理

数据结构与算法–二叉树实现原理

数据结构与算法–B树原理及实现

数据结构与算法–数字在排序数组中出现次数

数据结构与算法–死磕二叉树

数据结构与算法–二叉树第k个大的节点

  • 本文中树,二叉树的实现都用自己的实现方式,在以上列举的最后两篇文中有详细的说明

  • 二叉树,或者是说树经常用于大量的输入数据的场景下。大部分的操作运行时间平均是O(logN)。

  • 二叉树的变种题型多如牛毛,还是要掌握方法,多看不同题型,训练知识迁移的能力,如下题:

题目:输入一棵二叉树和他的两个节点,求他们的最低公共祖先。

  • 最低公共祖先的定义:给定一个有根树T 时候,对于任意两个节点 U, V,找到一个离根最远的节点X, 使得X同时 是U , V 的祖先,那么X便是 U,V的最近公共祖先。

最简模式

  • 上题中并没有明显给出树的特性,只是强调了一棵二叉树,那么我们用二叉搜索树为案例来分析如下:

    • 需要找到最低公共祖先,也就是找父节点,二叉树节点的特性,父节点比左节点大,比右节点小
    • 那么会有几种情况,如果两个节点分布在多个分支,那么我们需要找的父节点大小必然介于 V,U之间,情况一
    • 如果二叉树是一个单链,那么我们需要找到 V,U的其中一个父节点,此时改父节点要不不UV都打,要么比UV都小,情况二
    • 用如下图表示:

  • 如上所示的一颗二叉搜索树,当输入的是6, 8 时候,公共祖先就是7 ,介于6, 8 之间

  • 如果输入的是3, 4,公共祖先就是2, 比3,4 都要小,或者反过来都在左子树,那么比输入值都大

  • 经过如上分析,那么我们直接中序遍历树,每次得到节点与输入节点比较,如果介于 UV之间,则返回得到我们需要的节点

  • 如果写范问节点同时大于U,V,并且是U,或者V的父节点,那么该节点也是我们需要的节点

  • 如上分析有如下代码:

/*** 输入两个树的节点node1, node2,找到他们最低公共祖先.* 最近公共祖先的定义为:对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。** @author liaojiamin* @Date:Created in 16:31 2021/7/9*/
public class FindCommonNode {public static void main(String[] args) {BinaryNode node = new BinaryNode(null, null, null);BinarySearchTree searchTree = new BinarySearchTree();Random random = new Random();for (int i = 0; i < 10; i++) {node = searchTree.insert(random.nextInt(100), node);}BinaryNode node1 = new BinaryNode(29, null, null);node = searchTree.insert(node1, node);BinaryNode node2 = new BinaryNode(45, null, null);node = searchTree.insert(node2, node);BinaryNode result = findBinarySearchTree(node, node1, node2);System.out.println(result.getElement());}/*** 二叉排序树解法*/public static BinaryNode findBinarySearchTree(BinaryNode tree, BinaryNode node1, BinaryNode node2) {if (tree == null || node1 == null || node2 == null) {return null;}// node1< tree < node2if (node1.compareTo(node2) < 0 && tree.compareTo(node1) > 0 && tree.compareTo(node2) < 0) {return tree;}// node2< tree < node1if (node1.compareTo(node2) > 0 && tree.compareTo(node1) < 0 && tree.compareTo(node2) > 0) {return tree;}if (tree.compareTo(node1) > 0 & tree.compareTo(node2) > 0) {if (tree.getLeft() == node1 || tree.getLeft() == node2) {return tree;}return findBinarySearchTree(tree.getLeft(), node1, node2);}if (tree.compareTo(node1) < 0 & tree.compareTo(node2) < 0) {if (tree.getRight() == node1 || tree.getRight() == node2) {return tree;}return findBinarySearchTree(tree.getRight(), node1, node2);}return null;}
}

困难模式

  • 如果不是二叉排序树,只是一个普通的树或者二叉树,并且树中没有指向父节点的指针

  • 分析如下:

    • 不能用比较的方式找父节点,那么用遍历,两个节点都出现在某个节点的子节点,或者直接在某个节点下,如情况一:

  • 6, 8 都出现在了7 节点下,但是同时也都出现在了5 节点的子节点下

  • 我们需要求解的是最低公共祖先,那么离根节点越远的父节点才是我们需要求解的

  • 我们可以从根遍历一棵树,每次遍历一个节点,判断输入节点是否在他子树中

  • 如果在子树中,则分别遍历他所有子节点,并判断两个输入节点是否他们子树中,

  • 这样从上到下遍历,直到找到这样一个节点,他自己的子树中同时包含两个输入的节点,但是他的任何一个子节点都不会同时拥有这两个节点,那么这就是公共祖先

  • 我们举例说明,如上图:

    • 第一种情况,当输入的是如上图中65, 26 时候,还是中序遍历,
    • 根节点中判断是否存在有两个节点,存在与否的判断依然是用递归,如果存在,标记U,V中count=1即可
    • 遍历21 ,依然存在,在遍历24,依然存在,接着65 不存在,26 不存在,说明 24 是我们需要求解的值
    • 第二中情况,当输入的是一个单链上的 32,77 时候,判断就稍微不同
    • 依然中序遍历当遍历到21 时候,我们判断都在右子树中,接着遍历31
    • 此时有不同情况,如果依然继续遍历判断,我们会直接干到 32,发现都存在,到77,发现不存在,那么返回的是32
    • 显然不是我们需要的,此处我们需要判断31 的子节点是否是输入节点,如果是,那么当前节点就是需要求解的
  • 综上也就三种情况,记录 validateLeft为都存在left中, validateRight为都存在right中

    • 当遍历到节点 N, 发现validateLeft = false && validateRight = false,说明一个在左,一个在右,那么得到解 N
    • 当遍历到N 发现validateLeft = true,此时判断 N 的left节点是否是输入节点,如果是,那么N就是我们求解的,否则我们就遍历N的left节点
    • 剩下的就是N 的validateRight = true情况,还是一样,判断right节点是否是输入节点,那么N就是我们求解,否则我们遍历N的right节点
  • 经如上分析有如下代码:

/*** 输入两个树的节点node1, node2,找到他们最低公共祖先.* 最近公共祖先的定义为:对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。** @author liaojiamin* @Date:Created in 16:31 2021/7/9*/
public class FindCommonNode {public static void main(String[] args) {BinaryNode node = new BinaryNode(null, null, null);BinarySearchTree searchTree = new BinarySearchTree();Random random = new Random();for (int i = 0; i < 10; i++) {node = searchTree.insert(random.nextInt(100), node);}BinaryNode node1 = new BinaryNode(29, null, null);node = searchTree.insert(node1, node);BinaryNode node2 = new BinaryNode(45, null, null);node = searchTree.insert(node2, node);BinaryNode result2 = findBinaryTree(node, node1, node2);System.out.println(result2.getElement());}/*** 非二叉排序树*/public static BinaryNode findBinaryTree(BinaryNode tree, BinaryNode node1, BinaryNode node2) {if (tree == null || node1 == null || node2 == null) {return null;}//递归判断修改状态,所以每次都先初始化数量为0node1.setCount(0);node2.setCount(0);boolean left = validateNode(tree.getLeft(), node1, node2);node1.setCount(0);node2.setCount(0);boolean right = left ? false : validateNode(tree.getRight(), node1, node2);//情况一if (!left && !right) {return tree;}//情况二if (left) {//特殊情况二叉树为单条链的情况if (tree.getLeft() == node1 || tree.getLeft() == node2) {return tree;}return findBinaryTree(tree.getLeft(), node1, node2);}//情况三if (right) {if (tree.getRight() == node1 || tree.getRight() == node2) {return tree;}return findBinaryTree(tree.getRight(), node1, node2);}return null;}/*** 判断节点是否在二叉树中*/public static boolean validateNode(BinaryNode tree, BinaryNode node1, BinaryNode node2) {if (tree == null) {return false;}if (tree.compareTo(node1) == 0) {node1.setCount(2);}if (tree.compareTo(node2) == 0) {node2.setCount(2);}if (node1.getCount() == 2 && node2.getCount() == 2) {return true;}boolean leftIn = validateNode(tree.getLeft(), node1, node2);boolean rightIn = validateNode(tree.getRight(), node1, node2);return leftIn || rightIn;}
}

地狱模式

  • 在以上方案中,当输入是65, 26 时候,在判断完都在13节点下时候,我们其实已经遍历过21, 24 节点了,但是在之后的遍历中,我们任然需要在遍历21, 24,这种思路会出现很多重复的遍历,更快速的解决方案还是有的

  • 之前文章数据结构与算法–两个链表中第一个公共节点给我们启发,如下图

  • 上图其实就是一颗二叉树,只不过是斜的,之前用双指针,求第一个公共节点,或者用栈空间求第一个相同的节点接口

  • 受以上启发,如果我们将两个输入节点U, V 的范问路径分别放到两个链表中,不就将二叉树的问题转为以上链表的问题。

  • 还是如上图
    分析如下:

    • 还是先根遍历,当遍历到13 节点,我在13节点对象中定义一个链表,用来存放已经走过的路径,也就是 父节点路径+ 本子节点,得到本节点路径,
    • 那么遍历13,将13 添加进去
    • 遍历21,将21 添加到路径 得到 13 ->21
    • 遍历24,将24 添加到路径 得到 13 ->21 ->24
    • 遍历65,将65 添加到路径 得到 13 ->21 ->24 ->65
    • 遍历26 ,将26 添加到路径 得到 13 ->21 ->24 ->26
    • 此时两个节点都已经完成路径的查询,直接返回,接着分析两个链表
    • 此处我们需要求的是最后一个公共节点,那么我们用栈,分别将两个链表导入两个栈,接着依次导出,求第一个非输入节点,并且相同的节点 得到我们的解,
  • 如上分析有如下代码


/*** 输入两个树的节点node1, node2,找到他们最低公共祖先.* 最近公共祖先的定义为:对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。** @author liaojiamin* @Date:Created in 16:31 2021/7/9*/
public class FindCommonNode {public static void main(String[] args) {BinaryNode node = new BinaryNode(null, null, null);BinarySearchTree searchTree = new BinarySearchTree();Random random = new Random();for (int i = 0; i < 10; i++) {node = searchTree.insert(random.nextInt(100), node);}BinaryNode node1 = new BinaryNode(29, null, null);node = searchTree.insert(node1, node);BinaryNode node2 = new BinaryNode(45, null, null);node = searchTree.insert(node2, node);BinaryNode result = findBinarySearchTree(node, node1, node2);System.out.println(result.getElement());BinaryNode result2 = findBinaryTree(node, node1, node2);System.out.println(result2.getElement());BinaryNode result3 = buildBinaryLink(node, node1, node2);System.out.println(result3.getElement());}/*** 构造两个单向链表*/public static BinaryNode buildBinaryLink(BinaryNode tree, BinaryNode node1, BinaryNode node2) {if (tree == null || node1 == null || node2 == null) {return null;}buildListNode(tree, node1, node2);ListNode node1List = node1.getLinkedList();ListNode node2List = node2.getLinkedList();MyStack<BinaryNode> stack1 = new MyStack<>();MyStack<BinaryNode> stack2 = new MyStack<>();while (node1List != null) {if (node1List.getBinaryNode() != null) {stack1.push(node1List.getBinaryNode());}node1List = node1List.getNext();}while (node2List != null) {if (node2List.getBinaryNode() != null) {stack2.push(node2List.getBinaryNode());}node2List = node2List.getNext();}//去掉node1, node2 节点,可能出现单链情况的树,也就是node1, node2,同时出现在一个链中BinaryNode stackNode1 = stack1.pop();while (!stack1.isEmpty() && (stackNode1.compareTo(node1) == 0 || stackNode1.compareTo(node2) == 0)) {stackNode1 = stack1.pop();}BinaryNode stackNode2 = stack2.pop();while (!stack2.isEmpty() && (stackNode2.compareTo(node1) == 0 || stackNode2.compareTo(node2) == 0)){stackNode2 = stack2.pop();}do {if(stackNode1.compareTo(stackNode2) == 0){return stackNode1;}if(stack1.size() > stack2.size() && !stack1.isEmpty()){stackNode1 = stack1.pop();}else if(stack1.size() < stack2.size() && !stack2.isEmpty()){stackNode2 = stack2.pop();}else if(!stack2.isEmpty() && !stack1.isEmpty()){stackNode1 = stack1.pop();stackNode2 = stack2.pop();}else {return  null;}}while (true);}/*** 构造节点路径* */public static void buildListNode(BinaryNode tree, BinaryNode node1, BinaryNode node2) {if (tree == null) {return;}//初始化根节点路径if (tree.getLinkedList() == null) {ListNode treeList = new ListNode(tree);tree.setLinkedList(treeList);}if (tree.getLeft() != null) {//将父节点路径复制到子节点ListNode leftList = new ListNode();ListNode header = tree.getLinkedList();while (header != null) {ListNode newNode = new ListNode(header.getBinaryNode());MyLinkedList.addToTail(leftList, newNode);header = header.getNext();}//添加子节点本身,得到节点最终路径MyLinkedList.addToTail(leftList, new ListNode(tree.getLeft()));tree.getLeft().setLinkedList(leftList);}if (tree.getRight() != null) {//将父节点路径复制到子节点ListNode rightList = new ListNode();ListNode header = tree.getLinkedList();while (header != null) {ListNode newNode = new ListNode(header.getBinaryNode());MyLinkedList.addToTail(rightList, newNode);header = header.getNext();}//添加子节点本身,得到节点最终路径MyLinkedList.addToTail(rightList, new ListNode(tree.getRight()));tree.getRight().setLinkedList(rightList);}//当输入节点路径都不为空,则表示已经查找完毕if (node1.getLinkedList() != null && node2.getLinkedList() != null) {return;}buildListNode(tree.getLeft(), node1, node2);buildListNode(tree.getRight(), node1, node2);}
}
  • 时间复杂度分析,因为从开始到输入的两个节点的路径,只需要依次遍历,每次遍历复杂度是O(n),但是每个节点的路径负责还需要额外的开销,每个节点路径其实就是二叉树的深度 O(logn) 那么最终的世界复杂度是O(n)

  • 空间复杂度此处我们用额额外的链表存储路径,并且在分析链表时候用来额外的栈空间,链表只需要存储路径上的节点,也就是深度,那么空间复杂度O(logn),栈同样,O(logn)

  • 今天的代码分享就到这,之后还会有更多的练习,最后给一张神图

上一篇:数据结构与算法–这个需求很简单怎么实现我不管(发散思维)
下一篇:数据结构与算法–再来聊聊数组

数据结构与算法--死磕二叉树相关推荐

  1. 数据结构与算法-- 广度优先打印二叉树

    广度优先打印二叉树 题目:从上往下打印出二叉树的每一个节点,同一层节点按照从左到右顺序打印,例如下图中二叉树,依次打印出是8,6,10,5,7,9,11 如上题中二叉树的节点定义我们用之前文章 二叉树 ...

  2. 数据结构与算法——树和二叉树***

    第五章 :树和二叉树 树和图是两种重要的非线性结构.线性结构中结点具有唯一前驱和唯一后继的关系,而非线性结构中结点之间的关系不再具有这种唯一性.其中,树形结构中结点间的关系是前驱唯一而后继不唯一,即元 ...

  3. 【数据结构与算法基础】二叉树

    写在前面 上面一篇介绍了简单的线性的数据结构浅入浅出数据结构(二)堆栈与队列 这一篇研究一些复杂的数据结构:树和二叉树. 1.二叉树简介 二叉树是一种最简单的树形结构,其特点是树中每个结点至多关联到两 ...

  4. 【数据结构与算法基础】二叉树与其遍历序列的互化 附代码实现(C和java)

    前言 数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷. 也因如此,它作为博主大二上学期最重 ...

  5. Python__数据结构与算法——树、二叉树(实现先、中、后序遍历)

    目录 一.树 二.二叉树 树和前面所讲的表.堆栈和队列等这些线性数据结构不同,树不是线性的.在处理较多数据时,使用线性结构较慢,而使用树结构则可以提高处理速度.不过,相对于线性的表.堆栈和队列等线性数 ...

  6. 数据结构与算法 | 树与二叉树

    树的概念 二叉树的概念 树的概念和结构 树是一种非线性的数据结构,它是由n个有限结点组成一个具有层次关系的集合,把它叫做树是因为它看起来像一棵倒挂的树,如图所示 有一个特殊的结点,称为根节点,根节点没 ...

  7. [数据结构与算法综合实验]二叉树与哈夫曼图片压缩

    文章目录 一.实验要求 二.效果展示 三.源码 3.1.Compress.cpp 3.2.Compress.h 3.3.global.h 3.4.Huffman.cpp 3.5.Huffman.h 3 ...

  8. 常考数据结构与算法----给定一个二叉树和一个值 sum,请找出所有的根节点到叶子节点的节点值之和等于sum 的路径,

    题目描述 给定一个二叉树和一个值sum,请找出所有的根节点到叶子节点的节点值之和等于sum 的路径, 例如: 给出如下的二叉树,sum=22, 返回 [ [5,4,11,2], [5,8,9] ] 示 ...

  9. 数据结构与算法——树与二叉树详细分享

    一.树 1.定义:由n个有限节点组成一个具有层次关系的集合,看起来像一颗倒挂的树,特点: 2.特点: a.每个节点有0个或多个子节点 b.没有父节点的节点称为根节点(A) c.每一个非根节点有且只有一 ...

最新文章

  1. 98%的人没解出的德国面试逻辑题(离散数学篇)!?
  2. java-多线程安全-锁
  3. sed与awk命令小结
  4. 使用junit进行单元测试_使用JUnit对ADF应用程序进行单元测试
  5. ad如何选中当前层上的器件_82条AD转换设计经验总结!
  6. 《互联网理财一册通》一一第1章 做好互联网理财前的准备工作
  7. 什么是win10嵌入式安装Linux,Windows10自带Linux系统(WSL)安装过程
  8. ibatis结果集resultClass的几种类型
  9. MFC使用SaveAs函数保存Excel文件时,弹出“文件已存在”问题
  10. 黑客教父郭盛华:11个IDA Pro反汇编程序的替代品
  11. Windows下在MSDos窗口下打开指定目录
  12. 闵行区电动自行车上牌地址
  13. 移动应用中的第三方SDK隐私合规检测
  14. [Android]APP中保持屏幕不自动灭屏的一种方法
  15. Java 计算奇数偶数
  16. 上海国际能源交易中心大户持仓报告制度 操作指南
  17. 马来西亚SIRIM认证
  18. linux 问题-——退出vi编辑器 wq失效
  19. 【推荐系统】特征工程(1)
  20. 公开课:如何做一个合格的网络编辑

热门文章

  1. Android之context相关类图
  2. Git之如何解决Error:pathspec ‘/layout/radar_chart.xml‘ did not match any file(s) known to
  3. python获取键盘事件_50-用Python监听鼠标和键盘事件
  4. 从时速100公里行驶的车上向后发射时速100公里的棒球,会发生什么?
  5. 她,既是一个风华绝代的演员,更是WiFi之母...
  6. 这相册一出手,哪个长辈搞不定?
  7. 你家猫砸东西是不是也专挑贵的砸?
  8. 北方人的快乐。。。| 今日最佳
  9. 收藏 | 分享 3 种脑洞大开的Excel技巧
  10. python join_python join 和 split的常用使用方法