《数据结构(C语言版)》(严蔚敏 吴伟民)这本书也断断续续的看过了好几遍,但都是在书上做做笔记,这次想着就把总结放到网上,一是自己可以经常翻出来读读,二是可以和网友们交流。
因为是从非线性数据结构部分开始重读的,所以本书前面的线性数据结构内容以后再总结。树是很重要的非线性数据结构,在计算机领域的道理广泛的应用,程序员经常写程序后需要编译器编译运行,在编译程序的时候就用树形结构来表示源程序的语法结构,当然还有很多其它重要的应用,所以本章讲的树最一般本质的特征,在这个基础上会衍生出很多重要的应用。
树中最重要的是二叉树的存储结构及其各种操作,因为树也能转换为唯一的二叉树。所以主要总结二叉树的性质。
结点的度、树的度、层次、深度等基本概念就看书自己理解。二叉树的定义是:每个结点最多两颗子树,即不存在度大于2的结点,而且左右子树不能交换,是有序树。
二叉树的性质:好几个性质都是数值方面的,一般自己推也能推出来;满二叉树和完全二叉树等性质。
二叉树结构的定义:
typedef struct Bi{
char data;
int flag;//这个可以不定义
Bi *lchild,*rchild;
}Bi,*Bihead;
上面是链式存储结构,还有其它的存储结构。
结构有了,那么我们假设二叉树也就有了,就需要对现在存在的一棵二叉树进行一些操作,主要的操作有:遍历,即访问二叉树中所有的结点,当然在我们人去查看二叉树中的结点肯定一目了然,但机器不行,所以就有先根序、中根序和后根序遍历二叉树等操作的存在。当然跳出这个三个遍历,其实最本质的还是广度优先遍历和深度优先遍历:深度 就是那三种遍历方法;广度就是层次遍历,即一层一层遍历二叉树结点。
首先广度遍历的三种遍历,如果使用递归的方法,则其实就是一个程序:
void show(Bihead b){//递归的遍历结点算法
if(b!=NULL){
cout<<b->data;//一般都是对结点进行操作的函数,这里简单的只是输出结点值
show(b->lchild);
show(b->rchild);
}
}
上面是先根序遍历二叉树的递归算法,中根序和后根序就是把cout那句放到中间和最后,具体的就不详细讲了,还是读者自己去理解把,最好就是画图自己理解。
接下来是非递归的对二叉树进行遍历的算法:
void pre_in_order(Bihead b,stack &s){//非递归先根序和中根序遍历二叉树算法
while(b||s.top!=s.base){
if(b!=NULL){
push(s,b);//
cout<<b->data;//如果是先根序则这里输出
b=b->lchild;
}
else{
b=pop(s);//
cout<<b->data;//如果是中根序 则是这里输出
b=b->rchild;
}
}
}
自己写的堆栈结构:
typedef struct stack{
Bihead *base,*top;
int stacksize;
}stack;
void initStack(stack &s){
s.base=new Bihead[100];
s.top=s.base;
s.stacksize=100;
}
void push(stack &s,Bihead h){
*s.top=h;
s.top++;
}
Bihead pop(stack &s){//
if(s.top==s.base)
return NULL;
else
{
s.top--;
return *s.top;
}
}
Bihead gettop(stack &s){
if(s.top==s.base)
return NULL;
else
return *(s.top-1);
}
二叉树后根序非递归遍历算法有点难度,其实,只要理解了这三种遍历的本质就好了,因为非递归都是需要用到栈结构,可以 用STL的也可以自己写一个简单的,在遍历一个二叉树的时候,什么时候访问结点取决于第几次访问到这个结点,第一次就是先根序,第二次就是中根序,第三次就是后根序,举个例子吧,不然肯定不晓得我在说什么了,下图是个最简单的二叉树:
9
/ \
2 3
这是个简单的二叉树,如果纯粹从深度遍历来看,应该是这样子的:922293339,这样的那本书上是有的,先根序则是结点第一次访问到,中根序是结点第二次访问到,第三次访问到结点是后根序,前两种遍历只要结点一次进栈:进栈前访问是先根序,出栈时候访问是中根序。而后根序则需要出栈后再次进栈,这里是难点,则后根序的遍历,可以先判断栈顶与当前访问结点的关系,如果是右子树与双亲结点,则需要出栈,否则不用出栈,将栈顶的结点的右子树赋予当前结点就行操作。
void post_order2(Bihead b,stack &s){
Bihead fr=NULL;
while(b||s.top!=s.base){
if(b){
push(s,b);
b=b->lchild;
}
else{
fr=gettop(s);
if(!fr->rchild){
b=pop(s);
cout<<"out";
fr=gettop(s);
while(fr&&fr->rchild==b){//这里错误
b=pop(s);
cout<<" out";
fr=gettop(s);
if(!fr)
break;
}
if(!fr)
break;
b=fr->rchild;
}
else{
b=fr->rchild;//这里错误
}
}
}
}
以上就是二叉树的深度优先遍历递归与非递归算法的实现,而到目前为止还没分析怎么来创建一棵二叉树,创建二叉树其实很简单,就是用先根序递归的方法进行创建,例如上面那个二叉树,在电脑上输入 92空格空格3空格空格 ,利用下面算法建立二叉树:
void create(Bihead &b){//
char c;
c=cin.get();
if(c!=' '){
b=new Bi;
b->data=c;
b->flag=0;
b->lchild=NULL;
b->rchild=NULL;
create(b->lchild);
create(b->rchild);
}
}
以上就是二叉树初始化的操作,所以这里有一句话突然想到,先有计算机程序还是先有算法,我觉得是先有各种算法思想 ,然后计算机的各种软件都可以通过这种思想来实现。
上面讲的是二叉树遍历的深度优先,下面总结广度优先遍历,即层次遍历:
只需要把访问的当前访问的结点进入一个队列的结构(深度是利用了栈的结构);
void LevelTraverse(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return;
queue<BinaryTreeNode *> q;
q.push(pRoot);
while(!q.empty())
{
BinaryTreeNode * pNode = q.front();
q.pop();
Visit(pNode); // 访问节点
if(pNode->m_pLeft != NULL)
q.push(pNode->m_pLeft);
if(pNode->m_pRight != NULL)
q.push(pNode->m_pRight);
}
return;
}
上面这段代码是我拷贝的别人的,因为用到了队列结构,所以我们可以像上面一样,自己写一个队列结构及其操作,但其实STL中都有这些了,可以现成用就行。
以上只是二叉树最简单最基础的一些内容,更多的是以这个为基础的一些其它重要数据结构和算法,下面就简单总结一下:
1.最优二叉树,哈夫曼树,最优前缀码,这都是一个概念,这是利用二叉树来进行数据压缩的一种非常广泛和基础的方法,具体的介绍可以去看树上的内容,内容理解很简单,但是算法具体写出来稍稍有点难度,我自己也没有具体去实现。
2.计算机的文件系统是树的结构,比如Linux文件管理背景知识中所介绍的。在UNIX的文件系统中,每个文件(文件夹同样是一种文件),都可以看做是一个节点。非文件夹的文件被储存在叶节点。文件夹中有指向父节点和子节点的指针(在UNIX中,文件夹还包含一个指向自身的指针,这与我们上面见到的树有所区别)。在git中,也有类似的树状结构,用以表达整个文件系统的版本变。
其它具体的应用,以后再补充。