迭代中序遍历 B 树
Iterative Inorder Traversal B-Tree
我的最终目标是做一个findKthElement
函数,我能想到的唯一方法是执行迭代中序遍历,这样我就可以保留一个计数器,如果它是递归的,这显然是行不通的。我已尽力实现类似于 BST 的实现,但它不起作用,只是无限地打印相同的东西。这是我的尝试:
public void findKth() {
Stack<BTreeNode> s = new Stack<>();
BTreeNode current = this.root;
while(current != null || !s.isEmpty()) {
int i;
for(i = 0; i < current.numNodes; i++) {
if(!current.isLeaf) {
s.push(current);
current = current.children[i];
}
}
current = s.pop();
for(int j = 0; j < current.numNodes; j++) {
System.out.println(current.keys[j].getName());
}
}
}
keep a counter, which obviously doesn't work if its recursive
在递归解决方案中保留一个计数器没有问题。您只需要确保它是一个可变引用。例如:
public class Counter {
private int count;
public boolean complete() { return count == 0; }
public void decrement() { count--; }
}
Optional<Node> findKthChild(Node node, Counter counter) {
if (counter.isLeaf()) {
if (counter.complete())
return Optional.of(node);
counter.decrement();
} else {
for (Node child: getChildren()) {
Optional<Node> kthChild = findKthChild(child, counter);
if (kthChild.isPresent())
return kthChild;
}
}
return Optional.empty();
}
如果您熟悉流,内部 for 循环可能是:
return getChildren().stream()
.map(ch -> findKthChild(ch, counter))
.filter(Optional::isPresent)
.findFirst().orElse(Optional.empty());
这是家庭作业的味道。人们应该尝试通过用笔和纸手动跟踪所需的步骤来解决它。
我并不是说下面的代码是正确的或者很好。
表示中序遍历,深度优先,需要返回第ith个子分支继续下一个子节点
为此,我使用新的 record class 作为堆栈元素,class 仅包含 BTreeNode node
和 int index
.
public String findKth(int k) {
record NodePos(BTreeNode node, int index) {};
Stack<NodePos> stack = new Stack<>();
stack.push(new NodePos(this.root, -1);
while (!stack.isEmpty()) {
NodePos pos = stack.pop();
pos = new NodePos(pos.node, pos.index + 1);
if (pos.index >= pos.node.numNodes) { // Past end of child nodes.
continue;
}
// Sub-branch:
if (!pos.node.isLeaf) {
stack.push(new NodePos(pos.node.children[pos.index], -1);
continue;
}
// Key:
if (pos.index + 1 >= pos.node.numNodes) { // Past end of child keys.
continue;
}
System.console().printf("%d. %s%n", k, pos.node.keys[pos.index]);
if (k <= 0) {
return pos.node.keys[pos.index];
}
--k;
stack.push(pos);
}
}
一个节点(node.keys
)中有numNodes
个子分支(node.children
)和numNodes - 1
个键。
当你在第i个子分支时,可以先继续子树,当不够时(递减k仍大于0),再继续i-1 个键。
如您所见,当不手动执行代码时,很难阅读它。为此,自己解决这些问题是非常宝贵的建议。
顺便说一句,递归解决方案更简单。
好的,一个可行的解决方案
我上面的回答是想思考的,肯定不对,
由于 OP 没有表现出认真考虑过该算法,
给定操作代码。但是明显是有功夫的。
因此是一个可读的递归解决方案。仍然以一种不能
作为自己的功课还给别人。
static class BTreeNode {
int numNodes;
boolean isLeaf;
BTreeNode[] children;
int[] keys;
BTreeNode(int... keys) {
numNodes = keys.length + 1;
this.keys = keys.clone();
isLeaf = true;
}
public void addChildren(BTreeNode... children) {
assert children.length == numNodes;
this.children = children.clone();
isLeaf = false;
}
}
public static OptionalInt findKth(BTreeNode node, AtomicInteger k) {
if (node == null || k.get() < 0) {
return OptionalInt.empty();
}
for (int i = 0; i < node.numNodes; ++i) {
if (!node.isLeaf) {
OptionalInt result = findKth(node.children[i], k);
if (result.isPresent()) {
return result;
}
}
if (i + 1 < node.numNodes) {
int j = k.getAndDecrement();
System.out.printf("%d. %s%n", j, node.keys[i]);
if (j <= 0) {
return OptionalInt.of(node.keys[i]);
}
}
}
return OptionalInt.empty();
}
public static void main(String[] args) {
//
// (4 8 12)
// (1 2 3) (5 6 7) (9 10 11) (13 14 15)
BTreeNode n1to3 = new BTreeNode(1, 2, 3);
BTreeNode n5to7 = new BTreeNode(5, 6, 7);
BTreeNode n9to11 = new BTreeNode(9, 10, 11);
BTreeNode n13to15 = new BTreeNode(13, 14, 15);
BTreeNode root = new BTreeNode(4, 8, 12);
root.addChildren(n1to3, n5to7, n9to11, n13to15);
OptionalInt key5 = findKth(root, new AtomicInteger(5));
System.out.println("The result is " + key5.orElse(-1));
}
按顺序遍历 B 树,递减请求的 k 直到达到 0。按顺序遍历 numNodes 个子树分支和 numNodes - 1 个键需要一个 for+if.
AtomicInteger 用于有一个计数器,它是 findKth
的结果,否则需要一个输入参数 k,以及 return 上 k 的一个新值。这是可以做到的。
优化:如果知道整个子树中的元素数量,就可以跳过访问子树。对于将是 numNodes 的叶节点。
我的最终目标是做一个findKthElement
函数,我能想到的唯一方法是执行迭代中序遍历,这样我就可以保留一个计数器,如果它是递归的,这显然是行不通的。我已尽力实现类似于 BST 的实现,但它不起作用,只是无限地打印相同的东西。这是我的尝试:
public void findKth() {
Stack<BTreeNode> s = new Stack<>();
BTreeNode current = this.root;
while(current != null || !s.isEmpty()) {
int i;
for(i = 0; i < current.numNodes; i++) {
if(!current.isLeaf) {
s.push(current);
current = current.children[i];
}
}
current = s.pop();
for(int j = 0; j < current.numNodes; j++) {
System.out.println(current.keys[j].getName());
}
}
}
keep a counter, which obviously doesn't work if its recursive
在递归解决方案中保留一个计数器没有问题。您只需要确保它是一个可变引用。例如:
public class Counter {
private int count;
public boolean complete() { return count == 0; }
public void decrement() { count--; }
}
Optional<Node> findKthChild(Node node, Counter counter) {
if (counter.isLeaf()) {
if (counter.complete())
return Optional.of(node);
counter.decrement();
} else {
for (Node child: getChildren()) {
Optional<Node> kthChild = findKthChild(child, counter);
if (kthChild.isPresent())
return kthChild;
}
}
return Optional.empty();
}
如果您熟悉流,内部 for 循环可能是:
return getChildren().stream()
.map(ch -> findKthChild(ch, counter))
.filter(Optional::isPresent)
.findFirst().orElse(Optional.empty());
这是家庭作业的味道。人们应该尝试通过用笔和纸手动跟踪所需的步骤来解决它。
我并不是说下面的代码是正确的或者很好。
表示中序遍历,深度优先,需要返回第ith个子分支继续下一个子节点
为此,我使用新的 record class 作为堆栈元素,class 仅包含 BTreeNode node
和 int index
.
public String findKth(int k) {
record NodePos(BTreeNode node, int index) {};
Stack<NodePos> stack = new Stack<>();
stack.push(new NodePos(this.root, -1);
while (!stack.isEmpty()) {
NodePos pos = stack.pop();
pos = new NodePos(pos.node, pos.index + 1);
if (pos.index >= pos.node.numNodes) { // Past end of child nodes.
continue;
}
// Sub-branch:
if (!pos.node.isLeaf) {
stack.push(new NodePos(pos.node.children[pos.index], -1);
continue;
}
// Key:
if (pos.index + 1 >= pos.node.numNodes) { // Past end of child keys.
continue;
}
System.console().printf("%d. %s%n", k, pos.node.keys[pos.index]);
if (k <= 0) {
return pos.node.keys[pos.index];
}
--k;
stack.push(pos);
}
}
一个节点(node.keys
)中有numNodes
个子分支(node.children
)和numNodes - 1
个键。
当你在第i个子分支时,可以先继续子树,当不够时(递减k仍大于0),再继续i-1 个键。
如您所见,当不手动执行代码时,很难阅读它。为此,自己解决这些问题是非常宝贵的建议。
顺便说一句,递归解决方案更简单。
好的,一个可行的解决方案
我上面的回答是想思考的,肯定不对, 由于 OP 没有表现出认真考虑过该算法, 给定操作代码。但是明显是有功夫的。
因此是一个可读的递归解决方案。仍然以一种不能 作为自己的功课还给别人。
static class BTreeNode {
int numNodes;
boolean isLeaf;
BTreeNode[] children;
int[] keys;
BTreeNode(int... keys) {
numNodes = keys.length + 1;
this.keys = keys.clone();
isLeaf = true;
}
public void addChildren(BTreeNode... children) {
assert children.length == numNodes;
this.children = children.clone();
isLeaf = false;
}
}
public static OptionalInt findKth(BTreeNode node, AtomicInteger k) {
if (node == null || k.get() < 0) {
return OptionalInt.empty();
}
for (int i = 0; i < node.numNodes; ++i) {
if (!node.isLeaf) {
OptionalInt result = findKth(node.children[i], k);
if (result.isPresent()) {
return result;
}
}
if (i + 1 < node.numNodes) {
int j = k.getAndDecrement();
System.out.printf("%d. %s%n", j, node.keys[i]);
if (j <= 0) {
return OptionalInt.of(node.keys[i]);
}
}
}
return OptionalInt.empty();
}
public static void main(String[] args) {
//
// (4 8 12)
// (1 2 3) (5 6 7) (9 10 11) (13 14 15)
BTreeNode n1to3 = new BTreeNode(1, 2, 3);
BTreeNode n5to7 = new BTreeNode(5, 6, 7);
BTreeNode n9to11 = new BTreeNode(9, 10, 11);
BTreeNode n13to15 = new BTreeNode(13, 14, 15);
BTreeNode root = new BTreeNode(4, 8, 12);
root.addChildren(n1to3, n5to7, n9to11, n13to15);
OptionalInt key5 = findKth(root, new AtomicInteger(5));
System.out.println("The result is " + key5.orElse(-1));
}
按顺序遍历 B 树,递减请求的 k 直到达到 0。按顺序遍历 numNodes 个子树分支和 numNodes - 1 个键需要一个 for+if.
AtomicInteger 用于有一个计数器,它是 findKth
的结果,否则需要一个输入参数 k,以及 return 上 k 的一个新值。这是可以做到的。
优化:如果知道整个子树中的元素数量,就可以跳过访问子树。对于将是 numNodes 的叶节点。