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

企业系统规划seo俱乐部

企业系统规划,seo俱乐部,bilibili广告投放管理平台,江西企业网站建设公司文章目录 堆的概念性质图解 向上调整算法算法分析代码整体实现 向下调整算法算法分析整体代码实现 堆的接口实现初始化堆销毁堆插入元素删除元素打印元素判断是否为空取首元素实现堆 堆排序创建堆调整堆整合步骤 TopK问题 堆的概念 堆就是将一组数据所有元素按完全二叉树的顺序…

文章目录

  • 堆的概念
      • 性质
      • 图解
  • 向上调整算法
      • 算法分析
      • 代码整体实现
  • 向下调整算法
      • 算法分析
      • 整体代码实现
  • 堆的接口实现
      • 初始化堆
      • 销毁堆
      • 插入元素
      • 删除元素
      • 打印元素
      • 判断是否为空
      • 取首元素
      • 实现堆
  • 堆排序
      • 创建堆
      • 调整堆
      • 整合步骤
  • TopK问题

堆的概念

堆就是将一组数据所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足树中每一个父亲节点都要大于其子节点称为大堆(树中每一个父亲节点都要大于其子节点称为小堆)。

性质

①对于大堆(大根堆)来说,堆的顶部也就是数组首元素一定是最大的元素
②对于小堆(小根堆)来说,堆的顶部也就是数组首元素一定是最小的元素
(这两点对于下面的堆排序来说十分重要)

此外,堆总是一棵完全二叉树,因为堆本身就是二叉树的一种顺序存储结构的实现模式
注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

图解

在这里插入图片描述

在这里插入图片描述
通过图再去对比上面的概念和性质,理解会更加清晰

所谓的存储结构也就数据在内存中真实的存储情况,在一维数组中
逻辑结构就是我们想象出来的,能够帮助我们理解并且通过这个也是根据二叉树中父节点和子节点之间的下标关系来确定的

①已知父亲节点求子节点

LeftChild = Parent * 2 + 1; //左孩子的节点下标
RightChild = Parent * 2 + 2; //右孩子的节点下标

②已知子节点求父节点

Parent = (Child - 1) / 2;  //切记是减1之后再除以2

向上调整算法

向上调整算法主要在堆的插入堆排序中应用最为广泛

算法分析

对于堆的插入,就是在数组的末尾进行数字的插入,并且在插入数据之后,我们仍要保证现有的结构仍然是一个!
在这里插入图片描述
如上图,是一个小堆

然后在数组的末尾插入了一个数字,即最后一个孩子节点,但是在插入之后,我们自身的堆结构发生了变化,所以我们必须对堆的结构进行调整.

不难发现,在最后插入一个数之后,其他子树仍然保持了小堆的性质(即父节点的值小于子节点),而正在需要调整的就是该子节点的’祖宗’这条线路,如上图红色箭头一步一步指向的位置,
而利用的公式就是Parent = (Child - 1) / 2;把新插入的数和它的父节点作比较,如果这个新插入的数小于于父节点,那么就和父节点交换位置

在向上调整代码中,我们需要传入的参数是数组和插入的那个子孩子的节点的下标

void AdjustUp(HeapDataType* a, int child)  //child是下标

在实际的不断向上调整中,我们需要用循环来实现代码,并且要合理的设置循环

while (child > 0)   //不能设置为parent >= 0 {				//Parent = (Child - 1) / 2, 通过这个公式因为parent永远都不可能小于零...}

时间复杂度 -------O(logN)

根据最坏情况来看(比如上图),数据多少层,我们就需要调整多少次,所以次数=高度h
再根据二叉树节点数量和高度的关系可知:
在这里插入图片描述
所以可以得到关系: 次数 = h = logN
所以时间复杂度就为:O(logN)

代码整体实现

算法既可以实现小堆也可以大堆,具体看你函数内部符号的控制

整体实现如下:

