二叉树(1.二叉树的概念&&堆)
Posted 水澹澹兮生烟.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树(1.二叉树的概念&&堆)相关的知识,希望对你有一定的参考价值。
@[TOC](
一.树
1.1树的概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成的具有层次关系的集合。
1.2结构
- 有一个特殊的结点,称为根结点,根节点没有前驱结点
- 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<=
m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继 - 树是递归定义的
- 子树是不相交的
- 除了根节点外,每个节点有且仅有一个父节点,其一棵N个节点的树有N-1条边。
1.3相关的概念
- 节点的度:一个节点含有的子树的个数称为该节点的度
- 叶节点或终端节点:度为0的节点称为叶节点
- 非终端节点或分支节点:度不为0的节点
- 树的度:一棵树中,最大的节点的度称为树的度
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
- 树的高度或深度:树中节点的最大层次
- 森林:由m(m>0)棵互不相交的树的集合称为森林
1.4树的表示方法
- 孩子表示法
在该节点中表示出其孩子结点即可(优点,找孩子结点方标,找双亲结点不方便)
- 双亲表示法
在该结点中表示出双亲结点即可(找双亲结点方便,找孩子结点不方便)
//孩子表示法
typedef struct int DType;
typedef struct node{
DType data;
struct node* right;
struct node* left;
}node;
- 孩子双亲表示法
孩子兄弟表示法
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,常用的是孩子兄弟表示法。
二.二叉树
2.1二叉树的概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒。
特殊的二叉树:
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.2二叉树的存储结构
二叉树可以使用两种存储结构:顺序结构和链式结构。
顺序存储:
当二叉树满足完全二叉树时采用顺序存储,它一般采用数组进行存储,,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。(二叉树在物理存储上是一个数组,在逻辑上是一个二叉树)
链式存储:
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。
2.3二叉树的性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点。
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1。
- 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1。
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1).。(ps:Log2(n+1)是log以2为底,n+1为对数)
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
2.4堆
2.4.1概念及性质
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.4.2堆的性质
- 堆顶元素一定是最大或者最小元素
- 他是一个完全二叉树
- 双亲结点比任意结点要大或者小
2.4.3堆的实现
(完整代码自取–>包含小堆,大堆实现)
- 堆向下调整算法
让完全二叉树满足堆特点,需要将二叉树的数组进行调整,进行双亲结点和孩子结点进行比较,然后进行交换。
在这里要注意的几点:
- 结点编号:
左孩子–>2i+1
右孩子–>2i+2(给出父节点i)
双亲结点–>(i-1)/2(给出孩子节点) - 父节点可能会没有右孩子
void adjust(DType array[], int size, int parent){
//默认让child标记左孩子,因为parent可能不存在右孩子
int child = parent * 2 + 1;
while (child<size&&array[child]<array[parent]){
//右孩子存在的情况:
if (child<size&&array[child + 1]<array[child]) //要注意&&两边的先后次序,如果比较左右孩子放在前面,会造成访问越界
{
child += 1;//让child标记右孩子
}
//检测parent是否满足情况
if (array[child]<array[parent]){
swap(&array[child], &array[parent]);
//较大的双亲和孩子交换后,可能会导致子树不满足
parent = child;
child = parent * 2 + 1;
}
else{
//如果双亲比孩子节点还小
return;
}
}
}
- 堆的初始化
//初始化
heap* heapInit(heap* hp, DType* array, int size){
assert(hp);
int lastnotleaf = 0;
hp->array = (DType*)malloc(sizeof(DType)*size);
if (NULL == hp->array){
return NULL;
}
hp->capacity = size;
//memcpy(hp->array, array, sizeof(sizeof(DType)*size));//以字节为准进行拷贝,大小为(sizeof(DType)*capa)
for (int i = 0; i < size; i++){
hp->array[i] = array[i];
}
hp->size = size;
//找到非叶子结点
lastnotleaf = ((size - 1) - 1) / 2;
//从该结点位置逐个往前直到根
for (int root = lastnotleaf; root >= 0; root--){
adjust(hp->array, size, root);
}
return hp;
}
- 堆的插入
在进行对的插入时,我们要进行一下几个步骤:
- 先将插入元素直接插入堆尾
- 对数进行调整,知道满足堆的特性(找到插入结点的双亲进行比较)
//插入
//插入
void heapInsert(heap* hp, DType data){
//首先确定堆是否已满
if (hp->size == hp->capacity){
//已满先进行扩容
hp->array = (DType*)realloc(hp->array,sizeof(DType)*(hp->capacity)*2);
if (NULL == hp->array){
assert(0);
return;
}
hp->capacity *= 2;
}
//先将data插入堆中
hp->array[hp->size++] = data;
//然后对插入后的孩子节点与其父节点进行比较,调换
adjustup(hp->array, hp->size);
}
void adjustup(DType* array,int size){
int child = size-1;
int parent = (child - 1) / 2;
while (child){
if (array[child] < array[parent])
{
swap(&array[child], &array[parent]);
child = parent;
parent = (child - 1) / 2;
}
else return;
}
}
- 堆的删除
堆的删除进行的步骤:
- 交换堆顶元素和堆尾元素
- 进行调整堆顶元素的位置
//删除
void heapdel(heap* hp){
//判断堆是否为空
if (heapempty(hp)) return;
//交换堆顶和堆尾元素
swap(&hp->array[0], &hp->array[hp->size - 1]);
//有效个数-1
hp->size -= 1;
//对现在的二叉树进行重新排序,再次成堆
adjust(hp->array, hp->size, 0);
}
- 其余的一些方法
//获取堆顶元素
DType heaptopdata(heap* hp){
assert(!heapempty(hp));
return hp->array[0];
}
//检测是否为空
int heapempty(heap* hp){
assert(hp);
return 0 == hp->size;
}
//获取堆里有效元素的个数
int heapsize(heap* hp){
assert(hp);
return hp->size;
}
//对的销毁
void heapdestory(heap* hp){
assert(hp);
if (hp->array){
free(hp->array);
hp->array = NULL;
hp->capacity = 0;
hp->size = 0;
}
}
void swap(DType* left, DType* right){
int temp = *left;
*left = *right;
*right = temp;
}
2.4.4堆的应用
- 堆排序–假设我们排升序
1.建堆(在排序中升序选择–>大堆, 降序选择–>小堆)
2.排序–>利用堆删除的思想进行排序
例如下图:我们所求升序,因此在建堆的时候先建成大堆,保证堆顶值最大,然后一次将堆顶元素与堆为元素进行交换,使得最大元素在物理存储上即在数组的最后一位,然后对剩下的重新进行向下调整,使得每次都能将剩下的最大数值放到剩下此时空间的末位,最后得到升序数组(的到降序数组与其原理类似)。
降序:利用的是小堆
int less(DType left, DType right){//大堆
return left < right;
}
//堆排序-->降序
void sort(heap* hp){
if (heapempty(hp)) return;
int size = hp->size-1;
int count = 0;
while (size){
if (hp->array[0] < hp->array[size]){
heapdel(hp);
count++;
}
else{
size--;
}
}
hp->size += count;
}
升序:利用的是大堆
int less(DType left, DType right){//大堆
return left > right;
}
//堆排序-->降序
void sort(heap* hp){
if (heapempty(hp)) return;
int size = hp->size-1;
int count = 0;
while (size){
if (hp->array[0] > hp->array[size]){
heapdel(hp);
count++;
}
else{
size--;
}
}
hp->size += count;
}
- TOP-K问题(找最小的前K个)
1.方法一–排序(O(NlogN)),但是在数据量比较大的情况下使用不了。
2.方法二–用堆处理
步骤一:用前K个元素建堆(求前K个最大的建小堆;求前K个最小的建大堆)
步骤二:(求最大的前K个)使用剩余的N-K元素一次与堆顶进行比较,如果比堆顶大,则元素进行替换,然后再进行堆的重新排序(因为我们此事件的是小堆,每次将N-K的元素与堆顶进行比较的时候,都相当与与队中最小的比较,也就是剔除堆中最小的元素)
输出前K个最大值:
//TOP-K
void TOP_K(heap* hp, int k, int* array,int size){
//建前K给元素的小堆
heapInit(hp,array,k,less);
int dex = k;
while (dex<size){
if (array[dex]>hp->array[0]){
hp->array[0] = array[dex];
adjust(hp,0);
dex++;
}
else{
dex++;
}
}
}
输出前K个最小值:
void TOP_K(heap* hp, int k, int* array,int size){
//建前K给元素的大堆
heapInit(hp,array,k,less);
int dex = k;
while (dex<size){
if (array[dex]《hp->array[0]){
hp->array[0] = array[dex];
adjust(hp,0);
dex++;
}
else{
dex++;
}
}
}
以上是关于二叉树(1.二叉树的概念&&堆)的主要内容,如果未能解决你的问题,请参考以下文章