堆的概念
:::block-1
- 堆是一种完全二叉树,它的所有元素都按照完全二叉树的顺序存储方式存储,一般使用数组来实现。
:::
堆的分类
:::block-1
- 小堆
小堆是指满足k[i]<=k[2i+1] && k[i]<=k[2i+2]的堆,通俗来讲就是其父节点一定要小于任何一个子节点。 - 大堆
大堆是指满足k[i]>=k[2i+1] && k[i]>=k[2i+2]的堆,通俗来讲就是其父节点一定要大于任何一个子节点。
:::
通过子节点(父节点)下标寻找父节点(子节点)下标
- 子节点找父节点
父 = (子-1)/2 - 父节点找子节点
子 = (父*2)+1
堆的实现
:::block-1
- 堆的实现需要依靠两种算法,一种是向下调整算法、另一种是向上调整算法,这两种算法具体的应用在下文给出。
堆的结构
- 和顺序表的结构有点像,因为我们使用顺序表去实现堆。
typedef int phpDataType;
typedef struct Heap
{
phpDataType* a;
int size;
int capacity;
}Heap;
向下调整算法
- 过程为:给定父亲下标,向下寻找子节点,满足条件则交换。
void AdjustDwon(int* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child] > a[child + 1])
{
child++;
}
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
向上调整算法
- 过程为:给定孩子下标,向上寻找父节点,满足条件则交换。
void AdjustUp(Heap* php,int child)//堆,要调整的位置
{
assert(php);
int parent = (child - 1) / 2;
while (child > 0)//子节点不等于零代表有父节点,可继续调整
{
if (php->a[child] < php->a[parent])//给定条件
{
Swap(&(php->a[child]), &(php->a[parent]));//交换子父节点
}
else
{
break;//如果不满足条件,则跳出进入下一次调整
}
child = parent;
parent = (child - 1) / 2;
}
}
堆的创建
- 堆的创建有两种方式,一种是通过向下调整建堆(倒着建堆),另外一种是向上调整建堆(模拟插入的过程)
向下调整建堆
- 从数组最后开始建堆
//(n - 1 - 1)/2的含义是((n-1)-1)/2,n-1是数组最后一个元素的下标,减一后整体处二是为了找他的父亲节点,因为叶子节点没有必要去向下调整。
for (int i = (n - 1 - 1)/2; i >= 0; i--)
{
AdjustDwon(a, n, i);
}
向上调整建堆
- 这实际上是一个模拟插入的过程,将数组第一个元素看做是堆,将后面的元素看做要插入的元素,插入一个便向上调整一次
for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}
堆的初始化
- php->capacity = 0; 将堆的容量设置为0,表示当前堆中没有元素。
- php->size = 0; 将堆的大小设置为0,表示当前堆中没有元素。
- php->a = NULL; 将指向堆元素数组的指针a设置为NULL,表示当前堆中没有任何元素。
- 通过调用这个函数,可以将一个Heap对象初始化为空的状态,以便后续进行堆操作。
void InitHeap(Heap* php)
{
php->capacity = 0;
php->size = 0;
php->a = NULL;
}
堆的插入的实现
- assert(php); 使用断言确保传入的指针不为空。
- phpDataType NewCapacity = php->capacity == 0 ? 4 : 2 * php->capacity; 根据堆的当前容量来计算新的容量,如果当前容量为0,则将新容量设置为4,否则设置为当前容量的两倍。
- if (php->capacity == php->size) 如果堆的容量等于当前大小,说明堆已满,需要进行扩容操作。
- phpDataType* tmp = (phpDataType*)realloc(php->a,NewCapacity*sizeof(phpDataType)); 使用realloc函数重新分配堆的元素数组的内存空间,将容量调整为新容量。
- assert(tmp); 使用断言确保内存分配成功。
- php->capacity = NewCapacity; 更新堆的容量为新容量。
- php->a = tmp; 更新堆的元素数组指针为新的内存空间。
- php->a[php->size] = x; 将需要插入的数据存放在堆的末尾。
- php->size++; 堆的大小增加1,表示插入了一个新元素。
- AdjustUp(php,php->size-1); 调用AdjustUp函数对插入的新元素进行向上调整,以满足堆的条件。
- 通过调用这个函数,可以将数据插入到堆中,并确保堆的性质得到维护。
void HeapPush(Heap* php, phpDataType x)//堆,需要插入的数据
{
assert(php);
phpDataType NewCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
if (php->capacity == php->size)
{
phpDataType* tmp = (phpDataType*)realloc(php->a,NewCapacity*sizeof(phpDataType));
assert(tmp);
php->capacity = NewCapacity;
php->a = tmp;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php,php->size-1);//要调整的对,需要向上调整的新的位置
}
堆的销毁
- assert(php); 使用断言确保传入的指针不为空。
- php->size = 0; 将堆的大小设置为0,表示当前堆中没有元素。
- php->capacity = 0; 将堆的容量设置为0,表示当前堆中没有元素。
- free(php->a); 调用free函数释放堆元素数组的内存空间。
- 通过调用这个函数,可以销毁堆并释放相关的内存空间。
void HeapDestory(Heap* php)
{
assert(php);
php->size = 0;
php->capacity = 0;
free(php->a);
}
堆的删除(删除堆顶元素)
- assert(php); 使用断言确保传入的指针不为空。
- assert(php->size); 使用断言确保堆的大小大于0,即堆中至少有一个元素。
- int top = 0; 将top变量设置为0,表示堆顶的位置。
- int end = php->size - 1; 将end变量设置为堆的最后一个元素的位置。
- Swap(&(php->a[top]), &(php->a[end])); 调用Swap函数交换堆顶元素和最后一个元素的位置,将堆顶元素移到堆的末尾。
- php->size–; 堆的大小减少1,表示弹出了一个元素。
- AdjustDown(php); 调用AdjustDown函数对堆进行向下调整,以满足堆的条件。
通过调用这个函数,可以弹出堆顶元素并保持堆的性质。
void HeapPop(Heap* php)
{
assert(php);
assert(php->size);
int top = 0;
int end = php->size - 1;
Swap(&(php->a[top]), &(php->a[end]));
php->size--;
AdjustDown(php);
}
取堆顶的数据
- assert(php); 使用断言确保传入的指针不为空。
- int top = 0; 将top变量设置为0,表示堆顶的位置。
- return php->a[top]; 返回堆中位置为top的元素的值。
- 通过调用这个函数,可以获取堆顶元素的值而不对堆进行修改。
phpDataType HeapTop(Heap* php)
{
assert(php);
int top = 0;
return php->a[top];
}
堆的数据个数
- assert(php); 使用断言确保传入的指针不为空。
- return php->size; 返回堆的大小,即堆中元素的个数。
- 通过调用这个函数,可以获取堆的大小而不对堆进行修改。
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
堆的判空
- assert(php); 使用断言确保传入的指针不为空。
- return php->size == 0; 返回一个表达式的结果,该表达式判断堆的大小是否为0。如果堆的大小为0,则返回1代表堆为空;否则,返回0代表堆非空。
- 通过调用这个函数,可以判断堆是否为空。若返回值为1,则表示堆为空;若返回值为0,则表示堆非空。
int HeapEmpty(Heap* php)//返回1代表该堆为空,返回零代表非空
{
assert(php);
return php->size == 0;
}
堆排序(升序建大堆,降序建小堆)
- 向下调整建堆后,将堆顶元素不断换向堆底
- assert(a); 使用断言确保传入的数组指针不为空。
- 首先进行建堆操作。通过循环从堆的倒数第二层开始,依次对每个非叶子节点调用AdjustDown函数,将数组a调整为一个合法的堆。
- 接下来是排序阶段。使用一个while循环,不断地将堆顶元素(数组首元素a[0])与当前待排序范围内的最后一个元素交换,并将待排序范围缩小,再对堆顶元素进行一次AdjustDown操作,以满足堆的性质。
- 循环执行上述步骤,直到待排序范围的大小减为0,即完成了堆排序。
- 通过调用这个函数,可以对指定的整型数组进行堆排序,使其按照升序排列。
void HeapSort(int* a, int n)
{
assert(a);
//建堆
for (int i = (n - 1 - 1)/2; i >= 0; i--)
{
AdjustDwon(a, n, i);
}
//排序
while (n)
{
Swap(&a[0], &a[n - 1]);
n--;
AdjustDwon(a, n, 0);
}
}
top-k问题
- 首先,定义一个字符串变量file表示文件名,并使用 fopen 函数以只读方式打开文件。使用条件判断语句检查文件是否成功打开,如果打开失败,则输出错误信息并返回。
- 动态分配一个大小为k的整型数组a,用于存储前k个最大数。
- 使用循环从文件中读取k个整数并依次存入数组a中。
- 接下来进行建堆操作。通过循环从堆的倒数第二层开始,依次对每个非叶子节点调用AdjustDown函数,将数组a调整为一个合法的堆。
- 然后,使用一个while循环,在文件没有读取完的情况下不断读取下一个整数,并与数组a的堆顶元素比较。
- 如果新读取的整数大于数组a的堆顶元素,则将该整数替换堆顶元素,并调用AdjustDown函数重新调整堆。
- 最后,使用循环遍历数组a,并逐个打印出前k个最大数。
void PrintTopK(int k)
{
const char* file = "data.txt";
FILE* fin = fopen(file, "r");
if (fin == NULL)
{
perror("fopen error");
return;
}
int* a = (int*)malloc(k * sizeof(int));
for (int i = 0; i < k; i++)
{
fscanf(fin, "%d", &a[i]);
}
//建堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a,k,i);
}
int val = 0;
while (!feof(fin))
{
fscanf(fin, "%d", &val);
if (val > a[0])
{
a[0] = val;
AdjustDown(a, k, 0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d\n", a[i]);
}
}
:::