二叉树顺序结构的实现(堆)

二叉树的基本概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

有一个特殊的结点,称为根结点,根结点没有前驱结点

除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1)因此,树是递归定义的。

就像这样

这样

二叉树的基本性质

结点的度

一个结点含有的子树的个数称为该结点的度

如上图:A的为6(BCDEFG)

叶结点或终端结点

度为0的结点称为叶结点

如上图:B、C、H、I...等结点为叶结点(没有孩子的就是叶子节点)

非终端结点或分支结点

度不为0的结点

如上图:D、E、F、G...等结点为分支结点

双亲结点或父结点

若一个结点含有子结点,则这个结点称为其子结点的父结点

如上图:A是B的父结点

孩子结点或子结点

一个结点含有的子树的根结点称为该结点的子结点

如上图:B是A的孩子结点

兄弟结点

具有相同父结点的结点互称为兄弟结点

如上图:B、C是兄弟结点

树的度

一棵树中,最大的结点的度称为树的度

如上图:树的度为6(最大的多少就是多少)(BCDEFG)

结点的层次

从根开始定义起,根为第1层,根的子结点为第2层

以此类推;(一般情况下从1开始 有些会从0开始)

但是为了方便计算,右兄弟左孩子的时候我是从0 开始的

树的高度或深度

树中结点的最大层次

如上图:树的高度为4

  • 树的深度是4,因为从根节点A到最远的叶子节点F,需要经过4条边。

堂兄弟结点

双亲在同一层的结点互为堂兄弟

如上图:H、I互为兄弟结点

结点的祖先

从根到该结点所经分支上的所有结点

如上图:A是所有结点的祖先

子孙

以某结点为根的子树中任一结点都称为该结点的子孙

如上图:所有结点都是A的子孙

森林

由m(m>0)棵互不相交的树的集合称为森林

简单说 的说就是有好几个根节点,也就是好几个A组成(多棵树。并查集)

二叉树的定义

树的关键点是不知道定义几个树的度

1,明确知道的话我们可以写

2,不知道几个树的度,顺序表来写

3,右兄弟左孩子写法

不管多少,我们只定义两个树的度

特殊的二叉树:

1. 满二叉树:

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。

2. 完全二叉树:

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。从左到右必须连续(完全二叉树)

对比

右兄弟左孩子

右兄弟左孩子表示法,实际上是一种完全二叉树,需要按照顺序在二叉树里面进行放入

优缺点

优点

  • 空间效率:这种写法允许我们使用数组来存储完全二叉树,而不需要为每个节点分配两个指针的空间。

  • 简单性:它简化了对完全二叉树的遍历和操作。

缺点

  • 限制性:这种方法只适用于完全二叉树,对于非完全二叉树,这种写法不适用。

  • 复杂性:对于不熟悉这种表示法的人来说,理解和实现可能会有些复杂。

定义

 在定义里面不管多少,我们只定义两个树的度

逻辑讲解

右兄弟左孩子写法

在这种写法中,每个节点有两个子节点(左孩子和右兄弟),如果一个节点没有左子节点,那么它的左指针会指向它的右兄弟,而不是指向一个子节点。这种表示方法可以有效地利用数组来存储二叉树,同时保持树的结构信息。

例子

假设我们有以下完全二叉树:

在这个数组中,每个元素代表一个节点:

  • A是根节点。
  • B是A的左孩子。
  • C是A的右兄弟。
  • D是B的左孩子。
  • E是B的右兄弟,同时也是C的左孩子。
  • F是C的右兄弟。
  • G是F的右兄弟。

简单的说就是,我们存储的时候,我们会按照顺序进行存储,A下面只放两个数值,放满了,我们就往A下面放,A下面放满了两个,我们往B下面放,循环套娃

树的存储方式

顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。

存储方式

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

计算节点方式

我们可以通过计算找到父子节点,左右兄弟节点

节点计算总结

在数组中,我们可以通过节点的索引来确定其左孩子和右兄弟:

  • 假设父亲在数组里面的下标为i

  • 左孩子位于索引 2 * i + 1

  • 右兄弟位于索引 2 * i + 2

  • 假设孩子在数组里面的下标为j

  • 父亲位于(j-1)/2

如果一个节点是叶子节点,或者在最后一层并且没有右兄弟,那么它的右兄弟指针将指向一个空值或者一个表示终止的特殊值。

二叉树使用注意事项