typedef struct HeapNode
{HeapDataType* a;int size;int capacity;
}HP;
void Swap(HeapDataType* p1, HeapDataType* p2)
{HeapDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//循环写法
void AdjustUp(HeapDataType* a, int child)  //child,parent是下标
{int parent = (child - 1) / 2;while (child > 0){//小堆:判断子节点和父亲结点的大小if (a[child] < a[parent])//大堆:if (a[child] > a[parent]){Swap(&a[child], &a[parent]);//交换孩子和父亲child = parent;parent = (child - 1) / 2;}else{break;}}
}//递归写法
void AdjustUp(HeapDataType* a, int child)
{int parent = (child - 1) / 2;if (child > 0){//小堆:if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;AdjustUp(a, child);  //递归}else{return;}}else{return;}
}

向下调整算法

向上调整算法主要在堆的数据删除堆排序中应用最为广泛

算法分析

在这里插入图片描述
对于上图根节点27来说,它的左右子树都是小堆,所以就需要将27不断向下调整,保证其整体还是一个小堆
由此可见,向下调整的前提是左右子树必须是堆
利用的公式就是
LeftChild = Parent * 2 + 1;
RightChild = Parent * 2 + 2;

在每一轮的调整中你都需要比较左右子节点的大小,比如上图就是对于27来说,15和17两个节点,15更小,所以就将15和27进行交换,然后对于19这个子树来说本身就是一个小堆,就可以不用管了,并且15本身也小于19,所以也符合小堆性质,然后继续对左边的子树进行如此的调整

在向下调整代码中,我们需要传入的参数是数组,数组大小和整棵树根节点的下标

void AdjustDown(HeapDataType* a, int size, int parent)

时间复杂度 -------O(logN)

根据最坏情况来看(比如上图),数据多少层,最坏的情况我们就需要向下调整多少次,所以次数=高度h, 再根据二叉树节点数量和高度的关系可知:
在这里插入图片描述
所以可以得到关系: 次数 = h = logN
所以时间复杂度就为:O(logN)

整体代码实现

typedef int HeapDataType;typedef struct HeapNode
{HeapDataType* a;int size;int capacity;
}HP;
//转换
void Swap(HeapDataType* p1, HeapDataType* p2)
{HeapDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//循环写法
void AdjustDown(HeapDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size)//这里的child是左孩子下标,之所以不是child+1<size,是因为{			        //如果没有右孩子的话,这次循环将会终止,调整就会进行不彻底//小堆:if (child + 1 < size && a[child + 1] < a[child]){child++;}//小堆:if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;//交换位置}else{break;}}
}
//递归写法
void AdjustDown(HeapDataType* a, int size, int parent)
{int child = parent * 2 + 1;if (child < size) {//小堆:if (child + 1 < size && a[child + 1] < a[child]){child++; //如果右孩子小,那么下标就换成右孩子的下标}//小堆:if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;AdjustDown(a, size, parent);  //递归}else{return;}}else{return;}
}

堆的接口实现

接下里,我将把堆的实现过程一步一步实现出来

初始化堆

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

销毁堆

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

插入元素

在尾部插入之后,要用AdjustUp函数向上调整形成堆

void HeapPush(HP* hp, HPDataType x)
{assert(hp);// 扩容if (hp->size == hp->capacity){int new = hp->capacity == 0 ? 4 : hp->capacity * 2;HPDataType*tmp=(HPDataType*)realloc(hp->a, sizeof(HPDataType) * new);if (tmp == NULL){perror("realloc fail");exit(-1);}hp->a = tmp;hp->capacity = new;}hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);//插入之后向上调整堆
}

删除元素

一般指删除首元素,至于为什么HeapPop是删除首元素
根本就是因为要弹出尾元素很简单,直接size–不就完了

void HeapPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);//首元素换到尾部来,然后再size----php->size;AdjustDown(php->a, php->size, 0);//再用AdjustDown函数再来调整堆
}

打印元素

void HeapPrint(HP* php)
{assert(php);for (size_t i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");
}

判断是否为空


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

取首元素

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

实现堆

int main()
{HP hp;HeapInit(&hp);int a[] = { 20, 11, 28, 31, 111, 52, 34, 16, 7, 9 };for (int i = 0; i < sizeof(a) / sizeof(int); i++){HeapPush(&hp, a[i]);//插入}HeapPop(&hp);//把堆的首元素7删除删除HeapPrint(&hp);printf("堆顶元素:%d\n", HeapTop(&hp));HeapDestroy(&hp);return 0;
}

运行出来的结果:
在这里插入图片描述

堆排序

首先堆排有几个关键的步骤
①创建堆 ②调整堆

创建堆

创建堆的方式有两种①向上调整建堆 ②向下调整建堆

①首先我们来看第一种:向上调整建堆

这种方式的原理就是看作最开始堆中只有一个元素,从第一个元素开始就已经在向上调整,然后逐渐像堆中加入元素,随着一个一个元素的加入,也就形成了堆
图解如下:
在这里插入图片描述
而代码就是通过AdjustUp函数和一个for循环就可以完成上面步骤

//以前n个数建小堆
for (int i = 0; i < n; ++i)
{AdjustUp(a, i);  //a为数组的指针
}

时间复杂度: O(n*logn)

分析:首先我们上面详细分析了AdjustUp()的时间复杂度为O(logn),然后循环了n此每次建堆,所以两者相乘,时间复杂度也就是 n*logn


②我们来看第一种:向下调整建堆 -----堆排序中最主要用到的方法

这种建堆的关键就是从倒数第一个非叶子节点开始调(也就是树中最后一个父节点),然后逐渐+1,就可以调整从最后一个父节点开始的每一棵树.
不难发现这样也符合向下调整的前提,即左右子树都是堆
那么我们如何找到最后一个节点的父亲?
就需要用到公式:Parent = (Child - 1) / 2;
图解如下
在这里插入图片描述

而代码就是通过AdjustDown函数和一个for循环就可以完成上面步骤

for (int i = ((n-1)-1)/2; i >= 0; --i) 
//(n-1)是拿到树最后一个节点,然后再根据公式Parent = (Child - 1) / 2;
{AdjustDown(a, size, i);
}

时间复杂度:O(n)
根据下面的思路
在这里插入图片描述
在这里插入图片描述

因此建堆的时间复杂度为O(n)

总结😗:其实两种方式建堆之所以时间复杂度有差距,就是因为向下调整建堆可以看作忽略了最后一排的节点,直接从倒数第二排节点开始调整的,而在一棵满二叉树中最后一排的节点其实就占据了整棵树的二分之一,所以相当于向下调整比向上调整少经历了很多的节点
所以实际堆排序中我们更多的使用的是向下调整建堆,因此时间复杂度为O(n)

还有一点需要注意的是:如果你想要升序,即从小打大,需要建大堆.
建了大堆之后,再交换首元素(最大的)和末尾元素,然后把最大的元素不算入堆中的元素,
再进行向下调整
如果你建小堆,当你拿到首元素(最小的元素之后),需要将数组依次前移然后重新建堆,每次都前移然后每次都建堆,时间复杂度直接拉满!!!
同理 如果你想要降序,即从大打小,需要建小堆.


调整堆

在堆建好之后,就可以开始调整堆了,比如你是升序,即从小打大,需要建大堆.
建了大堆之后,循环N次 ,进行N次调整堆操作,每一次调整 堆得到的最大值,将此值和数组的最后一个元素进行交换,交换减小数组的长度(最后被减小的那几个值不参与堆的调整),直到最后一个元素,就完成了堆的排序.

如下图,降序—小堆, 展示了其中一个调整过程

在这里插入图片描述

整合步骤

综合建堆和调整,完整的堆排序代码就出来了

void HeapSort(int* a, int n)
{// 建堆 (大堆)or  (小堆)for (int i = 1; i < n; i++){AdjustUp(a, i);}
int end = n - 1;
while (end > 0)
{Swap(&a[0], &a[end]);  //交换AdjustDown(a, end, 0); //向下调整--end;   //换下来的最后一个数不计入堆中
}

升序建大堆,降序建小堆很重要!

TopK问题

最后我们再来解决一个堆在实际应用中很重要的Topk问题

通常这是在数据很大的情况下才会使用到的,如世界前500强,全省高考前十等等…
因为如果数据很大,你不可能在内存中创建一个这么大的数组来装下这么多数据,所以就要用topk问题的思路
举个简单的例子:
比如你有1000个数据,你要找前100个大的数据,那么你先随便拿100个数据(无论其大小多少)建小堆,然后另外900个数据依次与堆顶的最小数据进行比较,比它大就替换,然后再调整堆,这样1000个数据都参与了对比,对比了900次,900个最小的被拿走,剩下的100个一定是最大的,再进行堆排序

接下来用文件传输数据的形式进行举例

void CreateNDate()
{// 造数据int n = 10000000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 10000000;fprintf(fin, "%d\n", x);}fclose(fin);
}void TestTopK(const char* filename, int k)
{// 1. 建堆--用a中前k个元素建堆FILE* fout = fopen(filename, "r");if (fout == NULL){perror("fopen fail");return;}int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc fail");return;}for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);}// 前k个数建小堆for (int i = (k-2)/2; i >=0 ; --i){AdjustDown(minheap, k, i);}// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > minheap[0]){// 替换你进堆minheap[0] = x;AdjustDown(minheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}printf("\n");fclose(fout);
}int main()
{CreateNDate();TestTopK("data.txt", 5);return 0;
}
http://www.mmbaike.com/news/77169.html

相关文章:

  • 北京原创先锋网络科技发展有限公司金昌网站seo
  • 外贸网站使用什么品牌国外主机今日关键词
  • jsp网站开发与设计摘要网站seo推广seo教程
  • 滨州做企业网站连云港seo优化公司
  • 深圳网站建设公司哪家可以建app深圳网络推广团队
  • 网站建设的付款方式cps广告联盟网站
  • 网站域名注册哪个好seo搜索引擎优化方式
  • 河北企业自助建站西安seo王尘宇
  • 中山市中国建设银行网站山西网页制作
  • 东莞南城网站建设价格一个新产品怎么推广
  • 上海网站建设网站制百度竞价排名点击软件
  • 苹果网站设计重庆seo1
  • 怎么做免费个人网站佛山网站建设模板
  • 冒用公司名做网站优化快速排序
  • 做网站有什么要求营销策略的重要性
  • 哈尔滨做网站的公司推广营销平台
  • 做公众号的素材网站谷歌seo零基础教程
  • 电子商务网站建设目的官网seo
  • 做网站的任务书百家号seo
  • 自己房子做民宿挂什么网站郑州企业网站优化排名
  • 东营做网站m0536seo学堂
  • 网站建设未验收会计账务处理网站建设介绍ppt
  • 做网约车网站武汉网络推广
  • 网站怎么做投票百度搜索使用方法
  • 儿童玩具网站模板百度推广电话号码
  • 建设网站物业经理上岗证陈俊华aso优化app推广
  • 企业网站建设规划的基本原则是什么seo接单
  • wordpress极简网站为什么要seo
  • 外文网站做t检验分析谷歌google官方下载
  • 深圳企业专业网站设计品牌策划书案例