C++で二分探索木(BST)からターゲットに最も近いk個の値を検索する方法
問題概要
二分探索木(BST)とターゲット値が与えられたとき、BST内の値の中からターゲットに最も近いk個の値を見つけることを考えます。ここで、ターゲット値は浮動小数点数である点に注意してください。なお、kは常に有効であり、k ≤ 全ノード数が成り立つものと仮定できます。
例として、次のような木を考えてみましょう。

target = 3.714286、k = 2 の場合、出力は [4, 3] となります。
解法のアプローチ
この問題は、「ターゲットより小さい値」を管理するスタックと「ターゲット以上の値」を管理するスタックの2本を用いることで効率的に解けます。各スタックには中間順走査(in-order traversal)に相当する順序で候補ノードを積んでおき、両スタックの先頭同士をターゲットとの距離で比較しながら、近い方から順に結果へ追加していきます。
手順1:pushSmaller() 関数を定義する
この関数は、ノード、スタック st、ターゲットを引数に取ります。ノードが存在する限り、以下を繰り返します。
- ノードの値がターゲット未満の場合:
・そのノードを st にプッシュする
・node := ノードの右の子 とする - それ以外の場合:
・node := ノードの左の子 とする
手順2:pushLarger() 関数を定義する
同じくノード、スタック st、ターゲットを引数に取ります。ノードが存在する限り、以下を繰り返します。
- ノードの値がターゲット以上の場合:
・そのノードを st にプッシュする
・node := ノードの左の子 とする - それ以外の場合:
・node := ノードの右の子 とする
手順3:メインメソッドでの処理
- 結果格納用の配列 ret を用意する
- スタック smaller と larger を用意する
- pushLarger(root, larger, target) を呼び出す
- pushSmaller(root, smaller, target) を呼び出す
- k が 0 になるまで(各ステップで k をデクリメントしながら)以下を繰り返す
ループ内では、次のように処理を分岐させます。
- smaller が空でなく、かつ(larger が空、または |target − smaller の先頭の値| < |target − larger の先頭の値|)である場合:
- curr = smaller の先頭要素とする
- smaller から要素をポップする
- curr の値を ret の末尾に追加する
- pushSmaller(curr の左の子, smaller, target) を呼び出す
- それ以外の場合:
- curr = larger の先頭要素とする
- larger から要素をポップする
- curr の値を ret の末尾に追加する
- pushLarger(curr の右の子, larger, target) を呼び出す
ループが終わったら、ret を返します。
C++実装例
理解を深めるために、以下の実装例を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
for(int i = 0; i<v.size(); i++){
cout << v[i] << ", ";
}
cout << "]"<<endl;
}
class TreeNode{
public:
int val;
TreeNode *left, *right;
TreeNode(int data){
val = data;
left = NULL;
right = NULL;
}
};
void insert(TreeNode **root, int val){
queue<TreeNode*> q;
q.push(*root);
while(q.size()){
TreeNode *temp = q.front();
q.pop();
if(!temp->left){
if(val != NULL)
temp->left = new TreeNode(val);
else
temp->left = new TreeNode(0);
return;
}
else{
q.push(temp->left);
}
if(!temp->right){
if(val != NULL)
temp->right = new TreeNode(val);
else
temp->right = new TreeNode(0);
return;
}
else{
q.push(temp->right);
}
}
}
TreeNode *make_tree(vector<int> v){
TreeNode *root = new TreeNode(v[0]);
for(int i = 1; i<v.size(); i++){
insert(&root, v[i]);
}
return root;
}
class Solution {
public:
vector<int> closestKValues(TreeNode* root, double target, int k){
vector<int> ret;
stack<TreeNode*> smaller;
stack<TreeNode*> larger;
pushLarger(root, larger, target);
pushSmaller(root, smaller, target);
while (k--) {
if (!smaller.empty() && (larger.empty() || (abs(target - smaller.top()->val) < abs(target - larger.top()->val)))) {
TreeNode* curr = smaller.top();
smaller.pop();
ret.push_back(curr->val);
pushSmaller(curr->left, smaller, target);
}
else {
TreeNode* curr = larger.top();
larger.pop();
ret.push_back(curr->val);
pushLarger(curr->right, larger, target);
}
}
return ret;
}
void pushSmaller(TreeNode* node, stack <TreeNode*>& st, double target){
while (node) {
if (node->val < target) {
st.push(node);
node = node->right;
}
else {
node = node->left;
}
}
}
void pushLarger(TreeNode* node, stack <TreeNode*>& st, double target){
while (node) {
if (node->val >= target) {
st.push(node);
node = node->left;
}
else
node = node->right;
}
}
};
main(){
Solution ob;
vector<int> v = {4,2,5,1,3};
TreeNode *root = make_tree(v);
print_vector(ob.closestKValues(root, 3.7142, 2));
}
入力
{4,2,5,1,3}, 3.7142, 2
出力
[4, 3]
-
C++の二分探索木(BST)で最小値のノードを見つける方法
二分探索木(Binary Search Tree、BST)が与えられたとき、その木の中から最小の要素を見つけることを考えます。例えば、以下のようなBSTがあるとします。この場合、最小要素は 1 になります。考え方二分探索木の重要な性質として、左部分木には必ず親ノードより小さい値が格納されるというものがあります。この性質を利用すると、次の手順で最小要素を見つけることができます。ルートノードから探索を開始します。現在のノードの左の子が NULL でない間、左の子へ移動を繰り返します。左の子が NULL になったノードの値が、木全体の中で最小の要素です。この操作の計算量は木の高さに依存し、平衡な二分
-
【Python】二分探索木でk番目に小さい要素を効率的に求めるアルゴリズムと実装例
問題の概要 二分探索木(BST: Binary Search Tree)と整数 k が与えられたとき、木の中で k 番目に小さい値を見つけることを考えます。 例えば、次のような二分探索木があるとします。 5 / \ 4 10 / \ 7 15 / \ 6 8 このとき k = 3 であれば、出力は 7 になります。 アプローチ:スタックを使った中順走査(In-order Traversal) 二分探索木には「中順走査を行うと、ノードを値の昇順に訪問できる」という重要な性質が