树与堆
树
本文对树仅做简单介绍,不深入,以后会产出有关树的深入文章。
1、树的概念
先来看一张图:
这种结构是不是很赏心悦目呢?应该很适合用来存储数据吧。
树形结构就是与之类似的一种结构
树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构
每棵树都有根结点,结点与结点之间的关系在下图中有说明:
结点的度:一个结点含有子树的个数
树的度:一棵树中的最大结点的度
结点层次:从根结点开始定义,为第0层,其子结点为第1层(上图错误)
树的高度/深度:树中最大的结点层次,(空树深度为0),高度从底向上定义,深度从根结点向下定义
森林:N(>=0) 棵互不相交的树组成的集合
2、树的性质
3、树的储存
双亲表示法
每个结点存储其双亲在数组中的位置
typedef struct Node
{
int data;//数据
int parents;//双亲在数组中位置
}Node;
typedef struct
{
Node newnode[10];//大小可变
int r;//根
int n;//结点数
}tree;
这样的结构方便寻找双亲结点,但不利于寻找子结点。还有什么好方法呢?
孩子表示法
这个方法的逻辑就是:用单链表存储每个结点的子结点,这些单链表的头指针都被存储在一个一维数组中,可以结合上图理解
typedef struct LsNode
{
int child;
struct LsNode* next;
}ChildNode;
typedef struct
{
int data;
ChildNode FirChild;
}FirNode;
typedef struct
{
FirNode node[10];//大小可变
int r;//根
int n;//结点数
}
这个方法对于寻找子结点以及兄弟结点都是比较方便的,但是寻找双亲就麻烦了。
4、树的相关题目
-
在一颗度为3的树中,度为3的结点有2个,度为2的结点有1个,度为1的结点有2个,则叶子结点有( 6)个
设度为
i
的节点个数为ni
, 该树总共有n
个节点,则n=n0+n1+n2+n3
.有
n
个节点的树的总边数为n-1
条.根据度的定义,总边数与度之间的关系为:
n-1=0*n0+1*n1+2*n2+3*n3
.联立两个方程求解,可以得到
n0 = n2 + 2n3 + 1, n0=6
-
一颗拥有1000个结点的树度为4,则它的最小深度是(6 )
如果这棵树每一层都是满的,则它的深度最小,假设它为一个四叉树,高度为h,则这个数的节点个数为
(4^h - 1) / 3
,当h = 5, 最大节点数为341, 当h = 6, 最大节点数为1365,所以最小深度应该为6。 -
一颗完全二叉树有1001个结点,其叶子结点的个数是( 501)
该题需要用到二叉树性质:在任意二叉树中,度为0的节点都比度为2的节点多1个,即
n0 = n2 + 1
另外,在完全二叉树中,如果节点总个数为奇数,则没有度为1的节点,如果节点总个数为偶数,只有一个度为1的节点
因此:
n0 + n1 + n2 = 1001
节点总数为奇数,没有度为1的节点n0 + 0 + n2 = 2*n0-1 = 1001 n0 = 501
-
在一颗完全二叉树中,某一个结点没有其左孩子,则该结点一定( 是叶结点)
完全二叉树中如果一个节点没有左孩子,则一定没有右孩子,必定为一个叶子节点,最后一层一定为叶子节点,但是倒数第二层也可能存在叶子节点。
堆
1、堆的概念
堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
- 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。(见下图)
2、主要算法
以下算法都以小堆为例
向下调整算法(堆的删除)
- 先设定根节点为当前节点(通过下标获取,标记为cur),比较左右子树的值,找出更小的值,用child来标记。
- 比较child和cur的值,如果child比cur小,则不满足小堆的规则,需要进行交换。
- 如果child比cur大,满足小堆的规则,不需要交换,调整结束。
- 处理完一个节点之后,从当前的child出发,循环之前的过程。
图中为小堆的向下调整算法示意图,大端变换数字即可,就不再附图。
这个算法在堆的删除操作中会用到。
void ShiftDown(int* arr, int n, int cur)
{
int child = 2 * cur + 1;
while (child < n)
{
if (child + 1 < n && arr[child + 1] < arr[child])
{
++child;
}
if (arr[child] < arr[cur])
{
Swap(&arr[child], &arr[cur]);
cur = child;
child = 2 * cur + 1;
}
else
{
break;
}
}
}
向上调整算法(堆的插入)
- 先设定倒数的第一个叶子节点为当前节点(通过下标获取,标记为cur),找出他的父亲节点,用parent来标记。
- 比较parent和cur的值,如果cur比parent小,则不满足小堆的规则,需要进行交换。
- 如果cur比parent大,满足小堆的规则,不需要交换,调整结束。
- 处理完一个节点之后,从当前的parent出发,循环之前的过程。
逻辑与向下调整算法虽然相反,但是异曲同工。
void ShiftUp(int* arr, int n, int cur)
{
int father = (cur - 1) / 2;
while (cur > 0)//因为是向上调整,所以cur要满足>0的条件
{
if (arr[cur] < arr[father])
{
Swap(&arr[cur], &arr[father]);
cur = father;
father = (cur - 1) / 2;
}
else
{
break;
}
}
}
3、堆的实现(小堆)
1、堆的定义
typedef int HPDataType;//定义堆的数据类型
typedef struct Heap
{
HPDataType* data;
int size;
int capacity;
}Heap;
2、交换
void Swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
3、检查容量
void CheckCapacity(Heap* hp)
{
if (hp->capacity == hp->size)
{
int newcapacity = hp->capacity == 0 ? 1 : 2 * hp->capacity;
hp->data = (HPDataType*)realloc(hp->data, sizeof(HPDataType) * newcapacity);
hp->capacity = newcapacity;
}
}
4、堆的初始化
void HeapInit(Heap* hp)
{
assert(hp);
hp->data = NULL;
hp->capacity = hp->size = 0;
}
5、堆的创建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
assert(hp);
hp->data = (HPDataType*)malloc(sizeof(HPDataType) * n);
memcpy(hp->data, a, sizeof(HPDataType) * n);
hp->capacity = hp->size = n;
for (int i = (n - 2) / 2; i >= 0; i--)
{
ShiftDown(hp->data, n, i);
}
}
6、堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->data);
hp->data = NULL;
hp->capacity = hp->size = 0;
}
7、堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
CheckCapacity(hp);
hp->data[hp->size++] = x;
ShiftUp(hp->data, hp->size, hp->size - 1);
}
8、堆的删除
void HeapPop(Heap* hp)
{
if (hp->size > 0)
{
Swap(&hp->data[0], &hp->data[hp->size - 1]);//删除的是根,所以交换根与最后一个结点,再size--、向下调整
hp->size--;
ShiftDown(hp->data, hp->size - 1, 0);
}
}
9、获取根
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->data[0];
}
10、堆的大小
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
11、堆的判空
int HeapEmpty(Heap* hp)
{
if (hp->size == 0 || hp == NULL)
{
return 1;
}
else
{
return 0;
}
}
9、获取根
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->data[0];
}
10、堆的大小
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
11、堆的判空
int HeapEmpty(Heap* hp)
{
if (hp->size == 0 || hp == NULL)
{
return 1;
}
else
{
return 0;
}
}