相关术语
完全二叉树
- 最多只有最下面的两层结点度数可以小于2
- 最下一层的结点都集中最左边
满二叉树
如果一棵二叉树的 任何 结点,或者是树叶,或者恰有两棵非空子树,则此 二叉树称作 满二叉树
二叉树的抽象数据类型(ADT):
逻辑结构+运算:
- 针对树
- 初始化树
- 合并两棵树
- 针对节点
- 访问,删除,插入节点
- 访问节点数据
二叉树节点ADT
template <class T>
class BinaryTreeNode {
friend class BinaryTree<T>; // 声明二叉树类为友元类
private:
T info; // 二叉树结点数据域
public:
BinaryTreeNode(); // 缺省构造函数
BinaryTreeNode(const T& ele); // 给定数据的构造
BinaryTreeNode(const T& ele, BinaryTreeNode<T> *l,
BinaryTreeNode<T> *r); // 子树构造结点
T value() const; // 返回当前结点数据
BinaryTreeNode<T>* leftchild() const; // 返回左子树
BinaryTreeNode<T>* rightchild() const;// 返回右子树
void setLeftchild(BinaryTreeNode<T>*);// 设置左子树
void setRightchild(BinaryTreeNode<T>*);// 设置右子树
void setValue(const T& val); // 设置数据域
bool isLeaf() const; // 判断是否为叶结点
BinaryTreeNode<T>& operator =(const BinaryTreeNode<T>& Node); // 重载赋值操作符
};
BinaryTreeNode<T>* Parent(BinaryTreeNode<T> *current); // 返回父
BinaryTreeNode<T>* LeftSibling(BinaryTreeNode<T> *current);// 左兄
BinaryTreeNode<T>* RightSibling(BinaryTreeNode<T> *current);
复制代码
二叉树ADT
template <class T>
class BinaryTree {
private:
BinaryTreeNode<T>* root;// 二叉树根结点
public:
BinaryTree() {root = NULL;};
~BinaryTree() {DeleteBinaryTree(root);}; // 析构函数
bool isEmpty() const; // 判定二叉树是否为空树
BinaryTreeNode<T>* Root() {return root;}; // 返回根结点
};
void CreateTree(const T& info,
BinaryTree<T>& leftTree, BinaryTree<T>& rightTree); // 构造新树
void PreOrder(BinaryTreeNode<T> *root);
void InOrder(BinaryTreeNode<T> *root);
void PostOrder(BinaryTreeNode<T> *root);
void LevelOrder(BinaryTreeNode<T> *root); // 按层次遍历二叉树或其子树
void DeleteBinaryTree(BinaryTreeNode<T> *root);
复制代码
遍历二叉树
- 遍历 (或称周游,traversal)
- 系统地访问数据结构中的结点
- 每个结点都正好被访问到一次
- 二叉树的结点的 线性化
深度优先遍历二叉树(DFS)
//伪代码
template <class T>
void BinaryTree<T>::DepthOrder(BinaryTreeNode<T>* root)
{
if(root!=NULL){
visit(root); //前序遍历
DepthOrder(root->leftChild());
visit(root); //中序遍历
DepthOrder(root->rightChild());
visit(root); //后序遍历
}
}
复制代码
非递归前序遍历二叉树
思路: 遇到一个节点就访问该节点,把非空右子节点压入栈中,再把下降去遍历左子树 遍历完左子树之后,从栈顶弹出一个元素,然后遍历右子树
using std::stack; // 使用STL中的stack
stack<BinaryTreeNode<T>* > aStack;
BinaryTreeNode<T>* pointer=root;
aStack.push(NULL); //栈底放置监视哨
while(pointer){
Visit(pointer);
if(pointer->rightChild() != NULL){
aStack.push(pointer->rightChild()); //压入右子树
}
pointer = pointer->leftChild(); //左路下降
if(pointer == NULL){
pointer = aStack.top(); //左子树访问完毕,访问右子树
aStack.pop();
}
}
复制代码
非递归后序遍历二叉树:给栈中元素加上一个特征位:
Left 表示已进入该结点的左子树, 将从左边回来
Right 表示已进入该结点的右子树, 将从右边回来
复制代码
时间复杂度:O(N) 空间复杂度也就是栈的深度与树高有关,最好O(logN)最差O(n)
宽度优先遍历二叉树(BFS)
从二叉树的第 0 层(根结点)开始,自上至下 逐层遍历;
在同一层 中,按照 从左到右 的顺序对结点逐一访问。
void BinaryTreeNode<T>::LevelOrder(BinaryTreeNode<T> *root){
using std::queue;
queue<BinaryTreeNode<T>*> aQueue;
BinaryTreeNode<T>* pointer = root;
if(pointer) aQueue.push(pointer);
while(!aQueue.empty()){
pointer = aQueue.front();
aQueue.pop();
Visit(pointer);
if(pointer->leftChild())
aQueue.push(pointer->leftChild());
if(pointer->rightChild())
aQueue.push(pointer->rightChild())
}
}
复制代码
时间复杂度:O(n) 空间复杂度与树的宽度有关:最好O(1),最差O(n)
宽搜与前序遍历的非递归算法对比
他们的共同点就是访问一个结点直接读取数据。宽搜利用的是队列,先进先出,每次从头读取一个结点并把非空左、右子结点放入队列尾部。而前序遍历利用的是栈存储结点指向右子树的指针,不用保存当前访问的结点。
二叉树的存储结构
链式存储
二叉树的各结点随机地存储在内存空间中,结点之间的逻辑关系用指针来链接。
- 二叉链
- 指针left info 指针right
- 三叉链 增加一个父指针
BinaryTreeNode类中增加两个私有数据成员
private:
BinaryTreeNode<T> *left; // 指向左子树的指针
BinaryTreeNode<T> *right; // 指向右子树的指针
复制代码
空间开销分析: 根据满二叉树定理:一半的指针是空的
二叉链,每个结点存两个指针、一个数据域
- 总空间 (2p + d)n
- 结构性开销:2pn
- 如果 p = d, 则结构性开销 2p/ (2p + d ) = 2/3
完全二叉树的顺序存储结构
- 顺序方法存储二叉树
- 把结点按一定的顺序存储到一片连续的存储单元
- 使结点在序列中的位置反映出相应的结构信息
- 存储结构上是线性的
- 逻辑结构上它仍然是二叉树形结构