1,树的结点数据进行泛型处理;
2,树的增删改查;
3,增加父结点,目的是在树的遍历算法中不依赖栈结构;
4,解耦树的信息与树结点信息,定义两个class。
1,树的结点数据
template
struct treenode {
T val;
treenode *parent;
deque *> child;
}
deque
1)vector没有push_front接口;
2)list不能随机访问,也就是不能使用operator[ ];
3)unordered_map,在之后重新排序显得很难处理。
对比使用C语言构建树时,是转换二叉指针表示,左指针指向第一个孩子结点,右指针指向兄弟结点,该树的特点 根节点无右子树。表示如下:
struct treenode {
struct treenode *parent;
struct treenode *child;
struct treenode *sibling;
}
对比C使用二叉指针,C++使用容器类存储孩子指针实现起来变得很简单。
2,树
template
class tree {
public:
tree() : m_root(NULL),m_size(0) {}
tree(T rootval) {
m_root = new treenode(rootval);
}
~tree();
treenode *get_root() {
return m_root;
}
size_t size() {
return m_size;
}
treenode *find(T &val);
void insert(T parent, T child);
int high();
void level_travel(void (*visit_callback)(T &val));
private:
treenode *m_root;
size_t m_size;
};
对于整棵树,我们只要有root 指针即可。
1,树结点
treenode() : parent(NULL) {}
treenode(T val) : parent(NULL) {this->val = val;}
~treenode() {remove_childs();}
void remove_childs() {
auto it = child.begin();
if (parent) {
for (it=parent->child.begin(); it!=child.end(); it++) {
if (*it == this)
break;
}
parent->child.erase(it);
if (parent->child.size() > 0) {
for (it=child.begin(); it != child.end(); ++it) {
parent->child.front()->push_back_child(*it);
}
} else {
for (it=child.begin(); it != child.end(); ++it) {
parent->push_back_child(*it);
}
}
} else {
if (child.size() > 0) {
auto elder = child.front();
elder->parent = NULL;
for (it=child.begin()+1; it != child.end(); ++it) {
elder->push_back_child(*it);
}
}
}
deque *>().swap(child);
}
关键是析构函数,要给它的孩子结点要寻个父节点。策略如下:
1)先判断this结点是否存在父节点,若存在,则需要删除this->parent保留this信息。
再判断this->parent->child.size() > 0,相当于给this->child寻址叔父结点;若无,由parent收养。
2)若this为root结点,则选用this第一个孩子为根结点,其余孩子结点加入到第一个孩子的孩子容器类中。
2,树
1)find
treenode *tree::find(T &val)
{
treenode *result = NULL;
int qlen = 0;
queue*> q;
if (!m_root)
return result;
q.push(m_root);
while (!q.empty()) {
qlen = q.size();
for (; qlen > 0; qlen--) {
auto front = q.front();
q.pop();
if (front->val == val) {
result = front;
goto ret;
}
for (auto child : front->child) {
q.push(child);
}
}
}
ret:
return result;
}
采用层次遍历搜索。
2)insert
template
void tree::insert(T parent, T child)
{
treenode *pparent = find(parent);
pparent->push_back_child(child);
}
3)树的析构
template
tree::~tree()
{
if (!m_root)
return;
treenode *p = m_root;
m_root = NULL;
while (p != NULL) {
if (p->child.size() > 0) {
m_root = p->child[0];
}
delete p;
p = m_root;
m_root = NULL;
}
}
4)层次遍历
template
void tree::level_travel(void (*visit_callback)(T &val))
{
int qlen = 0;
queue*> q;
if (!m_root || !visit_callback)
return ;
q.push(m_root);
while (!q.empty()) {
qlen = q.size();
for (; qlen > 0; qlen--) {
auto front = q.front();
q.pop();
visit_callback(front->val);
for (auto child : front->child) {
q.push(child);
}
}
printf("\n");
}
}
如何验证数据结构写的是否正确很重要,通常验证目标有两点:
1,需求通路是否正常,编写测试用例;
2,内存是否泄漏,可通过编译开ASAN。
测试用例如下:
#include
void visit_print(int &val)
{
printf(" %d ", val);
}
int main()
{
tree mytree(1);
mytree.insert(1,2);
mytree.insert(1,32);
mytree.insert(2,31);
mytree.level_travel(visit_print);
return 0;
}
简易绘制测试用例所构建的树:
运行结果:
1
2 32
31
运行结果符合预期,且 无"==125916==ERROR: LeakSanitizer: detected memory leaks"这类报错。这下可以松口气啦~~。