データ構造とアルゴリズム Nine Tree Advanced

バランスの取れた木

以前にバイナリ検索ツリーを研究したところ、そのクエリ効率は単純なリンクされたリストや配列よりもはるかに高いことがわかりました。ほとんどの場合、これは実際に当てはまりますが、残念なことに、最悪の場合でも、バイナリ ルックアップ ツリーのパフォーマンスは依然としてひどいものです。 。

たとえば、9、8、7、6、5、4、3、2、1 の 9 つのデータを順番に二分探索ツリーに挿入すると、最終的に構築されるツリーは次のようになります。

ここに画像の説明を挿入
要素 1 を見つけたい場合、検索効率は依然として非常に低いことがわかります。非効率の原因はツリーのバランスが悪く、すべてが左側に枝分かれしているためですが、挿入されたデータの影響を受けずに、生成されたツリーを完全な二分木のように見せることができる方法があれば、最悪の場合でも、検索の効率は依然として非常に優れています。

1.1 2-3 検索ツリー

検索ツリーのバランスを保つためにはある程度の柔軟性が必要なので、ここではツリー内のノードが複数のキーを保持できるようにします。正確には、標準二分探索ツリー2-结点(1 つのキーと 2 つのチェーンを含む) のノードを呼び出しますが、ここでは、3-结点2 つのキーと 3 つのチェーンが含まれていることを紹介します。2-结点合計内の各3-结点チェーンは、そこに格納されているキーを分割することによって生成された間隔に対応します。

1.1.1 2-3 検索木の定義

2-3 検索ツリーは空であるか、次の 2 つの要件を満たします。

  • 2 ノード:
    キー (およびそれに対応する値) と 2 つのチェーンが含まれます。左側のリンクが指す 2-3 ツリー内のキーはすべてノードより小さく、左側のリンクが指す 2-3 ツリー内のキーはノードよりも小さくなります。右側のリンクはノードよりも大きいです。
  • 3 ノード:
    2 つのキー (およびそれらに対応する値) と 3 つのチェーンが含まれます。左のリンクが指す 2-3 ツリー内のキーはすべてノードより小さく、左のリンクが指す 2-3 ツリー内のキーはすべてノードよりも小さくなります。中央のリンクはノードの 2 つのキーの間に位置し、右側のリンクが指す 2 ~ 3 ツリー内のキーはすべてノードよりも大きくなります。

ここに画像の説明を挿入

1.1.2 検索

二分探索木の探索アルゴリズムを一般化すると、二分探索木の探索アルゴリズムを直接得ることができます。キーがツリー内にあるかどうかを判断するには、まずそのキーをルート ノードのキーと比較します。いずれかに一致する場合は検索がヒットしますが、そうでない場合は、比較結果に従って対応する区間を指す接続を見つけ、それが指すサブツリー内で再帰的に検索を続けます。これが空のリンクの場合、検索は失敗します。

ここに画像の説明を挿入
ここに画像の説明を挿入

1.1.3 挿入

1.1.3.1 2 ノードに新しいキーを挿入する

2-3 ツリーへの要素の挿入は、二分探索ツリーへの要素の挿入と同じであり、まず検索し、次に見つからないノードにノードをハングする必要があります。2-3 ツリーが最悪の場合の効率を保証できる理由は、挿入後もバランスのとれた状態を維持できるためです。検索後に見つからなかったノードが 2 ノードの場合は簡単です。この 2 ノードに新しい要素を追加して 3 ノードにするだけです。ただし、探しているノードが 3 ノードになった場合は、少し難しくなる可能性があります。

ここに画像の説明を挿入

1.1.3.2 3 ノードを 1 つだけ含むツリーへの新しいキーの挿入

2-3 ツリーには 3 ノードが 1 つだけ含まれており、このノードには 2 つのキーがあり、3 番目のキーを挿入する余地がない場合、最も自然な方法は、このノードが 3 つの要素を格納できると仮定し、一時的に次のようにすることです。 4 ノードであり、4 つのリンクが含まれています。次に、左キーを左の子として、右キーを右の子として、この 4 ノードの中央の要素をプロモートします。挿入が完了すると、バランスの取れた 2 ~ 3 の検索木になり、木の高さが 0 から 1 に変わります。
ここに画像の説明を挿入

