二分木とヒープの単純な実装

目次

1:木とは

[1]木の概念

[2] 木のその他の重要な概念

[3]木のいくつかの表現方法

2: 二分木とは

【1】コンセプトと特徴

[2] 2 つの特別な二分木

[3] 二分木の性質

[4] 二分木の2つの保存方法

3: ヒープの実装


1:木とは

[1]木の概念

前に学習したシーケンス テーブルとリンク リストは線形構造に属しますが、ツリーは非線形データ構造であり、 n (n>=0) 個の有限ノードで構成される階層関係のセットです根が上を向き、葉が下を向いている逆さまの木のように見えることから、ツリーと呼ばれます

図:

(1) ツリーには、ルート ノードと呼ばれる特別なノードがあり、図のノード A です

(2)ルート ノードを除いて、他のノードは M (M>0) 個の互いに素な集合T1 T2 ... Tmに分割され、各集合 Ti (1<= i <= m) は A 部分木であり、その構造は木に似ています各サブツリーのルート ノードには、先行ノードが 1 つだけあり、0 個以上の後続ノードを持つことができます。

(3) 親子ノード: たとえば、A は B、C、および D の親ノードであり、E および F はBの子ノードです。

(4) 葉ノード: E、F、C、G、 Hなどの子ノードを持たないノード。

注: ツリーのサブツリーはばらばらであり、ルート ノードを除く他のノードには 1 つの親ノードしかありません。

[2] 木のその他の重要な概念

図:

(1) ノードの次数:ノードに含まれるサブツリーの数をノードの次数と呼び、上図のようにA6
(2) 非終端ノードまたは分岐ノード:次数が 0 ではないノード;上図に示すように: D E F Gなどのノードは分岐ノードです。
(3) 兄弟ノード:同じ親ノードを持つノードは兄弟ノードと呼ばれます; 上図に示すように: BCは兄弟ノードです
(4) 木の次数: 木の中で最大のノードの次数を木の次数と呼ぶ; 上図のように: 木の次数は6
(5) ノードの階層:ルートの定義から始めて、ルートは最初のレイヤー、ルートの子ノードは 2 番目のレイヤーなどです。
(6) ツリーの高さまたは深さ:ツリー内のノードの最大レベル; 上の図に示すように: ツリーの高さは4
(7) ノードの祖先:ルートからノードのブランチ上のすべてのノードまで; 上図に示すように: Aはすべてのノードの祖先です
(8) 子と孫:特定のノードをルートとするサブツリー内のノードは、そのノードの子孫と呼ばれます上記のように、すべてのノードはAの子孫です
(9) 森: m ( m>0)個のバラバラの木の集まりを森と呼びます

[3]木のいくつかの表現方法

(1)シーケンステーブルを使用して子ノードのアドレスを格納する(複雑な構造)

(2)木の次数(最大ノードの次数)を説明し、子ノードのアドレスを格納するポインタ配列を設定

(3) 構造体配列の格納 

 (4)左子右兄弟の表記(一般的で、構造が比較的単純で論理が比較的明快)

2: 二分木とは

【1】コンセプトと特徴

コンセプト:

バイナリ ツリーはノードの有限セットであり、空であるかルート ノードと、左右のサブツリーと呼ばれる 2 つのバイナリ ツリーで構成されます

特徴:

1. 各ノードには最大で 2 つのサブツリーがあります。つまり、二分木には次数が 2 を超えるノードはありません。
2. 二分木の部分木は左右に分かれており、部分木の順序を逆にすることはできません。

[2] 2 つの特別な二分木

1. 完全二分木:

すべての葉ノードは最後の層にあります

すべての分岐ノードには 2 つの子があります

2. 完全な二分木:

このバイナリ ツリーに N 層があると仮定すると、最初の N-1 層がいっぱいになる必要があります。

最後のレイヤーはいっぱいにできますが、左から右に連続している必要があります