数组存储只适合满二叉树,或者特殊二叉树

像下面的情况就是不适合,不是不能实现,是不适合实现的

非完全二叉树,适合的实现方式方式是:二叉树链式结构实现

完全二叉树,适合的实现方式是:二叉树顺序结构的实现

下面我们实现,二叉树顺序结构的实现

二叉树顺序结构(堆):堆的概念:

存储方式

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

还是那句话,堆是一种顺序结构实现的完全二叉树

在逻辑上不是连续的,但是在实际上,是数组进行实现的

堆的性质:

在数据结构中,“堆”是一种特殊的完全二叉树,它满足以下两个性质:

  1. 结构性质:堆是一个完全二叉树,如果用数组表示,那么除了最后一层外,每一层都被完全填满,且最后一层从左到右填充。
  2. 堆性质:树中任何节点的值都必须大于或等于其子节点的值(最大堆),或小于或等于其子节点的值(最小堆)。

最大堆(大堆)

在最大堆中,父节点的值总是大于或等于其子节点的值。这意味着堆中的最大值位于根节点。最大堆常用于实现优先级队列,其中最大元素具有最高的优先级。

特点:
  • 根节点是堆中的最大元素。
  • 任何父节点的值都不小于其子节点的值。

应用场景:
  • 堆排序:在堆排序算法中,最大堆用于选择序列中的最大元素。
  • 优先级队列:在需要频繁访问最大元素的场景中使用。

最小堆(小堆)

在最小堆中,父节点的值总是小于或等于其子节点的值。这意味着堆中的最小值位于根节点。最小堆同样用于实现优先级队列,但最小元素具有最高的优先级。

特点:
  • 根节点是堆中最小元素。
  • 任何父节点的值都不大于其子节点的值。

应用场景:
  • 堆排序:在堆排序算法中,最小堆用于选择序列中的最小元素。
  • 优先级队列:在需要频繁访问最小元素的场景中使用。

二叉树顺序结构的实现(堆):堆的实现:

实现(小堆)

在实际的编程实现中,堆通常用数组来表示,因为这样可以有效地利用内存空间,并且可以快速地通过索引访问父节点和子节点。节点的索引和其父节点或子节点的索引之间有一定的关系:

  • 父节点索引:(i - 1) / 2
  • 左孩子节点索引:2 * i + 1
  • 右孩子节点索引:2 * i + 2

其中 i 是节点的索引。

理解大堆和小堆的概念对于在实际应用中选择合适的数据结构和算法非常重要。

创建文件(小堆)

这里我们依旧是创建三个文件来实现顺序结构的堆

创建堆(小堆)

定义一个堆(Heap)的数据结构

这里所需要的头文件,是文件所需要的,这里不做过多解释,主要看的是定义的是数据结构,这里我们是用数组实现的

  1. 定义数据类型 HPDataType:使用 typedef 创建了一个新的类型别名 HPDataType,这里指定为 int 类型。这意味着 HPDataType 可以用来声明整数类型的变量,但在堆结构中,它可以被用作更通用的数据类型。

  2. 定义结构体 Heap:创建了一个结构体 Heap,它将用于表示整个堆的元数据和存储空间。

  3. 成员变量

    • HPDataType* _a:这是一个指针,指向堆中第一个元素的地址,用于访问和操作堆中的元素。
    • int _size:表示当前堆中元素的数量,即已使用的元素个数。
    • int _capacity:表示堆的最大容量,即 _a 指针所指向的数组能够容纳的元素个数。

这个结构体定义为后续实现堆的操作(如插入、删除、调整等)提供了必要的数据结构支持。在实际使用中,你还需要实现一些函数来操作这个 Heap 结构体,比如初始化堆、插入元素、删除最大元素(在最大堆中)或最小元素(在最小堆中)、销毁堆等。

创建了 Heap 结构体后,你通常会通过调用相关函数来初始化堆、使用它进行操作,并在最后销毁堆以释放分配的内存

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
#include<time.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;//首元素地址int _size;//元素个数int _capacity;//元素容量
}Heap;

堆的初始化和销毁(小堆)

这里的初始化和销毁和顺序表的初始化以及销毁是差不多的

这里知识掌握不牢固的同学,可以看一下我写的顺序表的篇章

顺序表(增删减改)+通讯录项目(数据结构)+顺序表专用题型-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137484207

