当前位置: 首页 > news >正文

数据结构 | 详解二叉树——堆与堆排序

🥝堆

堆总是一棵完全二叉树。

大堆:父节点总是大于子节点。

小堆:父节点总是小于子节点。

注意:1.同一个节点下的两个子节点并无要求先后顺序。

           2.堆可以是无序的。

🍉堆的实现

🌴深度剖析

1.父节点和子节点之间的关系

子节点=(父节点*2)+1

或者子节点=(父节点*2)+2

父节点=(子节点-1)/2

2.堆的插入HeapPush实现

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆
void HeapPush(Heap* php, HPDataType x) {assert(php);if (php->size == php->capacity) {int newcapacity = 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * newcapacity);if (tmp == NULL) {perror("malloc fail!");}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;AdjustUp(php->a,php->size);php->size++;
}

3.堆的删除HeapPop函数的实现

函数目的:删除堆顶元素

为了避免破坏堆的整体结构,先将首尾元素进行交换,再对首元素进行向下调整,直到满足堆。最后php->size--即可删除原栈顶元素。

void HeapPop(Heap* php) {assert(php);swap(&php->a[0], &php->a[php->size - 1]);AdjustDown(php->a, php->size,0);php->size--;
}

🥳代码实现

Heap.h

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}Heap;void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);

Heap.c

