1. ツリーの概念と構造
1.1 ツリーの概念
ツリーは非線形データ構造であり、n (n>=0) 個の有限ノードで構成されます。階層関係のあるコレクション。逆さまの木、つまり根が上を向き、葉が下を向いているように見えるので、木と呼ばれます。
1. ルート ノードと呼ばれる特別なノードがあります。ルート ノードには先行ノードがありません。
2. ルート ノードを除く残りのノードは、M (M>0) 個の素集合 T1、T2、...、Tm に分割され、それぞれが Ti (1<= i<= m) は、ツリーと同様の構造を持つ別のサブツリーです。 各サブツリーのルート ノードには先行ノードが 1 つだけあり、0 個以上の後続ノードを持つことができます。
3. したがって、ツリーは再帰的に定義されます。
注: ツリー構造では、サブツリー間に交差があってはなりません。交差しないと、ツリー構造になりません。
1.2 ツリーの関連概念
ノードの次数: ノードに含まれるサブツリーの数をノードの次数と呼びます。上の図に示すように、A は 6 です。< a i=2 >リーフ ノードまたはターミナル ノード: 次数 0 のノードはリーフ ノードと呼ばれます; 上の図に示すように: B、C、H、I... などのノード< a i=4>非終端ノードまたは分岐ノード: 0 以外の次数を持つノード。上の図に示すように: D などのノード、E、F、G... はブランチ ノードです。親ノードまたは親ノード: ノードに子ノードが含まれる場合、このノードは親と呼ばれます。上の図に示すように、A は B の親ノードです。: m (m>0) の互いに素なツリーの集合はフォレストと呼ばれます。 フォレスト: 特定のノードをルートとするサブツリー内のノードはすべて、このノードの子孫と呼ばれます。上の図に示すように、すべてのノードは A の子孫です。子孫: ルートからノードへのパス ブランチ上のすべてのノード。上の図に示すように: A はすべてのノードの祖先です。ノードの祖先: 親ノード同じレイヤー上の は互いにいとこです。上の図に示すように、H と I は互いに兄弟ノードです。いとこノード: ツリー内のノードの最大レベル。上に示すように: ツリーの高さは 4 です。ツリーの高さまたは深さ: ルートの定義から開始すると、ルートは第 1 レベル、ルートの子ノードは第 2 レベル、というようになります。ノード : ツリーでは、最大のノードの次数はツリーの次数と呼ばれます。上の図に示すように、ツリーの次数は 6 です。ツリーの次数:同じ親ノードを持つノードは兄弟ノードと呼ばれます。上に示すように、B と C は兄弟ノードです。兄弟ノード: のルート ノードノードに含まれるサブツリーは、ノードの子ノードと呼ばれます。上の図に示すように、B は A の子ノードです。子ノードまたは子ノード
1.3 木の表現
木構造は線形テーブルに比べて複雑で、それを保存したり表現したりするのが面倒です 値の範囲を保存するので、ノードとノード間の関係も保存する必要があります 実際には、さまざまな方法があります親表現、子表現、子親表現、子兄弟表現などのツリーを表現します。ここでは、子供の兄弟の最も一般的に使用される表現を簡単に理解します。
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
2. 二分木の概念と構造
2.1 コンセプト
バイナリ ツリーはノードの有限セットです。セットは次のとおりです:
1. または空
2. ルート ノードによって追加されます。2 つのノードで構成されます。バイナリ ツリー。 左サブツリーおよび右サブツリーとも呼ばれます。
上の写真からわかるように:
1. バイナリ ツリーには次数が 2 より大きいノードはありません
2. バイナリ ツリーのサブツリーは左と右のサブツリーに分割できますが、順序は区別できません。逆になるので、二分木は順序付けられた木になります
注: バイナリ ツリーは次の状況で構成されます。
2.2 特殊なバイナリ ツリー:
1. 完全なバイナリ ツリー: バイナリ ツリー。各レイヤのノード数が最大値に達すると、バイナリ ツリーは完全なバイナリ ツリー。つまり、バイナリ ツリーのレベル数が K で、ノードの総数が である場合、それは完全なバイナリ ツリーです。
2. 完全なバイナリ ツリー: 完全なバイナリ ツリーは非常に効率的なデータ構造です。完全なバイナリ ツリーが導出されます。完全なバイナリ ツリーから。深さ K でノードが n のバイナリ ツリーの場合、各ノードが深さ K の完全なバイナリ ツリー内の 1 から n までの番号が付けられたノードと 1 対 1 で対応する場合にのみ、完全なバイナリ ツリーと呼ばれます。完全なバイナリ ツリーは特別な種類の完全なバイナリ ツリーであることに注意してください。
2.3 二分木の性質
1. ルート ノードのレベル数が 1 の場合、非ノードの i 番目のレベルには最大 2^(i-1) 個のノードが存在します。 -空のバイナリ ツリー。
2. ルート ノードのレベル数が 1 の場合、 深さ h のバイナリ ツリーの最大ノード数は 2^h-1 .
3. 任意の二分木について、 次数が 0 の場合、葉ノードが n0 で次数 2 の枝の場合、ノードの数は n2 になります。 n0=n2 +1
4.ルート ノードは 1、n 個のノードを持つ完全なバイナリ ツリーの深さは h= log2(n+1) です。(ps: log2(n+1) は底 2 と n の対数です)対数として +1)。< a i=10> 5. n 個のノードを持つ完全なバイナリ ツリーの場合、すべてのノードが配列内で上から下、左から右の順序で 0 から始まる番号が付けられている場合、シリアル番号 i のノード:1. i>0 の場合、位置 i のノードの親番号: (i-1)/2; i=0、i はルート ノード番号であり、親ノードは存在しません
2 . 2i+1
3. 2i+2
2.4 バイナリツリーの記憶構造
バイナリ ツリーは通常、シーケンシャル構造とチェーン構造の 2 つの構造を使用して保存できます。
1. シーケンシャル ストレージ
シーケンシャル構造ストレージでは、ストレージに配列が使用されます。一般に、配列は完全なバイナリ ツリーではないため、完全なバイナリ ツリーを表す場合にのみ適しています。 、スペースの無駄が生じます。実際には、ストレージに配列を使用するのはヒープだけですが、ヒープについては次の章で具体的に説明します。バイナリ ツリーの順次ストレージは、物理的には配列であり、論理的にはバイナリ ツリーです。
2. リンク ストレージ
バイナリ ツリーのリンク ストレージ構造は、リンク リストを使用してバイナリ ツリーを表すこと、つまり、チェーンを使用して論理的なものを示すことを意味します。要素の関係。通常の方法では、リンク リストの各ノードは、データ フィールドと左右のポインタ フィールドの 3 つのフィールドで構成されます。左ポインタと右ポインタは、左の子と右の子がポイントするリンクのストレージ アドレスを与えるために使用されます。ノードのそれぞれが配置されます。連鎖構造は二分鎖と三分岐鎖に分けられ、現在は二分鎖を中心に学習していますが、後の講座では赤黒木や三分岐鎖などの高次データ構造について学習していきます。
3. 二分木の逐次構造と実装
3.1 二分木の逐次構造
通常のバイナリ ツリーは、無駄な領域が多くなる可能性があるため、配列での保存には適していません。完全なバイナリ ツリーは、順次構造の保存に適しています。 実際には、通常、シーケンシャル構造の配列を使用してヒープ (バイナリ ツリー) を保存します。ここでのヒープとオペレーティング システムの仮想プロセス アドレス空間内のヒープは 2 つであることに注意してください。 1 つはデータ構造、もう 1 つはオペレーティング システムのメモリを管理する領域の分割です。
3.2 ヒープの概念と構造
ヒープのプロパティ:
ヒープ内のノードの値は、常にその親ノードの値よりも大きくも小さくもありません。
ヒープは常に完全なバイナリ ツリーです。
3.3 ヒープの実装
3.3.1 ヒープの定義
配列はヒープの一番下に定義できます。
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
3.3.2 ヒープの初期化
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
3.3.3 ヒープの破壊
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
3.3.4 データの挿入
まず、スペースがいっぱいかどうかを判断し、いっぱいの場合は拡張します。次に、三項演算子を使用して最初の拡張かどうかを判断し、対応する拡張を実行してから、realloc の特性を使用して容量を拡張または調整します。 size の位置にデータを挿入し、次に size++ を挿入します。しかし、今はヒープではないので、上方調整を使用して調整する必要があります。
void HeapPush(HP* php, HPDataType x)
{
assert(php);
// 扩容
if (php->size == php->capacity)
{
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - 1);
}
3.3.5 データを上方調整する
このような小さなヒープ データのセットがあり、20 が挿入されて上方に調整されたとします。
最初のステップは、添字parent=(child-1)/2 を通じて親ノードを見つけ、親ノードかどうかを判断することです。親ノードがこの子ノードより小さい場合、調整は必要ありませんが、それ以外の場合はスワップが必要です。交換後は、添え字 child=parent となり、下の図では 10 が 4 に置き換えられます。次に、親の添字を再計算します。この時点では親はまだ 4 なので、parent=(parent-1)/2 によって父の添字が計算され、父ノードが息子ノードより小さくなるか、このノードが調整されるまで上向きに判断されます。ルートノードに。
したがって、この関数は親ノードの計算から開始し、次に while ループに入りますが、ループの終了条件は child=0、つまりルート ノードの位置に調整されることを意味します。ループに入るときは、まず息子ノードと父ノードのサイズを決定します。息子ノードが父ノードより小さい場合は交換を開始します。Swap 関数を使用して値を交換し、息子ノードの添字を調整します。息子ノードが父ノードよりも大きい場合、息子ノードもループから抜け出します。
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
3.3.6 印刷ヒープ
size-1 は最後のデータの添字であるため、左閉右開の for ループを作成するだけです。
void HeapPrint(HP* php)
{
assert(php);
for (size_t i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
3.3.7 データを下方調整する
配列表の末尾の削除と末尾の挿入の効率が良いため、末尾ノードと先頭ノードの位置を交換し、末尾ノードのサイズを削除することができます。
データ 20 がヘッド ノードに配置されると、この小さなヒープを維持できなくなるため、下方に調整する必要があります。下方に調整することは、子ノード child=parent*2+1 を見つけて書き込むことと同じです。 while ループの終了条件は、child=n が範囲外であることです。この時点でもう 1 つ行うことは、左右の子のうち小さい方の子を見つけることです。if child+1
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child<n)
{
//找小的孩子
if (child + 1 < n && a[child] > a[child + 1])
{
child = child + 1;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
//继续往下调整
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
3.3.8 データの削除
まずルート ノードとテール ノード、サイズを交換し、次に下方に調整します。
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0],&php->a[php->size - 1]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
3.3.9 ルートノードデータの取得
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
3.3.10 ヒープが空かどうかを確認する
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
今日の共有はここで終わります、読んでくださった皆様、ありがとうございました!