//堆的初始化
void HeapInit(Heap* hp)
{//初始化这里不开辟空间hp->_a = NULL;hp->_capacity = hp->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);//这里不需要循环释放,因为这里是数组实现堆的free(hp->_a);hp->_a = NULL;hp->_capacity = hp->_size = 0;
}

HeapInit 函数接收一个指向 Heap 结构体的指针 hp。这个函数的作用是将 hp 指向的 Heap 结构体初始化为一个空堆:

  • _a 成员被设置为 NULL,表示没有分配任何内存空间。
  • _capacity 成员被设置为 0,表示堆的最大容量目前为 0,即没有预留空间。
  • _size 成员被设置为 0,表示堆中目前没有任何元素。

HeapDestory函数接收一个指向 Heap 结构体的指针 hp,并执行以下操作来销毁堆:

  • 使用 assert 确保传入的 hp 不是 NULL
  • 使用 free 函数释放 _a 指针指向的内存空间。由于 _a 在 HeapInit 中被初始化为 NULL,这里释放前应确保 _a 非 NULL(这通常在其他函数中进行判断和分配)。
  • 将 _a 设置回 NULL,确保指针不再指向任何内存空间。
  • 将 _capacity 和 _size 重置为 0,恢复堆为一个空状态。

加入数据(小堆)

首先我们需要看堆实现的时候是如何调整的

1,这里最后一个数值是新加入的数值,那么此时我们需要向上调整,也就是我们需要和上一个父亲节点进行对比,

2,如果孩子节点小于父亲节点,那么我们就需要进行交换,因为小堆根是最小的数值

3,并且更新父亲节点想下标和孩子节点的下标

4,备注:这里我们不需要三方进行对比,意思就是,我们不需要三个数值进行对比,因为如果左孩子存在,按照这个逻辑,左孩子最后如果小于父亲节点就一定会进行交换,不小于就不会进行交换,然后插入右孩子时候,右孩子只需要和父亲进行比较就可以了,就算右孩子比父亲数值小,那么此时会和父亲节点进行交换,这个时候我们发现,左孩子一定大于或者等于右孩子,而右孩子也一定大于或者等于父亲节点,因为这里是小堆,越往上越小。

解释一下备注4:

此时我们发现,我们还没有插入左右孩子的节点

此时插入节点还没有交换

进行交换,此时我们发现左节点一定大于父亲节点

插入右孩子,此时还没有交换

进行交换,我们只需要对新插入数值和父亲节点进行对比就可以,不需要1,7,0 三个数值进行对比,从而决定交换不交换,因为左孩子一定小于父亲,如果父亲小于左孩子,交换之后,那么左孩子也一定小于右孩子

这里我们了解一下循环逻辑

循环三要素

1,初始条件

2,循环条件

3,结束条件

那么向上调整的结束逻辑应该是什么,那就是当插入的数值,也就是孩子元素下标到最上面的时候,也就是到0的时候,也就是应该结束循环,所以chile>0,这里是这一种情况也是三种情况下唯一不会产生越界的逻辑。

代码实现

//交换
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int chile)
{//获取父母所在位置int parent = (chile - 1) / 2;while (chile > 0)//这个循环条件不会越界,其余两个循环条件都会导致越界,但是也会正常运行{if (a[chile] < a[parent]){//传递地址,指针接收Swap(&a[chile], &a[parent]);//更新父母和孩子的下标chile = parent;parent = (chile - 1) / 2;}else{break;}}这个循环条件产生了越界,但是	printf("%d ", ps._a[-1]);(特别大) printf("%d ", ps._a[100]);(特别小)//while (a[chile] < a[parent])//{//	//传递地址,指针接收//	Swap(&a[chile], &a[parent]);//	//更新父母和孩子的下标//	chile = parent;//	parent = (chile - 1) / 2;//}
}// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);//判断空间大小,并且开辟空间if (hp->_capacity == hp->_size){int newcapacity = hp->_a == 0 ? 4 : hp->_capacity * 2;HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("HeapPush:tmp:");exit(1);}hp->_a = tmp;hp->_capacity = newcapacity;}//插入hp->_a[hp->_size] = x;hp->_size++;//向上调整AdjustUp(hp->_a, hp->_size - 1);
}

