二分木のシーケンシャル構造と実現
1.二分木のシーケンシャル構造
通常の二分木は、無駄なスペースがたくさんある可能性があるため、配列に格納するのには適していません。完全な二分木は、順次構造での保管に適しています。実際には、通常、シーケンシャル構造の配列を使用してヒープ(一種のバイナリツリー)を格納します。ここでのヒープと、オペレーティングシステムの仮想プロセスアドレス空間のヒープは、2つの異なるものであることに注意してください。データ構造ともう1つは、オペレーティングシステムの管理システムです。メモリの領域はセグメント化されています。
2.ヒープの概念と構造
キーのセットK={k 0、k 1、k 2 ...、k n-1 }がある場合、そのすべての要素
を完全な二分木の順序で1次元配列に格納し、以下を満たします。 K i <= K 2 * i+1およびKi <= K 2 * i + 2(K i > =
K 2 * i+1およびKi <= K 2 * i + 2)i = 0、1、
2 ...は、小さなヒープ(または大きなヒープ)と呼ばれます。ルートノードが最大のヒープは最大ヒープまたはビッグルートヒープと呼ばれ、ルートノードが最小のヒープは最小ヒープまたはスモールルートヒープと呼ばれます。
簡単な説明:
ビッグヒープ(ビッグルートヒープ):ツリー内の父親は子供よりも大きい(等しい)。
小さなヒープ(小さなルートヒープ):ツリー内の父親は子供よりも小さい(等しい)。
ヒープのプロパティ:
-
ヒープ内のノードの値は、常にその親ノードの値より大きくも小さくもなりません。
-
ヒープは常に完全な二分木です。
アプリケーションシナリオ:ヒープソート、topk。
3.線形ヒープのシミュレーション実装
3.1ヒープの基本構造の定義
typedef int HPDataType;//堆中存放的数据,假设是整型
typedef struct Heap
{
HPDataType* a;//指针指向堆中存储的数据
size_t size;//堆中当前元素的数目
size_t capacity;//堆中所能存储数据的容量
}HP;
3.2ヒープの初期化
void HeapInit(HP* php)
{
assert(php);
php->capacity = php->size = 0;
php->a = NULL;
}
3.3ヒープの破壊
void HeapDestory(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
}
3.4ヒープデータの挿入
アイデア:
時間計算量:log(N)
void Swap(HPDataType* pa, HPDataType* pb)//交换函数:交换数组中的两个元素
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void AdjustUp(HPDataType* a, size_t child )//堆的向上调整
{
size_t parant = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parant])//此处如果是<就是小堆,如果是>就是大堆
{
Swap(&a[child], &a[parant]);
child = parant;
parant = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(HP* php, HPDataType x)
{
assert(php);
//判断是否需要扩充并进行扩充
if (php->size == php->capacity)
{
size_t newCapacity = php->capacity == 0 ? 2 : 2 * php->capacity;
HPDataType*tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*newCapacity);
if (tmp == NULL)
{
printf("realloc failed\n");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
//向上调整,控制保持是堆
AdjustUp(php->a, php->size - 1);
}
3.5ヒープの削除
アイデア:
-
最初の番号を最後の位置の番号と交換します
-
最後のデータを削除する
-
調整する
以下は、下方調整の図です。
時間計算量:O(log 2 N)
void Swap(HPDataType* pa, HPDataType* pb)//交换函数:交换数组中的两个元素
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void AdjustDown(HPDataType* a, size_t size,size_t root)
{
size_t parant = root;
size_t child = 2*parant+1;
while (child<size)
{
if (child+1<size &&a[child + 1] < a[child])//此时后面的这个如果是<就是小堆,如果是>就是大堆
++child;
if (a[child] < a[parant])//如果是<就是小堆,如果是>就是大堆
{
Swap(&a[child], &a[parant]);
parant = child;
child = 2 * parant + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
Q:ヒープの削除が後ろから前に直接カバーされず、最初の要素がカバーされないのはなぜですか?
回答:まず、時間計算量はO(N)です。次に、ヒープの元の構造が破壊される可能性があります。同時に、元のヒープのプロパティが失われ、元のヒープでない限り、ヒープではなくなる可能性があります。ヒープの配列要素は、小さいものから大きいもの、または小さいものから大きいものまであります。ヒープのプロパティは、大きいものから小さいものへの順序付けが行われた場合にのみ維持できますが、この場合でも、ヒープの構造は中断されます。つまり、それらの親子関係は破壊されます。
3.6線形ヒープが空かどうかを判断する
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
3.7線形ヒープ要素の数を見つける
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
3.8線形ヒープヘッダーの要素を返す
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
3.9線形ヒープの要素の印刷
void HeapPrint(HP* php)
{
assert(php);
for (size_t i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}