C ++データ構造バイナリソートツリー/バイナリ検索ツリー


序文

今日、赤黒木について学んでいたときに、入門クラスで二分探索木の例を見ましたが、二分探索木のコードを書いたことがないと思ったので、今回書きました。バイナリソートツリーの性質と、コードの記述中に発生した問題を共有します。

概念

前書き

書く前に、まず二分木とは何かを理解する必要があります

二分探索木、ルートノードがあり、各ノードは最大2つの子ノードしか持てません。左側の子ノードの値はその親ノードよりも小さく、右側の子ノードの値はその親ノードよりも大きくなります。 。

データを1つずつ挿入します。43,98,2,4,0,5
次の図、挿入が完了した後の二分探索木がどのように見えるかを示しています。

ここに画像の説明を挿入
データ操作は、追加、削除、変更、およびチェックにすぎません
バイナリソートツリーの場合、
追加は挿入ノード、
削除は削除ノード、
チェックはクエリ値xがツリーに存在するかどうかです。

バイナリソートツリーの存在と意味についての考え

このとき、誰かが「検索に配列を使用するのは良いことではありませんか?forループでそれを行うことができます」と尋ねるかもしれません。
ここでは
、ループが配列をトラバースするための時間計算量の波を分析します。最悪の場合は最後の要素までトラバースします。時間計算量はO(n)で
あり、バイナリ検索ツリーの検索方法は、バイナリ検索と同様です。探索木の高さとして、木の高さを見つける時間計算量はO(logn)であることは誰もが知っています。

このとき、誰かがもう一度尋ねるかもしれません。「時間計算量はすべてO(logn)なので、バイナリ検索を直接見つけて
も大丈夫ですか?ツリーを構築するのはとても面倒です。」良い質問ですが、考えてみましょう。それについてもう一度、バイナリ検索をする前に、我々は最初の配列をソートする必要がある。我々はより速くソートを使用する場合、マージするか、高速なソート時間計算量はO(nlogn)で、プラスの時間複雑バイナリサーチO(LOGN)私たちの避難所まだ話していません配列にデータを挿入する時間の複雑さと拡張が必要になる可能性のある問題)、つまり、最悪の場合最初のクエリ時間はバイナリソートツリーの2倍以上になります

現時点では、読者が私に簡単な方法を呼ぶかもしれません、「あなたは時間を分類したいと言います、あなたは時間の成果ではありませんか?」
良い侮辱、それは私たちが放っておく成果の時間と空間の複雑さを取ります、私たちは話しますこの操作を増やすことについて
以下のためにソートされた配列にデータの一部を挿入するだけ一度トラバース、再ソートせずに、および時間の複雑さはO(N)である。
しかしためのバイナリソートツリー新しいデータを挿入することが横断する必要があるため、挿入前最長の長さはツリーの高さであるため、時間計算量はO(logn)です。1つデータを挿入した場合の効果は明ら​​かではないかもしれませんが、10、10,000、または1億を挿入しますか?明らかに、長期的な観点から、バイナリソートツリーに費やされる時間は、バイナリ検索を使用するよりも大幅に優れています。

そんなに多くのことを言ったので、私はまだ乾物について話していません、トピックに取り掛かりましょう!

操作とコード表示

要素を挿入

二分探索木の特性に応じて、データを挿入するというアイデアを簡単に思いつくことができます。挿入されたノードが比較ノードよりも小さく、比較ノードの左側のブランチが空の場合、それは挿入されます比較ノードの左側のブランチで。空でない場合は、比較ノードの左側のブランチで同じ挿入操作(再帰)を実行します。挿入ノードが比較ノードよりも大きい場合、操作は同じです。方向を変えるだけです。
プロセスを少し分析すると、再帰を使用する必要があることがわかります。コードは次のとおりです。

void BinSearchTree::insert(int x)
{
    
    
	/*
	这个重载是为了插入方便
	只要insert一个值就可以了
	不用自己再新建节点
	*/
	Node *root=new Node(x);
	if(this->Root==NULL)
	{
    
    
		this->Root=root;
		return;
	}
	Node *p=this->Root;
	insert(p,root);
}

void BinSearchTree::insert(Node *p,Node *root)
{
    
    
	/*
	可不可以有相同的元素要根据需求来
	如果不要相同元素,最好在用户插入的时候说明
	如果有相同,插左边还是右边都可以,但定下左右就不能改了
	这里我写的时候就简单一点,遇到相同的就不插了
	*/

	//学了点算法,感觉写起来没以前难了(滑稽)
	if(root->data==p->data) return;//遇到相同,直接不插入
	if(root->data < p->data)
	{
    
    
		if(p->lChild==NULL)
		{
    
    
			p->lChild=root;
			return;
		}
		else insert(p->lChild,root);
	}
	else//root->data > p->data
	{
    
    
		if(p->rChild==NULL)
		{
    
    
			p->rChild=root;
			return;
		}
		else insert(p->rChild,root);
	}
}

必要に応じて同じ要素を使用できますか?同じ要素が必要ない
場合は
ユーザーがいつ挿入するかを説明することをお勧めします。同じ要素の場合は、左または右に挿入できますが、左右
設定すると変更できません。、同じプラグを差し込まないでください

要素を見つける

ここでは2つ書いています
。1つは要素が検索ツリーにあるかどうかを見つけることで、もう1つは

持つノードを返すことです。私のツリーは同一の要素を持たないように設定されているため、複数を見つけることに問題はありません。要素。

要素が検索ツリーにあるかどうかを確認します

bool BinSearchTree::search(int x)
{
    
    
	if(x==this->Root->data) return true;
	Node *p=this->Root;
	return search(p,x);
}

