树的介绍
- 数和无向图很相像,但是树是没有回路的。
- 因为树不包含回路这个特点,就被赋予了很多特性
1.一棵树中的任意两个结点有且仅有唯一的一条路径连通
2.一棵树中如果有n个结点,那么它一定恰好有n-1条边
3.在一棵树中加一条边将会构成一个回路 - 只要没有回路的连通无向图就是树
- 同一棵树可以有多个形态,是因为把不同的点当成根结点的原因
- 理解根结点,叶结点,父结点,子结点的定义
二叉树
- 二叉树是一种特殊的树,其特点是每个结点最多有两个儿子,分别称为右儿子和左儿子
- 更加严格的递归定义:二叉树要么为空,要么由根结点,左子树和右子树组成,而左子树和右子树分别是一颗二叉树
- 满二叉树:一颗深度为h且有2的h次方-1个结点的二叉树
- 完全二叉树:若设二叉树的高度为h,除第h层外,其他各层的结点个数都达到最大个数,第h层从右向左连续缺若干结点
堆-优先队列
- 我们把完全二叉树从上到下,从左到右进行编号
- 编完号后,我们可以发现,如果父结点编号为k,左儿子为2k,右儿子为2k+1。如果已知儿子的编号为x,则父结点编号为x/2
有两个定义:
1.最小堆:所有父结点都比子节点要小
2.最大堆:所有父结点都比子节点要大
- 如果一个堆中因为几个数字使得它不满足最小堆的要求,便要使那个树进行向上调整或向下调整
- 如要建立一个最小堆,当碰到一个父结点的值比两个子节点的值都大时,就要把父结点与较小的子节点进行交换,而且要从最后一个结点向第一个结点进行查找
- 向下调整的代码:
//n为结点的总数,h数组是储存结点的值
void siftdown(int i)//传入一个需要向下调整的结点编号
{
int t,flag=0;//flag作为一个标记,因为要一直向下调整,直至满足要求
while(i*2<=n&&flag==0){
//看当前要调整的结点还有没有左分支
//把较小的值的结点赋给t
if(h[i]>h[i*2])
t=i*2;
else
t=i;
//看有没有右节点
//把如果有就继续比较,把更小的值的结点赋给t
if(i*2+1<=n){
if(h[t]>h[i*2+1])
t=i*2+1;
}
//如果i不是最小的值,那么就要进行交换
if(t!=i){
//swap函数是要自己写,把h[i]和h[t]进行交换
swap(i,t);
//继续看交换后是否符合要求, i要变为被交换的数的结点,即为t
i=t;
}
//直到最大的值是自己,才能停止向下调整
else
flag=1;
}
return;
}
- 向上调整的代码:(向堆中添加一个数)
//向上调整
void siftup(int i)
{
int flag=0;
//因为是向上调整,所以当i等于1时,就不能继续调整了
if(i=1)
return;
while(i!=1&&flag==0){
if(h[i]<h[i/2])
swap(i,i/2);
else
flag=1;
i=i/2;
}
return;
}
- 依据这个向上调整代码,我们就可以建立一个最小堆,即从空堆开始,每加入一个数就对其进行向上调整,但这样的时间复杂度有点高
- 我们就可以先建立一个数组来存储数,把这个当作完全二叉树,然后从最后一个非叶结点(n/2)向上扫描,遇到不合适的则将当前结点向下调整,子树将符合堆的特性
for(i=n/2;i>=1;i--)
siftdown(i);
堆排序
- 比如我们要从小到大排序,我们就可以先建立一个最大堆,建立好之后,最大的元素就在h[1],我们的需求是从小到大排序,希望最大的放在最后,我们可以将h[1]和h[n]交换,此时h[n]就是数组中的最大元素,但还需将h[1]继续向下调整,n–,再将h[1]和h[n]进行交换,直到堆大小变为1为止
- 代码如下:
//建立最大堆
#include<stdio.h>
int h[101];//用来存放堆的数组
int n;//用来存放堆中元素的个数
//交换函数,用来交换堆中两个元素的值
void swap(int x,int y){
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
}
void siftdown(int i){//传入一个需要向下调整的结点编号i
//由于是最大堆,所以小的值要向下调整
int t,flag=0;//flag用来标记是否需要继续向下调整
//当i结点有儿子(至少有左儿子)并且需要继续调整的时候循环执行
while(i*2<=n&&flag==0){
//首先判断i和zuo儿子的关系,并用t记录较大的结点编号
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(t,i);//交换父节点与更大的子节点
i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来向下调整
}
else
flag=1;//否则说明当前的父节点已经是最大的,不需要再进行调整了
}
return;
}
//建立堆的函数
void creat(){
int i;
//从最后一个非叶结点到第1个结点依次进行向下调整
for(i=n/2;i>=1;i--){
siftdown(i);
}
return;
}
//堆排序
void heapsort(){
while(n>1){
swap(1,n);//把堆的最后一个变成当前最大的
n--;
siftdown(1);
}
return;
}
int main(){
int i,num;
//读入n个数
scanf("%d",&num);
n=num;
for(i=1;i<=num;i++){
scanf("%d",&h[i]);
}
//建堆
creat();
//堆排序
heapsort();
//输出
for(i=1;i<=num;i++)
printf("%d ",h[i]);
return 0;
}