1.1.3.3 親が 2 ノードである 3 ノードに新しいキーを挿入する

上記の場合と同様に、3 ノードに新しい要素を挿入して一時的な 4 ノードにし、ノード内の中央の要素を親ノード (2) にプロモートすることもできます。親ノードを 3 ノードにして、左右のノードを 3 ノードの適切な位置にぶら下げます。

ここに画像の説明を挿入
ここに画像の説明を挿入

1.3.1.4 親が 3 ノードである 3 ノードに新しいキーを挿入する

挿入したノードが3ノードの場合、ノードを分割して中間要素を親ノードに昇格させますが、このとき親ノードは3ノードであり、挿入後は親ノードが4ノードになります、その後、親ノードが 2 ノードになるまで中間要素を親ノードにプロモートし続け、その後 3 ノードに変えるため、分割を続ける必要はありません。
ここに画像の説明を挿入
ここに画像の説明を挿入

1.3.1.5 ルートノードの分解

挿入されたノードからルート ノードまでのパスがすべて 3 ノードである場合、ルート ノードは最終的に一時的な 4 ノードをプログラムします。このとき、ルート ノードを 2 つの 2 ノードに分割し、ノードに 1 を加える必要があります。木の高さ。
ここに画像の説明を挿入

1.3.4 2 ~ 3 本のツリーのプロパティ

2-3 ツリーの挿入操作の分析を通じて、挿入時に 2-3 ツリーのバランスを維持するために、2-3 ツリーはいくつかのローカル変換を行う必要があることがわかりました。

完全にバランスのとれた 2 ~ 3 ツリーには次の特性があります。

  1. ルート ノードへの空のリンクのパス長は等しいです。
  2. 4 ノードを 3 ノードに変換する場合、木の高さは変化しません。ルート ノードが一時的な 4 ノードの場合のみ、ルート ノードが分解されると木の高さは +1 になります。
  3. 2-3 ツリーと通常の二分探索ツリーの最大の違いは、通常の二分探索ツリーが上から下に成長するのに対し、2-3 ツリーは下から上に成長することです。

1.3.5 2-3ツリーの実装

2-3 ツリーを直接実装する場合は、次の理由からさらに複雑になります。

  • さまざまなノード タイプを処理する必要がありますが、これは非常に面倒です。
  • ノードを下に移動するには、複数の比較操作が必要です。
  • 4 ノードを分割するには上に移動する必要があります。
  • 4 ノードを分割する状況は数多くあります。

2-3 探索木の実装はより複雑になり、場合によっては挿入後のバランス操作により効率が低下する可能性があります。しかし、2-3 探索ツリーは比較的重要な概念とアイデアとして、後で説明する赤黒ツリー、B ツリー、B+ ツリーにとって非常に重要です。

1.2 赤黒の木

前に 2-3 ツリーを紹介しましたが、2-3 ツリーは要素を挿入した後もツリーがバランスの取れた状態に保たれることがわかります。最悪のケースでは、すべての子ノードが 2 ノードになり、高さは通常の二分探索木と比較して、木は lgN です。最悪の場合の木の高さは N です。これにより、最悪の場合の時間計算量が保証されますが、2-3 木は実装するには複雑すぎるため、 2-3 ツリーのアイデアの簡単な実装である赤黒ツリーを紹介します。

赤黒ツリーは主に 2-3 ツリーをエンコードします。赤黒ツリーの背後にある基本的な考え方は、標準の二分探索ツリー (完全に 2 ノードで構成) といくつかの追加
情報 (3 ノードの置き換え)木が2〜3本。ツリー内のリンクを 2 つのタイプに分類します。

赤いリンク: 2 つの 2 ノードを接続して 3 ノードを形成します; 黒のリンク: 2-3 ツリーの通常のリンクです。

具体的には、3 ノードを、左に傾斜した赤いリンクで接続された 2 つの 2 ノード (2 つの 2 ノード、そのうちの 1 つはもう一方の左側の子) として表します。この表記法の利点は、標準二分探索ツリーの get メソッドを変更せずに直接使用できることです。

