在进入主题之前, 先让我们来了解一下我们需要掌握的预备知识:树、二叉树。
有学习过算法的同学应该都听过图这个名词吧,树与图挺类似的,唯一的区别就是图是连同的,但树是不连通的,也就是任意两个节点之间有且只有一条路径。就像我们计算机文件夹的结构,如果我们要从一个文件夹到另一个文件夹,有且只有一条路径。
那二叉树又是什么呢?首先,二叉树是树,但它是特殊的数。二叉树的每一个节点最多只有两个子节点,左边的叫做左节点,右边的叫做右节点。二叉树又分为满二叉树与完全二叉树。满二叉树中每一个内部节点都具有两个儿子;如图:;完全二叉树除最高层外,其他各层节点数达到最大值, 同时;最高层只允许从右向左有连续若干缺节点;如图:
,性状就类似与
。二叉树为什么这么重要呢?二叉树具有什么样特殊的性质呢?笔者认为二叉树最重要的性质就是其父节点与子节点的关系了:如果父节点为 i 且其有子节点,则左子节点为 2*i ;右子节点为 (2*i+1);如果子节点为 i ;不论其是左子节点还是右子节点,其父节点为 (i/2)取整。根据这个性质;我们就可以用一维数组来存储一棵树了。
二叉树这个特殊的数据结构能给我们带来什么样的奇特功能呢?先让我们来看一张图:。不知道细心的你有没有发现什么玄机呢?是的,任何一个父节点都比它的子节点有小,这叫做最小二叉树;还有一种是任何一个父节点都比它的子节点的的,称为最大二叉树。如果我们这样安排数据,那会给我们什么样的方便呢?是的,如果我们这样做了,我们就可以准确的知道最小的数或最大的数在那,这样我们就不用遍历整个数组去找最大最小的数了。
下面我们讲一下怎样对二叉树进行操作。先讲插入:如果我们要插入一个数,我们先将这个数添加到数组的尾部,然后与其父节点比较,若小于父节点,就交换;交换后继续与对应的父节点比较,若还小,则继续交换,知道满足二叉树的性质。如果要删除最小的数呢?首先,我们把最后的一个数放在数组首部,让 其成为整个二叉树的根,然后在从根部一步步向下调整。那要怎么调整呢?首先,我们要让其先与左节点比较(因为若有子节点,则必有左节点);然后在与右节点比较;若都满足,则与最小的节点交换;重复次操作,直到满足二叉树的性质。
说了这么多,那我们要怎样实现一个二叉树呢?首先,我们先把所有的数据存进一个数组。在进行下面的操作之前,我们应知道:最后一个非叶节点为(n/2)。我们要把一颗杂乱的数整理成符合要求的二叉树;我们只需从最后一个非叶节点开始向下调整,直到根节点,则整理后的二叉树就是符合要求的二叉树。
全部代码如下:
#include<stdio.h>
int h[101];
int n;
void swap(int x,int y){ //用于交换两个数
int temp=h[x];h[x]=h[y];h[y]=temp;
}
void siftup(int i){ //用于插入一个数,向上调整
int flag=0;
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;
}
}
void siftdown(int i){ //向下调整
int t,flag=0;
while(i*2<=n&&flag==0){
if(h[i]>h[2*i]){
t=2*i;
}
else
t=i;
if(i*2+1<=n){
if(h[t]>h[2*i+1])
t=2*i+1;
}
if(t!=i){
swap(t,i);
i=t; //继续循环,直至符合二叉树性质
}
else
flag=1;
}
}
void creat(){ //创建二叉树
for(int i=n/2;i>=1;i--){
siftdown(i);
}
}
int deletemin(){ //用于取出最小的元素
int t=h[1];
h[1]=h[n--];
siftdown(1); //重新调整二叉树
return t ;
}
int main(){
int num;
scanf("%d",&num);
n=num;
for(int i=1;i<=n;i++){
scanf("%d",&h[i]);
}
creat();
for(int i=1;i<=num;i++){
printf("%d ",deletemin());
}
return 0;
}