#define _CRT_SECURE_NO_WARNINGS
#include "Heap.h"
void HeapInit(Heap* php) {assert(php);php->a = NULL;php->capacity = php->size = 0;
}void HeapDestory(Heap* php) {assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}void swap(int* a, int* b) {int tmp = *a;*a= *b;*b = tmp;
}
//小堆
void AdjustUp(HPDataType* a,int child) {assert(a);int parent = (child - 1) / 2;while (child > 0) {if (a[parent] > a[child]) {swap(&a[parent], &a[child]);child = parent;parent = (child - 1) / 2;}else {break;}}
}void HeapPush(Heap* php, HPDataType x) {assert(php);if (php->size == php->capacity) {int newcapacity = 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * newcapacity);if (tmp == NULL) {perror("malloc fail!");}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;AdjustUp(php->a,php->size);php->size++;
}
//从给定的子节点开始,不断向上与其父节点进行比较和可能的交换,直到达到根节点或找到一个满足最大堆性质的父节点为止。
void AdjustDown(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 HeapPop(Heap* php) {assert(php);swap(&php->a[0], &php->a[php->size - 1]);AdjustDown(php->a, php->size,0);php->size--;
}HPDataType HeapTop(Heap* php) {assert(php);assert(php->size > 0);return php->a[0];
}int HeapSize(Heap* php) {assert(php);return php->size;
}int HeapEmpty(Heap* php) {assert(php);return php->size;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "Heap.h"
int main() {Heap hp;HeapInit(&hp);HeapPush(&hp, 7);HeapPush(&hp, 6);HeapPush(&hp, 5);HeapPush(&hp, 4);HeapPush(&hp, 3);HeapPush(&hp, 2);HeapPush(&hp, 1);for (int i = 0; i < hp.size; i++) {printf("%d ", hp.a[i]);}HeapPop(&hp);printf("\n");for (int i = 0; i < hp.size; i++) {printf("%d ", hp.a[i]);}printf("\n");printf("堆顶元素为%d\n", HeapTop(&hp));if (HeapEmpty(&hp)) {printf("堆不为空\n");}else {printf("堆为空\n");}return 0;
}

🍇堆排序

🌴深度剖析

第一步:建堆

(升序建大堆,降序建小堆)

以升序为例:

从最后一个父节点开始向前遍历,向上调整(大的上小的下)。

	//建堆:从倒数第一个父节点开始向前遍历,向下调整for (int i = (n-1-1)/2; i >=0 ;i--) {AdjustDown(a,n,i);}

第二步:排序

1.首尾元素交换(左图)

2.再向下调整(大的上小的下),这样调整后的堆顶元素必为调整范围内的最大值,经过下一轮的首尾元素交换后,就可以放入调整完的区域内。

while (n - 1) {swap(&a[0], &a[n - 1]);AdjustDown(a, n-1,0);n--;

🥳代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>void swap(int* a, int* b) {int tmp = *a;*a = *b;*b = tmp;
}void AdjustUp(int* a, int child) {assert(a);int parent = (child - 1) / 2;while (child > 0) {if (a[parent] < a[child]) {swap(&a[parent], &a[child]);child = parent;parent = (child - 1) / 2;}else {break;}}
}
void AdjustDown(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 HeapSort(int* a, int n) {//建堆:从倒数第一个父节点开始向前遍历,向下调整for (int i = (n-1-1)/2; i >=0 ;i--) {AdjustDown(a,n,i);}//先将首尾元素进行交换,再向下调整while (n - 1) {swap(&a[0], &a[n - 1]);AdjustDown(a, n-1,0);n--;}
}int main() {int a[7] = { 2,6,5,1,7,4,3 };int n = sizeof(a) / sizeof(a[0]);HeapSort(a, n);for (int i = 0; i < n; i++) {printf("%d ",a[i]);}return 0;
}

🍉从时间复杂度角度分析建堆为何采取向下调整?

下面将分别分析向下调整算法建堆和向上调整算法建堆的区别:

向下调整建堆

假设节点数量为N,树的高度为h

第一层,2^0个节点,需要向下调整h-1层

第二层,2^1个节点,需要向下调整h-2层

第三层,2^2个节点,需要向下调整h-3层

……

第h层,2^h个节点,需要向下调整0层

可以看出:节点少的层向下调整得多,节点多的层向下调整得少

计算向下调整建堆最坏情况下合计的调整次数:

通过错位相减法可得:

因此向下调整建堆的时间复杂度为O(N)

向上调整建堆:

假设节点数量为N,树的高度为h

第一层,2^0个节点,需要向下调整0层

第二层,2^1个节点,需要向下调整1层

第三层,2^2个节点,需要向下调整2层

……

第h层,2^h个节点,需要向下调整h-1层

可以看出:节点少的层向上调整得少,节点多的层向上调整得多。

T(h)=2^1*1+2^2*2+……+2^(h-2)*(h-2)+2^(h-1)*(h-1)

同样由错位相减法可得:

T(h)=-(2^2+2^3+……+2^(h-1))+2^h*(h-1)-2^1

整理可得:

T(N)=-N+(N+1)*(log2(N+1)-1)+1

因此向上调整建堆的时间复杂度为O(N*logN)

所以我们选择向下建堆算法明显效率更高。

http://www.lryc.cn/news/357512.html

相关文章:

  • vb.net,C#强制结束进程,“优雅”的退出方式
  • 牛客热题:数据流中的中位数
  • JavaScript-JavaWeb
  • vue组件通讯$parent和$children获取单签组件的⽗组件和当前组件的⼦组件的例子
  • Util和utils
  • 拷贝构造、移动构造、拷贝赋值、移动赋值
  • Python3 笔记:math模块
  • python -【四】函数
  • 力扣 5. 最长回文子串 python AC
  • 【微机原理及接口技术】可编程计数器/定时器8253
  • 23种设计模式之一— — — —装饰模式详细介绍与讲解
  • 2024年2月28日 星期三
  • Java中的super关键字详解
  • 消消乐游戏开发,三消游戏,消除小游戏
  • 三十三、openlayers官网示例Drawing Features Style——在地图上绘制图形,并修改绘制过程中的颜色
  • Vue——事件修饰符
  • Go语言GoFly框架快速新增接口/上手写代码
  • 【Vue】v-else 和 v-else-if
  • 一致性hash算法原理图和负载均衡原理-urlhash与least_conn案例
  • MySQL建库
  • 系统资源监控器工具glances的使用详解
  • JDBC使用QreryRunner简化SQL查询注意事项
  • 前缀和(下)
  • 【排序算法】希尔排序
  • 数学建模--LaTex插入表格详细介绍
  • 未来已来:Flutter引领的安卓与跨平台开发奇幻之旅
  • 如何将Windows PC变成Wi-Fi热点?这里提供详细步骤
  • 报错:Cannot invoke “springfox.documentation.service.ParameterType.getIn()“
  • 一个生动的例子——通过ERC20接口访问Tether合约
  • 新媒体时代,LCD电子价签赋予零售场景新活力