ここに画像の説明を挿入

1.2.1 赤黒木の定義

赤黒ツリーは、赤黒リンクを含み、次の条件を満たす二分探索ツリーです。

  1. 赤いリンクはすべて左のリンクです。
  2. 2 つの赤いリンクに同時に接続されるノードはありません。
  3. ツリーは完全に黒のバランスが取れています。つまり、空のリンクからルート ノードまでのパス上の黒のリンクの数は同じです。

赤黒ツリーと 2-3 ツリーの対応は次のとおりです。
ここに画像の説明を挿入

1.2.2 赤黒ツリーノードAPI

各ノードには、それ自体を指す (親ノードからそのノードを指す) リンクが 1 つだけあるため、前のノード ノードにブール変数 color を追加して、リンクの色を表すことができます。それを指すリンクが赤の場合、変数の値は true であり、リンクが黒の場合、変数の値は false です。
ここに画像の説明を挿入
API 設計:
ここに画像の説明を挿入
コード

private class Node<Key,Value>{
    
    
	//存储键
	public Key key;
	//存储值
	private Value value;
	//记录左子结点
	public Node left;
	//记录右子结点
	public Node right;
	//由其父结点指向它的链接的颜色
	public boolean color;
	public Node(Key key, Value value, Node left,Node right,boolean color) {
    
    
		this.key = key;
		this.value = value;
		this.left = left;
		this.right = right;
		this.color = color;
	}
}

1.2.3 バランス

赤黒ツリーへの追加、削除、変更、およびクエリの後、赤の右リンクまたは 2 つの連続した赤リンクが存在する可能性が非常に高く、これらは赤黒ツリーの定義を満たしていません。これらのケースを回転させる必要があります。 赤と黒のツリーのバランスが保たれるように修正します。

1.2.3.1 左利き

ノードの左の子ノードが黒、右の子ノードが赤の場合、左回転が必要です。
前提: 現在のノードは h で、その右の子ノードは x です。

左手プロセス:

  1. x の左の子ノードが h の右の子ノードになるようにします。 h.right=x.left;
  2. h を x の左の子とします: x.left=h;
  3. h の色属性を x の色属性値に変更します。 x.color=h.color;
  4. h の color 属性を RED にします。 h.color=true;

ここに画像の説明を挿入

1.2.3.2 右回転

あるノードの左の子ノードが赤で、左の子ノードの左の子ノードも赤の場合、右回転が必要です 前提条件
: 現在のノードが h で、その左の子ノードが x であること。

右回転プロセス:

  1. x の右の子を h の左の子とします。 h.left = x.right;
  2. h を x の右側の子とします: x.right=h;
  3. x の色を h の color 属性値に変更します。 x.color = h.color;
  4. h の色を RED とします。

ここに画像の説明を挿入

1.2.4 単一の 2 ノードへの新しいキーの挿入

キーが 1 つだけある赤黒ツリーには、2 ノードが 1 つだけあります。別のキーが挿入されたらすぐに、キーを回転する必要があります。

  • 新しいキーが現在のノードのキーより小さい場合は、赤のノードを追加するだけでよく、新しい赤黒ツリーは単一の 3 ノードと完全に同等になります。

ここに画像の説明を挿入

  • 新しいキーが現在のノードのキーより大きい場合、新しく追加された赤いノードは赤い右リンクを生成します。このとき、赤い右リンクを左回転して左リンクに変える必要があります。操作が完了しました。形成された新しい赤黒ツリーは、2 つのキーと 1 つの赤いリンクを含む 3 ノードと同等です。
    ここに画像の説明を挿入

1.2.5 新しいキーを一番下の 2 ノードに挿入します

二分探索ツリーと同じ方法で新しいキーを赤黒ツリーに挿入すると、(順序を確保するために) ツリーの一番下に新しいノードが追加されます。唯一の違いは、新しいキーへの赤いリンクを使用することです。ノードはその親ノードに接続されます。親が 2 ノードの場合、今説明した 2 つのアプローチが引き続き適用されます。

