起因是这样的,最近算法课在上红黑树,写红黑树的时候肯定涉及一些内存管理的问题,主要是在插入结点的时候要用new分配一个新的结点,并在清空红黑树的时候把这些结点delete释放掉。
算法导论书上只提供了插入和删除操作的代码,但是因为要测试多组数据的运行时间,需要自己实现一个清空(destroy)操作。本着避免一切可能造成的内存泄漏的原则,一开始我写了完整的清空函数,也就是从根节点开始递归,回收每个结点的内存空间,其中我的结点和树定义如下。
enum RBTColor{
RED, BLACK};
class RBTNode {
public:
RBTColor color;
int key;
RBTNode *lson;
RBTNode *rson;
RBTNode *parent;
RBTNode(RBTColor c, int k) {
color = c;
key = k;
lson = rson = parent = NULL;
}
};
class RBTree {
public:
RBTree() {
root = NULL;
}
void destroy() {
destroy(root);
}
private:
void destroy(RBTNode *&root) {
if (root == NULL) return ;
if (root->lson) destroy(root->lson);
if (root->rson) destroy(root->rson);
delete root;
root = NULL;
}
}rb_tree;
然而运行时感觉运行时间很奇怪,于是修改测试数据,测了5组规模一样的数据,发现只有第一次的运行时间是正常的,后面四次的时间明显大很多。进一步记录了一下destroy函数所占用的时间,结果如下。
destroy time : 0 ms SIZE : 100000 RUNTIME : 71 ms
destroy time : 46 ms SIZE : 100000 RUNTIME : 3308 ms
destroy time : 38 ms SIZE : 100000 RUNTIME : 2442 ms
destroy time : 38 ms SIZE : 100000 RUNTIME : 3415 ms
destroy time : 39 ms SIZE : 100000 RUNTIME : 2267 ms
destroy time : 38 ms
意识到可能是destroy的过程中的某些操作产生了这样的结果,采取控制变量法,尝试去掉了内存回收的部分之后运行时间时间恢复正常。修改后的destroy和测试结果如下,但很明显,这样写又会有内存泄漏的问题,即没有在清空红黑树前回收每个结点的内存
void destroy() {
// clock_t rec = clock();
// destroy(root);
// cout << "destroy time : " << clock() - rec << " ms ";
root = NULL;
}
SIZE : 100000 RUNTIME : 61 ms
SIZE : 100000 RUNTIME : 64 ms
SIZE : 100000 RUNTIME : 71 ms
SIZE : 100000 RUNTIME : 67 ms
SIZE : 100000 RUNTIME : 65 ms
可以确定是清空操作(destroy)造成了这一现象。此时我陷入了疑惑:RUNTIME所记录的仅仅是执行红黑树“插入”操作所消耗的时间,并没有记录清空操作的时间,与清空操作唯一的联系是对于内存的操作。但清空操作完成后,此时红黑树的根结点指针指向空结点,整个程序的执行状态和程序刚开始时几乎完全相同,但实际运行时间却差了40-50倍。
一个猜想是清空时的delete操作影响到了程序所能够使用的内存空间,但delete是起到回收内存的作用的,理论上来说意味着可供使用的内存空间更大了,为什么会比不回收内存空间的写法耗时更久呢?这一猜想完全不能解释这种现象。
以上就是我目前遇到的最主要的问题,对此我还做了以下尝试:
-
修改测试数据规模大小,测试了10^6的情况,仍有这个问题
destroy time : 0 ms SIZE : 1000000 RUNTIME : 669 ms destroy time : 470 ms SIZE : 1000000 RUNTIME : 7070 ms destroy time : 462 ms SIZE : 1000000 RUNTIME : 7294 ms destroy time : 473 ms SIZE : 1000000 RUNTIME : 7754 ms destroy time : 471 ms SIZE : 1000000 RUNTIME : 6117 ms destroy time : 468 ms
-
把释放内存的部分写到了RBTNode的析构函数中,并修改对应的destroy:
class RBTNode { public: RBTColor color; int key; RBTNode *lson; RBTNode *rson; RBTNode *parent; RBTNode(RBTColor c, int k) { color = c; key = k; lson = rson = parent = NULL; } ~RBTNode() { if (lson) delete(lson); lson = NULL; if (rson) delete(rson); rson = NULL; } }; void destroy() { clock_t rec = clock(); delete(root); root = NULL; cout << "destroy time : " << clock() - rec << " ms "; }
从结果来说,和原来的没有任何区别
-
写了一个简化版的测试程序,通过最简单的数据结构(链表)进行测试,问题仍然存在,可见不是红黑树操作的问题
#include <bits/stdc++.h> using namespace std; int cnt; class Node { public: Node(int x) { key = x; nxt = NULL; } int key; Node *nxt; }