二叉树的存储结构
顺序存储
用一组连续的存储单元依次自上而下,自左至右存储完全二叉树上的节点元素。但是在最坏情况下,一个高度为h的且仅有h个节点的单支树却需要2^h-1个存储单元,造成极大的空间浪费
链式存储
- 数据域
- 左指针
- 右指针
二叉树遍历
先序遍历
- 访问根节点
- 先序遍历左子树
- 先序遍历右子树
对应的递归算法
void preOrder(BiTree t) {
if (T != null) {
visit(t);
preOrder(t.lChild);
preOrder(t.rChild);
}
}
中序遍历
- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
对应的递归算法
void inOrder(BiTree t) {
if (T != null) {
inOrder(t.lChild);
visit(t);
inOrder(t.rChild);
}
}
后序遍历
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
对应的递归算法
void postOrder(BiTree t) {
if (T != null) {
postOrder(t.lChild);
postOrder(t.rChild);
visit(t);
}
}
时间复杂度
- 时间复杂度O(n)
- 空间复杂度O(n)
- 递归工作栈深度为树的高度
三种遍历方式的区别:
- 仅仅是访问非叶子节点的顺序不同
- 通过递归到最深处的出口条件,假设父节点A,左右节点B,C,则先序遍历为ABC,中序遍历BAC,后序遍历为BCA
探索非递归的访问
借助栈,分析中序遍历的的访问过程
- 沿着根的左孩子,依次入栈,直到左孩子为空,说明已找到可以输出的节点
- 栈顶元素出栈并访问,若右孩子为空,继续从2执行;若右孩子不为空,将右子树转执行1
分析以上二叉树如下:
- 先将ABD依次压入栈,此时栈顶元素为D
- 访问D并出栈,发现右子树根节点为G
- 将G压入栈,G的左孩子为空,访问G并出栈
- G的右孩子为空,访问B并出栈,B的右孩子为空,访问A出栈
- A的右孩子不为空,将CE依次压入栈
- 访问E出栈,E的右孩子为空,访问C出栈
- C的右孩子不为空,将F压入栈
- 访问F并出栈
由上可得访问顺序为DGBAECF
根据分析可得中序遍历得非递归算法
void inOrder(biTree t){
//初始化栈
initStack(s);
//p是遍历指针
biTree p = t;
//栈不空或者p不空是循环
while (p != null || !isEmpty(s)){
//一路向左
if (p != null) {
//当前节点入栈
push(s,p);
//左孩子不空,一直向左走
p = p.lChild();
}
//出栈,并转向出栈节点得右子树
else{
//栈顶元素出栈
pop(s,p);
//访问出栈的元素
vivist(p);
//向右子树走,p赋值为当前节点右孩子
p = p.rChild();
}
}
}
先序遍历的非递归访问和中序遍历是一样的,只需把访问节点元素放在入栈前面
void preOrder(biTree t){
//初始化栈
initStack(s);
//p是遍历指针
biTree p = t;
//栈不空或者p不空是循环
while (p != null || !isEmpty(s)){
//一路向左
if (p != null) {
//访问元素
vivist(p);
//当前节点入栈
push(s,p);
//左孩子不空,一直向左走
p = p.lChild();
}
//出栈,并转向出栈节点得右子树
else{
//栈顶元素出栈
pop(s,p);
//向右子树走,p赋值为当前节点右孩子
p = p.rChild();
}
}
}
后续遍历跟先序、中序遍历有所区别,分析如下
- 沿着跟的左孩子,依次入栈,直到左孩子为空
- 读取栈定元素:若其右孩子不空且未被访问过,将右孩子转执行1;否则栈顶元素出栈并访问
void postOrder(biTree t) {
initStack(s);
biTree p = t;
biTree r = null;
while(p != null || !isEmpty(s)) {
if (p != null) {
push(s ,p);
//走到最左
p = p.lChild();
}else {
//读取栈定节点(非出栈)
getTop(s ,p);
//右子树存在,且未被访问过
if (p.rChild() != null && p.rChild != r) {
//转向右
p = p.rChild();
//压入栈
push(s,p);
//再走到最左
p = p.lChild();
//否则,弹出节点并访问
}else{
//弹出节点
pop(s ,p);
//访问节点
visit(p);
//记录最近访问的节点
r = p;
//节点访问完后,重置p指针
p = null;
}
}
}
}
层次遍历
顾名思义就是一层一层的访问,从最上层开始往下,从左往右
- 借助一个队列
- 将二叉树的根节点入队,然后出队
- 若他有左子树,则将左子树根节点入队;若它有右子树,则将右子树根节点入队
- 然后出队,访问出队节点
- 如此反复,直到队列为空
void levelOrder(biTree t){
initQueue(Q);
biTree p;
//根节点入队
enQueue(Q ,t);
while (!isEmpty(Q)) {
//出队
deQueue(Q ,p);
visit(p);
if (p.lChild() != null) {
enQueue(Q ,p.lChild());
}
if (p.rChild() != null) {
enQueue(Q ,p.rChild());
}
}
}