数据结构 二叉树
Posted Suk_god
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 二叉树相关的知识,希望对你有一定的参考价值。
文章目录
概念及结构
二叉树基本概念
二叉树是结点的一个有限集合,该集合:
1.为空
2.由一个根节点和它的左子树与右子树构成
由上图可以得知:
1.二叉树不存在度大于2的结点
2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
特殊的二叉树
满二叉树
1.是一棵空树
2.非空,则该二叉树的每一层节点数达到该层若能容纳结点的最大值
也即:层数为k时,总结点数为2^k -1 个结点
完全二叉树
完全二叉树是由满二叉树引出的~~
1.是一棵空树
2.若不空,对于层数为k的二叉树来说:
前 k-1 层是一个 满二叉树
第 k 层 从左到右依次按序排列剩余节点数
二叉树的性质
1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h -1 个
3.对任何一棵二叉树,若 度为 0 的结点个数 为 n0, 度为 2 的结点个数 为 n2, 则有关系 n0 = n2+1成立
对于3的证明如下:
4.若规定根节点的层数为1,具有n个结点的满二叉树的深度为h = log(n+1) (表示log以2为底n+1的对数)
5.对于具有n个结点的完全二叉树,如果按照从上至下,从左至右的数组顺序对所有结点从0开始编号,则对于序号为 i 的结点有:1.若 i > 0, i 位置结点的双亲序号:(i-1)/ 2; i = 0, i 为根节点编号,无双亲结点
2.若2i+1 < n,左孩子序号:2i+1 否则无左孩子
3.若2i+2 < n,右孩子序号:2i+2 否则无右孩子
对于5的简单说明
二叉树的存储结构
对于二叉树来说,一般有两种存储结构
顺序存储
用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中只有堆才会使用数组来存储。
二叉树顺序存储在物理上是一个数组,在逻辑上是一棵二叉树
完全二叉树的顺序存储
非完全二叉树的顺序存储
链式存储
用链表来表示一棵二叉树
链表中的每个结点由3部分组成,数据域和左右指针域,其中左右指针域分别指向该结点的左右孩子所在结点,目前采用二叉链来存储。
对应的数据结构类型为
typedef char BTDataType;
typedef struct BinaryTree
BTDataType data;//数据域
struct BinaryTree* left;//指向当前结点的左孩子
struct BinaryTree* right;//指向当前结点的右孩子
BTNode;
二叉树的顺序结构及实现
前面提到,顺序存储对于完全二叉树比较实用,顺序存储主要是用来对堆进行存储。下面介绍堆的相关概念
堆的概念及结构
概念:有一组数据将其按照完全二叉树的顺序存储方式存储在数组中。这一组数据需要满足以下条件:
该二叉树的父节点始终大于它的孩子结点,称它为大堆
该二叉树的父节点始终小于于它的孩子结点,称它为小堆
堆的性质
堆中某个结点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
堆的实现
问题引入(以小堆为例)
如上图所示:红色部分都已经符合小堆的特性,那么我们应该如何调整绿色部分的结点,使整个二叉树是一个小堆呢?
我们只需要进行以下步骤:
1.找到该结点左右孩子中较小的结点
2.交换这两个结点
3.对已交换的部分进行同样的操作(1.2)
4.直到该结点没有孩子结点时停止
那么对于上述情况的操作步骤如下图所示:
调整算法
针对于上述情况的执行过程,大佬们总结出一个算法来实现这样的功能,那就是向下调整算法
前提:左右子树必须是一个堆时,才能调整
所以执行步骤为:
1.找到倒数第一个非叶子结点,从该结点开始调整
2.找到该结点左右孩子中较小的结点
3.交换这两个结点
4.对已交换的部分进行同样的操作(2.3)
5.直到该结点没有孩子结点时表示一轮调整完成
6.紧接着调整前一个非叶子结点,直到调整到根节点停止
7.此时构建的堆为小堆
若要构建大堆,只需要每次交换时将上述的较小换为较大即可
堆的创建
对于给定的一个任意数据的数组,起初它是不符合堆的特性的。
由于根节点的左右子树并不是堆,所以我们从倒数第一个非叶子结点开始调整,一直调整到根节点,就可以调整为堆
e.g int a[] = 1,5,3,8,7,6;
调整成一个大堆
对于该数组的调整过程如下图所示:
在每一次调换元素的时候,有可能破坏其子堆的结构,因此在每一次发生元素交换的时候,都需要递归调用重新构造堆的结构。
建堆的时间复杂度
由于堆是一个完全二叉树,而满二叉树也是一种特殊的完全二叉树,因此使用满二叉树来求解时间复杂度(时间复杂度本里就是一个近似值,多几个结点不影响最终结果)
设树的高度为h
第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层
因此需要移动结点总的移动步数为:
堆的插入
将要插入的数据放在数组的末尾,再进行向上调整算法直到满足堆为止
e.g 向建堆时的例子中插入数据100
则新数组为8,7,6,5,1,3,100
对应的调整过程见下图
堆的删除
删除堆是删除堆顶的数据,将堆顶的数据与最后一个数据交换,然后删除最后一个元素,再进行向下调整算法
e.g 删除刚刚建好的大堆中的堆顶元素100
堆的代码实现
heap.h
申明了一些堆的基本操作
#pragma once
#include <stdio.h>
#include <windows.h>
typedef int HPDataType;
//定义一个返回值为int且有两个int型参数的函数指针类型
typedef int(*Com) (HPDataType, HPDataType);
typedef struct Heap
HPDataType* arr;
int size;
int capacity;
Com Compare;
Heap;
// 堆的构建
extern void HeapCreat(Heap* hp, HPDataType* a, int n,Com com);
// 堆的销毁
extern void HeapDestory(Heap* hp);
// 取堆顶的数据
extern HPDataType HeapTop(Heap* hp);
//堆的数据个数
extern int HeapsSize(Heap* hp);
// 堆的判空
extern int HeapEmpty(Heap* hp);
// 堆的插入
extern void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
extern void HeapPop(Heap* hp);
extern void HeapTest();
heap.c
#include "heap.h"
#include <malloc.h>
#include <assert.h>
static void Swap(HPDataType* xp, HPDataType* yp)
HPDataType temp = *xp;
*xp = *yp;
*yp = temp;
static void AdjustDown(Heap* hp, int parent)
assert(hp);
//1.child标记左右孩子较小的,默认父节点的左孩子为左右孩子中较小的一个
int child = 2 * parent + 1;
while (child < hp->size)
//2.确定实际较小的孩子结点
if (child + 1 < hp->size && hp->Compare(hp->arr[child+1],hp->arr[child]))
child += 1;
//3.将较小的孩子结点与父节点比较,判断是否需要交换
if (hp->Compare(hp->arr[child] , hp->arr[parent]))
Swap(&hp->arr[child], &hp->arr[parent]);
parent = child;
child = parent * 2 + 1;
else
return;
// 堆的构建
void HeapCreat(Heap* hp, HPDataType* a, int n,Com com)
assert(a);
assert(hp);
//1.开辟空间
hp->arr = (HPDataType*)malloc(sizeof(HPDataType)*n);
if (NULL == hp->arr)
assert(0);
return;
//2.空间开辟成功,对新空间初始化
for (int i = 0; i < n; i++)
hp->arr[i] = a[i];
hp->capacity = n;
hp->size = n;
hp->Compare = com;
//3.使用向下调整方法进行堆的构建
int parent = (n - 2) / 2;
while (parent >= 0)
AdjustDown(hp, parent);
parent--;
// 堆的销毁
void HeapDestory(Heap* hp)
assert(hp);
if (hp->arr)
free(hp->arr);
hp->arr = NULL;
hp->capacity = hp->size = 0;
// 堆的判空 返回值:1 空的 0 不空
int HeapEmpty(Heap* hp)
assert(hp);
return hp->size == 0? 1:0;
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
assert(hp);
if (!HeapEmpty(hp))
return hp->arr[0];
else
printf("堆为空!返回值为-1\\n");
return -1;
//或取堆的元素个数
int HeapsSize(Heap* hp)
assert(hp);
return hp->size;
// 堆的删除
void HeapPop(Heap* hp)
assert(hp);
//1.将堆顶元素与堆的最后一个元素交换
Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
//2.对hp的size-1,控制有效元素个数
hp->size--;
if (hp->size == 0)
free(hp->arr);
hp->arr = NULL;
hp->capacity = 0;
//3.对新的堆顶元素进行向下调整操作
AdjustDown(hp,0);
//扩容函数
static void HeapExpand(Heap* hp)
int newCapacity = hp->capacity * 2;
//1.开辟新空间
HPDataType *tmp = (HPDataType*)malloc(sizeof(HPDataType)*newCapacity);
if (NULL == tmp)
assert(0);
return;
//2.将旧空间的内容拷贝至新空间
memcpy(tmp, hp->arr, sizeof(HPDataType)*hp->capacity);
//3.释放旧空间
free(hp->arr);
hp->arr = tmp;
hp->capacity = newCapacity;
//向上调整
static void AdjustUp(Heap* hp)
int child = hp->size - 1;
int parent = (child - 1) / 2;
while (child > 0)
if (hp->Compare(hp->arr[child] , hp->arr[parent]))
Swap(&hp->arr[parent], &hp->arr[child]);
child = parent;
parent = (child - 1) / 2;
else
return;
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
assert(hp);
//1.判断能否进行插入
if (hp->size == hp->capacity)
//不能插入,先扩容
HeapExpand(hp);
//2.执行插入操作
hp->arr[hp->size] = x;
hp->size++;
AdjustUp(hp);
/
static int Less(HPDataType left,HPDataType right)
return left < right;
static int Greater(HPDataType left, HPDataType right)
return left > right;
void HeapTest()
Heap hp;
HPDataType array[] = 16, 72, 31, 23, 94, 53 ;
printf("初始化堆\\n");
HeapCreat(&hp, array, sizeof(array) / sizeof(array[0]), Greater);
printf("堆顶元素:%d\\n", HeapTop(&hp));
printf("堆的有效元素个数:%d\\n",HeapsSize(&hp));
printf("插入新元素后\\n");
HeapPush(&hp, 1000);
printf("堆顶元素:%d\\n", HeapTop(&hp));
printf("堆的有效元素个数:%d\\n", HeapsSize(&hp));
printf("删除堆顶元素后\\n");
HeapPop(&hp);
printf("堆顶元素:%d\\n", HeapTop(&hp));
printf("堆的有效元素个数:%d\\n", HeapsSize(&hp));
HeapDestory(&hp);
main.c
#include "heap.h"
int main()
HeapTest();
system("pause");
return 0;
堆的相关操作见源码 堆!
堆的应用
堆排序
顾名思义:就是利用堆的思想来对数据进行排序
主要分成两个步骤:
1.建堆
升序:建大堆
降序:建小堆
2.利用堆删除的思想来进行排序
利用向下调整算法实现
e.g 对数组8,7,6,5,1,3进行升序排列
首先,升序需要建立大堆
其次:对该对结构进行删除的操作
主要过程如下
实现代码
#include "heap.h"
static void Swap(int* xp,int* yp)
int temp = *xp;
*xp = *yp;
*yp = temp;
static void AdjustHeap(int* array, int parent, int size)
//使用child来标记parent的较小孩子结点
//由于堆是一个完全二叉树,所以对于任意一个父节点来说,
//它一定会有左孩子,但不一定有右孩子,
//所以先让child标记它的左孩子
int child = parent * 2 + 1;
//左孩子存在
if (child < size)
//右孩子存在,并且右孩子小于左孩子
if (child + 1 < size && array[child] > array[child + 1])
//child重新标记右孩子
child += 1;
if (array[parent]>array[child])
Swap(&array[parent], &array[child]);
else
return;
void HeapSort(int *array, int size)
//1.找到第一个非叶节点
int parent = (size - 1 - 1) / 2;
//2.从该结点开始,采用向下调整的方式进行排列
//3.直到数组的第一个元素被排完-后结束
while (parent >= 0)
AdjustHeap(array, parent, size);
parent--;
void testHeap()
int array[] = 16, 72, 31, 23, 94, 53 ;
int size = sizeof(array) / sizeof(array[0]);
printf("Before:\\n");
for (int i = 0; i < size; i++)
printf("%d ",array[i]);
printf("\\n");
HeapSort(array, size);
printf("After:\\n");
for (int i = 0; i < size; i++)
printf("%d ", array[i]);
printf("\\n");
源码见 堆排序
Top-K问题
求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
以上是关于数据结构 二叉树的主要内容,如果未能解决你的问题,请参考以下文章