目录

二叉树介绍

概念

特殊的二叉树

二叉树的性质

二叉树的存储方式

1.顺序存储

2.链式存储

堆实现二叉树

代码

Heap.h

Heap.c

以下是测试用例

Test.c

解释示例1

解释示例2

结束语


二叉树介绍

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;

概念

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:

特殊的二叉树

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

二叉树的性质

1. 若规定根节点的层数为1,则一棵非空二叉树的i层上最多有 2^(i-1) 个结点.
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h-1
3. 对任何一棵二叉树, 如果度为0其叶结点个数为n₀ , 度为2的分支结点个数为n₂ ,则有n₀=n₂ +1
4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度h=log₂(n+1).
(ps:log₂(n+1)是log以2为底,n+1为对数)
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
1. i>0i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
2. 2i+1<n,左孩子序号:2i+12i+1>=n则无左孩子
3. 2i+2<n,右孩子序号:2i+22i+2>=n则无右孩子

二叉树的存储方式

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1.顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,下面实现关于堆的二叉树实现,不过需要注意的是二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

根据上图可知,顺序存储只适合存储完全二叉树,并不适用于非完全二叉树的存储

2.链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,先接触二叉链,后面接触的数据结构如红黑树等会用到三叉链。

堆实现二叉树

引入逻辑概念物理概念

逻辑概念:即上面图所示的二叉树概念图

物理概念:以数组实现二叉树

也就是说树状图是我们为方便理解而设立的一个假想的概念图,实际上我们操作的还是一个数组,利用下标来实现数据的跳跃访问来帮助实现一些问题

代码

Heap.h

#pragma once#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;//打印
void HeapPrint(HP* php);//初始化
void HeapInit(HP* php);//销毁
void HeapDestroy(HP* php);//插入 -- 根据堆的特性,只会尾插,但是插入x继续保持堆形态
void HeapPush(HP* php , HPDataType x);//出堆 -- 删除堆顶的元素
void HeapPop(HP* php);//调整顺序
void Swap(HPDataType* p1, HPDataType* p2);//向上调整元素 -- 保持堆的形态
void AdjustUp(HPDataType* a, int child);//向下调整 -- 从头开始调
void AdjustDown(HPDataType* a, int n, int parent);//看头 -- 返回堆顶的元素
HPDataType HeapTop(HP* php);//判断是否为空
bool HeapEmpty(HP* php);//返回元素个数
int HeapSize(HP* php);

Heap.c

#include "Heap.h"//打印
void HeapPrint(HP* php)
{for (int i = 0; i < php->size; ++i){printf("%d ", php->a[i]);}printf("\n");
}//初始化
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 Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整元素 -- 保持堆的形态
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2; //计算方法,无论是奇数的还是偶数的都可以求出父节点while (child > 0)   //当孩子等于0的时候就停下{if (a[child] < a[parent]) //这里大于还是小于可以控制是大堆还是小堆,这里是在建小堆{//传地址过去Swap(&a[child], &a[parent]);//更新父节点child = parent;parent = (child - 1) / 2;}else{break;   //当满足堆的性质时就跳出循环}}
}//插入 -- 根据堆的特性,只会尾插,但是插入x继续保持堆形态
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, sizeof(HPDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}//元素放到最后php->a[php->size] = x;php->size++;//调整顺序 -- 传最后一个元素过去,注意要减一,因为前面++了AdjustUp(php->a, php->size - 1);}//向下调整 -- 从头开始调
void AdjustDown(HPDataType* a, int n, int parent)
{//假设左边小int minChild = parent * 2 + 1;while (minChild < n) //防止越界{if (minChild + 1 < n && a[minChild + 1] < a[minChild]){minChild++;}if (a[minChild] < a[parent]){Swap(&a[minChild], &a[parent]);parent = minChild;  //minChild = parent * 2 + 1;  //计算左孩子}else{break;}}}//出堆 -- 删除堆顶的元素
//时间复杂度: O(logN)
void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);}//看头 -- 返回堆顶的元素
HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(php));return php->a[0];}//判断是否为空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}//返回元素个数
int HeapSize(HP* php)
{assert(php);return php->size;
}

以下是测试用例

Test.c

#include "Heap.h"//测试一
//int main()
//{
//  int a[] = { 15,18,19,25,28,34,65,49,27,37 };
//  int a[] = { 65,100,70,32,50,60 };
//  HP hp;
//  //先创立一个再初始化
//  HeapInit(&hp);
//
//  for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
//  {
//      HeapPush(&hp, a[i]);
//  }
//
//  //HeapPush(&hp, 10);
//  //HeapPrint(&hp);
//
//  //HeapPop(&hp);
//  //HeapPrint(&hp);
//
//  //HeapPop(&hp);
//  //HeapPrint(&hp);
//
//  while (!HeapEmpty(&hp))
//  {
//      printf("%d ", HeapTop(&hp));
//      HeapPop(&hp);
//  }
//
//  return 0;
//}//测试二
void HeapSort(int* a, int n)
{//建堆 -- 向上调整建堆 -- 时间复杂度: -- O(N*logN)//for (int i = 1; i < n; i++)//{//   AdjustUp(a, i);//}//大思路:选择排序,依次选数,从后往前排//升序 -- 建大堆//降序 -- 建小堆//建堆 -- 向下调整建堆 -- 时间复杂度更简单 -- 解释示例1//建堆 -- 向下调整建堆 -- 时间复杂度: -- O(N)for (int i =(n-1-1)/2; i >= 0; --i){AdjustDown(a, n, i);}//选数int i = 1;   //只需要选n-1个数,最后留下的自然是最大或最小的数while (i < n){Swap(&a[0], &a[n - i]);  //把第一个数与最后一个数交换//向下调整建堆AdjustDown(a, n - i, 0);++i;}}//int main()
//{
//  //int a[] = { 65,100,70,32,50,60 };
//
//  int a[] = { 15,1,19,25,8,34,65,4,27,7 };
//  HeapSort(a, sizeof(a) / sizeof(int));
//
//  for (size_t i = 0; i < sizeof(a)/sizeof(int); ++i)
//  {
//      printf("%d ", a[i]);
//  }
//  printf("\n");
//
//  return 0;
//}//TOP - K问题 -- 解释示例2
//TOP - K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
//比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
//对于Top - K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
//  数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
//  1. 用数据集合中前K个元素来建堆
//  前k个最大的元素,则建小堆
//  前k个最小的元素,则建大堆
//  2. 用剩余的N - K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
//  比特就业课
//  将剩余N - K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素//以文件的方式进行堆排序选数
//测试用例三void CreateDataFile(const char* filename, int N)
{FILE* fin = fopen(filename, "w");   //以写文件的方式打开if (fin == NULL){perror("fopen fail");return;}srand(time(NULL));    //给定时间种子让其随机生成数据for (int i = 0; i < N ; ++i){fprintf(fin, "%d\n", rand()%1000000);  //%的原因是为了测试该项目的正确性,下面可以手动在文件里添加超过一百万的数据,让其测试,看是否可以选出来}fclose(fin);
}void PrintTopK(const char* filename, int k)
{assert(filename);FILE* fout = fopen(filename, "r");if (fout == NULL){perror("fopen fail");return;}int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("minHeap fail");return;}//如何读取前k个数据for (int i = 0; i < k; ++i){fscanf(fout, "%d", &minHeap[i]); //默认空格或者换行键为改数据的终止}//建k个数的小堆for (int j = (k-2)/2; j >= 0; --j){AdjustDown(minHeap, k, j);}//继续读取后N-K个数据int val = 0;while (fscanf(fout , "%d", &val) != EOF){if (val > minHeap[0]){minHeap[0] = val;    //直接赋值AdjustDown(minHeap, k, 0);}}for (int i = 0; i < k; ++i){printf("%d ", minHeap[i]);}free(minHeap);fclose(fout);
}int main()
{const char* filename = "Data.txt";int N = 10000;   int k = 10;//CreateDataFile(filename, N);//随机创立一些数据PrintTopK(filename, k);return 0;
}

解释示例1

调整次数 = 每一层节点个数 * 这一层最坏向下调整次数

使用向下调整建堆

第1层,2^0个节点,需要向下移动h-1层

第2层,2^1个节点,需要向下移动h-2层

第3层,2^2个节点,需要向下移动h-3层

第4层,2^3个节点,需要向下移动h-4层

……

第h-1层,2^(h-2)个节点,需要向下移动1层

第h层,2^(h-1)个节点,不需要向下移动

向上调整建堆

第1层,2^0个节点,不需要向下移动

第2层,2^1个节点,需要向下移动1次

第3层,2^2个节点,需要向下移动2次

第4层,2^3个节点,需要向下移动3次

……

第h-1层,2^(h-2)个节点,需要向下移动h-2次

第h层,2^(h-1)个节点,需要向下移动h-1次

而且根据二叉树的性质,最后一层一定是占最大节点数的大概有一半,综上所述使用向下调整是最优解

解释示例2

N个数,找k个最大的

1、排序 -- O(N*logN)

2、堆选数

a、建大堆 -- 选K次即可(Pop k次)-- 时间复杂度:O(N+logN*K)

一般而言:K较小,而当N很大的时候就不行了,比如:N = 100亿 K = 100,那么a方法就不行了 -- 空间浪费并且需要注意的是在堆实现中,内存是存不下这么多的数据 例:100亿个整数大约就是40G了,这对于内存来说是一个很大的空间

b、 建小堆

1、用前K个数,建K个数的小堆

2、依次遍历后续N-K个数,比堆顶的数据打,就替换堆顶的数据,向下调整进堆,最后堆里面的数据就是最大的前K个了

时间复杂度:K + logK*(N-K) ≈ O(N)  空间复杂度:O(K)