AdjustUp 函数用于在插入新元素后,保持最小堆的性质。它接收一个数组 a 和一个元素的索引 child。这个元素通常是刚刚插入到数组中的,并且可能违反了堆的性质(即子节点的值可能小于其父节点的值)。

  • parent 变量用于存储当前 child 元素的父节点索引。
  • 循环会一直执行,直到 child 元素不再违反堆的性质或者 child 元素已经是根节点(此时 child 为 0)。
  • 如果子节点的值小于父节点的值,那么通过 Swap 函数交换这两个元素的位置,并更新 child 和 parent 以继续进行比较。

HeapPush 函数用于将一个新元素 x 插入到堆 hp 中,并保持堆的性质。

  • 首先,使用 assert 确保传入的 Heap 结构体指针 hp 不是 NULL
  • 如果当前堆的 _size 等于 _capacity,意味着堆已经满了,需要扩展空间。通过 realloc 函数为数组分配新的更大的空间。新的空间大小是当前容量的两倍或至少为 4(如果数组尚未分配空间)。
  • 如果 realloc 失败,会打印错误并退出程序。
  • 将新元素 x 插入到数组的末尾,即索引 hp->_size 处,并增加 _size
  • 最后,调用 AdjustUp 函数来调整堆,确保插入新元素后的堆仍然满足最小堆的性质。

删除数据(小堆)

数据的删除的逻辑这里我们不能--,或者++什么的,这些会导致堆变的不是堆

所以我们需要的是逻辑:

1,数组的首元素和尾元素进行交换

2,删除尾元素

3,首元素向下调整(这里看清楚了,这里是选取最小的数值进行交换)

代码实现

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{//我们假设左孩子的数值是最小的int chile = (parent * 2) + 1;while (chile < size){//我们需要判断,右孩子是不是存在,并且筛选出最小的数值if (chile + 1 < size && a[chile] > a[chile + 1]){++chile;}//交换条件if (a[chile] < a[parent]){Swap(&a[chile], &a[parent]);parent = chile;chile = (chile * 2) + 1;}else{break;}}
}
// 堆的删除
void HeapPop(Heap* hp)
{//首先删除之前不能为nullassert(hp && hp->_size > 0);//删除我们删除的是堆头元素,删除尾部是没有意义的//1,进行交换//2,删除尾部//3,向下排序Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;//传递的参数分别是,数组的头,头下标(parent是需要变化的,所以需要传递一个参数)AdjustDown(hp->_a, hp->_size, 0);
}

AdjustDown 函数用于在堆中移除根节点后,重新调整堆以保持堆的性质。它接收一个数组 a,数组的当前大小 size,以及需要向下调整的节点的父节点索引 parent

  • child 变量用于存储当前 parent 节点的左孩子的索引。
  • 循环会一直执行,直到 child 变量超出数组的边界或者当前节点不再违反堆的性质。
  • 如果右孩子存在且右孩子的值小于左孩子的值,那么将 child 更新为右孩子的索引。
  • 如果子节点的值小于父节点的值,那么通过 Swap 函数交换这两个元素的位置,并更新 parent 和 child 以继续进行比较。

HeapPop 函数用于从堆中移除根节点(即堆顶元素),这通常是堆中最大或最小的元素。这个函数适用于最大堆或最小堆,具体取决于堆的性质。

  • 首先,使用 assert 确保传入的 Heap 结构体指针 hp 不是 NULL,并且堆中至少有一个元素。
  • 将堆顶元素(索引 0)与最后一个元素交换位置。这样做的原因是,移除最后一个元素的成本较低,因为它不需要调整堆。
  • 减少 _size,表示堆中的元素数量减少了一个。
  • 调用 AdjustDown 函数,传入数组的头部、新的堆大小和根节点索引(0),以调整堆并保持堆的性质。

取堆顶的数据(小堆)

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{assert(hp && hp->_size > 0);return hp->_a[0];
}
  1. 参数接收:函数接收一个指向 Heap 结构体的指针 hp

  2. 断言检查

    • assert(hp);:确保传入的 hp 不是 NULL。如果 hp 是 NULLassert 将触发断言失败,这通常会导致程序终止。
    • assert(hp->_size > 0);:确保堆不为空(即 _size 大于 0)。如果 _size 不大于 0,说明堆为空,此时没有元素可以返回,assert 同样会触发断言失败。
  3. 返回堆顶元素

    • return hp->_a[0];:返回位于数组 _a 第一个元素的值,这个元素在堆的表示中对应堆顶元素。

堆的数据个数(小堆)

