這兩天看了第13章,看了好長一段時間,主要花在理解和編程實現上面,感覺自己的理解能力還有待提高。
這一章主要講如何實現一個有序集合(Set),該集合插入元素時不能插入重複元素,每次插入完後集閤中元素的排列是有序的。書上一共使用了6種數據結構實現這個集合:STL中的set(紅黑數)、數組、鏈表、二分查找樹、位向量、桶,使用了3種優化方案:哨兵(標記元素)、指針的指針(用於實現遞歸向迭代的轉換)、塊內存分配(一次性分配一大塊內存,使用時從分配好的空間中取,不用每次用的時候new一次)。
下面就二分查找樹的方法來舉例說明如何使用這3種優化方法:
首先,實現一個未優化的方案,使用遞歸實現,沒有使用哨兵,每次都比較一下是否到達鏈表尾,沒有使用塊內存分配,每次插入一個節點的時候分配一次內存,C++實現如下:
class IntSetBSTRecurse
{
private:
struct node
{
int val;
node *left, *right;
node (int t)
{
val = t;
left = right = 0;
}
};
int n, vn, *v;
node *root;
public:
IntSetBSTRecurse(int maxelements, int maxval) : n(0), root(0) { }
int size()
{
return n;
}
void insert(int t)
{
root = rinsert(root, t);
}
void report(int *x)
{
v = x;
vn = 0;
traverse(root);
}
~IntSetBSTRecurse()
{
deletenode(root);
}
private:
node* rinsert(node* p, int t)
{
if (!p)
{
p = new node(t);
++n;
}
else if (p->val < t)
p->right = rinsert(p->right, t);
else if (p->val > t)
p->left = rinsert(p->left, t);
return p;
}
void traverse(node* p)
{
if (p)
{
traverse(p->left);
v[vn++] = p->val;
traverse(p->right);
}
}
void deletenode(node* p)
{
if(p)
{
deletenode(p->left);
deletenode(p->right);
delete p;
p = 0;
}
}
};
然後把插入元素的函數改爲迭代實現,暫不使用哨兵、塊內存分配、指針的指針等優化方案,實現如下:
void insert(int t)
{
if (!root)
{
root = new node(t);
++n;
return;
}
node* p = root;
while (p)
{
if (t < p->val)
{
if (p->left)
p = p->left;
else
{
p->left = new node(t);
++n;
return;
}
}
else if (t > p->val)
{
if (p->right)
p = p->right;
else
{
p->right = new node(t);
++n;
return;
}
}
else
return;
}
}
可以看出,改成迭代的方式要考慮很多情況,代碼比較複雜,可以使用指針的指針簡化這種複雜性,不過這個有點難理解,需要畫畫圖才能更好的理解(我比較笨,一眼沒法看透)。
再使用上面3種優化方案,實現如下:
class IntSetBSTIterate
{
private:
struct node
{
int val;
node *left, *right;
node()
{
val = 0;
left = right = 0;
}
node(int t)
{
val = t;
left = right = 0;
}
node(int t, node* s)
{
val = t;
left = right = s;
}
};
node *root, *sentinel, *freenode, *pnewnode;
int n, *v, vn;
public:
IntSetBSTIterate(int maxelements, int maxval) : n(0), v(0), vn(0)
{
root = sentinel = new node(maxval, 0);
pnewnode = new node[maxelements];
freenode = pnewnode;
}
int size()
{
return n;
}
void insert(int t)
{
sentinel->val = t;
node **p = &root; // pointer to pointer
while ((*p)->val != t)
if (t < (*p)->val)
p = &((*p)->left);
else
p = &((*p)->right);
if (*p == sentinel) {
*p = freenode++;
(*p)->val = t;
(*p)->left = (*p)->right = sentinel;
n++;
}
}
void report(int *x)
{
node* p = root;
v = x;
vn = 0;
traverse(p);
}
~IntSetBSTIterate()
{
delete sentinel;
root = sentinel = 0;
delete[] pnewnode;
pnewnode = freenode = 0;
}
private:
void traverse(node* p)
{
if (p != sentinel)
{
traverse(p->left);
v[vn++] = p->val;
traverse(p->right);
}
}
};
上面使用freenode進行預分配內存,一次性把所有的內存都分配好,在插入時要用的內存時從已經分配好的內存塊中取出使用;同時使用哨兵sentinel,讓每個葉節點都指向哨兵,不用每次都擔心指針是否指向最後的空元素,哨兵可以減少比較的次數;最後一個優化是使用指向指針的指針實現迭代,用法很巧妙,p指向的是節點的next指向的地址,改變*p的值就是改變next的指向,巧妙的插入一個節點,同時改變整個鏈表的指向。