二叉树基本学习

heap.h

#pragma once

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

#include <stdbool.h>//堆,规定的是父亲与孩子的关系

// 物理:数组

// 也可以链式存储,但不是那么方便

// 逻辑:完全二叉树

//

// 大堆:父亲大于等于孩子

// 小堆:父亲小于等于孩子

// 满二叉树为h,节点个数为N,若最后一层是满的,F(h)= 2^h - 1; N = logn;

// 若最后一层只有一个,则F(h)= 2^(h-1);------也有上面的式子可以得到

//

// 若忽略掉细节,堆的向上和向下调整都可以认为h = log n

//

//

//物理上用数组来实现,涉及扩容问题

typedef int HPDataType;

typedef struct Heap

{

HPDataType* a;

int size;

int capacity;

}HP;

void Swap(HPDataType* p1, HPDataType* p2);

void AdjustUp(HPDataType* a, int child);

void AdjustDown(HPDataType* a, int n, int parent);

void HPInit(HP* php);

void HPDestroy(HP* php);

void HPPush(HP* php, HPDataType x);

void HPPop(HP* php);

HPDataType HPTop(HP* php);

bool HPEmpty(HP* php);


heap.c

#include "heap.h"

void HPInit(HP* php)

{

assert(php);

php->a = NULL;

php->size = php->capacity = 0;

}

void HPDestroy(HP* php)

{

assert(php);

free(php->a);

php->a = NULL;

php->size = php->capacity = 0;

free(php);

}

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 = (child - 1) / 2;//结束条件写parent>=0的话,最后会parent= child= 0;歪打正着能跑

}

else

{

break;

}

}

//初始条件、中间过程、结束条件

}

void HPPush(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");

return;

}

php->a = tmp;

php->capacity = newcapacity;

}

php->a[php->size] = x;//前面的size-1个都是元素

php->size++;

AdjustUp(php->a, php->size - 1);

}

void AdjustDown(HPDataType* a, int n, int parent)

{

//先假设左孩子小

int child = parent * 2 + 1;

while (child < n)//若>=n,说明孩子已经不存在,已经调整到叶子了

{

//否则会有数组越界风险

if (a[child + 1] > a[child] && child + 1 < n)

{

++child;

}

if (a[child] > a[parent])

{

Swap(&a[child], &a[parent]);

parent = child;

child = parent * 2 + 1;

}

else

{

break;

}

}

}

//时间复杂度是O(log N)

void HPPop(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);

}

HPDataType HPTop(HP* php)

{

assert(php);

assert(php->size > 0);

return php->a[0];

}

bool HPEmpty(HP* php)

{

assert(php);

return php->size == 0;

}


test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "heap.h"

void TestHeap1()

{

int a[] = {4,3,4,7,6,5,4,3};

HP hp;

HPInit(&hp);

//要改变大堆、小堆,只需要改变向上、向下调整的判断逻辑改变

for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)

{

HPPush(&hp, a[i]);//这里相当于在堆上开辟的数组,再将排好的放进在栈上的数组

}

int i = 0;

while (!HPEmpty(&hp))

{

//printf("%d ", HPTop(&hp));

a[i++] = HPTop(&hp);

HPPop(&hp);

}

//找出最大的前k个

/*int k = 0;

scanf("%d", &k);

while (k--)

{

printf("%d ", HPTop(&hp));

HPPop(&hp);

}*/

HPDestroy(&hp);

}

//向上建堆排序的时间复杂度是O(N*logN)

void HPSort(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--)

{

//这里的i相当于是数组中的第i个元素,而我们在这里做的事情是对每个节点都一步步进行向下调整(这里的节点是每个叶节点的父节点)

//向下调整建堆,是从倒数第一个非叶子节点往下调

AdjustDown(a, n, i);//向下建堆的方法

//节点数量多的层,调整次数少

}

int end = n - 1;

while (end > 0)//这里是为了解决兄弟节点之间没有顺序的问题

{

Swap(&a[0], &a[end]);

AdjustDown(a, end, 0);

--end;

//这里时间复杂度和向上建堆一样,是O(N* logN)

}

}

//进行堆排序的步骤:(因为向下建堆排序的时间复杂度更低,所以只用向下建堆排序)

//首先,取数组首元素地址,然后取得数组大小size,再将最后一个元素传给AdjustDown进行调整

//这里进行的目的是让完全二叉树成为堆,所以从最后一个节点的父节点开始,一直到根节点的每个节点进行AdjustDown

//这里就完成了建堆,但是只满足了父带节点与子代节点之间的大小关系,但是同代之间并不确定

//所以在从最后一个元素开始,首先将从最后一个元素开始的每个元素与根节点交换内容

//然后让新的根节点内容AdjustDown,直到处理到最新一个根节点,到此时也就完成了堆排序

void TestHeap2()

{

int a[] = { 4,3,4,7,6,5,4,3 };

//其实就是模拟插入的过程,只不过已经有了空间,不需要再开辟空间的过程

HPSort(a, sizeof(a) / sizeof(int));

for (int i = 0; i < sizeof(a) / sizeof(int); i++)

{

printf("%d ", a[i]);

}

}

int main()

{

//TestHeap1();

TestHeap2();

return 0;

}

//Top k 问题

// 方法一

// 建一个大堆,Pop k次

// 但是此方法有一个致命缺点:对空间要求较高,如排10亿个数据的堆,需要4G左右的内存

// 方法二:

// 用前k个数,建一个小堆,剩下的数据跟堆顶比较,若比堆顶的数据大,则替换堆顶的数据进堆

// 覆盖跟位置,然后向下调整,最后这个小堆的k个,就是最大的前k个

//

//

//不是完全二叉树的采用的存储方法是链式存储方法

//有leftbrother data rightbrother组成,或者再加上一个parent指针

//搜索二叉树:左子树所有值 < 跟 < 右子树所有值

// 最多找树的高度次,有一点点像二分查找,但二分查找并不实用

// 一方面需要先进行排序,另一方面只能对数组进行操作

//

//

//

// 前序、中序、后序遍历

// 前序:根、左子树、右子树

// 中序:左子树、根、右子树

// 后序:左子树、右子树、根

// ------这里有递归的思想,对于前序,任何一棵树的访问,都要符合前序,只有空才不能再拆,也就是循环的回归条件

//

//

相关推荐
Lei活在当下2 小时前
【业务场景架构实战】7. 多代智能手表适配:Android APP 表盘编辑页的功能驱动设计
android·设计模式·架构
手机不死我是天子5 小时前
《Android 核心组件深度系列 · 第 2 篇 Service》
android
前行的小黑炭6 小时前
Compose页面切换的几种方式:Navigation、NavigationBar+HorizontalPager,会导致LaunchedEffect执行?
android·kotlin·app
前行的小黑炭6 小时前
Android :Comnpose各种副作用的使用
android·kotlin·app
BD_Marathon20 小时前
【MySQL】函数
android·数据库·mysql
西西学代码20 小时前
安卓开发---耳机的按键设置的UI实例
android·ui
maki0771 天前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架1 天前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid1 天前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl1 天前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea