367 lines
9.1 KiB
C++
367 lines
9.1 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
#define PL_ARENA_CONST_ALIGN_MASK 7
|
|
#include "nslayout.h"
|
|
#include "nsDST.h"
|
|
#ifdef NS_DEBUG
|
|
#include <string.h>
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Structure that represents a node in the DST
|
|
|
|
inline nsDST::Node::Node(void* aKey, void* aValue)
|
|
: mKey(aKey), mValue(aValue), mLeft(0), mRight(0)
|
|
{
|
|
}
|
|
|
|
inline int nsDST::Node::IsLeaf() const
|
|
{
|
|
return !mLeft && !mRight;
|
|
}
|
|
|
|
// Overloaded placement operator for allocating from an arena
|
|
inline void* nsDST::Node::operator new(size_t aSize, NodeArena& aArena)
|
|
{
|
|
return aArena.AllocNode(aSize);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Arena used for fast allocation and deallocation of Node structures.
|
|
// Maintains a free-list of freed objects
|
|
|
|
#define NS_DST_ARENA_BLOCK_SIZE 1024
|
|
|
|
nsDST::NodeArena::NodeArena()
|
|
: mFreeList(0)
|
|
{
|
|
PL_INIT_ARENA_POOL(&mPool, "DSTNodeArena", NS_DST_ARENA_BLOCK_SIZE);
|
|
}
|
|
|
|
nsDST::NodeArena::~NodeArena()
|
|
{
|
|
// Free the arena in the pool and finish using it
|
|
PL_FinishArenaPool(&mPool);
|
|
}
|
|
|
|
void*
|
|
nsDST::NodeArena::AllocNode(size_t aSize)
|
|
{
|
|
void* p;
|
|
|
|
if (mFreeList) {
|
|
// Remove the node at the head of the free-list
|
|
p = mFreeList;
|
|
mFreeList = mFreeList->mLeft;
|
|
} else {
|
|
PL_ARENA_ALLOCATE(p, &mPool, aSize);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void
|
|
nsDST::NodeArena::FreeNode(void* p)
|
|
{
|
|
#ifdef NS_DEBUG
|
|
memset(p, 0xde, sizeof(Node));
|
|
#endif
|
|
// Add this node to the head of the free-list
|
|
((Node*)p)->mLeft = mFreeList;
|
|
mFreeList = (Node*)p;
|
|
}
|
|
|
|
void
|
|
nsDST::NodeArena::FreeArenaPool()
|
|
{
|
|
// Free the arena in the pool, but continue using it
|
|
PL_FreeArenaPool(&mPool);
|
|
mFreeList = 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Digital search tree for doing a radix-search of pointer-based keys
|
|
|
|
nsDST::nsDST(PtrBits aLevelZeroBit)
|
|
: mRoot(0), mLevelZeroBit(aLevelZeroBit)
|
|
{
|
|
}
|
|
|
|
nsDST::~nsDST()
|
|
{
|
|
}
|
|
|
|
void
|
|
nsDST::Clear()
|
|
{
|
|
mArena.FreeArenaPool();
|
|
mRoot = 0;
|
|
}
|
|
|
|
void*
|
|
nsDST::Insert(void* aKey, void* aValue)
|
|
{
|
|
// See if there's a matching key
|
|
Node** node = SearchTree(aKey);
|
|
void* previousValue;
|
|
|
|
if (*node) {
|
|
previousValue = (*node)->mValue;
|
|
|
|
// Replace the current value with the new value
|
|
(*node)->mValue = aValue;
|
|
|
|
} else {
|
|
// Allocate a new node and insert it into the tree
|
|
*node = new (mArena)Node(aKey, aValue);
|
|
previousValue = 0;
|
|
}
|
|
|
|
#ifdef NS_DEBUG
|
|
VerifyTree(mRoot);
|
|
#endif
|
|
return previousValue;
|
|
}
|
|
|
|
// Helper function that returns one of the leaf nodes of the specified
|
|
// subtree
|
|
nsDST::Node**
|
|
nsDST::FindLeafNode(Node** aNode) const
|
|
{
|
|
keepLooking:
|
|
// See if the node has none, one, or two child nodes
|
|
if ((*aNode)->mLeft) {
|
|
// Walk down the left branch
|
|
aNode = &(*aNode)->mLeft;
|
|
goto keepLooking;
|
|
|
|
} else if ((*aNode)->mRight) {
|
|
// Walk down the right branch
|
|
aNode = &(*aNode)->mRight;
|
|
goto keepLooking;
|
|
|
|
} else {
|
|
// We found a leaf node
|
|
return aNode;
|
|
}
|
|
}
|
|
|
|
inline void
|
|
nsDST::DestroyNode(Node* aNode)
|
|
{
|
|
aNode->~Node(); // call destructor
|
|
mArena.FreeNode(aNode); // free memory
|
|
}
|
|
|
|
void*
|
|
nsDST::Remove(void* aKey)
|
|
{
|
|
Node** node = SearchTree(aKey);
|
|
|
|
if (*node) {
|
|
Node* tmp = *node;
|
|
void* value = (*node)->mValue;
|
|
|
|
if ((*node)->IsLeaf()) {
|
|
// Just disconnect the node from its parent node
|
|
*node = 0;
|
|
} else {
|
|
// We can't just move the left or right subtree up one level, because
|
|
// then we would have to re-sort the tree. Instead replace the node
|
|
// with any leaf node below it in the tree
|
|
Node** leaf = FindLeafNode(node);
|
|
|
|
// Copy over both the left and right subtree pointers
|
|
if ((*node)->mLeft != (*leaf)) {
|
|
(*leaf)->mLeft = (*node)->mLeft;
|
|
}
|
|
if ((*node)->mRight != (*leaf)) {
|
|
(*leaf)->mRight = (*node)->mRight;
|
|
}
|
|
|
|
// Insert the leaf node in its new level, and disconnect it from its old
|
|
// parent node
|
|
*node = *leaf;
|
|
*leaf = 0;
|
|
}
|
|
|
|
DestroyNode(tmp);
|
|
#ifdef NS_DEBUG
|
|
VerifyTree(mRoot);
|
|
#endif
|
|
return value;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void*
|
|
nsDST::Search(void* aKey) const
|
|
{
|
|
Node** result = SearchTree(aKey);
|
|
|
|
#ifdef NS_DEBUG
|
|
if (!*result) {
|
|
// Use an alternative algorithm to verify that the key is not in
|
|
// the tree
|
|
NS_POSTCONDITION(!DepthFirstSearch(mRoot, aKey), "DST search failed");
|
|
}
|
|
#endif
|
|
|
|
return (*result) ? (*result)->mValue : 0;
|
|
}
|
|
|
|
// Non-recursive search function. Returns a pointer to the pointer to the
|
|
// node
|
|
nsDST::Node**
|
|
nsDST::SearchTree(void* aKey) const
|
|
{
|
|
NS_PRECONDITION(0 == (PtrBits(aKey) & (mLevelZeroBit - 1)),
|
|
"ignored low-order bits are not zero");
|
|
|
|
Node** result = (Node**)&mRoot;
|
|
PtrBits bitMask = mLevelZeroBit;
|
|
|
|
while (*result) {
|
|
// Check if the node matches
|
|
if ((*result)->mKey == aKey) {
|
|
return result;
|
|
}
|
|
|
|
// Check whether we search the left branch or the right branch
|
|
if (0 == (PtrBits(aKey) & bitMask)) {
|
|
result = &(*result)->mLeft;
|
|
} else {
|
|
result = &(*result)->mRight;
|
|
}
|
|
bitMask <<= 1;
|
|
}
|
|
|
|
// We failed to find the key: return where the node would be inserted
|
|
return result;
|
|
}
|
|
|
|
#ifdef NS_DEBUG
|
|
nsDST::Node*
|
|
nsDST::DepthFirstSearch(Node* aNode, void* aKey) const
|
|
{
|
|
if (!aNode) {
|
|
return 0;
|
|
} else if (aNode->mKey == aKey) {
|
|
return aNode;
|
|
} else {
|
|
Node* result = DepthFirstSearch(aNode->mLeft, aKey);
|
|
|
|
if (!result) {
|
|
result = DepthFirstSearch(aNode->mRight, aKey);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Helper function that verifies the integrity of the tree
|
|
void
|
|
nsDST::VerifyTree(Node* aNode, int aLevel, PtrBits aLevelKeyBits) const
|
|
{
|
|
if (aNode) {
|
|
// Verify that the first "aLevel" bits of this node's key agree with the
|
|
// accumulated key bits that apply to this branch of the tree, i.e., the
|
|
// path to this node as specified by the bits of the key
|
|
if (aLevel > 0) {
|
|
// When calculating the bit mask, take into consideration the low-order
|
|
// bits we ignore
|
|
PtrBits bitMask = (mLevelZeroBit << aLevel) - 1;
|
|
NS_ASSERTION(aLevelKeyBits == (PtrBits(aNode->mKey) & bitMask),
|
|
"key's bits don't match");
|
|
}
|
|
|
|
// All node keys in the left subtree should have the next bit set to 0
|
|
VerifyTree(aNode->mLeft, aLevel + 1, aLevelKeyBits);
|
|
|
|
// All node keys in the left subtree should have the next bit set to 1
|
|
VerifyTree(aNode->mRight, aLevel + 1, aLevelKeyBits | (mLevelZeroBit << aLevel));
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDST::GatherStatistics(Node* aNode,
|
|
int aLevel,
|
|
int& aNumNodes,
|
|
int aNodesPerLevel[]) const
|
|
{
|
|
aNumNodes++;
|
|
aNodesPerLevel[aLevel]++;
|
|
if (aNode->mLeft) {
|
|
GatherStatistics(aNode->mLeft, aLevel + 1, aNumNodes, aNodesPerLevel);
|
|
}
|
|
if (aNode->mRight) {
|
|
GatherStatistics(aNode->mRight, aLevel + 1, aNumNodes, aNodesPerLevel);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDST::Dump(FILE* out) const
|
|
{
|
|
// Walk the tree gathering statistics about the number of nodes, the height
|
|
// of the tree (maximum node level), the average node level, and the median
|
|
// node level
|
|
static const int maxLevels = sizeof(void*) * 8;
|
|
|
|
int numNodes = 0;
|
|
int nodesPerLevel[maxLevels]; // count of the number of nodes at a given level
|
|
memset(&nodesPerLevel, 0, sizeof(int) * maxLevels);
|
|
|
|
// Walk each node in the tree recording its node level
|
|
GatherStatistics(mRoot, 0, numNodes, nodesPerLevel);
|
|
|
|
// Calculate the height, average node level, and median node level
|
|
int height, medianLevel = 0, pathLength = 0;
|
|
for (height = 0; height < maxLevels; height++) {
|
|
int count = nodesPerLevel[height];
|
|
if (0 == count) {
|
|
break;
|
|
}
|
|
|
|
// Update the median node level
|
|
if (count > nodesPerLevel[medianLevel]) {
|
|
medianLevel = height;
|
|
}
|
|
|
|
// Update the path length
|
|
pathLength += height * count;
|
|
}
|
|
|
|
// Output the statistics
|
|
fputs("DST statistics\n", out);
|
|
fprintf(out, " Number of nodes: %d\n", numNodes);
|
|
fprintf(out, " Height (maximum node level) of the tree: %d\n", height - 1);
|
|
fprintf(out, " Average node level: %.1f\n", float(pathLength) / float(numNodes));
|
|
fprintf(out, " Median node level: %d\n", medianLevel);
|
|
fprintf(out, " Path length: %d\n", pathLength);
|
|
|
|
// Output the number of nodes at each level of the tree
|
|
fputs(" Nodes per level: ", out);
|
|
fprintf(out, "%d", nodesPerLevel[0]);
|
|
for (int i = 1; i < height; i++) {
|
|
fprintf(out, ", %d", nodesPerLevel[i]);
|
|
}
|
|
fputs("\n", out);
|
|
}
|
|
#endif
|
|
|