二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
一棵深度为k,且有2^k-1个节点的二叉树,称为满二叉树。这种树的特点是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。具有n个节点的完全二叉树的深度为floor(log2n)+1。深度为k的完全二叉树,至少有2^k-1个叶子节点,至多有2^k-1个节点。
一般二叉树性质
在非空二叉树的i层上,至多有2^i-1个节点(i>=1)。
在深度为K的二叉树上最多有2^k-1个结点(k>=1)。
对于任何一棵非空的二叉树,如果叶节点个数为n0,度数为2的节点个数为n2,则有: n0 = n2 + 1
完全二叉树性质
具有n的结点的完全二叉树的深度为log2n+1.
如果有一颗有n个节点的完全二叉树的节点按层次序编号,对任一层的节点i(1<=i<=n)有
1.如果i=1,则节点是二叉树的根,无双亲,如果i>1,则其双亲节点为[i/2],向下取整
2.如果2i>n那么节点i没有左孩子,否则其左孩子为2i
3.如果2i+1>n那么节点没有右孩子,否则右孩子为2i+1
二叉树遍历
- 前序遍历
先访问根结点,再先序遍历左子树,最后再先序遍历右子树即根—左—右。
1
2
3
4
5
6
7void PreOrderTraverse(BiTree t) {
if(t != NULL) {
printf("%c ", t->data);
PreOrderTraverse(t -> lchild);
PreOrderTraverse(t -> rchild);
}
}- 中序遍历
先中序遍历左子树,然后再访问根结点,最后再中序遍历右子树即左—根—右。
1
2
3
4
5
6
7void InOrderTraverse(BiTree t) {
if(t != NULL) {
InOrderTraverse(t -> lchild);
printf("%c ", t->data);
InOrderTraverse(t -> rchild);
}
}- 后序遍历
先后序遍历左子树,然后再后序遍历右子树,最后再访问根结点即左—右—根。
1
2
3
4
5
6
7void PostOrderTraverse(BiTree t) {
if(t != NULL) {
PostOrderTraverse(t -> lchild);
PostOrderTraverse(t -> rchild);
printf("%c ", t->data);
}
}建树
1 | typedef struct node { |
例题 YZOJ P1196 Postorder Traversal
大体思路:因为后序遍历的最后一个字母就是根结点,在中序遍历中根结点的左边就是左子树,右边就是右子树,所以可以计算出左子树的结点总数和右子树的节点总数,再用结点的总数将后序遍历中的结点分开,分开后就是两个子树的后序遍历。
代码:
1 |
|
堆是一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
- 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆主要的作用是用来实现优先队列。
堆分为大根堆和小根堆,下面用小根堆做示例。
- 插入操作
手写实现:1
2
3
4
5
6void push(int x) {//swap是交换两个数的值
q[++sz] = x;
int pos = sz;
while(pos > 1 && q[pos >> 1] > q[pos])
swap(q[pos], q[pos >> 1]), pos = pos >> 1;
}
- 删除操作
手写实现:1
2
3
4
5
6
7
8
9
10
11
12void pop() {//swap是交换两个数的值
int rt = q[sz--], pos = 1;
q[pos] = rt;
while(true) {
int lv = (pos << 1) > sz ? inf : q[pos << 1];
int rv = (pos << 1 | 1) > sz ? inf : q[pos << 1 | 1];
int v = min(lv, rv), nt = lv < rv ? (pos << 1) : (pos << 1 | 1);
if(q[pos] > v)
swap(q[pos], q[nt]), pos = nt;
else break;
}
}
- 其他操作
手写实现:
1 | int top() {//返回队首元素 |
STL实现优先队列
1 | empty()//如果队列为空,则返回真 |
实际上你可以去YZOJ里的AAS里有一个pb_ds库的介绍,可以实现优先队列,速度接近手写。
例题 YZOJ P2704 [NOIP 2016 四校联训 Round 4]打败黑熊
大体思路:维护一个小根堆,每个守卫机关相当于向堆中加入一个数,每个审判机关相当于删除堆中最小值,直到堆中的元素个数小于上限。最后直接把堆中所有的数加起来。
代码:
1 | //顺便演示一下pb_ds中的priority_queue |