ここに画像の説明を挿入
ここに画像の説明を挿入

1.2.6 色の反転

ノードの左の子ノードと右の子ノードの色が赤色の場合、つまり仮の4ノードが出現します この時は左の子ノードと右の子ノードの色を変更するだけで済みますノードを黒に変更し、同時に現在のノードの色を赤に変更します。
ここに画像の説明を挿入

1.2.7 新しいキーを二重キー ツリー (つまり 3 ノード) に挿入します。

この状況は、次の 3 つのサブケースに分類できます。

  1. 新しいキーは元のツリーの両方のキーよりも大きいです
    ここに画像の説明を挿入
  2. 新しいキーは元のツリーの 2 キー未満です
    ここに画像の説明を挿入
    ここに画像の説明を挿入
  3. 新しいキーは元の番号の 2 つのキーの間にあります
    ここに画像の説明を挿入
    ここに画像の説明を挿入

1.2.8 ルートノードの色は常に黒です

以前にノード API を導入したとき、Node オブジェクトの color 属性は、親ノードから現在のノードへの接続の色を表していました。ルート ノードには親ノードがないため、挿入操作のたびに、両方ともルートノードの色を黒に設定します。

1.2.9 ツリーの一番下の 3 つのノードに新しいキーを挿入します。

ツリーの一番下にある 3 つのノードの下に新しいノードが追加されたとします。上で述べた 3 つの状況が発生します。新しいノードへのリンクは、3 ノードの右リンク (この場合は色を変換するだけです)、左リンク (この場合は右に回転してから変換する必要があります)、または中央のリンクになります。リンク (この場合、最初に左に回転し、次に右に回転し、最後に色を変換する必要がある場合)。色変換により中間ノードの色が赤になります。これは、中間ノードを親ノードに送信することと同じです。これは、新しいキーが親ノードに挿入され続けることを意味し、2 ノードまたはルート ノードが見つかるまで同じ方法を使用するだけで問題を解決できます。

ここに画像の説明を挿入
ここに画像の説明を挿入

1.2.10 赤黒ツリーのAPI設計

ここに画像の説明を挿入

1.2.11 赤黒ツリーの実装

