二叉排序树,又叫二叉搜索树,它的主要特点是对于每个结点,它的左子树上所有结点的值都要小于该结点的值,而右子树所有结点的值都要大于该结点的值,由此我们知道,在对二叉排序树进行中序遍历时,得到的序列一定是一个递增序列。所以这类问题的重点就是如何构造二叉树。
注意create()函数只有在新建一个结点时才需要,类似于new或malloc出一个,分配适当的空间,要注意的是Node* cur, 如果是 cur = stk.top()就没必要new,因为它指向已经存在的结点。
1. 先看问题:
题目描述:
输入一系列整数,建立二叉排序数,并进行前序,中序,后序遍历。
输入:
输入第一行包括一个整数 n(1<=n<=100)。接下来的一行包括 n 个整数。
输出:
可能有多组测试数据,对于每组数据,将题目所给数据建立一个二叉排序树,并对二叉排序树进行前序、中序和后序遍历。每种遍历结果输出一行。每行最后一个数据之后有一个空格。
样例输入:
5
1 6 5 9 8
样例输出:
1 6 5 9 8
1 5 6 8 9
5 8 9 6 1
提示:
输入中可能有重复元素,但是输出的二叉树遍历序列中重复元素不用输出。
注://忽略重复的元素。
重复的元素因为代码中只有 x > T->c 和 x < T->c的判断,所以当有重复的值时不会被考虑,直接忽略了。
代码为:
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
struct Node {
Node *lchild;
Node *rchild;
int c;
}Tree[50];
int loc;
Node *create()
{
Tree[loc].lchild = Tree[loc].rchild = NULL;
return &Tree[loc++];
}
void postOrder(Node *T)
{
if(T->lchild != NULL)
postOrder(T->lchild);
if (T->rchild != NULL)
postOrder(T->rchild);
printf("%d ",T->c);
}
void inOrder(Node *T)
{
if (T->lchild != NULL)
inOrder(T->lchild);
printf("%d ", T->c);
if (T->rchild != NULL)
inOrder(T->rchild);
}
void preOrder(Node *T)
{
printf("%d ",T->c);
if (T->lchild != NULL)
preOrder(T->lchild);
if (T->rchild != NULL)
preOrder(T->rchild);
}
Node *insert(Node *T, int x)
{
if (T == NULL)
{
T = create();
T->c = x;
}
else if (x < T->c)
T->lchild = insert(T->lchild, x);
else if (x > T->c)
T->rchild = insert(T->rchild, x);
return T; //最后返回的是整棵树的根结点指针
}
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
loc = 0;
Node *T = NULL; //二叉排序树的根结点初始化
for (int i = 0; i < n; i++)
{
int x;
scanf("%d",&x);
T = insert(T, x);
}
printf("先序遍历: ");
preOrder(T);
printf("\n");
printf("中序遍历: ");
inOrder(T);
printf("\n");
printf("后序遍历: ");
postOrder(T);
printf("\n");
}
}
这里要注意的是使用的是静态结构体数组Tree[50], 如果没有用结构体数组的话,需要使用 malloc 和 free 的组合拳。构建排序二叉树的过程,仍然是一个递归的过程,注意 T = insert(T, x) 中的 T 是整棵树的根结点,返回的也是根结点,但是由于使用的 Node *指针的形式,形参在子函数中的遍历及判断过程中事实上得到了改变,这也是链表问题中,多数使用指针的原因。每次传入的 x ,先与 根结点->c 进行一次比较,然后再与左子树右子树 根结点->c 比较, 直到插入到合适位置,即 T == NULL的位置。
运行结果(可以看到中序遍历是递增的):
5
1 6 5 9 8
先序遍历: 1 6 5 9 8
中序遍历: 1 5 6 8 9
后序遍历: 5 8 9 6 1
上面的Insert方法层层返回,最终返回主函数的是整棵树的根结点,为了统一起见,我们用下面的方法来构造二叉树:
#include<cstdio>
#include<queue>
#include<stack>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct NODE{
NODE *lchild;
NODE *rchild;
int c;
}Tree[50];//Tree只在create中被用到,这样就不用new了
int loc;
char str[50];
int flag = 0;
queue<NODE*> q;//存入的是指针类型,队列保存的只是原元素的副本,无法直接修改,但当为地址可以对指向的值进行的修改
//创建一个新的结点,返回的是指针即地址,主要是定义左右子树指向的结点
NODE *create(int ch)
{
Tree[loc].c = ch;
Tree[loc].lchild = Tree[loc].rchild = NULL;
return &Tree[loc++];
}
void preOrder(NODE *T)
{
if(T == NULL)
return;
printf("%d ", T->c);
preOrder(T->lchild);
preOrder(T->rchild);
}
void inOrder(NODE *T)
{
if(T == NULL)
return;
inOrder(T->lchild);
printf("%d ", T->c);
inOrder(T->rchild);
}
void postOrder(NODE *T)
{
if(T == NULL)
return;
postOrder(T->lchild);
postOrder(T->rchild);
printf("%d ", T->c);
}
//层序遍历非递归形式,传入的参数是根结点指针
//层序遍历与广度优先搜索比较类似
void layerOrder(NODE *root)
{
q.push(root);
while(!q.empty())
{
NODE *T = q.front();
q.pop();
printf("%d ", T->c);//访问值
if(T->lchild != NULL)
q.push(T->lchild);
if(T->rchild != NULL)
q.push(T->rchild);
}
}
//非递归先序遍历,只对传入的根结点进行处理
//先将当前结点的右子树入栈,再将左子树入栈即可,和层序遍历非常的类似
void none_preOrder(NODE *root)
{
if(root == NULL)
return;
stack<NODE*> stk;
stk.push(root);
//和层序遍历的队列的思想有点类似
while(!stk.empty())
{
NODE *T = stk.top();
stk.pop();
printf("%d ", T->c);
if(T->rchild != NULL)
stk.push(T->rchild);
if(T->lchild != NULL)
stk.push(T->lchild);
}
}
//对于中序遍历,从根结点开始一直到最左边的结点
void none_inOrder(NODE *root)
{
if(root == NULL)
return;
stack<NODE*> stk;
NODE *T = root;
while(T != NULL || !stk.empty())
{
//T可能出现为NULL的情况
while(T != NULL)
{
stk.push(T);
T = T->lchild;
}
if(!stk.empty())
{
T = stk.top();
printf("%d ", T->c);
stk.pop();
T = T->rchild;//对右子树实现中序遍历
}
}
}
//对于后序遍历,每次访问的结点必然是在访问过它的右结点之后直接访问,右节点必然为空
//一定是紧接着它的右节点后被访问,当然可能为空
//或者没有左右子树才被访问
void none_postOrder(NODE *root)
{
if(root == NULL)
return;
stack<NODE*> stk;
NODE *cur, *pre = NULL;//注意这个写法
cur = root;
while(cur)
{
stk.push(cur);
cur = cur->lchild;
}//最左入栈
while(!stk.empty())
{
cur = stk.top();
stk.pop();
if(cur->rchild == pre || cur->rchild == NULL)//同样满足叶子结点
{
printf("%d ", cur->c);
pre = cur;
}
else
{
stk.push(cur);
cur = cur->rchild;//由上面的判断可知rchild一定不为NULL,叶子结点已经被访问过
while(cur)
{
stk.push(cur);
cur = cur->lchild;
}
}
}
}
//按照从从左到右的顺序,优先向左子树插入,下面的这种方式最终会把数据都插入到根结点的左子树上
//注意&T是引用,实现对T本身的create()而不是仅对T指向的值的修改
void Insert(NODE* &T, int x)
{
//只有当前为NULL的时候才能插入
if(T == NULL)
{
T = create(x);
return;
}
if(x < T->c)//注意题目要求的是左子树小于等于还是小于
{
Insert(T->lchild, x);
}
else
{
Insert(T->rchild, x);
}
}
//对于寻找插入位置,也可以用递归的形式,当此时的结点指针为NULL时,就可以调用create(ch)了
//对于查找相应元素并进行修改,也可以进行递归,用先序遍历的方式即可
int main()
{
NODE* root = NULL;
loc = 0;
int n;
int a[20];
//每次插入的对象都是整棵树的根结点
scanf("%d", &n);
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
Insert(root, a[i]);
}
printf("先序遍历: \n");
preOrder(root);
printf("\n");
printf("非递归先序遍历: \n");
none_preOrder(root);
printf("\n");
printf("中序遍历: \n");
inOrder(root);
printf("\n");
printf("非递归中序遍历: \n");
none_inOrder(root);
printf("\n");
printf("后序遍历: \n");
postOrder(root);
printf("\n");
printf("非递归后序遍历: \n");
none_postOrder(root);
printf("\n");
printf("层序遍历: \n");
layerOrder(root);
printf("\n");
printf("Tree: ");
for(int i = 0; i < 5; i++)
printf("%d ", Tree[i].c);
return 0;
}
运行结果为:
树的形状为:
2. 第二个问题,当我们给定 2 个所有元素完全相同的序列,按照上面的方式构建排序二叉树,是不是我们的得到的排序二叉树一定是完全一样的。很可惜,答案是否定的,相同的元素构成的序列,当它们的排列顺序不同时,得到的二叉树不一定相同, 例如,上面的方法一定是将序列第一个元素作为排序二叉树的根结点,当2个序列从第一个结点开始就不相同时,又怎么能要求它们构造的二叉树是完全一致的呢,即使不是第一个元素,假设是第二个,一个为3,一个为4,它们都比根结点5小,分别成为各自左子树的根结点,由此开始它们固定下来,不会随着插入的过程发生变化,2 棵二叉树也是不同的。
问题描述:
题目描述:
判断两序列是否为同一二叉搜索树序列
输入:
开始一个数 n,(1<=n<=20) 表示有 n 个需要判断,n= 0 的时候输入结束。接下去一行是一个序列,序列长度小于 10,包含(0~9)的数字,没有重复数字,根据这个序列可以构造出一颗二叉搜索树。接下去的 n 行有 n 个序列,每个序列格式跟第一个序列一样,请判断这两个序列是否能组成同一颗二叉搜索树。
输出:
如果序列相同则输出 YES,否则输出 NO
样例输入:
2
5 6 7 4 3 2
5 4 3 2 6 7
5 7 6 3 4 2
0
样例输出:
YES
NO
判断 2 棵二叉树是否完全一样的方法是,如果它们的先序和中序或者中序和后序(一定要包括中序)序列元素排列相同时,则它们必然是同一棵二叉树,即树的形状完全是一致的。