BinaryTree要实现的方法
总结
-
remove不在BinNode里,而是BinTree里
-
递归的两种写法
从上往下:同一对象的递归(参数多一个,判空用一句话),子对象的递归(参数void,判空用两句话)(因为分叉,所以递归是真递归)
从下往上:不分叉,为了效率,可以用循环
// 我的最初写法(递归更新一条路径上的全部节点high值)
template <typename T>
void BinNode<T>::updateHigh(void)
{int oldHigh = high;high = std::max(getHigh(left), getHigh(right)) + 1;if (parent != NULL && oldHigh != high) parent->updateHigh();
}// 调用
rt->updateHigh();
// 改良版(循环更新一条路径上的全部节点high值)
template <typename T>
void BinTree<T>::updateHighAbove(BinNode<T> * rt)
{while (rt){if (!rt->updateHigh()) break; //这是书里说的优化,但书中并未实现rt = rt->parent;}
}
template <typename T>
bool BinNode<T>::updateHigh(void) //返回是否更新了树高
{int oldHigh = high;high = std::max(getHigh(left), getHigh(right)) + 1;return oldHigh != high;
}
// 调用
updateHighAbove(rt);
-
整棵树与根节点的区别:整棵树的类型是BinTree 而根节点的类型是BinNode。二者都有成员变量high和size改了根节点的size,没改整棵树的size
-
发现high也有上一条的问题。书上BinTree没有设置high,BinNode没有size,那就删掉,省的单独更新
-
为什么remove写两个,因为
切断来自parent的指针
和更新高度
不用层层进行,还是为了提高效率。remove_core负责递归,remove在最外层负责切断来自parent的指针
和更新高度
-
remove如果rt是根节点那么不进行
切断来自parent的指针
和更新高度
,析构函数要用 -
静态成员函数
可以不依附于具体对象来调用。为了调用时简洁。静态成员函数就很像是面向过程
了
code BinNode
# pragma once
# include <algorithm>
# define getHigh(x) x ? x->high : -1// 仿函数:打印功能
template <typename T>
struct print {void operator ()(T data){std::cout << data << std::endl;}
};template <typename T>
struct BinNode {int high;T data;BinNode<T> * left;BinNode<T> * right;BinNode<T> * parent;int Size(BinNode<T> * x){if (x){return 1 + Size(x->left) + Size(x->right);}return 0;}BinNode(T const & d, BinNode<T> * p) : data(d), parent(p), left(NULL), right(NULL), size(1), high(0) {}BinNode<T> * insertAsLeft(T const & val){left = new BinNode<T>(val, this);return left;}BinNode<T> * insertAsRight(T const & val){right = new BinNode<T>(val, this);return right;}bool updateHigh(void){int oldHigh = high;high = std::max(getHigh(left), getHigh(right)) + 1;return oldHigh != high;}template <typename T2>void travPre(T2 visit){visit(data);if (left) left->travPre(visit);if (right) right->travPre(visit);}template <typename T2>void travIn(T2 visit){if (left) left->travIn(visit);visit(data);if (right) right->travIn(visit);}template <typename T2>void travPost(T2 visit){if (left) left->travPost(visit);if (right) right->travPost(visit);visit(data);}
};
code BinTree
# pragma once
# include "BinNode.h"template <typename T>
class BinTree
{
protected:
public:int size;BinNode<T> * root;public:BinTree():size(0), root(NULL){}~BinTree() { if (root) remove(root); }//***********************************************************只读*********************************************************int Size(void) const { return size; }bool empty(void) const { return size == 0; }BinNode<T> * Root(void) const { return root; }//***********************************************************可写*********************************************************// 节点插入BinNode<T> * insertAsRoot(T const & e){size = 1;root = new BinNode<T>(e, NULL);return root;}BinNode<T> * insertAsLeft(T const & e, BinNode<T> * rt){++size;rt->insertAsLeft(e);updateHighAbove(rt);return rt->left;}BinNode<T> * insertAsRight(T const & e, BinNode<T> * rt){++size;rt->insertAsRight(e);updateHighAbove(rt);return rt->right;}// 子树接入,返回接入位置rtBinNode<T> * attachAsLeft(BinTree<T> * newSubtree, BinNode<T> * rt){size += newSubtree->size;rt->left = newSubtree->root;updateHighAbove(rt);// 清空newSubtree原来的东西newSubtree->size = 0;newSubtree->root = NULL;return rt;}BinNode<T> * attachAsRight(BinNode<T> * newSubtree, BinNode<T> * rt){size += newSubtree->size;rt->right = newSubtree->root;updateHighAbove(rt);// 清空newSubtree原来的东西newSubtree->size = 0;newSubtree->root = NULL;return rt;}int remove(BinNode<T> * rt){// 切断来自parent的指针fromParentTo(rt) = NULL;// 更新高度updateHighAbove(rt->parent);int ans = remove_core(BinNode<T> * rt);size -= ansreturn ans;}int remove_core(BinNode<T> * rt){if (!rt) return 0; // 递归出口int ans = remove(rt->left) + remove(rt->right) + 1;delete rt;return ans;}BinTree<T> * secede(BinNode<T> * rt) // 先不考虑 如果rt是树根{// 切断来自parent的指针fromParentTo(rt) = NULL;size -= BinNode<T>::Size(rt);BinTree<T> * newTree = new BinTree<T>();newTree->root = rt;rt->parent = NULL;updateHighAbove(rt->parent);return newTree;}void updateHighAbove(BinNode<T> * rt){while (rt){if (!rt->updateHigh()) break;rt = rt->parent;}}//***********************************************************遍历*********************************************************template <typename T2>void travPre(T2 visit){if (root) root->travPre(visit);}template <typename T2>void travIn(T2 visit){if (root) root->travIn(visit);}template <typename T2>void travPost(T2 visit){if (root) root->travPost(visit);}private:BinNode<T> * & fromParentTo(BinNode<T> * x){if (x == x->parent->left) return x->parent->left;else return x->parent->right;}
};
测试程序(树的结构如图)
# include <iostream>
# include "BinTree.h"int main(void)
{BinTree<char> T1;BinNode<char> * pA = T1.insertAsRoot('A');BinNode<char> * pB = T1.insertAsLeft('B', pA);BinNode<char> * pG = T1.insertAsLeft('G', pB);BinNode<char> * pC = T1.insertAsRight('C', pA);BinNode<char> * pE = T1.insertAsRight('E', pC);BinNode<char> * pH = T1.insertAsRight('H', pG);BinNode<char> * pD = T1.insertAsLeft('D', pC);BinNode<char> * pF = T1.insertAsLeft('F', pD);print<int> p;std::cout << T1.Size() << '\n';// 高度测试std::cout << "根高度:"; std::cout << T1.Root()->high; std::cout << "\n";std::cout << "C高度:"; std::cout << pC->high; std::cout << "\n";// 遍历测试std::cout << "后序遍历:"; T1.travPost(p); std::cout << "\n";std::cout << "中序遍历:"; T1.travIn(p); std::cout << "\n";// 移除测试std::cout << "删掉:" << T1.remove(pD) << "个节点" << '\n';std::cout << "C高度:"; std::cout << pC->high; std::cout << "\n";std::cout << "中序遍历:"; T1.travIn(p); std::cout << "\n";std::cout << T1.Size() << '\n';// 切断测试BinTree<char> * pT2 = T1.secede(pC);std::cout << "中序遍历T1:"; T1.travIn(p); std::cout << "\n";std::cout << "中序遍历T2:"; pT2->travIn(p); std::cout << "\n";std::cout << T1.Size() << '\n';std::cout << pT2->Size() << '\n';return 0;
}
输出
8
根高度:3
C高度:2
后序遍历:H G B F D E C A
中序遍历:G H B A F D C E
删掉:2个节点
C高度:1
中序遍历:G H B A C E
6
中序遍历T1:G H B A
中序遍历T2:C E
4
2
迭代遍历
上面的代码是递归的遍历。现在再写迭代的前序、中序、后序遍历。
尾递归
前序遍历的递归代码,左右子树都属于尾递归。中序遍历的递归代码,右子树属于尾递归。而后序遍历完全不属于尾递归。所以后序遍历比前、中序遍历复杂得多。要理解这句话,还要先了解尾递归转成迭代的方法,而这也是编译器在编译之前常做的优化手段。
以汉诺塔为例,理解递归转迭代
BinTree 的三个遍历接口不变,只需修改BinNode 的遍历函数。
travPre迭代1:直接转化(但不可推广到中、后序)
仅说思路:根节点进栈。当栈非空:出栈访问,然后右子树入栈,左子树入栈。
这个完全模拟了尾递归时编译器的操作。至于为什么先右后左,因为这样出栈时才能先左后右。
(汉诺塔那篇文章如果你把结果打印出来,发现迭代的结果跟递归结果是反过来的,如果将入栈的两句话交换位置,那么输出结果一模一样)
travPre迭代2:新思路(可推广)
我们通过仔细观察travPre,得到前序遍历的迭代算法。
template <typename T2>void travPre(T2 visit){// 出栈,访问visit(data);// 左侧链一路访问到底if (left) left->travPre(visit);// 每访问一个左孩子,将对应右孩子进栈备用if (right) right->travPre(visit);}
迭代2的算法
template <typename T2>void travPre2(T2 visit){// 出栈顶。右孩子进栈,访问左孩子。转到左孩子,重复上一步。直到没有左孩子,出栈顶。BinNode<T> * current;Stack<BinNode<T>*> s;s.push(this);while (!s.empty()){current = s.pop(); while (current){visit(current->data);if (current->right) s.push(current->right);current = current->left;}}}
travIn迭代
按图索骥,继续先观察递归算法
void travIn(T2 visit){// 沿左侧链一路向左进栈if (left) left->travIn(visit);// 没有左子树时,出栈访问visit(data);// 访问完马上转到右儿子,进一个栈if (right) right->travIn(visit);}
travIn迭代算法
template <typename T2>void travIn2(T2 visit){// 左孩子进栈,没有左孩子时,出栈顶访问,一个右孩子进栈,右孩子的左孩子到底进栈BinNode<T> * current = this;Stack<BinNode<T>*> s;while (current){s.push(current);current = current->left;}while (!s.empty()){current = s.pop();visit(current->data);current = current->right;while (current){s.push(current);current = current->left;}}}
travPost迭代
比较复杂。邓老师将其描述为,藤绕树。先找藤的头。
善后工作
为了调用更加友好,BinTree 的三个遍历接口加一个tag参数,缺省为0,宏定义ITER(意为迭代)为1
template <typename T2>void travPre(T2 visit, int tag = 0){if (root){if (tag & ITER) root->travPre2(visit);else root->travPre(visit);}}template <typename T2>void travIn(T2 visit, int tag = 0){if (root){if (tag & ITER) root->travIn2(visit);else root->travIn(visit);}}template <typename T2>void travPost(T2 visit, int tag = 0){if (root){if (tag & ITER) root->travPost2(visit);else root->travPost(visit);}}