数据结构与算法–面试必问AVL树原理及实现
数据结构与算法–二叉树的深度问题
数据结构与算法–二叉堆(最大堆,最小堆)实现及原理
数据结构与算法–二叉查找树转顺序排列双向链表
数据结构与算法-- 二叉树中和为某一值的路径
数据结构与算法-- 二叉树后续遍历序列校验
数据结构与算法-- 广度优先打印二叉树
数据结构与算法–解决问题的方法- 二叉树的的镜像
数据结构与算法–重建二叉树
数据结构与算法–二叉查找树实现原理
数据结构与算法–二叉树实现原理
数据结构与算法–B树原理及实现
数据结构与算法–数字在排序数组中出现次数
数据结构与算法–死磕二叉树
数据结构与算法–二叉树第k个大的节点
二叉树,或者是说树经常用于大量的输入数据的场景下。大部分的操作运行时间平均是O(logN)。
二叉树的变种题型多如牛毛,还是要掌握方法,多看不同题型,训练知识迁移的能力,如下题:
如上所示的一颗二叉搜索树,当输入的是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 < node2
if (node1.compareTo(node2) < 0 && tree.compareTo(node1) > 0 && tree.compareTo(node2) < 0) {
return tree;
}
// node2< tree < node1
if (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 节点的子节点下
我们需要求解的是最低公共祖先,那么离根节点越远的父节点才是我们需要求解的
我们可以从根遍历一棵树,每次遍历一个节点,判断输入节点是否在他子树中
如果在子树中,则分别遍历他所有子节点,并判断两个输入节点是否他们子树中,
这样从上到下遍历,直到找到这样一个节点,他自己的子树中同时包含两个输入的节点,但是他的任何一个子节点都不会同时拥有这两个节点,那么这就是公共祖先
我们举例说明,如上图:
综上也就三种情况,记录 validateLeft为都存在left中, validateRight为都存在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;
}
//递归判断修改状态,所以每次都先初始化数量为0
node1.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 的范问路径分别放到两个链表中,不就将二叉树的问题转为以上链表的问题。
如上分析有如下代码
/**
* 输入两个树的节点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)
今天的代码分享就到这,之后还会有更多的练习,最后给一张神图
上一篇:数据结构与算法–这个需求很简单怎么实现我不管(发散思维)
下一篇:数据结构与算法–再来聊聊数组