//红黑树代码
public class RedBlackTree<Key extends Comparable<Key>, Value> {
    
    
	//根节点
	private Node root;
	//记录树中元素的个数
	private int N;
	//红色链接
	private static final boolean RED = true;
	//黑色链接
	private static final boolean BLACK = false;
	/**
	* 判断当前节点的父指向链接是否为红色
	*
	* @param x
	* @return
	*/
	private boolean isRed(Node x) {
    
    
		//空结点默认是黑色链接
		if (x == null) {
    
    
			return false;
		}
		//非空结点需要判断结点color属性的值
		return x.color == RED;
	}
	/**
	* 左旋转
	*
	* @param h
	* @return
	*/
	private Node rotateLeft(Node h) {
    
    
		//找出当前结点h的右子结点
		Node hRight = h.right;
		//找出右子结点的左子结点
		Node lhRight = hRight.left;
		//让当前结点h的右子结点的左子结点成为当前结点的右子结点
		h.right = lhRight;
		//让当前结点h称为右子结点的左子结点
		hRight.left = h;
		//让当前结点h的color编程右子结点的color
		hRight.color = h.color;
		//让当前结点h的color变为RED
		h.color = RED;
		//返回当前结点的右子结点
		return hRight;
	}
	/**
	* 右旋
	*
	* @param h
	* @return
	*/
	private Node rotateRight(Node h) {
    
    
		//找出当前结点h的左子结点
		Node hLeft = h.left;
		//找出当前结点h的左子结点的右子结点
		Node rHleft = hLeft.right;
		//让当前结点h的左子结点的右子结点称为当前结点的左子结点
		h.left = rHleft;
		//让当前结点称为左子结点的右子结点
		hLeft.right = h;
		//让当前结点h的color值称为左子结点的color值
		hLeft.color = h.color;
		//让当前结点h的color变为RED
		h.color = RED;
		//返回当前结点的左子结点
		return hLeft;
	}
	/**
	* 颜色反转,相当于完成拆分4-节点
	*
	* @param h
	*/
	private void flipColors(Node h) {
    
    
		//当前结点的color属性值变为RED;
		h.color = RED;
		//当前结点的左右子结点的color属性值都变为黑色
		h.left.color = BLACK;
		h.right.color = BLACK;
	}
	/**
	* 在整个树上完成插入操作
	*
	* @param key
	* @param val
	*/
	public void put(Key key, Value val) {
    
    
		//在root整个树上插入key-val
		root = put(root, key, val);
		//让根结点的颜色变为BLACK
		root.color = BLACK;
	}
	/**
	* 在指定树中,完成插入操作,并返回添加元素后新的树
	*
	* @param h
	* @param key
	* @param val
	*/
	private Node put(Node h, Key key, Value val) {
    
    
		if (h == null) {
    
    
			//标准的插入操作,和父结点用红链接相连
			N++;
			return new Node(key, val, null, null, RED);
		}
		//比较要插入的键和当前结点的键
		int cmp = key.compareTo(h.key);
		if (cmp < 0) {
    
    
			//继续寻找左子树插入
			h.left = put(h.left, key, val);
		} else if (cmp > 0) {
    
    
			//继续寻找右子树插入
			h.right = put(h.right, key, val);
		} else {
    
    
			//已经有相同的结点存在,修改节点的值;
			h.value = val;
		}
		//如果当前结点的右链接是红色,左链接是黑色,需要左旋
		if (isRed(h.right) && !isRed(h.left)) {
    
    
			h=rotateLeft(h);
		}
		//如果当前结点的左子结点和左子结点的左子结点都是红色链接,则需要右旋
		if (isRed(h.left) && isRed(h.left.left)) {
    
    
			h=rotateRight(h);
		}
		//如果当前结点的左链接和右链接都是红色,需要颜色变换
		if (isRed(h.left) && isRed(h.right)) {
    
    
			flipColors(h);
		}
		//返回当前结点
		return h;
	}
	//根据key,从树中找出对应的值
	public Value get(Key key) {
    
    
		return get(root, key);
	}
	//从指定的树x中,查找key对应的值
	public Value get(Node x, Key key) {
    
    
		//如果当前结点为空,则没有找到,返回null
		if (x == null) {
    
    
			return null;
		}
		//比较当前结点的键和key
		int cmp = key.compareTo(x.key);
		if (cmp < 0) {
    
    
			//如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
			return get(x.left, key);
		} else if (cmp > 0) {
    
    
			//如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
			return get(x.right, key);
		} else {
    
    
			//如果要查询的key等于当前结点的key,则树中返回当前结点的value。
			return x.value;
		}
	}
	//获取树中元素的个数
	public int size() {
    
    
		return N;
	}
	//结点类
	private class Node {
    
    
		//存储键
		public Key key;
		//存储值
		private Value value;
		//记录左子结点
		public Node left;
		//记录右子结点
		public Node right;
		//由其父结点指向它的链接的颜色
		public boolean color;
		public Node(Key key, Value value, Node left, Node right, boolean color) {
    
    
			this.key = key;
			this.value = value;
			this.left = left;
			this.right = right;
			this.color = color;
		}
	}
}
//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		RedBlackTree<Integer, String> bt = new RedBlackTree<>();
		bt.put(4, "二哈");
		bt.put(1, "张三");
		bt.put(3, "李四");
		bt.put(5, "王五");
		System.out.println(bt.size());
		bt.put(1,"老三");
		System.out.println(bt.get(1));
		System.out.println(bt.size());
	}
}

2 つの B ツリー

これまでに、二分探索木、2-3 木、およびその実現赤黒木について学びました。2-3 ツリーでは、ノードは最大 2 つのキーを持つことができ、その実装の赤黒ツリーでは、リンクに色を付ける方法を使用してこれら 2 つのキーを表現します。次に、もう 1 つの木構造 B-tree について学習します。このデータ構造では、1 つのノードに 2 つ以上のキーが存在することが許可されます。

B ツリーは、データの保存、並べ替え、検索、順次読み取り、挿入、削除などの操作を O(logn) の時間計算量で実行できるツリー状のデータ構造です。