[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=LogN となります。

[4] 二分木の2つの保存方法

(1) 順次構造体格納(配列)

順次構造ストレージは、ストレージに配列を使用することです. 一般に、配列は完全なバイナリ ツリーを表現する場合にのみ適しています. 完全なバイナリ ツリーはスペースを無駄にするからです. 実際には、ストレージに配列を使用するのはヒープだけです二分木順次記憶域は、物理的には配列であり、論理的には二分木です。

(2) チェーン構造収納

リンク リストは、バイナリ ツリーを表すために使用されます。つまり、要素の論理関係を示すためにチェーンが使用されます。

通常の方法では、連結リストの各ノードは、データ フィールドと左右のポインター フィールドの 3 つのフィールドで構成され、左右のポインターを使用して、左側の子とノードの右の子が配置されます。

チェーン構造は、さらにバイナリ チェーンとトリプル チェーンに分けられます.現在、バイナリ チェーンが一般的に使用されており、赤黒木などの高レベルのデータ構造はトリプル チェーンを使用します。

3: ヒープの実装

ヒープは、配列で実装されたバイナリ ツリーであり、通常、完全なバイナリ ツリーを実装するために使用されます。

ヒープは大きなヒープと小さなヒープに分けられます

大きな山: 父 >= 子供

小さな山: 子供 >= 父親

このホワイト ペーパーでは、多くの機能を実装しています。

ヒープの実装はシーケンス テーブルに似ているため、ここでは詳細には触れず、メイン インターフェイスの実装についてのみ説明します。

シーケンス テーブルのリンクを添付します: https://blog.csdn.net/2301_76269963/article/details/129352041?spm=1001.2014.3001.5501

【1】データ挿入

(1) 判定展開が系列表と一致している。

(2) 配列表と整合性のとれたデータを格納する。

(3) データ挿入後、挿入後も大きな山が残っていることを確認する必要があるため、親子関係を調整する必要があります。

調整を考える前に、父親の添字と子の添字の関係を見てみましょう 

このようなルールを見つけることができます

親の添字 = (子の添字 - 1)/2。 

このルールに従って調整関数を設計し、挿入するデータが親よりも大きい場合は親よりも小さくなるか、ルート ノードになるまで2 つを置き換えます

その後の削除でもスワップ調整が必要になるため、スワップを関数 HeapSwap( ) に個別にパッケージ化できます。

コード:

[2] データの削除(ヒープソートの基本)

基本的な考え方:

(1) ヒープのデータ削除には、ルート データの削除が必要です。

(2)シーケンステーブルのように直接上書きすることはできず、元のヒープの構造が破壊されることに注意してください

図:

(3)元のルートデータを削除して調整する必要があります

削除: ルートを最後のリーフと交換し、サイズ (有効なデータの数) を直接 1 減らすことができます。

図:

 調整: 添字 0から下向きに調整.子が父親よりも大きい場合,両方の子よりも大きいか葉が調整されるまで交換.各判定の前に,より大きい 2 つの子を比較して防止するための構造を破壊する.大きなヒープ古い子を親と交換し、繰り返します。

図:

コード:

 

完全なコード:

Heap.h (必要なヘッダー ファイルのインクルード、関数および構造の宣言)

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
	//存储数据
	HPDataType* a;
	//有效数据个数
	int size;
	//容量
	int capacity;
}HP;
//初始化
void HeapInit(HP* hp);
//销毁
void HeapDestory(HP* hp);
//交换函数
void HeapSwap(int* p1, int* p2);
//判空函数
bool HeapEmpty(HP* hp);
//调整函数
void AdjustUp(HPDataType* a,int child);
//向下调整,n是有效个数
void AdjustDown(HPDataType* a, int n, int parent);
//插入数据
void HeapPush(HP* hp, HPDataType x);
//打印数据
void HeapPrint(HP* hp);
//删除数据
void HeapPop(HP* hp);

Heap.c (インターフェースの実装)

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//初始化
void HeapInit(HP* hp)
{
	//断言,不能传空的结构体指针
	assert(hp);
	hp->a = NULL;
	//初始化size和容量都为0
	hp->size = hp->capacity = 0;
}

//销毁
void HeapDestory(HP* hp)
{
	free(hp->a);
	hp->size = hp->capacity = 0;
}

//交换函数
void HeapSwap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//判空函数
bool HeapEmpty(HP* hp)
{
	return hp->size == 0;
}

//调整函数
//向上调整
void AdjustUp(HPDataType* a, int child)
{
	//断言,不能传空指针
	assert(a);
	//找到父结点的下标
	int parent = (child - 1) / 2;
	//循环,以child到树根为结束条件
	while (child > 0)
	{
		//如果父结点比child下,交换并更新
		if (a[child] > a[parent])
		{
			HeapSwap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		//如果父结点比child大,跳出循环
		else
		{
			break;
		}
	}
}
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	//默认左孩子最大
	int child = parent * 2 + 1;
	//当已经调整到超出数组时结束
	while (child<n)
	{
		//找出两个孩子中大的一方
		//考虑右孩子不存在的情况
		if (child+1<n&&a[child + 1] > a[child])
		{
			//如果右孩子大,child加1变成右孩子
			child++;
		}
		//如果父亲比大孩子小,进行调整,否则跳出
		if (a[child] > a[parent])
		{
			HeapSwap(&a[child], &a[parent]);
			//迭代
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//插入数据
void HeapPush(HP* hp, HPDataType x)
{
	if (hp->size == hp->capacity)
	{
		//判断扩容多少
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		//扩容
		HPDataType* tmp =
			(HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		//更新
		hp->capacity = newcapacity;
		hp->a = tmp;
	}
	//存储数据
	hp->a[hp->size] = x;
	hp->size++;
	//进行调整
	AdjustUp(hp->a, hp->size-1);
}

//打印数据
void HeapPrint(HP* hp)
{
	//断言,不能传空的结构体指针
	assert(hp);
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

//删除数据
void HeapPop(HP* hp)
{
	//断言,不能传空的结构体指针
	assert(hp);
	//如果为空,不能删除,避免数组越界
	assert(!HeapEmpty(hp));
	//不为空,先交换根和最后一片叶子,然后size减1
	HeapSwap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->a, hp->size, 0);
}

text.c (テスト)

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

void text1()
{
	HP hp;
	HeapInit(&hp);
	HPDataType a[] = { 70,30,56,25,15,10.85,79};
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
	HeapDestory(&hp);
}

int main()
{
	text1();
}

おすすめ

転載: blog.csdn.net/2301_76269963/article/details/129941907