二叉树基本学习

发布于:2025-06-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

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指针
//搜索二叉树:左子树所有值 < 跟 < 右子树所有值
// 最多找树的高度次,有一点点像二分查找,但二分查找并不实用
// 一方面需要先进行排序,另一方面只能对数组进行操作
//  
// 
// 
// 前序、中序、后序遍历
// 前序:根、左子树、右子树
// 中序:左子树、根、右子树
// 后序:左子树、右子树、根
// ——这里有递归的思想,对于前序,任何一棵树的访问,都要符合前序,只有空才不能再拆,也就是循环的回归条件
// 
//


网站公告

今日签到

点亮在社区的每一天
去签到