// 堆的数据个数
int HeapSize(Heap* hp)
{ assert(hp);return hp->_size;
}
  1. 参数接收:函数接收一个指向 Heap 结构体的指针 hp

  2. 断言检查

    • assert(hp);:确保传入的 hp 不是 NULL。如果 hp 是 NULLassert 将触发断言失败,这通常会导致程序终止。
  3. 返回元素数量

    • return hp->_size;:返回 Heap 结构体中的 _size 成员的值。_size 成员表示堆中当前存储的数据元素的数量。

堆的判空(小堆)

// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);return hp->_size == 0;
}
  1. assert(hp);:这行代码使用 assert 宏来确保传入的 hp 不是 NULL。如果 hpNULLassert 将触发一个断言失败,这通常会导致程序终止。assert 是一种运行时检查,用于调试目的,确保代码的正确性。

  2. return hp->_size == 0;:这行代码返回 hp 指向的 Heap 结构体的 _size 成员是否等于 0。_size 成员表示堆中元素的数量。如果 _size 等于 0,表示堆中没有任何元素,函数返回 1(在 C 语言中,非零值被视为真),表示堆为空。如果 _size 不等于 0,函数返回 0,表示堆不为空。

堆的实现代码(小堆)

#include"Heap.h"
//堆的初始化
void HeapInit(Heap* hp)
{//初始化这里不开辟空间hp->_a = NULL;hp->_capacity = hp->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);//这里不需要循环释放,因为这里是数组实现堆的free(hp->_a);hp->_a = NULL;hp->_capacity = hp->_size = 0;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int chile)
{//获取父母所在位置int parent = (chile - 1) / 2;while (chile > 0)//这个循环条件不会越界,其余两个循环条件都会导致越界,但是也会正常运行{if (a[chile] < a[parent]){//传递地址,指针接收Swap(&a[chile], &a[parent]);//更新父母和孩子的下标chile = parent;parent = (chile - 1) / 2;}else{break;}}这个循环条件产生了越界,但是	printf("%d ", ps._a[-1]);(特别大) printf("%d ", ps._a[100]);(特别小)//while (a[chile] < a[parent])//{//	//传递地址,指针接收//	Swap(&a[chile], &a[parent]);//	//更新父母和孩子的下标//	chile = parent;//	parent = (chile - 1) / 2;//}
}// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);//判断空间大小,并且开辟空间if (hp->_capacity == hp->_size){int newcapacity = hp->_a == 0 ? 4 : hp->_capacity * 2;HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("HeapPush:tmp:");exit(1);}hp->_a = tmp;hp->_capacity = newcapacity;}//插入hp->_a[hp->_size] = x;hp->_size++;//向上调整AdjustUp(hp->_a, hp->_size - 1);
}
//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{//我们假设左孩子的数值是最小的int chile = (parent * 2) + 1;while (chile < size){//我们需要判断,右孩子是不是存在,并且筛选出最小的数值if (chile + 1 < size && a[chile] > a[chile + 1]){++chile;}//交换条件if (a[chile] < a[parent]){Swap(&a[chile], &a[parent]);parent = chile;chile = (chile * 2) + 1;}else{break;}}
}
// 堆的删除
void HeapPop(Heap* hp)
{//首先删除之前不能为nullassert(hp && hp->_size > 0);//删除我们删除的是堆头元素,删除尾部是没有意义的//1,进行交换//2,删除尾部//3,向下排序Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;//传递的参数分别是,数组的头,头下标(parent是需要变化的,所以需要传递一个参数)AdjustDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{assert(hp && hp->_size > 0);return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{ assert(hp);return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);return hp->_size == 0;
}

堆的实现代码(大堆)


