二分木(1)-概要(二分探索木実装あり)

ツリーとグラフは、典型的な非線形データ構造です。


ツリーの定義:ツリーは、n(n> = 0)ノードの有限集合です。n = 0の場合、空のツリーになります。空でないツリーでは、次の特性があります。

  1. 1つの特定のノードのみがルートノードになります
  2. n> 1の場合、残りのノードはm(m> 0)の無関係な有限集合に分割できます。各集合もツリーであり、ルートのサブツリーになります。
树的最大层级数,称为树的高度或者深度


二分木の定義:二分木は该树的每个节点最多有2个子节点,有可能时两个,也可能只有1个,或者是没有

二分木の2つの特別な形式:

满二叉树:一个二叉树的所有非叶子节点都存在左孩子和右孩子,并且所有叶子节点都在同一层级上,这个树就是满二叉树
完全二叉树:一个二叉树的`最后一个节点之前`所有非叶子节点都存在左孩子和右孩子,并且所有叶子节点都在同一层级上

二分木の物理ストレージ構造

1.チェーン収納構造

リンクリストのストレージ構造を使用して、バイナリツリーの各ノードには3つの部分が含まれます

  • データを格納するためのデータ変数
  • 左の子への左ポインタ
  • 右の子への右のポインタ

2.アレイ

配列ストレージを使用する場合、バイナリツリーのノードは、階層順序に従って配列内の対応する位置に配置されます。ノードの左または右の子が空の場合、配列の対応する位置も空になります

なぜこのように設計されているのですか?これにより、配列内のバイナリツリーの子ノードと親ノードを簡単に見つけることができるためです。

親ノードの添え字が親であるとすると、その左側の子ノードの添え字は2親+ 1であり、右側の子ノードの添え字は2親+2です。

順番に。左の子ノードの添え字がleftchildであるとすると、その親ノードの添え字は(leftchild-1)/ 2です。

显然,对于一个稀疏的二叉树来说,用数组表示法是非常浪费空间的。
然而因为完全二叉树的特殊性,用数组来存储是非常合适的,比如二叉堆,一种特殊的完全二叉树

二分木の応用

上記の完全な二分木、完全な二分木、二分ヒープなどから、一般的な二分木に加えて、多くの形式の二分木があります。

では、非常に多くの種類の二分木の機能は何ですか?

  1. 検索操作に使用されます
  2. 要素間の相対的な順序を維持する

二分探索木

バイナリソートツリー、バイナリ検索ツリーなどとも呼ばれます。名前が示すように、この二分木の要素には特定の順序があり、簡単に見つけることができます

定義:

  1. 左側のサブツリーが空でない場合は、左子树上所有节点的值均小于根节点的值
  2. 右側のサブツリーが空でない場合は、右子树上所有节点的值均大于根节点的值
  3. 左のサブツリーと右のサブツリーも二分探索木です
  4. 没有键值相等的结点

ここでは定義1を使用します。3つの定義(バイナリ検索ツリーBaidu Encyclopedia)が正しく、開発中にさまざまなニーズに応じて選択する必要があります。

Golangはバイナリ検索ツリーを実装しています。

package main

import "fmt"

// 二叉树
type BinaryTree struct {
    
    
	HeadNode *TreeNode
}

//二叉树节点
type TreeNode struct {
    
    
	Data   int32     // 链表上的数据
	Left   *TreeNode // 指针指向左孩子节点
	Right  *TreeNode // 指针指向右孩子节点
}

// 判断二叉树是否为空树,只需要判断第一个元素是否是 nil
func (self *BinaryTree) IsEmpty() bool {
    
    
	if self.HeadNode == nil {
    
    
		return true
	} else {
    
    
		return false
	}
}

// 在二叉查找树里添加数据
func (self *BinaryTree) Add(value int32) bool {
    
    
	neWNode := &TreeNode{
    
    Data: value,}
	node := self.HeadNode
	if node == nil {
    
    
		self.HeadNode = neWNode
		return true
	}
	for node.Data!=value {
    
    
		if value < node.Data {
    
    
			if node.Left!=nil{
    
    
				node = node.Left
			}else {
    
    
				node.Left=neWNode
				return true
			}
		}
		if value > node.Data {
    
    
			if  node.Right!=nil{
    
    
				node = node.Right
			}else {
    
    
				node.Right=neWNode
				return true
			}
		}
	}
	return false
}

// 查找元素所在的节点
func (self *BinaryTree) Get(value int32) *TreeNode {
    
    
	node := self.HeadNode
	for node != nil {
    
    
		if value < node.Data {
    
    
			node = node.Left
		} else if value > node.Data {
    
    
			node = node.Right
		} else {
    
    
			return node
		}
	}
	return nil
}

func NewBinaryTree() BinaryTree {
    
    
	BinaryTree := BinaryTree{
    
    HeadNode: nil}
	return BinaryTree
}

func main() {
    
    
	binaryTree := NewBinaryTree()
	fmt.Println(binaryTree.IsEmpty())
	fmt.Println(binaryTree.Add(3))
	fmt.Println(binaryTree.Add(3))
	fmt.Println(binaryTree.Add(2))
	fmt.Println(binaryTree.Add(4))
	fmt.Println(binaryTree.HeadNode.Data)
	fmt.Println(binaryTree.HeadNode.Right.Data)
	fmt.Println(binaryTree.HeadNode.Left.Data)
	fmt.Println(binaryTree.Get(4).Data)
	fmt.Println(binaryTree.IsEmpty())
}

二分探索木の時間計算量:

比較的バランスの取れたノード分布を持つ二分探索木では、ノードの総数がnの場合、ノードの検索の時間計算量はO(logn)であり、これはツリーの深さと同じです。
サイズを比較して段階的に検索するこの方法は、二分探索アルゴリズムと非常によく似ています。

ただし、極端な場合(データが挿入されるたびにデータのサイズが増減する)、ツリーの外観は半分になり、検索時間の複雑さはO(n)に減少します。

この問題を解決するには、後で紹介する二分木の自己平衡化が必要です。

おすすめ

転載: blog.csdn.net/csdniter/article/details/109788469