2.1 Bツリーの特徴

B ツリーでは、ノードに複数のキー (3、4、5、またはそれ以上) を含めることができますが、特定の実装に依存するため、確実ではありません。ここで、B ツリーを構築するためにパラメーター M を選択します。これを M 次 B ツリーと呼ぶことができます。その場合、ツリーは次の特性を持つようになります。

  • 各ノードには最大で M-1 個のキーがあり、昇順に配置されます。
  • 各ノードは最大 M 個の子ノードを持つことができます。
  • ルート ノードには少なくとも 2 つの子ノードがあります。

ここに画像の説明を挿入
実際のアプリケーションでは、B ツリーの次数は一般に比較的大きい (通常は 100 より大きい) ため、大量のデータが格納されている場合でも、B ツリーの高さは依然として比較的小さいため、アプリケーションによってはシナリオに応じて、その利点を反映できます。

2.2 Bツリー格納データ

パラメーター M が 5 に選択されている場合、各ノードには最大 4 つのキーと値のペアが含まれます。B ツリーのデータ ストレージを確認するために 5 次の B ツリーを例に挙げてみましょう。
ここに画像の説明を挿入

2.3 ディスクファイルへの B ツリーの適用

私たちのプログラムでは、IO を介してファイルを操作することが避けられず、ファイルはディスクに保存されます。コンピュータはファイルシステムを通じてディスク上のファイルを操作しますが、ファイルシステムではBツリーデータ構造が使用されます。

2.3.1 ディスク

ディスクには GB から TB の大量のデータを保存できますが、機械の動作を伴うため読み取り速度は比較的遅く、読み取り速度はミリ秒レベルです。
ここに画像の説明を挿入
ディスクはプラッターで構成されており、各プラッターにはプラッターとも呼ばれる 2 つの面があります。ディスクの中心には回転可能なスピンドルがあり、これによりディスクは一定の回転速度 (通常は 5400 rpm または 7200 rpm) で回転します。ディスクにはそのようなディスクが複数枚含まれ、密閉容器に梱包されます。プラッターの各表面はトラックと呼ばれる同心円のセットで構成され、各トラックはセクターのセットに分割され、各セクターには同数のデータ ビット (通常は 512 のサブセクション) が含まれ、セクター間にはデータが存在しないギャップで区切られています。保管されています。

2.3.2 ディスク I/O

ここに画像の説明を挿入
ディスクは、プラッターの表面に保存されているビットの読み書きにヘッドを使用します。ヘッドは、任意のトラック上にヘッドを配置するためにプラッターの半径に沿って前後に移動する可動アームに取り付けられています。これはシーク操作と呼ばれます。トラック上に配置されるとプラッターが回転し、トラック上の各ビットがヘッドを通過すると、読み書きヘッドがビットの値を認識し、その値を変更することもできます。ディスクへのアクセス時間は、シーク時間、回転時間、転送時間に分けられます。

記憶媒体の特性上、ディスク自体のアクセスはメインメモリに比べて非常に遅く、また機械的な移動コストもかかるため、効率を高めるためにはディスクのI/Oを最小限に抑える必要があり、読み取りおよび書き込み操作を減らします。この目標を達成するために、多くの場合、ディスクは厳密にオンデマンドで読み取られるのではなく、毎回先読みされます。たとえ 1 バイトだけが必要な場合でも、ディスクはこの位置から開始して、一定の長さのデータを順にメモリに逆方向に読み取ります。 。この理論的根拠は、よく知られているコンピューターサイエンスです。

局所性の原則: あるデータが使用されると、通常は近くのデータがすぐに使用されます。シーケンシャル ディスク読み取りは効率が高いため (シーク
時間なし、回転時間は非常に短い)、先読みによって I/O 効率が向上します。

ページはコンピュータ管理メモリの論理ブロックです。ハードウェアとオペレーティング システムは、多くの場合、メイン メモリとディスク ストレージ領域を同じサイズの連続したブロックに分割します。各ストレージ ブロックはページ (1024 バイトまたはその整数倍) と呼ばれます。通常、ページの整数倍になります。メインメモリとディスクはページ単位でデータをやり取りします。プログラムが読み込むデータがメインメモリにない場合、ページフォールト例外が発生し、システムはディスクに読み取り信号を送り、ディスクはデータの開始位置を見つけます。連続して 1 ページまたは数ページを逆方向に読み取り、メモリにロードして異常終了し、プログラムは実行を続けます。