bool BinSearchTree::search(Node *p,int x)
{
    
    
	if(p==NULL) return false;//找到空的地方都没有,说明真没有
	if(p->data==x) return true;
	if(x<p->data) search(p->lChild,x);
	else search(p->rChild,x);
}

値を所有するノードを返します

Node * BinSearchTree::searchNode(int x)
{
    
    
	if(this->Root->data==x) return this->Root;
	Node *p=this->Root;
	return searchNode(p,x);
}

Node * BinSearchTree::searchNode(Node *p,int x)
{
    
    
	if(p->data==x) return p;
	if(x<p->data) return searchNode(p->lChild,x);
	else return searchNode(p->rChild,x);
}

要素を削除


削除には3つのケースがあります:1。削除されたノードには左と右の子があります
2.削除されたノードには子が1つだけあり
ます3.削除されたノードには子がありません

以下のために
ケース1削除するノードの値が削除されるノードの右の子の中で最小のノードを交換します。
これは、ケース2やケース3に問題を回す
ケース2に削除されたノード点の親ノード削除されたノードの子ノード、次にノードを削除します。3
ノードを直接削除します

ps:最初のケースで正しいサブツリーの最小値を取る必要があるのはなぜですか?これは実際には非常に賢い方法です。
まず、削除するノードの右側のサブツリーは、削除するノードの左側のサブツリーよりも大きくする必要があります。
次に、削除するノードの右側のサブツリーのサブツリーの最小数として、削除するノードのすべての右側のサブツリーノードよりも大きい
ため、小さいため、削除するノードを置き換えるのが最適です。

void BinSearchTree::removeNode(int x)
{
    
    
	/*
	删除节点一共有三种情况
	1.删除的节点左右孩子都有
	2.删除的节点只有一个孩子
	3.删除的节点没有孩子

	对于
	1.将要删除节点的右孩子中的最小节点与要删除节点的值替换
	这样就将该问题变成了情况2 或者 情况3
	2.被删除节点的父节点指向被删除节点的子节点,然后删除该节点
	3.直接删除该节点

	ps:为什么第一种情况要取右子树的最小值?这其实十分巧妙
	首先,作为待删节点的右子树,肯定比待删节点的左子树大
	其次,作为待删节点右子树的最小子树数,比待删节点所有的右子树节点小
	所以其代替待删节点最好
	*/
	Node *p=searchNode(x);//找到待删除节点
	if(p->lChild==NULL && p->rChild==NULL) withNoChild(p);
	else if(p->lChild!=NULL && p->rChild!=NULL) withTwoChild(p);
	else withOneChild(p);
	return;
}
void BinSearchTree::withTwoChild(Node *p)//因为withOneChile有毛病,所以它也有毛病<---withOneChild好像没毛病
{
    
    //应该是它本身的毛病
	//情况1,将要删除节点的右孩子中的最小节点与要删除节点的值替换
	//这样就将该问题变成了情况2 或者 情况3
	Node *min=getMinNode(p->rChild);
	std::cout<<"右子树最小值为:";
	std::cout<<min->data<<std::endl;

	int minVal=min->data;

	if(min->lChild==NULL && min->rChild==NULL) withNoChild(min);
	else withOneChild(min);//<======我知道为什么会出问题了,当改了待删节点的数值后,就会出现两个min值
	//找父节点就会起冲突
	//所以应该先删在改

	p->data=minVal;//<-----要先删再改,不然会出现问题!!!
}

void BinSearchTree::withOneChild(Node *p)//有毛病<---貌似也没有毛病
{
    
    
	Node *father=getFather(p);
	Node *child;
	if(p->lChild==NULL) child=p->rChild;
	else child=p->lChild;

	//如果待删节点是其父节点的左孩子
	if(father->lChild->data==p->data) father->lChild=child;
	//如果待删节点是其父节点的右孩子
	else father->rChild=child;
	delete p;
}

void BinSearchTree::withNoChild(Node *p)//没毛病
{
    
    
	Node *father=getFather(p);
	std::cout<<father->data<<std::endl;
	if(father->lChild->data==p->data) father->lChild=NULL;
	else father->rChild=NULL;
	delete p;
}

運転結果

ここでは、例に43、98、2、4、0、5の数字を入力して、バイナリソートツリーを構築します。
ここに画像の説明を挿入
幅優先の結果は理論構造に準拠しています。
ここに画像の説明を挿入
ここでは、ノード2を削除し、
ここに画像の説明を挿入
削除プロセスは次の
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ようになります。上の図。ツリーを削除した後、階層トラバーサルを実行します。結果は理論上の結果と同じであり、コードが正しいことを大まかに確認できます。

バイナリソートツリーとそのソリューションの長所と短所

利点

利点私は二分探索木の意味の列に深く関わっているので、ここでは繰り返しません。

不利益

欠点について話す前に、下の写真
ここに画像の説明を挿入
を見てみましょう何か問題を見つけましたか?そうです、これもバイナリソートであり、単なる機能不全です。挿入されたデータが増減すると、バイナリソートツリーはリンクリストになり、ルックアップの時間計算量はO(n)に低下しますが、これは目的の結果ではありません。

解決

この問題を解決する方法は?これには、高度なデータ構造の赤黒木が含まれます

「どうして話さなかったの?」と聞かれるかもしれませんが、実はまだ赤黒木を読んでいないので、勉強が終わったら共有しましょう。

-------------------------------------------------- ------ 6.17更新------------------------------------------ --------------------
私は私の研究から戻ってきた、ここにある赤黒木の共有は、

おすすめ

転載: blog.csdn.net/weixin_44062380/article/details/106779951