目次
1.コンセプト
二分探索ツリーは二分ソート ツリーとも呼ばれ、空のツリー
、または次のプロパティを持つ二分ツリーのいずれかです。
左のサブツリーが空でない場合、左のサブツリー上のすべてのノードの値はルート ノードの値より小さくなります。右のサブツリーが空でない場合、右のサブツリー上のすべてのノードの値はルート ノードの値より大きくなります。その左側と右側のサブツリーもそれぞれ二分探索ツリーです。
なぜバイナリソートツリーとも呼ばれるのでしょうか? ツリーをレベル順に移動すると、ツリーは昇順になります。
2. 分析の実施
実験例:
int a[] = {8, 3, 1, 10, 6, 4, 5, 7, 14, 13};
1.挿入する
(1.) 非再帰バージョン
a. ルートから比較検索を開始し、ルートより大きい場合は右へ、ルートより小さい場合は左へ検索します。b. ほとんどの場合、高さを検索します。空になった場合は、まだ見つかっていないため、この値は存在しません。
比較的簡単です。コードをここに直接配置するだけです。
template <class K>
class BinarySeaTree_node
{
typedef BinarySeaTree_node<K> BST_node;
public:
BinarySeaTree_node(const K& val)
: _val(val)
,_left(nullptr)
,_right(nullptr)
{}
K _val;
BST_node* _left;
BST_node* _right;
};
template <class T>
class BSTree
{
typedef BinarySeaTree_node<T> BST_node;
private:
BST_node* root = nullptr;
public:
bool Insert(const T& val)
{
BST_node* key = new BST_node(val);
BST_node* cur = root;
BST_node* parent = nullptr;
while (cur)
{
if (key->_val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (key->_val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
return 0;
}
}
// 查询好位置后,建立链接
if (!root)
{
root = key;
return 0;
}
if (key->_val > parent->_val)
{
parent->_right = key;
}
else
{
parent->_left = key;
}
return 1;
}
};
(2.) 再帰バージョン
ここではたくさんのことが起こっています、皆さん注意してください!!!
bool Re_Insert(const T& val){ return Re_Insert_table(root, val);}
bool Re_Insert_table(BST_node*& node, const T& val)
{
if (node == nullptr)
{
node = new BST_node(val);
return 1;
}
if (val < node->_left)
{
return Re_Insert_table(node->_left, val);
}
else if (val > node->_right)
{
return Re_Insert_table(node->_right, val);
}
else
{
return 0;
}
}
理解を容易にするために、再帰的展開図を示します。
2. 印刷検索二分木
具体的な挿入手順は以下の通りです。
a. ツリーが空の場合は、ノードを直接追加し、ルート ポインタに割り当てます。b. ツリーは空ではないので、二分探索木のプロパティに従って挿入位置を見つけ、新しいノードを挿入します。
ここではコードのみを共有します。
void Print_table() { Re_Print(root); }
void Re_Print(const BST_node* node)
{
if (node == nullptr)
return;
Re_Print(node->_left);
cout << node->_val << " ";
Re_Print(node->_right);
}
3.検索機能
アイデア: 実際にはアイデアはありません。ノードが親ノードより小さい場合は左側を探し、そうでない場合は右側を探します。
(1.) 非再帰バージョン
BST_node* Find(const T& val)
{
//直接跟寻找位置一样
BST_node* parent = nullptr;
BST_node* cur = root; // 以返回cur的方式返回
while (cur) // 非递归版本
{
if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
return cur;
}
}
return cur;
}
(2.) 再帰バージョン
BST_node* Re_Find(const T& val){ return Re_Find_table(root, val); }
BST_node* Re_Find_table(BST_node* node, const T& val)
{
if (node == nullptr)
return nullptr;
if (val < node->_val)
{
return Re_Find_table(node->_left, val);
}
else if (val > node->_val)
{
return Re_Find_table(node->_right, val);
}
else
{
return node;
}
}
4. 機能の削除(重要かつ困難)
次のようにアイデアを簡単に探しました。
しかし、これらの考え方はあくまで大まかな方向性であり、そこには多くの落とし穴が潜んでいます。
まず、ターゲットをクエリします。
これは比較的単純なので、ここでは説明しません。
//首先寻找到目标,并且记录到parent
BST_node* parent = nullptr;
BST_node* cur = root;
while (cur)
{
if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
break;
}
}
if (!cur)
{
return 0;
}
よくある間違いを分析して確実に学習できるようにする
(1.) 左右の子を持たないターゲットを削除します。
(2.) ターゲットを削除し、子を 1 つだけ持つ
一般的なアイデア:
しかし、そこには抜け穴があるのです!
約束:
(3.) ターゲットを削除し、子供を 2 人持つ
さて、前菜が提供されたところで、今回のメインを見ていきましょう。
コード
(1.) 非再帰バージョン
bool Erase(const T& val)
{
//首先寻找到指定值,并且记录到parent
BST_node* parent = nullptr;
BST_node* cur = root;
while (cur)
{
if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
break;
}
}
if (!cur)
{
return 0;
}
// 查询成功,开始删除
if (!cur->_left && !cur->_right) // cur没有左右孩子
{ // 当要删除目标是根
if (cur == root)
{
root = nullptr;
delete cur;
}
// 判断cur是左右孩子
else if (cur->_val < parent->_val)
{
parent->_left = nullptr;
delete cur;
}
else
{
parent->_right = nullptr;
delete cur;
}
return 1;
}
else if (!cur->_left || !cur->_right) // 只有一个孩子
{
if (!parent) // 判断是否是目标是根
{
root = cur->_left != nullptr ? cur->_left : cur->_right;
delete cur;
}
// 判断cur为啥孩子
else if (cur->_val < parent->_val) // 左侧
{
parent->_left = cur->_left != nullptr ? cur->_left : cur->_right;
delete cur;
}
else // 右侧
{
parent->_right = cur->_left != nullptr ? cur->_left : cur->_right;
delete cur;
}
}
else // 有2个孩子
{ // 使用左侧最大的孩子来领养
// 寻找左侧最大
BST_node* maxnode = cur->_left;
BST_node* max_parent = cur;
while (maxnode->_right)
{
max_parent = maxnode;
maxnode = maxnode->_right;
}
// 现在又进入一种特殊情况,1.max_parent就没进入循环,2.进入了循环
if (max_parent == cur)
{
max_parent->_left = maxnode->_left;
}
else
{
max_parent->_right = maxnode->_left;
}
// 值转移
cur->_val = maxnode->_val;
delete maxnode;
}
return 1;
}
(2.) 再帰バージョン
bool Re_Erease( const T& val)
{
return Re_Erease_table(root, val);
}
bool Re_Erease_table(BST_node*& node, const T& val)
{
// 首先我们先找到值
if (node == nullptr)
{
return 0; // 如果访问到了空,则说明删除失败,原因是:不存在
}
if (val < node->_val)
{
return Re_Erease_table(node->_left, val);
}
else if (val > node->_val)
{
return Re_Erease_table(node->_right, val);
}
else
{
// 开始删除目标数据。方法如下;
// 1. 就按照非递归的思路,不用改多少代码
// 2. 使用递归方法,优势就是代码简洁
// 这里使用方法2
BST_node* del = node; // 保存每次访问的对象,如果是目标,就备份好了
if (node->_left == nullptr)
{
node = node->_right;
}
else if (node->_right == nullptr)
{
node = node->_left;
}
else
{
//处理左右都有孩子的目标
// 左侧查找最大值,右侧查找最小值
BST_node* max_node = node->_left;
while (max_node->_right)
{
max_node = max_node->_right;
}
// 完成循环后,max_node最多有左孩子,然后数据交换,我们以目标左侧树为起点
// 再次递归删除替换数据。
swap(max_node->_val, node->_val);
return Re_Erease_table(node->_left, val); //已经完成删除,就直接退出,以免触发删除delete
}
//处理前两种情况
delete del;
}
}
5. デストラクター
アイデア:
~BSTree()
{
Distroy_Re(root);
root = nullptr;
}
void Distroy_Re(BST_node*& node) // 我们采用递归删除
{
if (node == nullptr)
return ;
// 先处理左右孩子
Distroy_Re(node->_left);
Distroy_Re(node->_right);
delete node;
node = nullptr;
}
6.コピー構築
// 我们实现了拷贝构造,默认构造函数则不会生成
// 解决方案:1.实现构造函数 2.使用default关键字,强制生成默认构造
BSTree()
{
}
// BSTree() = default
BSTree(const BSTree& Tree) // 拷贝构造
{
root = copy(Tree.root);
}
BST_node* copy(BST_node* root)
{
if (root == nullptr)
return nullptr;
BST_node* new_node = new BST_node(root->_val);
new_node->_left = copy(root->_left);
new_node->_right = copy(root->_right);
return new_node;
}
3、申請
1.
K モデル: K モデルはキー コードとして key のみを持ち、構造体に Key のみを格納する必要があり、検索する必要があるのはキー コードです。
値
。
例:
単語 word が与えられた場合、その単語のスペルが正しいかどうかを判断します
。具体的な方法は次のとおりです: 語彙内のすべての単語のコレクション内の各単語をキーとして使用し、二分探索木を構築し、その単語が正しいかどうかを取得します。二分探索ツリーでのスペルは正しいです。存在する場合はスペルが正しく、存在しない場合はスペルが間違っています。
2.
KV モデル: 各キー key には対応する値 Value、つまり <Key, Value> のキーと値のペアがあります
。この方法は実生活でも非常に一般的です。
たとえば、
英語-中国語辞書は英語と中国語の対応関係です
。英語から対応する中国語をすぐに見つけることができます。英語の単語とそれに対応する中国語 <word, chinese> はキーと値のペアを形成します。
別の例
は、単語の数をカウントすることです
。カウントが成功すると、特定の単語の出現数がすぐにわかります。
単語とその出現数は <word, count> であり、キーと値のペア
(これは比較的単純なので、変更するだけです)
4. 二分木の探索の欠陥と最適化
n 個のノードを持つ二分探索ツリーの場合、各要素の検索確率が等しい場合、二分探索ツリーの平均検索長は二分探索ツリー内のノードの深さの関数になります。ノードが多いほど、回数が多くなります。
ただし、同じキー セットでも、各キーの挿入順序が異なる場合は、異なる構造の二分探索ツリーが取得される可能性があります。
最悪の場合: N
平均的なケース: O(logN)
問題: 単一のツリーに縮退すると、二分探索ツリーのパフォーマンスが失われます。キーコードがどのような順序で挿入されたとしても二分探索木のパフォーマンスが最適化されるように改善することはできますか? その後、後続の章で学習する AVL ツリーと赤黒ツリーを使用できます。
5. コードの概要
namespace key
{
template <class K>
class BinarySeaTree_node
{
typedef BinarySeaTree_node<K> BST_node;
public:
BinarySeaTree_node(const K& val)
: _val(val)
,_left(nullptr)
,_right(nullptr)
{}
K _val;
BST_node* _left;
BST_node* _right;
};
template <class T>
class BSTree
{
public:
typedef BinarySeaTree_node<T> BST_node;
// 我们实现了拷贝构造,默认构造函数则不会生成
// 解决方案:1.实现构造函数 2.使用default关键字,强制生成默认构造
BSTree()
{
}
// BSTree() = default
BSTree(const BSTree& Tree) // 拷贝构造
{
root = copy(Tree.root);
}
BSTree<T>& operator=(BSTree<T> t)
{
swap(root, t.root);
return *this;
}
BST_node* copy(BST_node* root)
{
if (root == nullptr)
return nullptr;
BST_node* new_node = new BST_node(root->_val);
new_node->_left = copy(root->_left);
new_node->_right = copy(root->_right);
return new_node;
}
bool Re_Insert(const T& val) { return Re_Insert_table(root, val); }
void Re_Print() { Re_Print_table(root); }
bool Re_Erease(const T& val) { return Re_Erease_table(root, val); }
BST_node* Re_Find(const T& val) { return Re_Find_table(root, val); }
bool Insert(const T& val)
{
BST_node* key = new BST_node(val);
BST_node* cur = root;
BST_node* parent = nullptr;
while (cur)
{
if (key->_val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (key->_val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
return 0;
}
}
// 查询好位置后,建立链接
if (!root)
{
root = key;
return 0;
}
if (key->_val > parent->_val)
{
parent->_right = key;
}
else
{
parent->_left = key;
}
return 1;
}
BST_node* Find(const T& val)
{
//直接跟寻找位置一样
BST_node* parent = nullptr;
BST_node* cur = root; // 以返回cur的方式返回
while (cur) // 非递归版本
{
if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
return cur;
}
}
return cur;
}
bool Erase(const T& val)
{
//首先寻找到指定值,并且记录到parent
BST_node* parent = nullptr;
BST_node* cur = root;
while (cur)
{
if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else
{
break;
}
}
if (!cur)
{
return 0;
}
// 查询成功,开始删除
if (!cur->_left && !cur->_right) // cur没有左右孩子
{ // 当要删除目标是根
if (cur == root)
{
root = nullptr;
delete cur;
}
// 判断cur是左右孩子
else if (cur->_val < parent->_val)
{
parent->_left = nullptr;
delete cur;
}
else
{
parent->_right = nullptr;
delete cur;
}
return 1;
}
else if (!cur->_left || !cur->_right) // 只有一个孩子
{
if (!parent) // 判断是否是目标是根
{
root = cur->_left != nullptr ? cur->_left : cur->_right;
delete cur;
}
// 判断cur为啥孩子
else if (cur->_val < parent->_val) // 左侧
{
parent->_left = cur->_left != nullptr ? cur->_left : cur->_right;
delete cur;
}
else // 右侧
{
parent->_right = cur->_left != nullptr ? cur->_left : cur->_right;
delete cur;
}
}
else // 有2个孩子
{ // 使用左侧最大的孩子来领养
// 寻找左侧最大
BST_node* maxnode = cur->_left;
BST_node* max_parent = cur;
while (maxnode->_right)
{
max_parent = maxnode;
maxnode = maxnode->_right;
}
// 现在又进入一种特殊情况,1.max_parent就没进入循环,2.进入了循环
if (max_parent == cur)
{
max_parent->_left = maxnode->_left;
}
else
{
max_parent->_right = maxnode->_left;
}
// 值转移
cur->_val = maxnode->_val;
delete maxnode;
}
return 1;
}
~BSTree()
{
Distroy_Re(root);
root = nullptr;
}
protected:
bool Re_Insert_table(BST_node*& node, const T& val)
{
if (node == nullptr)
{
node = new BST_node(val);
return 1;
}
if (val < node->_val)
{
return Re_Insert_table(node->_left, val);
}
else if (val > node->_val)
{
return Re_Insert_table(node->_right, val);
}
else
{
return 0;
}
}
void Re_Print_table(const BST_node* node)
{
if (node == nullptr)
return;
Re_Print_table(node->_left);
cout << node->_val << " ";
Re_Print_table(node->_right);
}
BST_node* Re_Find_table(BST_node* node, const T& val)
{
if (node == nullptr)
return nullptr;
if (val < node->_val)
{
return Re_Find_table(node->_left, val);
}
else if (val > node->_val)
{
return Re_Find_table(node->_right, val);
}
else
{
return node;
}
}
bool Re_Erease_table(BST_node*& node, const T& val)
{
// 首先我们先找到值
if (node == nullptr)
{
return 0; // 如果访问到了空,则说明删除失败,原因是:不存在
}
if (val < node->_val)
{
return Re_Erease_table(node->_left, val);
}
else if (val > node->_val)
{
return Re_Erease_table(node->_right, val);
}
else
{
// 开始删除目标数据。方法如下;
// 1. 就按照非递归的思路,不用改多少代码
// 2. 使用递归方法,优势就是代码简洁
// 这里使用方法2
BST_node* del = node; // 保存每次访问的对象,如果是目标,就备份好了
if (node->_left == nullptr)
{
node = node->_right;
}
else if (node->_right == nullptr)
{
node = node->_left;
}
else
{
//处理左右都有孩子的目标
// 左侧查找最大值,右侧查找最小值
BST_node* max_node = node->_left;
while (max_node->_right)
{
max_node = max_node->_right;
}
// 完成循环后,max_node最多有左孩子,然后数据交换,我们以目标左侧树为起点
// 再次递归删除替换数据。
swap(max_node->_val, node->_val);
return Re_Erease_table(node->_left, val); //已经完成删除,就直接退出,以免触发删除delete
}
// 查找到删除数据
delete del;
}
}
void Distroy_Re(BST_node*& node) // 我们采用递归删除
{
if (node == nullptr)
return;
// 先处理左右孩子
Distroy_Re(node->_left);
Distroy_Re(node->_right);
delete node;
node = nullptr;
}
private:
BST_node* root = nullptr;
};
}
結論
このセクションは以上です。閲覧していただきありがとうございます。ご提案がある場合は、コメント欄に残してください。お友達に何かを持ってきた場合は、「いいね!」を残してください。あなたの「いいね!」と注目がブログ投稿になります。主な動機創造のための