这几天在搞树和图,转自邓俊辉老师的数据结构。
二叉树的实现
主要还是树节点的实现。方法用来实现功能的可以先不看,主要是节点的属性,左孩子,右孩子,父节点,数据。
树节点BinNode
#define BinNodePosi(T) BinNode<T>*
template<typename T>
struct BinNode {
BinNodePosi(T) parent;
BinNodePosi(T)lChild;
BinNodePosi(T) rChild;//父亲、孩子
T data; int height;
int size();//子树规模
BinNodePosi(T) insertAsLC(T const& e);//作为左孩子插入新节点
BinNodePosi(T) insertAsRC(T const& e);//作为右孩子插入新节点
BinNodePosi(T) succ();//中序遍历意义下,返回当前节点的直接后继
BinNode() {};
BinNode(T d, BinNodePosi(T) p = nullptr) :data(d), parent(p),lChild(nullptr),rChild(nullptr) {};
//遍历的辅助函数
//沿着左分支一直向下,并访问
static void visitAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack);
//沿着左分支一直向下,不访问
static void goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack);
//沿着左分支一直向下,并将右孩子放入栈
static void goToLeftMostBranch(Stack<BinNodePosi(T)>& stack);
static void travLevel(BinNodePosi(T) x);//层次遍历
static void travPre1(BinNodePosi(T) x);//先序遍历
static void travPre2(BinNodePosi(T) x);
static void travIn(BinNodePosi(T) x);//中序遍历
static void travPost(BinNodePosi(T) x);//后序遍历
static void travRecursion(BinNodePosi(T) x, int index);
static BinNodePosi(T) getNodePostIn(Vector<T> inOrder, Vector<T> postOrder);
static BinNodePosi(T) getNodePreIn(Vector<T> inOrder, Vector<T> preOrder);
};
template<typename T>
BinNodePosi(T) BinNode<T>::insertAsLC(T const& e) {
return lChild = new BinNode(e, this);
};
template<typename T>
BinNodePosi(T) BinNode<T>::insertAsRC(T const& e) {
return rChild = new BinNode(e, this);
};
template<typename T>
int BinNode<T>::size() {
int s = 1;
if (lChild) s += lChild->size();
if (rChild) s += rChild->size();
return s;
}
树的实现,用来储存根节点,封装节点的方法,主要实现还是在节点那里。
#include"BinNode.h"
#define stature(p) ((p)?p->height:-1)
template<typename T>
class BinTree {
protected:
int _size;//规模
BinNodePosi(T) _root;//根节点
public:
BinTree() { _size = 0; _root = nullptr; }
~BinTree();
int updateHeight(BinNodePosi(T) x);//更新节点高度
void updateHeightAbove(BinNodePosi(T) x);//更新x及祖先的高度
int size() const { return _size; }//规模
bool empty() const { return !_root; }//判空
BinNodePosi(T) root() const { return _root; }//树根
void setRoot(BinNodePosi(T) x) { _root = x; }
BinNodePosi(T) insertAsLC(BinNodePosi(T) x, T const& e);//为x插入左孩子
BinNodePosi(T) insertAsRC(BinNodePosi(T) x, T const& e);//为x插入右孩子
};
template<typename T>
int BinTree<T>::updateHeight(BinNodePosi(T) x) {
return x->height = 1 +
(stature(x->lChild) > stature(x->rChild) ? stature(x->lChild) : stature(x->rChild));
};
template<typename T>
void BinTree<T>::updateHeightAbove(BinNodePosi(T) x) {
while (x) {
updateHeight(x);
x = x->parent;
}
};
template<typename T>
BinNodePosi(T) BinTree<T>::insertAsRC(BinNodePosi(T) x, T const& e) {
_size++;
x->insertAsRC(e);
updateHeightAbove(x);
return x->rChild;
};
template<typename T>
BinNodePosi(T) BinTree<T>::insertAsLC(BinNodePosi(T) x, T const& e) {
_size++;
x->insertAsLC(e);
updateHeightAbove(x);
return x->lChild;
};
template<typename T>
BinTree<T>::~BinTree() {
Queue<BinNodePosi(T)> queue;
if (_root) queue.enqueue(_root);
while (!queue.empty()) {
BinNodePosi(T) tempNode = queue.dequeue();
if (tempNode->lChild)
queue.enqueue(tempNode->lChild);
if (tempNode->rChild)
queue.enqueue(tempNode->rChild);
delete tempNode;
}
_root = nullptr;
};
需要注意的是updateHeight,对空节点(nullptr)的高度定义为-1,节点高度定义为到最远的叶节点的距离,有点递归的味道。
二叉树的析构
相当与一个层次遍历,原理在下面,一定要注意,差点忘了。
不能用先序,中序之类的,因为释放了一个节点,可能他的孩子还没有被释放,这样孩子就没有引用指向他了,相当于这个节点的孩子没被删掉。
template<typename T>
BinTree<T>::~BinTree() {
Queue<BinNodePosi(T)> queue;
if (_root) queue.enqueue(_root);
while (!queue.empty()) {
BinNodePosi(T) tempNode = queue.dequeue();
if (tempNode->lChild)
queue.enqueue(tempNode->lChild);
if (tempNode->rChild)
queue.enqueue(tempNode->rChild);
delete tempNode;
}
_root = nullptr;
};
遍历
先清楚定义
层次遍历:越靠上的,越靠左的,越先输出
前序遍历:父节点 左孩子 右孩子
中序遍历:左孩子 父节点 右孩子
后序遍历:左孩子 右孩子 父节点
层次遍历
利用队列先进后出的原则,我们实现层次遍历。
思路:
从queue中取出队头,访问,要是他有左孩子,入队,要是他有右孩子,也入队
循环,直至queue为空
对于队列中的任意一个节点x,后面的先是他的右兄弟,同一层次的右边的兄弟,然后是下一层次的节点。
template<typename T>
void BinNode<T>::travLevel(BinNodePosi(T) x) {
Queue<BinNodePosi(T)> queue;
queue.enqueue(x);
while (!queue.empty()) {
BinNodePosi(T) temp = queue.dequeue();
cout << temp->data << " ";
if (temp->lChild) queue.enqueue(temp->lChild);
if (temp->rChild) queue.enqueue(temp->rChild);
}
};
递归实现
从定义出发,写的递归顺序,一目了然。
template<typename T>
void BinNode<T>::travRecursion(BinNodePosi(T) x, int index) {
if (index == 1) {
//前序遍历
if (!x) return;
cout << x->data << " ";
travRecursion(x->lChild, index);
travRecursion(x->rChild, index);
}
else if (index == 2) {
//中序遍历
if (!x) return;
travRecursion(x->lChild, index);
cout << x->data << " ";
travRecursion(x->rChild, index);
}
else if (index == 3) {
//后序遍历
if (!x) return;
travRecursion(x->lChild, index);
travRecursion(x->rChild, index);
cout << x->data << " ";
}
};
前序遍历迭代实现
版本一:
思路:
前序遍历的顺序是----父节点,左孩子,右孩子。
从stack中取出一个节点,直接访问
如果有右孩子,先存入stack
如果有左孩子,后存入stack
循环,直至stack中的size为空
template<typename T>
void BinNode<T>::travLevel(BinNodePosi(T) x) {
Queue<BinNodePosi(T)> queue;
queue.enqueue(x);
while (!queue.empty()) {
BinNodePosi(T) temp = queue.dequeue();
cout << temp->data << " ";
if (temp->lChild) queue.enqueue(temp->lChild);
if (temp->rChild) queue.enqueue(temp->rChild);
}
};
template<typename T>
void BinNode<T>::travPre1(BinNodePosi(T) x) {
Stack<BinNodePosi(T)> stack;
stack.push(x);
while (!stack.empty()) {
BinNodePosi(T) temp = stack.pop();
cout << temp->data << " ";
if (temp->rChild) stack.push(temp->rChild);
if (temp->lChild) stack.push(temp->lChild);
}
};
stack储存,不适合用同样的方式实现中序和后序遍历。
版本二
思路
沿着根节点向左,碰到一个节点访问一个节点,同时,他要是有右孩子,直接存入stack中
到叶节点,从stack中取出一个节点
沿着新节点向左,循环,至stack的size为0
版本二比版本一好,版本一中每一个节点要想访问,必需在stack中走一遍,而版本二中,并不是所有节点都会进入到stack中,而是作为右孩子的节点。而且版本二可以作为一种通用的思路。
template<typename T>
void BinNode<T>::visitAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack) {
while (x) {
cout << x->data << " ";
if (x->rChild) stack.push(x->rChild);
x = x->lChild;
}
};
template<typename T>
void BinNode<T>::travPre2(BinNodePosi(T) x) {
Stack<BinNodePosi(T)> stack;
BinNodePosi(T) temp = x;
while (true) {
visitAlongLeftBranch(x, stack);
if (stack.empty()) break;
x = stack.pop();
}
}
visitAlongLeftBranch,一边沿着左子树向下,一边遍历
中序遍历迭代实现
思路:
拿到一个节点,沿着他的左孩子一直向下,把根节点和他的左孩子依次压入stack中,直至nullptr
从stack拿出一个节点,访问(这个节点肯定是叶节点或者刚刚被遍历完左孩子的父节点)
x=x->rChild,对他的右孩子进行goAlongLeftBranch
template<typename T>
void BinNode<T>::goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack) {
while (x) {
stack.push(x);
x = x->lChild;
}
};
template<typename T>
void BinNode<T>::travIn(BinNodePosi(T) x) {
Stack<BinNodePosi(T)> stack;
while (true) {
goAlongLeftBranch(x, stack);
if (stack.empty()) break;
x = stack.pop();
cout << x->data << " ";
x = x->rChild;
}
}
goAlongLeftBranch,沿着左子树向下,把父节点放入栈中,直到访问完左子树,再访问父节点。画个图就明白了。
后序遍历迭代实现
思路:
stack顶部的一个节点,如果有右孩子,入栈,如果有左孩子,入栈
循环,直至来到左孩子的叶节点
输出,更新节点x
stack顶部的一个节点,如果他不是x的父节点,就是x的右兄弟,这时候,进行步骤一;如果他是,直接输出即可,因为他的左孩子和右孩子都已经访问完了
template<typename T>
void BinNode<T>::goToLeftMostBranch( Stack<BinNodePosi(T)>& stack) {
while (BinNodePosi(T) x=stack.top()) {
if (x->lChild != nullptr) {
if (x->rChild != nullptr)
stack.push(x->rChild);
stack.push(x->lChild);
}
else
stack.push(x->lChild);
}
stack.pop();
};
template<typename T>
void BinNode<T>::travPost(BinNodePosi(T) x) {
Stack<BinNodePosi(T)> stack;
stack.push(x);
BinNodePosi(T) tempNode = x;
while (!stack.empty()) {
if (stack.top() != x->parent) {//重点
goToLeftMostBranch(stack);
}
x = stack.pop();
cout << x->data << " ";
}
};
重点在父节点判断的位置,这个地方有些糊涂。
重构(递归版本)
利用二叉树的中序遍历和其他的任意一个遍历,还原二叉树。当只有前序遍历和后序遍历的时候,没有办法区分哪个部分是左子树,哪个部分是右子树,就无法还原一个唯一的二叉树。
中序+前序,还原二叉树
思路:
前序遍历的第一个数字既为父节点
在中序遍历中找到父节点的位置,开头到父节点即为左子树,父节点到结尾即为右子树
递归
template<typename T>
BinNodePosi(T) BinNode<T>::getNodePostIn(Vector<T> inOrder, Vector<T> postOrder) {
//后序、中序还原二叉树
if (postOrder.size() == 0) return nullptr;
T rootValue = postOrder[postOrder.size() - 1];
BinNodePosi(T) root = new BinNode(rootValue);
if (postOrder.size() == 1) return root;
//找父节点的位置
int index = 0;
for (int i = 0; i < inOrder.size(); i++)
if (rootValue == inOrder[i])
{
index = i;
break;
}
root->lChild = getNodePostIn(Vector<T>(inOrder, 0, index), Vector<T>(postOrder, 0, index));
root->rChild = getNodePostIn(Vector<T>(inOrder, index + 1, inOrder.size()), Vector<T>(postOrder, index, postOrder.size() - 1));
return root;
};
中序+后序,还原二叉树
思路:
后序遍历的最后一个数字既为父节点
在中序遍历中找到父节点的位置,开头到父节点即为左子树,父节点到结尾即为右子树
递归
template<typename T>
BinNodePosi(T) BinNode<T>::getNodePreIn(Vector<T> inOrder, Vector<T> preOrder) {
if (preOrder.size() == 0) return nullptr;
T rootValue = preOrder[0];
BinNodePosi(T) root = new BinNode(rootValue);
if (preOrder.size() == 1) return root;
//找父节点的位置
int index = 0;
for (int i = 0; i < inOrder.size(); i++)
if (rootValue == inOrder[i])
{
index = i;
break;
}
root->lChild = getNodePreIn(Vector<T>(inOrder, 0, index), Vector<T>(preOrder, 1, index + 1));
root->rChild = getNodePreIn(Vector<T>(inOrder, index + 1, inOrder.size() + 1), Vector<T>(preOrder, index + 1, preOrder.size()));
return root;
};
重点在找父节点,区分左右子树上。