结束语

上穷碧落下黄泉,两处茫茫皆不见。
                                                         唐·白居易 《长恨歌》

数据结构·堆·完全二叉树相关推荐

  1. 数据结构——堆(C++)

    数据结构--堆 文章目录 数据结构--堆 堆与堆排序 二叉树 满二叉树 完全二叉树 二叉堆 堆的存储 堆的插入删除 具体的实现 堆排序 堆与堆排序 堆(heap)分为二叉堆.二项式堆.斐波那契堆,堆是 ...

  2. 数据结构-堆(Heap)

    数据结构-堆(Heap) 我认识的堆: 1.建立在完全二叉树的基础上 2.排序算法的一种,也是稳定效率最高的一种 3.可用于实现STL中的优先队列(priority_queue)  优先队列:一种特殊 ...

  3. java堆 数据结构 堆_Java中的紧凑堆外结构/组合

    java堆 数据结构 堆 在上一篇文章中,我详细介绍了代码对主内存的访问方式的含义. 从那时起,我就在Java中可以做什么以实现更可预测的内存布局提出了很多疑问. 有些模式可以使用数组支持的结构来应用 ...

  4. 数据结构-堆 Java实现

    数据结构-堆 Java实现. 实现堆自动增长 1 /** 2 * 数据结构-堆. 自动增长 3 * 4 * @author caiyao 5 */ 6 public class Heap<T e ...

  5. java堆 数据结构 堆_快速堆数据结构

    java堆 数据结构 堆 In this tutorial, we'll be discussing and implementing Heap data structures in Swift. 在 ...

  6. 数据结构——堆(转载)

    堆常用来实现优先队列,在这种队列中,待删除的元素为优先级最高(最低)的那个.在任何时候,任意优先元素都是可以插入到队列中去的,是计算机科学中一类特殊的数据结构的统称 一.堆的定义 最大(最小)堆是一棵 ...

  7. 数据结构-堆(最大堆)

    最大堆 实质是一棵完全二叉树 每个根结点元素的值都比左右儿子的大 每次都是取出堆顶元素(可以说是优先队列) 代码 参考自浙大数据结构 #include <iostream> #includ ...

  8. 经典数据结构——堆的实现

    一.完全二叉树 堆是一种完全二叉树,什么是完全二叉树? 简单的说,一棵满二叉树表示的是所有节点全部饱和,最后一层全部占满: 而完全二叉树指的是满二叉树的最后一层,所有叶子节点都从左往顺序排满: 完全二 ...

  9. Java数据结构—堆(Heap)

    堆是一种基于完全二叉树的数据结构,其中每个节点都满足堆特性:父节点的值总小于或等于(大于或等于)其子节点的值,这被称为小根堆(大根堆). 在Java中,可以使用PriorityQueue类来实现堆,它 ...

最新文章

  1. ubuntu eclipse CDT 问题
  2. acm算法模板(1)
  3. HihoCoder - 1591 锦标赛(最大费用最大流)
  4. 自定义repeater带分页功能的DataGrid(仿PetShop)
  5. CF5E-Bindian Signalizing【单调栈】
  6. python储存_python数据储存
  7. .NET4.0 之 Dynamic VS Refle“.NET研究”ction 效率
  8. NA-NP-IE系列实验28:HDLC 和PPP 封装
  9. Mac|技巧:Mac电脑如何使用ping命令呢?
  10. 我的世界Java版最大村庄_《我的世界》MC中最大的村庄种子,PC和PE都可以用
  11. 基于Java保险员工管理系统的设计与实现
  12. 一个简单的dw网页制作作业,学生个人html静态网页制作成品代码——怪盗基德动漫主题网页成品(15页)
  13. 日志收集之--将Kafka数据导入elasticsearch
  14. 前端JavaScript DOM BOM 自学复盘 D1(DOM-获取DOM元素、修改HTML标签/表单/css样式属性、定时器-间歇函数)
  15. 英特尔 超核芯显卡 620mac_显卡性能翻倍,AI能力加持:英特尔发布10代酷睿处理器...
  16. 达梦8,关于参数CTAB_SEL_WITH_CONS的验证
  17. python循环语句打印三角形_python循环输出三角形图案的例子
  18. 西门子采用三井化学的UNISTOLE™作为3D打印医用级面罩的涂层剂
  19. 【整理】SIMD、MMX、SSE、AVX、3D Now!、neon——指令集大全
  20. 深入编程之QQ盗号核心代码

热门文章

  1. Qemu kvm_qemu详细教程
  2. 解决跨域设置Cookie问题
  3. Cookie跨域setDomain
  4. OpenCV 文字绘制----cv::putText详解
  5. socket编程接口
  6. python的math库函数汇总
  7. Redis的安装和卸载--linux环境MobaXterm操作
  8. MySQL数据存储空间
  9. Google Open Images Dataset V4 百度网盘地址。
  10. run fsck manually