参考:《啊哈算法》
树
树其实就是不包含回路的连通无向图
**
连通是说其其中任意两点之间是可达的
不包含回路就是说一定能够不会出现走一圈的情况
**
树的特性
一棵树中的任意两个结点有且仅有唯一的一条路径连通
一棵树如果有n个结点,那么它一定恰好有n-1条边
在一棵树中加一条边将会构成回路
为了确定一棵树的形态,在树中可以指定一个特殊的结点—–根
如果一个结点没有子结点,这个结点就称为叶结点。
没有父结点的结点称为根结点。
如果一个结点既不是根结点也不是叶结点,则称为内部结点。
每个结点还有深度,深度是指从根结点到这个结点的层数(根为第一层)
二叉树
严格递归定义:二叉树要么为空,要么由根结点、左子树和右子树组成,而左子树和右子树分别是一棵二叉树
二叉树中还有两种特殊的二叉树:
满二叉树
所有的叶结点都有相同的深度完全二叉树
如果一棵二叉树除了最右边位置上有一个或者几个叶结点缺少外,其他事丰满的二叉树,就是满二叉树。
(完全二叉树的典型应用就是堆)
堆—神奇的优先队列
所有的父结点都比子结点要小的完全二叉树称为最小堆(小顶堆)
所有的父结点都比子结点要大的完全二叉树称为最大堆(大顶堆)
完全二叉树一个性质:最后一个非叶结点是第n/2个结点
建堆及堆排序的展示(小顶堆)
算法分析:先输入数据存在数组中建立一棵完全二叉树,然后从最后一个非叶结点一次向前进行调整。调整结束后就满足小顶堆,根结点就是最小值,此时记录下根结点,并将其从堆中删除,将最后一个结点送到根结点的位置,此时不满足小顶堆,我们就向下调整使其再次满足。重复步骤至完全二叉树为空
#include <iostream>
#include <cstdio>
using namespace std;
//向下调整
int h[102];
int n;
void siftdown(int i) {
int t,flag;
while(i*2<=n&&flag==0) {
if(h[i]>h[i*2]) {//先于左结点比
t=i*2;
} else {
t=i;
}
if(i*2+1<=n) {//是否有右结点
if(h[t]>h[i*2+1]) {
t=i*2+1;
}
}
if(t!=i) {
swap(h[i],h[t]);//交换
i=t;
} else {
flag=1;//满足顶堆条件的信号传出
}
}
}
//建堆
void create() {
//从最后一个非叶结点到第一个结点依次进行向下调整
for(int i=n/2; i>=1; i--) {
siftdown(i);
}
}
//删除最大元素
int deletemax() {
int t;
t=h[1];//用临时变量记录堆顶点的值
h[1]=h[n];//将堆的最后一个顶点赋值到堆顶
n--;//堆元素少一
siftdown(1);//向下调整
return t;//返回记录的堆的顶点的最小值
}
int main() {
int num;
scanf("%d",&num);
for(int i=1; i<=num; i++) {
scanf("%d",&h[i]);
}
n=num;
create();
for(int i=1; i<=num; i++) {
printf("%d ",deletemax());
}
return 0;
}
测试样例
14
99 5 36 7 22 17 46 12 2 19 25 28 1 92
当然还可以通过建立大顶堆的方式进行堆排序。
虽然这里的堆排序看上去很是费时,但是要是使用在添加一个数,或者添加一个数同时删除一个数的情况时就比较的高效了。比如我们删除一组数的最小值,并新增一个数,同时求新数组的最小值时。直接将新增的数放到小顶堆的堆顶(本身这里之前是最小的数),然后调整到符合小顶堆时的堆顶就是新的最小值。
另外要是我们新增一个数,但是不进行任何删除操作,只是最快求出新的最小值时,只需要将新增的数添加到最后。然后一路向上调整至符合要求,新的堆顶就是新的最小值。
堆还经常被用作求一个数列中第k大的数,只需要建立一个大小为k的小顶堆,堆顶就是第k大的数