正常套路,数据结构无非就是结构定义、插入、查找、删除操作。树有几种存储方式,一种是孩子兄弟结点表示法,一种是父结点表示法,对于二叉树采用的是孩子表示法。
struct node
{
int data;
node* lchild;
node* rchild;
};
这里只考虑都左右子结点和自己存储的值data,为了满足平衡二叉树的操作,需要知道树的高度height,故其定义应修改为:
struct node
{
int data;
int height;//树高
node* lchild;
node* rchild;
};
到这里一个普通二叉树的链式定义算是完成了,下面讨论创建结点操作,数据结构中比较复杂的结构都是通过一个个比较简单的结点堆积而成的,树也不例外。
node* newnode(int v)//v为结点存储的值
{
node*Node=new node;
Node->data=v;
Node->lchild=Node->rchild=NULL;//左右子树置为NULL
return Node;
}
有了一个个小节点,下一步就是一个一个结点之间的连接操作,这里通过一定的规则,即可实现一棵树的构建
void insert(node*&root,int x)//root指针本身可能会改变,用引用
{
if(root==NULL)
{
root=newnode(x);
return;
}
if(必须插入左子树的某要求)
{
insert(root->lchild,x);
}
if(必须插入右子树的某要求)
{
insert(root->rchild,x);
}
}
比如我这里如果是BST,那么相应的插入操作就是
void insert(node*&root,int x)
{
if(root==NULL)
{
root=newnode(x);
return;
}
if(x<root->data)//待插结点值小于根节点,插入左子树
insert(root->lchild,x);
if(x>root->data)
insert(root->rchild,x);
}
当然,如果是一棵AVL树,那么插入操作又会复杂一点,要考虑平衡的问题,这里还是先不写了。有了插入操作,建树就很简单了,无非就是一个一个插入节点直到整棵树建成。下面是给出了节点值和节点个数的一种写法,当然也可以不写成函数的形式,可以直接在main函数里面直接进行操作,更加灵活方便。
node* creat(int data[],int n)
{
node*root=new node;
root=NULL;
for(int i=0;i<n;++i)
{
insert(root,data[i]);
}
return root;
}
到这里就已经有了一棵成形的树,但是有了这棵树却无法从树里面提取信息,和不存在没有什么区别,最简单的信息当然是查看某结点是否在树中,一般都是给出结点值然后给出查找结果(通过输出信息,或者返回的信息来表示),如:
void search(node*root,int x)//这里没有进行剪枝,如果给的条件足够,为了调高查找效率,是需要剪枝的
{
if(root==NULL)//空树直接返回
return;
if(root->data==x)//找到后可以直接给出结果或者对找到的结果进行修改操作
{
printf("success\n");
return;
}
search(root->rchild,x,newdata);//分别向左右子树递归
search(root->lchild,x,newdata);
}
对于二叉树的删除操作,若只是简单随意的删除是没有意义的,因为在删除非叶结点后,可以用任意叶结点来替代,这样编码就有很多种,一般情况下删除结点是有一定的要求限制的,比如AVL树要求删除后保持平衡,BST要求删除后还是BST。
有了一棵二叉树,要是想显示出来,也需要一定的规则,也就是遍历的规则,一般由四种遍历方法,分别是:前序遍历(preorder traversal),中序遍历(inorder traversal),后序遍历(postorder traversal),层序遍历(level-order traversal)。前三种的代码比较像,其中的前序遍历和DFS很相似,一般通过递归(栈)实现,层序遍历和BFS相似,一般通过队列来实现。
void preorder(node*root)//先序遍历输出结点
{
if(root==NULL) return;//空结点直接返回
printf("%d\t",root->data);//根
preorder(root->lchild);//递归左子树
preorder(root->rchild);//递归右子树
}
void inorder(node*root)//中序遍历输出结点
{
if(root==NULL) return;
inorder(root->lchild);
printf("%d\t",root->data);
inorder(root->rchild);
}
void postorder(node*root)//后序遍历输出结点
{
if(root==NULL) return;
postorder(root->lchild);
postorder(root->rchild);
printf("%d\t",root->data);
}
下面是二叉树层序遍历的代码:
void layer_print(node *root)
{
queue<node*> q;//建立队列q
q.push(root);
while(!q.empty())
{
node*top=q.front();
printf("%d ",top->data);
q.pop();
if(top->lchild!=NULL) q.push(top->lchild);//左子结点非空,加入队列
if(top->rchild!=NULL) q.push(top->rchild);//右子节点非空,加入队列
}
}
可以对比一下BFS的模板代码,可以发现层序遍历和bfs本质是一样的:
void BFS()
{
queue<typename> q;
q.push(k);
while(!q.empty())
{
typename top=q.top();//取出队首元素
q.pop();//队首元素出队
对top进行相关操作;//比如输出
q.push(下层所有相邻结点);//下层所有结点入队,若有必要一般还设置结点为已访问
}
}
到这里二叉树的基本操作就已经介绍完了,下一次该写一些特殊的树结构。