ファイル システムの設計者は、ディスク先読みの原理を使用してノードのサイズをページ (1024 バイトまたはその整数倍) に等しく設定し、各ノードが 1 つの I/O だけで完全にロードできるようにします。3 層の B ツリーは 1024 1024 1024 のほぼ 10 億のデータを保持できますが、これを二分探索ツリーに置き換えると、30 層が必要になります。オペレーティング システムが一度に 1 つのノードを読み取り、ルート ノードがメモリ内に保持されると仮定すると、B ツリーは 10 億のデータからターゲット値を見つけることができ、ターゲットを見つけるのに必要なハード ディスクの読み取りは 3 回未満だけです。ただし、赤黒ツリーは 30 倍未満で済むため、B ツリーにより IO の演算効率が大幅に向上します。

トリプルB+ツリー

B+ ツリーは B ツリーを変更したツリーであり、B ツリーとの違いは次のとおりです。

  1. 非リーフ ノードにはインデックス機能のみがあります。つまり、非リーフ ノードはキーのみを格納し、値は格納しません。
  2. ツリーのすべてのリーフ ノードは順序付けされたリンク リストを形成し、すべてのデータをキー ソートの順序でたどることができます。

3.1 B+ ツリー ストレージ データ

パラメーター M が 5 に選択されている場合、各ノードには最大 4 つのキーと値のペアが含まれます。B+ ツリーのデータ ストレージを確認するために、5 次の B+ ツリーを例として考えてみましょう。
ここに画像の説明を挿入

2.2 B+ ツリーと B ツリーの比較

B+ ツリーの利点は次のとおりです。
1. B+ ツリーには非リーフ ノードの実データが含まれず、インデックスとしてのみ使用されるため、同じメモリでより多くのキーを格納できます。2. B+ ツリーのリーフ ノードはすべて接続されているため、ツリー全体のトラバースにはリーフ ノードの線形トラバースのみが必要です。また、データが順番に並んでつながっているので、区間検索や検索に便利です。B ツリーでは、各レイヤーを再帰的に走査する必要があります。

B ツリーの利点は、
B ツリーの各ノードにキーと値が含まれているため、キーに基づいて値を検索する場合、値を見つけるにはキーの場所を見つけるだけで済みますが、 B+ ツリーはリーフ ノードにのみデータを格納するため、インデックスを検索するたびに、値を見つけるためにツリーの最大の深さ、つまりリーフ ノードの深さを見つける必要があります。

3.3 データベースへの B+ ツリーの適用

データベースの運用において、クエリ操作は最も頻繁に行われる操作と言えます。そのため、データベースを設計する際にはクエリの効率を考慮する必要があります。多くのデータベースでは、クエリの効率を向上させるために B+ ツリーが使用されています。クエリ;

データベースを運用する際、クエリ効率を向上させるために、テーブルの特定のフィールドに基づいてインデックスを構築することでクエリ効率を向上させることができますが、実際、このインデックスは B+ ツリーのデータ構造によって実現されています。

3.3.1 主キーインデックスクエリなし

ここに画像の説明を挿入
select * from user where id=18 を実行します。最初のデータから開始して 6 番目のデータまで移動し、id=18 を見つける必要があります。その後、ターゲットの結果をクエリできます。次のことが必要です。合計6回比較します。

3.3.2 主キーインデックスクエリの作成

ここに画像の説明を挿入

3.3.3 範囲クエリ

実行select * from user where id>=12 and id<=18: インデックスがある場合、B+ ツリーのリーフ ノードは順序付けされたリンク リストを形成するため、ID が 12 のリーフ ノードを見つけて、リンク リストをたどる順序で逆方向に検索するだけで済みます。非常に効率的です。

おすすめ

転載: blog.csdn.net/qq_33417321/article/details/122009572