二叉树顺序结构与堆的概念及性质(c语言实现堆)

简介: 二叉树顺序结构与堆的概念及性质(c语言实现堆)

上次介绍了树,二叉树的基本概念结构及性质

今天带来的是:二叉树顺序结构与堆的概念及性质,还会用c语言来实现堆


1. 二叉树的顺序结构


普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。完全二叉树就比较适合使用顺序结构存储(数组)。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储


注意:此堆非“彼堆”——操作系统虚拟进程地址空间中的堆。二者一个是一个是数据结构,一个是操作系统中管理内存的一块区域


2.堆的概念和结构


堆需要满足两点:


堆是一个完全二叉树,即除了最底层,其他层都是完全填满,最底层从左到右填充

堆中的每个节点的值都必须大于等于(最大堆)或小于等于(最小堆)其子节点的值

根据节点值的大小关系,堆可以分为最大堆和最小堆。在最大堆中,根节点的值最大,每个节点的值都大于等于其子节点的值。在最小堆中,根节点的值最小,每个节点的值都小于等于其子节点的值356.png


3.堆的实现(小堆)


3.1项目文件规划


  • 头文件Heap.h:用来基础准备(常量定义,typedef),链表表的基本框架,函数的声明
  • 源文件Heap.c:用来各种功能函数的具体实现
  • 源文件test.c:用来测试功能是否有问题,进行基本功能的使用


3.2结构体和各功能一览(Heap.h)


typedef int HPDataType;
typedef struct Heap//用顺序表来实现,跟顺序表的结构一样
{
  HPDataType* a;
  int size;//数量
  int capacity;//容量
}HP;
void HeapInit(HP* php);//初始化
void HeapDestroy(HP* php);//销毁
void HeapPush(HP* php, HPDataType x);//插入
// 规定删除堆顶(根节点)
void HeapPop(HP* php);//删除
HPDataType HeapTop(HP* php);//返回根(堆顶)的存储的数据
int HeapSize(HP* php);//堆的数据个数
bool HeapEmpty(HP* php);//是否为空


3.3重要函数详解(Heap.c)


3.3.1堆向上调整算法


i位置节点的双亲序号:(i-1)/2

void Swap(HPDataType* p1, HPDataType* p2)
{
  HPDataType tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)//传入数组和下标索引
{
  int father = (child - 1) / 2;//利用公式来算出父亲节点下标
  while (child > 0)
  {
    if (a[child] < a[father])
    {
      Swap(&a[child], &a[father]);
      //更新下标
      child = father;
      father = (father - 1) / 2;
    }
    else
    {
      break;//一旦符合小堆了,就直接退出
    }
  }
}

Swap 函数用于交换两个指针指向的值,而 AdjustUp 函数用于通过比较子节点与父节点并在有必要时交换它们来调整堆的结构,然后向上移动树,直到满足堆的性质


3.3.2堆向下调整算法


i位置的左孩子是2 ∗ i + 1 2*i+12∗i+1,右孩子2 ∗ i + 2 2*i+22∗i+2

void AdjustDown(HPDataType* a, int n, int father)
{
  int child = father * 2 + 1;//假设左孩子小  找出两者较小的来跟父节点比(大堆就找二者中较大的了)
  while (child < n)
  {
    if (child + 1 < n && a[child] > a[child + 1])
    {
      child++;
    }
    if (a[child] < a[father])
    {
      Swap(&a[child], &a[father]);
      father = child;
      child = father * 2 + 1;
    }
    else
    {
      break;
    }
  }
}

给定一个数组 a,表示堆的结构,以及数组的大小 n 和要进行调整的父节点的索引 father

计算父节点的左孩子的索引为 father * 2 + 1

进入一个 while 循环,只要左孩子的索引小于 n (不会出数组)就会继续

在循环内部,首先检查右孩子是否存在且右孩子的值是否大于左孩子的值,如果是,则更新 child 为右孩子的索引。这是为了找出左右孩子中值较大的那个

比较左孩子的值和父节点的值,如果左孩子的值小于父节点的值,则调用 Swap 函数交换这两个索引处的值,并更新 father 为 child 的值,然后重新计算 child 的索引。这一步的目的是将较大的子节点值向上移动,以满足堆的性质

如果左孩子的值不小于父节点的值,则跳出循环,因为堆的性质已经满足


3.4各功能实现(Heap.c)


初始化和销毁

void HeapInit(HP* php)
{
  assert(php);
  php->a = NULL;
  php->size = php->capacity = 0;
}
void HeapDestroy(HP* php)
{
  assert(php);
  free(php->a);
  php->a = NULL;
  php->size = php->capacity = 0;
}

插入

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 -1;
    }
    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(php->size > 0);//保证有东西可以删
  Swap(php->a[0], php->a[php->size - 1]);
  php->size--;
  AdjustDown(php->a, php->size, 0);
}

返回根(堆顶)的存储的数据

HPDataType HeapTop(HP* php)
{
  assert(php);
  assert(php->size > 0);
  return php->a[0];
}

节点数量

int HeapSize(HP* php)
{
  assert(php);
  return php->size;
}

是否为空

bool HeapEmpty(HP* php)
{
  assert(php);
  return php->size == 0;
}


3.5建堆时间复杂度


777.png建堆的时间复杂度为O(N)


目录
相关文章
|
1月前
|
网络协议 编译器 Linux
【C语言】结构体内存对齐:热门面试话题
【C语言】结构体内存对齐:热门面试话题
|
17天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
59 16
|
17天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
66 8
|
1月前
|
编译器 C语言 Python
C语言结构
C语言结构
17 0
|
2月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
2月前
|
存储 算法 C语言
C语言手撕实战代码_二叉树_构造二叉树_层序遍历二叉树_二叉树深度的超详细代码实现
这段代码和文本介绍了一系列二叉树相关的问题及其解决方案。其中包括根据前序和中序序列构建二叉树、通过层次遍历序列和中序序列创建二叉树、计算二叉树节点数量、叶子节点数量、度为1的节点数量、二叉树高度、特定节点子树深度、判断两棵树是否相似、将叶子节点链接成双向链表、计算算术表达式的值、判断是否为完全二叉树以及求二叉树的最大宽度等。每道题目均提供了详细的算法思路及相应的C/C++代码实现,帮助读者理解和掌握二叉树的基本操作与应用。
|
2月前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
10天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
27 6