大堆
//堆的初始化
void HeapInit(Heap* hp)
{hp->_a = NULL;hp->_capacity = hp->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->_a);hp->_capacity = hp->_size = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//向上排序
void AdjustUp(HPDataType* a ,int chile)
{int parent = (chile - 1) / 2;while (chile > 0){if (a[chile] > a[parent]){Swap(&a[chile], &a[parent]);chile = parent;parent= (chile - 1) / 2;}else{break;}}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{if (hp->_capacity == hp->_size){int new_capacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * new_capacity);if (tmp == NULL){perror(" ");exit(1);}hp->_capacity = new_capacity;hp->_a = tmp;}//插入数值hp->_a[hp->_size] = x;hp->_size++;//向上排序//首元素地址,孩子所在地址AdjustUp(hp->_a, hp->_size - 1);
}
//向下调整(大堆)
void AdjustDown(HPDataType* a, int n, int parent)
{int chile = parent * 2 + 1;//循环条件不能是父亲,不然会导致越界while (chile < n){//三个孩子进行比较if (chile + 1 < n && a[chile] < a[chile + 1]){chile++;}if (a[chile] > a[parent]){Swap(&a[chile], &a[parent]);parent = chile;chile = parent * 2 + 1;}else{break;}}
}
// 堆的删除
void HeapPop(Heap* hp)
{Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;//数值的长度,和父亲下标AdjustDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{assert(hp && hp->_size > 0);return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{ assert(hp);return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);return hp->_size == 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/842786.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

How to record real IP of user on nginx?

应用(Docker)使用WAF接入internet&#xff0c;nginx log 查不到用户的真实IP地址&#xff0c;于是修改nginx 设置&#xff0c;以下都是在linux下操作&#xff1a; 由于没有WAF权限&#xff0c;所以在 docker上启动了两个container&#xff0c;一个模拟WAF(r-proxy)&#xff0c…

软考 系统架构设计师系列知识点之SOME/IP与DDS(2)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之SOME/IP与DDS&#xff08;1&#xff09; 本文内容参考&#xff1a; 车载以太网 - SOME/IP简介_someip-CSDN博客 https://zhuanlan.zhihu.com/p/369422441 什么是SOME/IP?_someip-CSDN博客 SOME/IP 详解系列&#…

基础—SQL—DML(数据操作语言)插入数据

一、介绍 分类全称说明DMLData Manipulation Language数据操作语言。用来对数据库表中的数据进行增删改(插入、删除、修改) 则增、删、改是三个操作也就对应着三个关键字&#xff0c;分别是&#xff1a; 添加数据&#xff1a;&#xff08; INSERT &#xff09;修改数据&#…

521源码-免费音乐源码-最新流媒体在线音乐系统网站源码| 英文版源码| 音乐社区 | 多语言 | 开心版

免费音乐源码 一键自动安装&#xff1a;安装用翻译看提示操作即可 本源码下载地址&#xff1a;最新流媒体在线音乐系统网站源码| 英文版源码| 音乐社区 | 多语言 | 开心版 - 521源码 更多网站源码学习教程&#xff0c;请点击&#x1f449;-521源码-&#x1f448;获取最新资源…

Shell脚本学习笔记(更新中...)

一、什么是shell shell的作用是&#xff1a; 解释执行用户输入的命令程序等。 用户输入一条命令&#xff0c;shell就解释一条。 键盘输入命令&#xff0c;LInux给与响应的方式&#xff0c;称之为交互式。 shell是一块包裹着系统核心的壳&#xff0c;处于操作系统的最外层&a…

Java从坚持到精通-SpringBoot项目-多来米云客(持续更新中)

1.项目介绍 该项目模仿动力云客制作&#xff0c;是一款商业的集营销销售为一体的客户关系管理系统&#xff0c;其采用信息化、数字化方式来进行营销销售及客户管理。 云客指的是海量客户&#xff0c;通过技术方式实现的这一套系统&#xff0c;可用于自动化分析销售、市场营销…

饮料添加剂新型褪色光照试验仪器太阳光模拟器

太阳光模拟器的定义和功能 太阳光模拟器是一种高科技设备&#xff0c;它可以模拟太阳光的光谱、光强和光照条件&#xff0c;用于实验室环境中对太阳能电池、光电器件以及其他需要太阳光条件的设备和材料进行评估。太阳光模拟器的主要功能包括模拟太阳光的光谱分布、辐照度、光…

阿里云获取nginx头部

k8s 配置 可以修改kube-system/nginx-configuration configmap的方式&#xff0c; compute-full-forwarded-for: “true” forwarded-for-header: “X-Forwarded-For” use-forwarded-headers: “true” https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-g…

ubuntu 22.04 appearance设置没有dock选项

1、问题描述 解决办法可以直接跳到后面见2 下图是我同学电脑的appearance界面选项&#xff0c;她有Dock的界面显示。 下面是我的界面&#xff0c; 没有Dock&#xff1a; 然后各种app的界面都在最底下&#xff0c;而且每次只能点击左上角的activities才能显示。 但是如果不打开某…

使用 Django 与 Redis 实现缓存优化

文章目录 什么是Redis&#xff1f;为什么选择Django与Redis&#xff1f;如何在Django中使用Redis&#xff1f;总结与拓展 在Web开发中&#xff0c;性能优化是一个至关重要的方面。而使用缓存是提高Web应用性能的常见方法之一。在这篇文章中&#xff0c;我们将探讨如何结合Djang…

[JDK工具-10] jvisualvm 多合一故障处理工具

文章目录 1. 介绍2. 查看堆的变化3. 查看堆快照4. 导出堆快照文件5. 查看class对象加载信息6. CPU分析&#xff1a;发现cpu使用率最高的方法7. 查看线程快照&#xff1a;发现死锁问题 1. 介绍 VisualVM 是一款免费的&#xff0c;集成了多个 JDK 命令行工具的可视化工具&#xf…

Chrome谷歌浏览器如何打开不安全页面的禁止权限?

目录 一、背景二、如何打开不安全页面被禁止的权限&#xff1f;2.1 第一步&#xff0c;添加信任站点2.2 第二步&#xff0c;打开不安全页面的权限2.3 结果展示 一、背景 在开发过程中&#xff0c;由于测试环境没有配置 HTTPS 请求&#xff0c;所以谷歌浏览器的地址栏会有这样一…

很耐看的Go快速开发后台系统框架

序言 秉承Go语言设计思路&#xff0c;我们集成框架简单易用、扩展性好、性能优异、兼顾安全稳定&#xff0c;适合企业及初学者用来开发项目、学习。我们框架和市面上其他家设计的不同&#xff0c;简单一步做到的我们不会两步&#xff0c;框架能自动处理&#xff0c;绝不手动处…

Android LAME原生音频

前言 我想大家都做过录音的功能吧&#xff0c;首先想到的是不是MediaRecorder&#xff1f;今天我们不用MediaRecorder&#xff0c;而是使用LAME库自己编译音频编码模块&#xff0c;很明显&#xff0c;这个需要用到NDK。凡是涉及到音视频编解码这块的&#xff0c;都需要用到And…

驱动开发:内核MDL读写进程内存

100编程书屋_孔夫子旧书网 MDL内存读写是最常用的一种读写模式,通常需要附加到指定进程空间内然后调用内存拷贝得到对端内存中的数据,在调用结束后再将其空间释放掉,通过这种方式实现内存读写操作,此种模式的读写操作也是最推荐使用的相比于CR3切换来说,此方式更稳定并不会…

ios:Command PhaseScriptExecution failed with a nonzero exit code

问题 使用 xcode 跑项目真机调试的时候&#xff0c;一直报错 Command PhaseScriptExecution failed with a nonzero exit code。 解决 最终靠以下方法解决 删除Podfile.lock文件删除Pods文件删除.xcworkspace文件Pod installCommandShiftK 清理一下缓存 亲测有效

滑动窗口-java

主要通过单调队列来解决滑动窗口问题&#xff0c;得到滑动窗口中元素的最大值和最小值。 目录 前言 一、滑动窗口 二、算法思路 1.滑动窗口 2.算法思路 3.代码详解 三、代码如下 1.代码如下 2.读入数据 3.代码运行结果 总结 前言 主要通过单调队列来解决滑动窗口问题&#xff…

Leetcode刷题笔记5

76. 最小覆盖子串 76. 最小覆盖子串 - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a; 暴力枚举 哈希表 先定义left和right&#xff0c;可以在随机位置 枚举一个位置向后找&#xff0c;找到一个位置之后&#xff0c;发现这段区间是一个最小的区间之后&#xff0c…

【探索数据之美】“从基础到精通——深入解析数据结构与二叉树的秘密“

gitee代码获取链接&#xff1a;https://gitee.com/flying-wolf-loves-learning/data-structure.git 一、树的概念 1.1 概念简述 数据结构中的树是一种层次结构&#xff0c;它由节点&#xff08;node&#xff09;和边&#xff08;edge&#xff09;组成。每个节点都有零个或多…

解决鼠标滚动时element-ui下拉框错位的问题

问题描述&#xff1a;elementUi的el-select下拉选择框,打开之后,直到失去焦点才会自动关闭。 在有滚动条的弹窗中使用时就会出现打开下拉框,滚动弹窗,el-select下拉框会超出弹窗范围的问题. 解决方案&#xff1a; 1、先在util文件夹下创建个hideSelect.js文件&#xff0c;代码…