目次
5. バイナリ ツリーの K 番目のレベルのノードの数を見つけます。
序文
このブログでは概念的な知識ポイントをほとんど取り上げていないため、初心者が学習することはお勧めできません。ブロガーは、読者の理解が容易になるよう、イラストやコードを使用するよう最善を尽くします。ブロガーのレベルが低いことをご容赦ください。読者の皆様は、提案、批判、修正を歓迎します。
二分木
1.バイナリツリーをたどる
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <assert.h> typedef int BTDataType; typedef struct BinaryTreeNode { BTDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; BTNode* BuyNode(BTDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { perror("malloc fail"); return NULL; } node->data = x; node->left = NULL; node->right = NULL; return node; } BTNode* CreatTree() { BTNode* node1 = BuyNode(1); BTNode* node2 = BuyNode(2); BTNode* node3 = BuyNode(3); BTNode* node4 = BuyNode(4); BTNode* node5 = BuyNode(5); BTNode* node6 = BuyNode(6); node1->left = node2; node1->right = node4; node2->left = node3; node4->left = node5; node4->right = node6; return node1; } void PreOrder(BTNode* root) { if (root == NULL) { printf("NULL "); return; } printf("%d ", root->data); PreOrder(root->left); PreOrder(root->right); } void InOrder(BTNode* root) { if (root == NULL) { printf("NULL "); return; } InOrder(root->left); printf("%d ", root->data); InOrder(root->right); } void PostOrder(BTNode* root) { if (root == NULL) { printf("NULL "); return; } PostOrder(root->left); PostOrder(root->right); printf("%d ", root->data); }
2. 二分木のノード数
//全局变量 //int size = 0; //void TreeSize1(BTNode* root) //{ // if (root == NULL) // return; // // ++size; // TreeSize1(root->left); // TreeSize1(root->right); //} //传地址 //void TreeSize2(BTNode* root, int* psize) //{ // if (root == NULL) // return; // // ++(*psize); // TreeSize2(root->left, psize); // TreeSize2(root->right, psize); //} //优化 int TreeSize3(BTNode* root) { return root == NULL ? 0 : TreeSize3(root->left) + TreeSize3(root->right) + 1; }
3. リーフノードの数を見つける
int BTreeLeafSize(BTNode* root) { if (root == NULL) return 0; if (root->left == NULL && root->right == NULL) return 1; return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); }
4. 二分木の高さを求める
int TreeHeight(BTNode* root) { if (root == NULL) return 0; int leftHeight = TreeHeight(root->left); int rightHeight = TreeHeight(root->right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }
5. バイナリ ツリーの K 番目のレベルのノードの数を見つけます。
int TreeKLevel(BTNode* root, int k) { assert(k > 0); if (root == NULL) return 0; if (k == 1) return 1; return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1); }
ヒープ
導入:
1. 完全なバイナリツリー
2. 大きなパイル: ツリーの親ノードは子ノード以上です。
小規模ヒープ: ツリーの親ノードは子ノード以下です。
応用:
1. ヒープソート - O(N*logN)
//O(N*logN) void HeapSort(int* a, int n) { // 升序 -- 建大堆 // 降序 -- 建小堆 // 建堆--向上调整建堆 /*for (int i = 1; i < n; i++) { AdjustUp(a, i); }*/ // 建堆--向下调整建堆 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); } // N*logN int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); // 调整,选出次小的数 AdjustDown(a, end, 0); --end; } }
2.TOP-K
データ内の上位 K 個の最大 (または最小) 要素を検索します (例: 世界の上位 500 社、大学入試ランキング上位 200 位、人気の専攻上位 10 位)
a. ヒープソート (上記のサンプルコードを参照)
短所:
(1) 最初にヒープを作成する必要があります
(2) スペースの複雑さ + データのコピー
b. ヒープソートのアイデアを使用して配列を直接調整する
//时间复杂度 -- logN void HeapSort(int* a, int n) { // 向上调整 for (int i = 1; i < n; i++) { AdjustUp(a, i); } // 向下调整 -- O(N) /*for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); }*/ //O(N*logN)--相对于一趟堆排序 int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); AdjustDown(a, end, 0); end--; } } void TestSort() { int a[] = { 3,1,4,1,0,2,5 }; HeapSort(a, sizeof(a) / sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { printf("%d ", a[i]); } printf("\n"); }
c.制限事項
N 個の整数について、上位 K 個の最大のものを見つけます。通常の考え方は、これらの N 整数を大きな山に構築し、それらを K 回ポップすることです。
ただし、シナリオによっては、上記の考え方では問題を解決できない場合があります (たとえば、N が 10 億の整数など非常に大きい場合)。
ソリューション
(1) 最初の K 個の数値を取得して小さなヒープを構築します
(2) 残りのデータをヒープの先頭要素と順番に比較し、ヒープの先頭要素より大きい場合はヒープに置き換えます。
(3) この小さなヒープ内の最後のデータは、N 個の整数の中で最大の上位 K 個のデータです。
3.優先キュー
コード:
アレイはヒープ + 動的メモリ管理を実装します
1.ヒープ.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }HP; //向上调整 void AdjustUp(HPDataType* a, int child); //向下调整 void AdjustDown(int* a, int n, int parent); void HeapInit(HP* php); void HeapDestroy(HP* php); //插入一个结点 void HeapPush(HP* php, HPDataType x); //删除堆顶的元素 void HeapPop(HP* php); //取堆顶元素 HPDataType HeapTop(HP* php); bool HeapEmpty(HP* php); int HeapSize(HP* php);
2.ヒープ.c
#include "Heap.h" void HeapInit(HP* php) { assert(php); php->a = NULL; php->capacity = 0; php->size = 0; } void HeapDestroy(HP* php) { assert(php); free(php->a); php->a = NULL; php->capacity = 0; php->size = 0; } 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(parent >= 0) 行不行? while (child > 0) { if (a[child] > a[parent]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void AdjustDown(int* a, int n, int parent) { int child = parent * 2 + 1; while (child < n) { //选出左右孩子中较小的那一个 if (child + 1 < n && a[child + 1] > a[child]) { child++; } if (a[child] > a[parent]) { Swap(&a[child], &a[parent]); parent = child; child = parent * 2 + 1; } else { break; } } } //log_2(N) 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, newCapacity * sizeof(HPDataType)); if (tmp == NULL) { perror("realloc fail"); return; } php->a = tmp; php->capacity = newCapacity; } php->a[php->size] = x; php->size++; AdjustUp(php->a, php->size - 1); } void HeapPop(HP* php) { assert(php); assert(!HeapEmpty(php)); Swap(&php->a[0], &php->a[php->size - 1]); php->size--; AdjustDown(php->a, php->size, 0); } HPDataType HeapTop(HP* php) { assert(php); assert(!HeapEmpty(php)); return php->a[0]; } bool HeapEmpty(HP* php) { assert(php); return php->size == 0; } int HeapSize(HP* php) { assert(php); return php->size; }
説明に役立つイラスト:
a.HeapPush (要素の挿入) - 時間計算量 O(logN)
完全な二分木の場合、ノードの添字 n がわかっている場合、その親ノードと左右の子ノードを見つけるにはどうすればよいでしょうか?
親 = (n - 1) / 2;
leftchild = n * 2 + 1、rightchild = n * 2 + 2;
b.HeapPop (ヒープの先頭要素を削除) - 時間計算量 O(logN)
3.テストc
#include "Heap.h" int Test0() { HP hp; HeapInit(&hp); int a[] = { 65,100,21,32,520,64 }; for (int i = 0; i < sizeof(a) / sizeof(int); ++i) { HeapPush(&hp, a[i]); } while (!HeapEmpty(&hp)) { int top = HeapTop(&hp); printf("%d\n", top); HeapPop(&hp); } return 0; } //对数组进行堆排序 // 可以这么玩吗?--可以 // 弊端: // 1、需要先创建一个堆,太麻烦 // 2、空间复杂度+拷贝数据 // //void HeapSort(int* a, int n) //{ // HP hp; // HeapInit(&hp); // // N*logN // for (int i = 0; i < n; ++i) // { // HeapPush(&hp, a[i]); // } // // // N*logN // int j = 0; // while (!HeapEmpty(&hp)) // { // int top = HeapTop(&hp); // a[j++] = top; // HeapPop(&hp); // } // // HeapDestroy(&hp); //} //O(N*logN) void HeapSort(int* a, int n) { // 升序 -- 建大堆 // 降序 -- 建小堆 // 建堆--向上调整建堆 /*for (int i = 1; i < n; i++) { AdjustUp(a, i); }*/ // 建堆--向下调整建堆 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); } // N*logN int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); // 再调整,选出次小的数 AdjustDown(a, end, 0); --end; } } void TestSort() { int a[] = { 3,1,8,4,5,2,0 }; HeapSort(a, sizeof(a) / sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { printf("%d ", a[i]); } printf("\n"); } int main() { //Test0(); TestSort(); return 0; }