1 特殊二叉树
1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2 二叉树的性质:
1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1
4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2
为底,n+1为对数)
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
于序号为i的结点有:
1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
3 二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆总是一个完全二叉树
3.1 堆的大根堆示例
3.2 堆的小根堆示例
大根堆最大的数在堆顶,小根堆最小数在堆顶。
堆排序,排升序建小根堆。排降序建大根堆。
向下调整算法:从根结点开始,依次与根左右结点相比较,如果是大根堆,则把孩子结点中最大的选出来与根节点比较,如果大于根节点则与根节点交换。如果是小根堆,就选出孩子结点中最小的与根节点比较,如果小于根节点则与根节点交换。
//向下调整(小根堆)
void AdjustDown(Heap* php, int size, int phead)
{
int parent = phead;//根节点为父亲结点
int child = parent * 2 + 1;//根结点的左孩子,由于是完全二叉树,所以左孩子一定存在。
while (child < size)//保证不越界
{
if (child + 1 < size && php->a[child + 1] < php->a[child])//选出孩子中最小的值
{
child++;
}
if (php->a[child] < php->a[parent])//最小的和父亲节点相比较
{
Swap(&php->a[child], &php->a[parent]);//成立则交换
parent=child;//迭代
child = parent * 2 + 1;
}
else//不满足的时候则代表已经属于小根堆
{
break;
}
}
}
3.3 堆的声明
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int HPDataType;
typedef struct Heap
{
int capatity;
int size;
HPDataType* a;
}Heap;
//初始化
void HeapInit(Heap* php,HPDataType *a,int n);
//插入
void HeapPush(Heap* php,HPDataType x);
//删除
void HeapPop(Heap* php);
//获取顶元素
HPDataType* HeapTop(Heap* php);
//打印
void HeapPrint(Heap* php);
//销毁
void HeapDestory(Heap* php);
3.4 创建堆
堆的创建建立在向下调整算法,根据堆在数组中存储的性质,选择出堆最后的结点的父节点然后进行向下调整,然后迭代直到存储结构到0的小标结束。最后结点的父节点等于最后=(结点总数-1-1)/2。
void HeapCreate(Heap* php, int n)
{
int parent = (n - 1 - 1) / 2;//找到最后的子树父节点
while (parent >= 0)//直到堆顶结点
{
AdjustDown(php, n, parent);//向下调整
parent--;//找到上一个父节点
}
}
3.5 堆排序
升序建大堆,降序建小堆。
升序建大堆之后,堆顶为最大的数,将存储数组中0小标的数与最后一个数小标N-1中的数交换。再对小标0~N-1的N个数进行向下调整,重复这个步骤。
void HeapSort(Heap* php, int n)
{
int end = n - 1; //最后元素下标
while (end>0)
{
Swap(& php->a[0], & php->a[end]);//交换
HeapCreate(php, end);//end个元素再建堆
end--;//迭代
}
}
向上调整:从最后一个元素的父节点开始,与孩子节点比较,满足条件就交换,然后迭代。
void AdjustUp(Heap* php, int n, int child)
{
int parent = (child - 1) / 2;//最后元素的父节点
while (child>0)//到堆顶
{
if (php->a[child] < php->a[parent])
{
Swap(&php->a[child], &php->a[parent]);
child = parent;//迭代
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
3.6 堆的插入
先插入一个数,在进行向上调整。
void HeapPush(Heap* php, HPDataType x)
{
if (php->size == php->capatity)//扩容
{
php->a = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capatity * 2);
php->capatity *= 2;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php, php->size, php->size - 1);//向上调整
}
3.7 堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
void HeapPop(Heap* php)
{
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php, php->size, 0);
}
3.8 Top-k问题
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
4 二叉树遍历
前序遍历:根结点 左子树 右子树。
中序遍历:左节点 根节点 右节点。
后序遍历:左节点 根节点 右节点。
前序遍历递归图解: