前言:
二叉树是什么?
同样也和之前的"栈"跟"队列"是一样的,是一种存储数据的方式,只不过二叉树的结构更为复杂。
那么为什么要用二叉树存储数据呢?真的多此一举吗?
它的实际作用是什么呢?
堆:
树的定义:
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根节点没有前驱结点
除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继因此,树是递归定义的。
节点的度:一个节点含有的子树的个数称为该节点的度;
叶节点或终端节点:度为0的节点称为叶节点;
非终端节点或分支节点:度不为0的节点;
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; :一个节点含有的子树的根节点称为该节点的子节点;
兄弟节点:具有相同父节点的节点互称为兄弟节点;
树的度:一棵树中,最大的节点的度称为树的度;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:从根到该节点所经分支上的所有节点;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
森林:由m(m>0)棵互不相交的树的集合称为森林;
二叉树的定义:
二叉树是特殊的树,满足以下特点:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
例如:
两种特殊的二叉树:
1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
例如:
堆的定义:
如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆首先是一个完全二叉树,并且满足以下条件:
堆中某个节点的值总是不大于或不小于其父节点的值;
当堆中某个节点的值总大于其父亲结点的值,称为大堆。
当堆中某个节点的值总大于其父亲结点的值,称为小堆。
例如:
堆的实现:
由于堆的实现可以用链表实现,但是同样可以用顺序表实现,在这里由于是初阶,首选利用顺序表实现。
利用顺序表实现堆需要进行有序存储。
例如存储一个小堆:
通过观察,双亲的下标和子节点的下标关系是:
双亲下标 = (子节点下标 -1)/2;
左孩子节点下标 = 双亲下标*2+1;
右孩子节点下标 = 双亲下标*2+2;
Heap.h文件中定义结构体及函数的声明:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
//初始化堆
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
这里结构体的定义,函数的声明不做过多的解释。
在之前的数据结构中都有一定的说明。
(主要是懒)
Heap.c文件中实现函数的定义:
初始化堆:
在未开辟空间之前将指针置为NULL,capacity 与size 置为0。
void HeapInit(Heap* hp)
{
assert(hp);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
销毁堆:
话不多说直接写:
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
堆的插入:
建大堆:
先画图,分析一下:
先插入一个3,再插入一个10
发现10比3大,也就是孩子比双亲大,需要交换。
双亲下标 = (子节点下标 -1)/2;
左孩子节点下标 = 双亲下标*2+1;
右孩子节点下标 = 双亲下标*2+2;
再插入一个8。
再插入一个15。
此时需要交换一次双亲节点和孩子节点。
此时还是需要交换一次双亲节点和孩子节点。
以上的过程称之为向上调整!
每插入一个值之后就需要进行向上调整!!
void swap(HPDataType *a, HPDataType *b)
{
HPDataType c = 0;
c = *a;
*a = *b;
*b = c;
}
void AdjustUp(HPDataType *a,int child)
{
int parent = (child - 1) / 2;
while (child>0)
{
if (a[child]>a[parent])
{
swap(&a[child],&a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->_capacity == hp->_size)
{
int newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->_a ,newcapacity*sizeof(HPDataType));
if (tmp == NULL)
{
perror("malloc::error");
exit(-1);
}
hp->_capacity = newcapacity;
hp->_a = tmp;
}
hp->_a[hp->_size] = x;
hp->_size++;
AdjustUp(hp->_a,hp->_size-1);
}
堆的删除:
堆的删除是从对堆顶开始删除,但是删完之后大堆的顺序会变得混乱,该怎么办?
画图分析:
如果直接删除,结果会是这样:
此时发现空出了一个位置,也就不满足完全二叉树了!!
不适合这样删数据。
第一步首先应该将需要删除的堆顶的数据与堆底进行交换。
直接size--,然后变成这样:
之后进行向下调整,当双亲结点的值小于左孩子与右孩子的最大值时交换:
继续进行:
直到双亲节点>size为止。
代码如下:
void AdjustDown(HPDataType* a, int size, int parent)
{
//假设最大的孩子的值是左孩子对应的数值
int childmax = (parent * 2) + 1;
while (childmax<size)
{
//如果右有孩子并且右孩子的值是大于左孩子将最大的孩子换成右孩子
if (childmax + 1 < size && a[childmax + 1] > a[childmax])
{
childmax = childmax + 1;
}
if (a[parent] < a[childmax])
{
swap(&a[parent], &a[childmax]);
parent = childmax;
childmax = (parent * 2) + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* hp)
{
assert(hp);
swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
hp->_size--;
AdjustDown(hp->_a,hp->_size,0);
}
取堆顶的数据:
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(hp->_size);
return hp->_a[hp->_size - 1];
}
堆的数据个数:
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
堆的判空:
// 堆的判空
int HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size == 0;
}
test.c中实现堆:
#include"Heap.h"
void test1()
{
Heap hp;
int arr[] = {1,2,3,4,5,6,7,8};
HeapInit(&hp);
int i = 0;
//插入并调整
for (i = 0; i < sizeof(arr) / sizeof(int); i++)
{
HeapPush(&hp, arr[i]);
}
//输出并删除
while (!HeapEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HeapDestory(&hp);
}
int main()
{
test1();
return 0;
}