【发布时间】:2011-07-25 05:03:56
【问题描述】:
给定 BST 中的一个节点,如何找到下一个更高的键?
【问题讨论】:
-
if (node->right) return min_tree(node->right);如果节点没有右子树怎么办?
标签: algorithm data-structures tree binary-search-tree
给定 BST 中的一个节点,如何找到下一个更高的键?
【问题讨论】:
if (node->right) return min_tree(node->right);如果节点没有右子树怎么办?
标签: algorithm data-structures tree binary-search-tree
一般来说,使用order statistic tree 可以在 O(log n) 时间内完成对树中第 n 个最小(顺序统计)元素的查询。一种实现是在GNU's C++ extensions: Policy-Based Data Structures 中。用法见this CodeForces blog。
实现这一点的方法是简单地在自平衡树的每个节点上存储一个附加值,即以该节点为根的子树的大小。然后操作Select(i)找到第i个最小元素和Rank(x)找到索引使得x是第i个最小元素可以以对数时间复杂度实现;请参阅 CLRS 或 Wikipedia 页面。那么查询就是Select(Rank(x)+1)。
【讨论】:
好吧,我会试一试,因为每个人都在发布他们的实现。该技术来自《算法简介》一书。基本上,您需要一种从子节点回溯到父节点的方法。
首先,你需要一个类来表示节点:
public class TreeNode<V extends Comparable<V>> {
TreeNode<V> parent;
TreeNode<V> left;
TreeNode<V> right;
V data;
public TreeNode(TreeNode<V> parent, V data) {
this.parent = parent;
this.data = data;
}
public void insert(TreeNode<V> parent, V data) {
if (data.compareTo(this.data) < 0) {
if (left == null) {
left = new TreeNode<>(parent, data);
} else {
left.insert(left, data);
}
} else if (data.compareTo(this.data) > 0) {
if (right == null) {
right = new TreeNode<>(parent, data);
} else {
right.insert(right, data);
}
}
// ignore duplicates
}
@Override
public String toString() {
return data + " -> [parent: " + (parent != null ? parent.data : null) + "]";
}
}
你可以有另一个类来运行操作:
public class BinarySearchTree<E extends Comparable<E>> {
private TreeNode<E> root;
public void insert(E data) {
if (root == null) {
root = new TreeNode<>(null, data);
} else {
root.insert(root, data);
}
}
public TreeNode<E> successor(TreeNode<E> x) {
if (x != null && x.right != null) {
return min(x.right);
}
TreeNode<E> y = x.parent;
while (y != null && x == y.right) {
x = y;
y = y.parent;
}
return y;
}
public TreeNode<E> min() {
return min(root);
}
private TreeNode<E> min(TreeNode<E> node) {
if (node.left != null) {
return min(node.left);
}
return node;
}
public TreeNode<E> predecessor(TreeNode<E> x) {
if(x != null && x.left != null) {
return max(x.left);
}
TreeNode<E> y = x.parent;
while(y != null && x == y.left) {
x = y;
y = y.parent;
}
return y;
}
public TreeNode<E> max() {
return max(root);
}
private TreeNode<E> max(TreeNode<E> node) {
if (node.right != null) {
return max(node.right);
}
return node;
}
}
寻找访问器的思路是:
而寻找前任,反之亦然。
您可以在我的 GitHub in this package 上找到完整的工作示例。
【讨论】:
Python 代码到 Lasse 的answer:
def findNext(node):
# Case 1
if node.right != None:
node = node.right:
while node.left:
node = node.left
return node
# Case 2
parent = node.parent
while parent != None:
if parent.left == node:
break
node = parent
parent = node.parent
return parent
【讨论】:
Node successor(int data) {
return successor(root, data);
}
// look for the successor to data in the tree rooted at curr
private Node successor(Node curr, int data) {
if (curr == null) {
return null;
} else if (data < curr.data) {
Node suc = successor(curr.left, data);
// if a successor is found use it otherwise we know this node
// is the successor since the target node was in this nodes left subtree
return suc == null ? curr : suc;
} else if (data > curr.data) {
return successor(curr.right, data);
} else {
// we found the node so the successor might be the min of the right subtree
return findMin(curr.right);
}
}
private Node findMin(Node curr) {
if (curr == null) {
return null;
}
while (curr.left != null) {
curr = curr.left;
}
return curr;
}
【讨论】:
我们不需要父链接或堆栈来找到 O(log n) 中的顺序后继(假设平衡树)。 保留一个临时变量,其中包含在中序遍历中遇到的大于键的最新值。如果中序遍历发现该节点没有右孩子,那么这将是中序后继。否则,右孩子的最左边的后代。
【讨论】:
我们可以在 O(log n) 中找到后继者,而无需使用父指针(对于平衡树)。
这个想法与你有父指针时非常相似。
我们可以定义一个递归函数来实现这一点,如下所示:
伪代码:
Key successor(Node current, Key target):
if current == null
return null
if target == current.key
if current.right != null
return leftMost(current.right).key
else
return specialKey
else
if target < current.key
s = successor(current.left, target)
if s == specialKey
return current.key
else
return s
else
return successor(current.right, target)
Node leftMost(Node current):
while current.left != null
current = current.left
return current
【讨论】:
C# 实现(非递归!)在二叉搜索树中查找给定节点的“下一个”节点,其中每个节点都有到其父节点的链接。
public static Node WhoIsNextInOrder(Node root, Node node)
{
if (node.Right != null)
{
return GetLeftMost(node.Right);
}
else
{
Node p = new Node(null,null,-1);
Node Next = new Node(null, null, -1);
bool found = false;
p = FindParent(root, node);
while (found == false)
{
if (p.Left == node) { Next = p; return Next; }
node = p;
p = FindParent(root, node);
}
return Next;
}
}
public static Node FindParent(Node root, Node node)
{
if (root == null || node == null)
{
return null;
}
else if ( (root.Right != null && root.Right.Value == node.Value) || (root.Left != null && root.Left.Value == node.Value))
{
return root;
}
else
{
Node found = FindParent(root.Right, node);
if (found == null)
{
found = FindParent(root.Left, node);
}
return found;
}
}
public static Node GetLeftMost (Node node)
{
if (node.Left == null)
{
return node;
}
return GetLeftMost(node.Left);
}
【讨论】:
我在谷歌上查看的每个“教程”以及该线程中的所有答案都使用以下逻辑:“如果节点没有正确的孩子,那么它的有序后继者将是它的祖先之一。使用父链接一直向上移动,直到你得到它的父节点的左子节点。然后这个父节点将是有序的后继。"
这与思考“如果我的父母比我大,那么我就是左孩子”(二叉搜索树的属性)是一样的。这意味着您可以简单地沿着父链向上走,直到上述属性为真。在我看来,这会产生更优雅的代码。
我猜为什么每个人都通过查看分支而不是使用父链接的代码路径中的值来检查“我是左孩子吗”的原因来自于“借用”逻辑从 no-链接到父算法。
此外,从下面包含的代码中,我们可以看到 不需要堆栈数据结构,正如其他答案所建议的那样。
以下是一个简单的 C++ 函数,适用于两种用例(使用和不使用到父级的链接)。
Node* nextInOrder(const Node *node, bool useParentLink) const
{
if (!node)
return nullptr;
// when has a right sub-tree
if (node->right) {
// get left-most node from the right sub-tree
node = node->right;
while (node->left)
node = node->left;
return node;
}
// when does not have a right sub-tree
if (useParentLink) {
Node *parent = node->parent;
while (parent) {
if (parent->value > node->value)
return parent;
parent = parent->parent;
}
return nullptr;
} else {
Node *nextInOrder = nullptr;
// 'root' is a class member pointing to the root of the tree
Node *current = root;
while (current != node) {
if (node->value < current->value) {
nextInOrder = current;
current = current->left;
} else {
current = current->right;
}
}
return nextInOrder;
}
}
Node* previousInOrder(const Node *node, bool useParentLink) const
{
if (!node)
return nullptr;
// when has a left sub-tree
if (node->left) {
// get right-most node from the left sub-tree
node = node->left;
while (node->right)
node = node->right;
return node;
}
// when does not have a left sub-tree
if (useParentLink) {
Node *parent = node->parent;
while (parent) {
if (parent->value < node->value)
return parent;
parent = parent->parent;
}
return nullptr;
} else {
Node *prevInOrder = nullptr;
// 'root' is a class member pointing to the root of the tree
Node *current = root;
while (current != node) {
if (node->value < current->value) {
current = current->left;
} else {
prevInOrder = current;
current = current->right;
}
}
return prevInOrder;
}
}
【讨论】:
我们可以分为三种情况:
如果节点是父节点:在这种情况下,我们查找它是否有右节点并遍历右节点的最左边的子节点。如果右节点没有子节点,则右节点是它的中序继任者。如果没有正确的节点,我们需要向上移动树以找到中序继任者。
如果节点是左子节点:在这种情况下,父节点是中序继任者。
如果节点(称为 x)是(其直接父节点的)右子节点:我们向上遍历树,直到找到其左子树具有 x 的节点。
极端情况:如果节点是最右角节点,则没有中序后继。
【讨论】:
在 Java 中这样做
TreeNode getSuccessor(TreeNode treeNode) {
if (treeNode.right != null) {
return getLeftMostChild(treeNode.right);
} else {
TreeNode p = treeNode.parent;
while (p != null && treeNode == p.right) { // traverse upwards until there is no parent (at the last node of BST and when current treeNode is still the parent's right child
treeNode = p;
p = p.parent; // traverse upwards
}
return p; // returns the parent node
}
}
TreeNode getLeftMostChild(TreeNode treeNode) {
if (treeNode.left == null) {
return treeNode;
} else {
return getLeftMostChild(treeNode.left);
}
}
【讨论】:
JavaScript 解决方案 - 如果给定节点有右节点,则返回右子树中最小的节点 - 如果不是,那么有两种可能性: - 给定节点是父节点的左子节点。如果是,则返回父节点。否则,给定节点是父节点的右子节点。如果是,则返回父节点的右孩子
function nextNode(node) {
var nextLargest = null;
if (node.right != null) {
// Return the smallest item in the right subtree
nextLargest = node.right;
while (nextLargest.left !== null) {
nextLargest = nextLargest.left;
}
return nextLargest;
} else {
// Node is the left child of the parent
if (node === node.parent.left) return node.parent;
// Node is the right child of the parent
nextLargest = node.parent;
while (nextLargest.parent !== null && nextLargest !== nextLargest.parent.left) {
nextLargest = nextLargest.parent
}
return nextLargest.parent;
}
}
【讨论】:
如果我们执行顺序遍历,那么我们访问左子树,然后是根节点,最后是树中每个节点的右子树。 执行按顺序遍历将按升序为我们提供二叉搜索树的键,因此当我们提到检索属于二叉搜索树的节点的顺序后继时,我们的意思是序列中的下一个节点是什么给定的节点。
假设我们有一个节点 R,我们想要它的后继节点,我们会遇到以下情况。
[1]根R有一个右节点,所以我们需要做的就是遍历到R->right最左边的节点。
[2] 根 R 没有右节点,在这种情况下,我们沿着父链接向后遍历树,直到节点 R 是其父节点的左子节点,当这种情况发生时,我们将父节点 P 作为顺序后继节点。
[3]我们在树的最右边节点,在这种情况下没有顺序后继。
实现基于以下节点定义
class node
{
private:
node* left;
node* right;
node* parent
int data;
public:
//public interface not shown, these are just setters and getters
.......
};
//go up the tree until we have our root node a left child of its parent
node* getParent(node* root)
{
if(root->parent == NULL)
return NULL;
if(root->parent->left == root)
return root->parent;
else
return getParent(root->parent);
}
node* getLeftMostNode(node* root)
{
if(root == NULL)
return NULL;
node* left = getLeftMostNode(root->left);
if(left)
return left;
return root;
}
//return the in order successor if there is one.
//parameters - root, the node whose in order successor we are 'searching' for
node* getInOrderSucc(node* root)
{
//no tree, therefore no successor
if(root == NULL)
return NULL;
//if we have a right tree, get its left most node
if(root->right)
return getLeftMostNode(root->right);
else
//bubble up so the root node becomes the left child of its
//parent, the parent will be the inorder successor.
return getParent(root);
}
【讨论】:
这些答案对我来说似乎都过于复杂。我们真的不需要父指针或任何辅助数据结构,如堆栈。我们需要做的就是从根开始按顺序遍历树,一旦找到目标节点就设置一个标志,我们访问的树中的下一个节点将是有序的后继节点。这是我写的一个快速而肮脏的例程。
Node* FindNextInorderSuccessor(Node* root, int target, bool& done)
{
if (!root)
return NULL;
// go left
Node* result = FindNextInorderSuccessor(root->left, target, done);
if (result)
return result;
// visit
if (done)
{
// flag is set, this must be our in-order successor node
return root;
}
else
{
if (root->value == target)
{
// found target node, set flag so that we stop at next node
done = true;
}
}
// go right
return FindNextInorderSuccessor(root->right, target, done);
}
【讨论】:
您可以阅读更多信息here(Rus lung)
Node next(Node x)
if x.right != null
return minimum(x.right)
y = x.parent
while y != null and x == y.right
x = y
y = y.parent
return y
Node prev(Node x)
if x.left != null
return maximum(x.left)
y = x.parent
while y != null and x == y.left
x = y
y = y.parent
return y
【讨论】:
这说明了函数Node* getNextNodeInOrder(Node),它按顺序返回二叉搜索树的下一个键。
#include <cstdlib>
#include <iostream>
using namespace std;
struct Node{
int data;
Node *parent;
Node *left, *right;
};
Node *createNode(int data){
Node *node = new Node();
node->data = data;
node->left = node->right = NULL;
return node;
}
Node* getFirstRightParent(Node *node){
if (node->parent == NULL)
return NULL;
while (node->parent != NULL && node->parent->left != node){
node = node->parent;
}
return node->parent;
}
Node* getLeftMostRightChild(Node *node){
node = node->right;
while (node->left != NULL){
node = node->left;
}
return node;
}
Node *getNextNodeInOrder(Node *node){
//if you pass in the last Node this will return NULL
if (node->right != NULL)
return getLeftMostRightChild(node);
else
return getFirstRightParent(node);
}
void inOrderPrint(Node *root)
{
if (root->left != NULL) inOrderPrint(root->left);
cout << root->data << " ";
if (root->right != NULL) inOrderPrint(root->right);
}
int main(int argc, char** argv) {
//Purpose of this program is to demonstrate the function getNextNodeInOrder
//of a binary tree in-order. Below the tree is listed with the order
//of the items in-order. 1 is the beginning, 11 is the end. If you
//pass in the node 4, getNextNode returns the node for 5, the next in the
//sequence.
//test tree:
//
// 4
// / \
// 2 11
// / \ /
// 1 3 10
// /
// 5
// \
// 6
// \
// 8
// / \
// 7 9
Node *root = createNode(4);
root->parent = NULL;
root->left = createNode(2);
root->left->parent = root;
root->right = createNode(11);
root->right->parent = root;
root->left->left = createNode(1);
root->left->left->parent = root->left;
root->right->left = createNode(10);
root->right->left->parent = root->right;
root->left->right = createNode(3);
root->left->right->parent = root->left;
root->right->left->left = createNode(5);
root->right->left->left->parent = root->right->left;
root->right->left->left->right = createNode(6);
root->right->left->left->right->parent = root->right->left->left;
root->right->left->left->right->right = createNode(8);
root->right->left->left->right->right->parent =
root->right->left->left->right;
root->right->left->left->right->right->left = createNode(7);
root->right->left->left->right->right->left->parent =
root->right->left->left->right->right;
root->right->left->left->right->right->right = createNode(9);
root->right->left->left->right->right->right->parent =
root->right->left->left->right->right;
inOrderPrint(root);
//UNIT TESTING FOLLOWS
cout << endl << "unit tests: " << endl;
if (getNextNodeInOrder(root)->data != 5)
cout << "failed01" << endl;
else
cout << "passed01" << endl;
if (getNextNodeInOrder(root->right) != NULL)
cout << "failed02" << endl;
else
cout << "passed02" << endl;
if (getNextNodeInOrder(root->right->left)->data != 11)
cout << "failed03" << endl;
else
cout << "passed03" << endl;
if (getNextNodeInOrder(root->left)->data != 3)
cout << "failed04" << endl;
else
cout << "passed04" << endl;
if (getNextNodeInOrder(root->left->left)->data != 2)
cout << "failed05" << endl;
else
cout << "passed05" << endl;
if (getNextNodeInOrder(root->left->right)->data != 4)
cout << "failed06" << endl;
else
cout << "passed06" << endl;
if (getNextNodeInOrder(root->right->left->left)->data != 6)
cout << "failed07" << endl;
else
cout << "passed07" << endl;
if (getNextNodeInOrder(root->right->left->left->right)->data != 7)
cout << "failed08 it came up with: " <<
getNextNodeInOrder(root->right->left->left->right)->data << endl;
else
cout << "passed08" << endl;
if (getNextNodeInOrder(root->right->left->left->right->right)->data != 9)
cout << "failed09 it came up with: "
<< getNextNodeInOrder(root->right->left->left->right->right)->data
<< endl;
else
cout << "passed09" << endl;
return 0;
}
哪些打印:
1 2 3 4 5 6 7 8 9 10 11
unit tests:
passed01
passed02
passed03
passed04
passed05
passed06
passed07
passed08
passed09
【讨论】:
使用二叉搜索树,找到给定节点的下一个最高节点的算法基本上是找到该节点右子树的最低节点。
算法可以很简单:
重复 2 和 3 直到找到下一个最高节点。
【讨论】:
这是一个不需要父链接或中间结构(如堆栈)的实现。这个有序的后继函数与大多数人可能正在寻找的有点不同,因为它对键而不是节点进行操作。此外,即使它不存在于树中,它也会找到密钥的后继者。不过,如果您需要,也不太难更改。
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> left;
private Node<T> right;
public Node(T data, Node<T> left, Node<T> right) {
this.data = data;
this.left = left;
this.right = right;
}
/*
* Returns the left-most node of the current node. If there is no left child, the current node is the left-most.
*/
private Node<T> getLeftMost() {
Node<T> curr = this;
while(curr.left != null) curr = curr.left;
return curr;
}
/*
* Returns the right-most node of the current node. If there is no right child, the current node is the right-most.
*/
private Node<T> getRightMost() {
Node<T> curr = this;
while(curr.right != null) curr = curr.right;
return curr;
}
/**
* Returns the in-order successor of the specified key.
* @param key The key.
* @return
*/
public T getSuccessor(T key) {
Node<T> curr = this;
T successor = null;
while(curr != null) {
// If this.data < key, search to the right.
if(curr.data.compareTo(key) < 0 && curr.right != null) {
curr = curr.right;
}
// If this.data > key, search to the left.
else if(curr.data.compareTo(key) > 0) {
// If the right-most on the left side has bigger than the key, search left.
if(curr.left != null && curr.left.getRightMost().data.compareTo(key) > 0) {
curr = curr.left;
}
// If there's no left, or the right-most on the left branch is smaller than the key, we're at the successor.
else {
successor = curr.data;
curr = null;
}
}
// this.data == key...
else {
// so get the right-most data.
if(curr.right != null) {
successor = curr.right.getLeftMost().data;
}
// there is no successor.
else {
successor = null;
}
curr = null;
}
}
return successor;
}
public static void main(String[] args) {
Node<Integer> one, three, five, seven, two, six, four;
one = new Node<Integer>(Integer.valueOf(1), null, null);
three = new Node<Integer>(Integer.valueOf(3), null, null);
five = new Node<Integer>(Integer.valueOf(5), null, null);
seven = new Node<Integer>(Integer.valueOf(7), null, null);
two = new Node<Integer>(Integer.valueOf(2), one, three);
six = new Node<Integer>(Integer.valueOf(6), five, seven);
four = new Node<Integer>(Integer.valueOf(4), two, six);
Node<Integer> head = four;
for(int i = 0; i <= 7; i++) {
System.out.println(head.getSuccessor(i));
}
}
}
【讨论】:
一般方式取决于您的节点中是否有父链接。
然后你选择:
如果您有正确的孩子,请执行此方法(上面的案例 1):
如果您没有正确的孩子,请执行此方法(上面的案例 2):
然后您需要运行树的完整扫描,跟踪节点,通常使用堆栈,以便您拥有基本执行与依赖父链接的第一个方法相同的必要信息。
【讨论】:
在这里查看:InOrder Successor in a Binary Search Tree
在二叉树中,a 的中序后继 node 是 Inorder 中的下一个节点 二叉树的遍历。为了 中的最后一个节点的后继为 NULL 无序遍历。在二分搜索中 树,输入的顺序后继 节点也可以定义为节点 最小的键大于 输入节点的key。
【讨论】: