二叉树第k个大的节点
- 二叉树文章列表:
- 之前遇到过多个类型的题型但是都是针对数组这种数据结构,如果两篇:
数据结构与算法–最小的k个数
数据结构与算法–查找与排序另类用法-旋转数组中的最小数字
- 其中最小k个数我们用二分法的思路,很快得出解,还有就是用二叉堆的特性求解
- 在旋转数组中查找最小值,直接二分查找完成
- 由上可见,二分法在数组中查找第k个大小的数还是很好用的。但是我们此处针对的是二叉排序树,二分法可能排不上用处
方法一统计节点法
-
利用二叉排序数的特性,左节点 比 根小, 右节点比根大,那么需要找第 k 大的,直接统计左右节点个数
- 如果右边节点个数 rightCount > k,那么第k 大的必然在右节点
- 如果右节点个数 rightCount < k,那么有两种情况:
- 当rightCount < k, 并且rightCount - k = 1 此时,最大的就是当前根节点
- 如果rightCount < k,并且rigntCount- k > 1 此时,那么第 k 大的节点就是左节点的 k - rightCount - 1 个大的节点
-
我们依次筛选左右节点,直到找到k 的具体节点,或者父节点,将k范围缩小到1或 0
-
当k = 1 ,就是最右节点,当k = 0 就是最左节点。我们用如下图实例
-
情况一:正好是root节点情况
-
情况二:在left节点中
-
情况三,在right节点中
-
经如上分析有如下代码:
/**
* 二叉搜索树中查找第 k 大的节点
*
* @author liaojiamin
* @Date:Created in 10:18 2021/7/16
*/
public class FinMaxKNumberInBinary {
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 < 5; i++) {
node = searchTree.insert(random.nextInt(100), node);
}
searchTree.printTree(node);
System.out.println();
System.out.println(getMaxKNumber(node, 4).getElement());
}
/**
* 遍历统计,在比较
*/
public static BinaryNode getMaxKNumber(BinaryNode binaryNode, Integer k) {
if (binaryNode == null || k < 0) {
return null;
}
if (k == 1) {
BinaryNode right = binaryNode;
while (right.getRight() != null) {
right = right.getRight();
}
return right;
}
if (k == 0) {
BinaryNode left = binaryNode;
while (left.getLeft() != null) {
left = left.getLeft();
}
return left;
}
BinaryNode baseCount = new BinaryNode(null, null, null);
baseCount.setCount(0);
int rightCount = countNode(binaryNode.getRight(), baseCount).getCount();
//第k大的在right
if (rightCount >= k) {
return getMaxKNumber(binaryNode.getRight(), k);
}
//此时root节点是当前第 k大的数据
if (k - rightCount == 1) {
return binaryNode;
}
//第k大的在left
if (k - rightCount > 1) {
return getMaxKNumber(binaryNode.getLeft(), k - rightCount - 1);
}
return null;
}
public static BinaryNode countNode(BinaryNode binaryNode, BinaryNode baseCount) {
if (binaryNode == null) {
return baseCount;
}
baseCount.setCount(baseCount.getCount() + 1);
countNode(binaryNode.getLeft(), baseCount);
countNode(binaryNode.getRight(), baseCount);
return baseCount;
}
}
- 以上实现方案中通过递归不断将 第 k大的节点范围缩小,在最后的2个节点中找出我们的值,问题在于,存在太多重复的遍历,如上情况三种:
- 当遍历右子树 C的时候其实已经遍历过 G,F
- 但是在之后的步骤中还依然需要遍历G, F继续缩小范围,因此时间效率很低
方法二逆中序遍历
- 还是利用二叉搜索树的特性,我们需要找最大的第 k位置,但是在二叉树三种遍历方式中,前序,中序,后续遍历,只有中序遍历是按顺序排列二叉搜索树的所有节点,但是是小到大的顺序
- 由此我们得到启发:
- 我们利用中序遍历,求第k个大的,也就是从小到大排列的第 s - k+ 1 个数据,但是此时我们并不知道二叉树的总节点,无法得出这个值
- 如果我们反过来遍历,中序遍历是 左,中,右, 我们换成 右,根,左,那么直接求第k个位置的遍历到的节点就得到我们的解
- 因此最简单的遍历查找方式如下
/**
* 二叉搜索树中查找第 k 大的节点
*
* @author liaojiamin
* @Date:Created in 10:18 2021/7/16
*/
public class FinMaxKNumberInBinary {
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 < 5; i++) {
node = searchTree.insert(random.nextInt(100), node);
}
searchTree.printTree(node);
System.out.println();
System.out.println(getMaxKNumber(node, 4).getElement());
System.out.println(getMaxKNumberOver(node, 4).getElement());
}
/**
* 直接从最大的遍历,同时统计遍历节点数,当统计到k个,则是第k个大
*/
public static BinaryNode getMaxKNumberOver(BinaryNode binaryNode, Integer k) {
if (binaryNode == null || k < 0) {
return null;
}
BinaryNode baseCount = new BinaryNode(null, null, null);
baseCount.setCount(0);
return printOver(binaryNode, baseCount, k);
}
/**
* 右, 中, 左,方式遍历树,与之前树遍历三种都不同
*/
public static BinaryNode printOver(BinaryNode node, BinaryNode baseCount, Integer k) {
if(node == null){
return baseCount;
}
baseCount = printOver(node.getRight(), baseCount, k);
baseCount.setCount(baseCount.getCount() + 1);
if(baseCount.getCount() == k){
baseCount = node;
baseCount.setCount(k);
return baseCount;
}
return printOver(node.getLeft(), baseCount, k);
}
}