(C++) Red Black Tree for a core::map class

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
katoun
Posts: 239
Joined: Mon Nov 15, 2004 9:39 am
Location: Romania
Contact:

(C++) Red Black Tree for a core::map class

Post by katoun »

Hello,
I like the idea of Irrlicht having it's oun std classes they seem to bee fast.
Well there is one thing Irrlicht misses a std::map and I finaly found some docs and some sourses and made a RB tree with wich std::maps are made.
TODO:
1. the tree does not have a remove function implemented(I don't know how yet)
2. make a class core::map wich hase a RB treee inside to be able to add, remove and search items.

Good luck and here is the code :

RedBlackTree.h

Code: Select all

#ifndef _RED_BLACK_TREE_H_
#define _RED_BLACK_TREE_H_

#include <iostream>

using namespace std;
      
// Red-black tree class
//
// CONSTRUCTION: with negative infinity object also
//               used to signal failed finds
//
// ******************PUBLIC OPERATIONS*********************
// void insert(key, item) --> Insert x
// void remove(key)       --> Remove x (unimplemented)
// Item find(key)         --> Return item that matches x
// Item findMin( )        --> Return smallest item
// Item findMax( )        --> Return largest item
// boolean isEmpty( )     --> Return true if empty; else false
// void makeEmpty( )      --> Remove all items
// void printTree( )      --> Print tree in sorted order



// Node and forward declaration because g++ does
// not understand nested classes.
template <class Key,class Item>
class RedBlackTree;

template <class Key,class Item>
class RedBlackNode
{
            Key    key;
			Item   item;
            RedBlackNode *left;
            RedBlackNode *right;
            int           color;

            // c = 1 should be c = RedBlackTree<Comparable>::BLACK
            // But Visual 5.0 does not comprehend it.
            RedBlackNode(const Key &theKey=Key(),const Item &theItem=Item(),
                              RedBlackNode *lt=NULL,RedBlackNode *rt=NULL,
                              int c=1)
              : key(theKey),item(theItem),left(lt),right(rt),color(c){}
            friend class RedBlackTree<Key,Item>;
};

template <class Key,class Item>
class RedBlackTree
{
public:
            explicit RedBlackTree(Item &negInf);
            RedBlackTree(RedBlackTree &rhs);
            ~RedBlackTree();

            Item & findMin();
            Item & findMax();
            Item & find(Key &key);
            bool isEmpty();
            void printTree();

            void makeEmpty();
            void insert(Key &key,Item &item);
            void remove(Key &key);

            enum {RED, BLACK};

            RedBlackTree &operator=(RedBlackTree &rhs);

private:
            RedBlackNode<Key,Item> *header;   // The tree header (contains negInf)
            Item ITEM_NOT_FOUND;
            RedBlackNode<Key,Item> *nullNode;

            // Used in insert routine and its helpers (logically static)
            RedBlackNode<Key,Item> *current;
            RedBlackNode<Key,Item> *parent;
            RedBlackNode<Key,Item> *grand;
            RedBlackNode<Key,Item> *great;

            // Usual recursive stuff
            void reclaimMemory(RedBlackNode<Key,Item> *t);
			void printTree( RedBlackNode<Key,Item> *t);
			RedBlackNode<Key,Item> *find(RedBlackNode<Key,Item> *t,Key &key);
            RedBlackNode<Key,Item> *clone( RedBlackNode<Key,Item> *t);

            // Red-black tree manipulations
            void handleReorient(Key &key);
            RedBlackNode<Key,Item> *rotate(Key & ey,
                                        RedBlackNode<Key,Item> *parent);
            void rotateWithLeftChild(RedBlackNode<Key,Item> * &k2);
            void rotateWithRightChild(RedBlackNode<Key,Item> * &k1);
 };

///////////////////////////
///////////////////////////

/**
* Construct the tree.
* negInf is a value less than or equal to all others.
* It is also used as ITEM_NOT_FOUND.
*/
template <class Key,class Item>
RedBlackTree<Key,Item>::RedBlackTree(Item &negInf)
:ITEM_NOT_FOUND(negInf)
{
	nullNode    = new RedBlackNode<Key,Item>;
	nullNode->left = nullNode->right = nullNode;
	header      = new RedBlackNode<Key,Item>(Key(),negInf);
	header->left = header->right = nullNode;
}

/**
* Copy constructor.
*/
template <class Key,class Item>
RedBlackTree<Key,Item>::RedBlackTree(RedBlackTree<Key,Item> &rhs)
: ITEM_NOT_FOUND(rhs.ITEM_NOT_FOUND)
{
            nullNode    = new RedBlackNode<Key,Item>;
            nullNode->left = nullNode->right = nullNode;
            header      = new RedBlackNode<Key,Item>(Key(),ITEM_NOT_FOUND );
            header->left = header->right = nullNode;
            *this = rhs;

}

/**
* Destroy the tree.
*/
template <class Key,class Item>
RedBlackTree<Key,Item>::~RedBlackTree()
{
            makeEmpty( );
            delete nullNode;
            delete header;

}

/**
* Insert item wiht key into the tree. Does nothing if key already present.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::insert(Key &key,Item &item)
{
            current = parent = grand = header;
			nullNode->key=key;
			nullNode->item=item;

            while(current->key != key)
            {
                great = grand; grand = parent; parent = current;
                current = key<current->key ? current->left : current->right;

                    // Check if two red children; fix if so
                if( current->left->color == RED && current->right->color == RED )
                     handleReorient(key);
            }

                // Insertion fails if already present
            if( current != nullNode )
                return;
            current = new RedBlackNode<Key,Item>(key,item,nullNode,nullNode);

                // Attach to parent
            if(key < parent->key)
                parent->left = current;
            else
                parent->right = current;
            handleReorient(key);

}

/**
* Remove item x from the tree.
* Not implemented in this version.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::remove(Key &key)
{
     cout << "Sorry, remove unimplemented; " <<key<<
                 " still present" << endl;
}

/**
* Find the smallest item  the tree.
* Return the smallest item or ITEM_NOT_FOUND if empty.
*/
template <class Key,class Item>
Item & RedBlackTree<Key,Item>::findMin()
{
            if(isEmpty())
                return ITEM_NOT_FOUND;

            RedBlackNode<Key,Item> *itr = header->right;

            while(itr->left != nullNode)
                itr = itr->left;

            return itr->item;

}

/**
* Find the largest item in the tree.
* Return the largest item or ITEM_NOT_FOUND if empty.
*/
template <class Key,class Item>
Item & RedBlackTree<Key,Item>::findMax()
{
            if(isEmpty())
                return ITEM_NOT_FOUND;

            RedBlackNode<Key,Item> *itr = header->right;

            while(itr->right != nullNode)
                itr = itr->right;

            return itr->item;

}

/**
* Find item x in the tree.
* Return the matching item or ITEM_NOT_FOUND if not found.
*/
template <class Key,class Item>
Item & RedBlackTree<Key,Item>::find(Key &key)
{
    RedBlackNode<Key,Item> *curr = find(header,key);
	if(curr!= nullNode)
		return curr->item;
	else
		return ITEM_NOT_FOUND;
}

/**
* Make the tree logically empty.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::makeEmpty()
{
            reclaimMemory(header->right);
            header->right = nullNode;

}

/**
* Test if the tree is logically empty.
* Return true if empty, false otherwise.
*/
template <class Key,class Item>
bool RedBlackTree<Key,Item>::isEmpty()
{
            return header->right == nullNode;

}

/**
* Print the tree contents in sorted order.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::printTree()
{
            if(header->right == nullNode)
                cout<<"Empty tree"<<endl;
            else
                printTree(header->right);
}

/**
* Deep copy.
*/
template <class Key,class Item>
RedBlackTree<Key,Item> &
RedBlackTree<Key,Item>::operator=(RedBlackTree<Key,Item> &rhs)
{
            if( this != &rhs )
            {
                makeEmpty( );
                header->right = clone(rhs.header->right);
            }

            return *this;

}

/**
* Internal method to print a subtree t in sorted order.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::printTree(RedBlackNode<Key,Item> *t)
{
            if( t != t->left )
            {
                printTree(t->left);
				cout<<"Key:"<<t->key<<" Item:"<<t->item<<endl;
                printTree(t->right);
            }
}

template <class Key,class Item>
RedBlackNode<Key,Item> *RedBlackTree<Key,Item>::find(RedBlackNode<Key,Item> *t,Key &key)
{
	if(t!=NULL)
	{
		if(key < t->key)
			return find(t->left,key);
		else
		if(key > t->key)
			return find(t->right,key);
		else
			return t;
	}
}

/**
* Internal method to clone subtree.
*/
template <class Key,class Item>
RedBlackNode<Key,Item> *
RedBlackTree<Key,Item>::clone(RedBlackNode<Key,Item> * t)
{
            if(t == t->left)  // Cannot test against nullNode!!!
                return nullNode;
            else
				return new RedBlackNode<Key,Item>(t->key,t->item,clone(t->left),
                                               clone(t->right),t->color);

}


/**
* Internal routine that is called during an insertion
*     if a node has two red children. Performs flip
*     and rotatons.
* item is the item being inserted.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::handleReorient(Key &key)
{
                // Do the color flip
            current->color = RED;
            current->left->color = BLACK;
            current->right->color = BLACK;

            if(parent->color == RED)   // Have to rotate
            {
                grand->color = RED;
                if(key < grand->key != key < parent->key)
                    parent = rotate(key,grand);  // Start dbl rotate
                current = rotate(key,great);
                current->color = BLACK;
            }
            header->right->color = BLACK; // Make root black

}

/**
* Internal routine that performs a single or double rotation.
* Because the result is attached to the parent, there are four cases.
* Called by handleReorient.
* item is the item in handleReorient.
* parent is the parent of the root of the rotated subtree.
* Return the root of the rotated subtree.
*/

template <class Key,class Item>
RedBlackNode<Key,Item> *
RedBlackTree<Key,Item>::rotate(Key & key,
								 RedBlackNode<Key,Item> *theParent)
{
            if(key < theParent->key )
            {
				key < theParent->left->key ?
                    rotateWithLeftChild(theParent->left) :  // LL
                    rotateWithRightChild(theParent->left);  // LR
                return theParent->left;
            }
            else
            {
				key < theParent->right->key ?
                    rotateWithLeftChild(theParent->right) :  // RL
                    rotateWithRightChild(theParent->right);  // RR
                return theParent->right;
            }

}

/**
* Rotate binary tree node with left child.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::
rotateWithLeftChild(RedBlackNode<Key,Item> * &k2)
{
            RedBlackNode<Key,Item> *k1 = k2->left;
            k2->left = k1->right;
            k1->right = k2;
            k2 = k1;

}

/**
* Rotate binary tree node with right child.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::
rotateWithRightChild(RedBlackNode<Key,Item> * &k1)
{
            RedBlackNode<Key,Item> *k2 = k1->right;
            k1->right = k2->left;
            k2->left = k1;
            k1 = k2;

}


/**
* Internal method to reclaim internal nodes
* in subtree t.
*/
template <class Key,class Item>
void RedBlackTree<Key,Item>::reclaimMemory(RedBlackNode<Key,Item> *t)
{
	if( t != t->left )
	{
		reclaimMemory( t->left );
		reclaimMemory( t->right );
    
		delete t;
	}
}


////////////////////////////
////////////////////////////


 #endif
RedBlackTree.cpp

Code: Select all

#include "RedBlackTree.h"

#include <iostream>

using namespace std;
// Test program
int main( )
{
    float ITEM_NOT_FOUND =-9999.99f ;
	RedBlackTree<int,float> t(ITEM_NOT_FOUND);

	int NUMS = 40000;
    int GAP  =   37;
	int i;
	float f;

	cout<<"Checking... (no more output means success)"<<endl;

    for(i=rand()%GAP,f=i+0.1f;i<rand()%NUMS;i=i+rand()%GAP,f=i+0.1f)
		t.insert(i,f);

	t.printTree();

	cout<<t.findMin()<<" "<<t.findMax()<<endl;

	

    return 0;

}
Kat'Oun
Baal Cadar
Posts: 377
Joined: Fri Oct 28, 2005 10:28 am
Contact:

Re: Red Black Tree for a core::map class

Post by Baal Cadar »

katoun wrote:I like the idea of Irrlicht having it's oun std classes they seem to be fast.
According to what test case and setup?
As already discussed in another thread, I find it highly unlikely that Irrlicht's array and list classes are faster than those in the commonly used STL implementations. If there is any difference they are probably slower.

That said, implementing a container for learning purpose is still a good idea.

katoun, as for your source code, I didn't test run it, but I have a few recommendations/remarks:

Try to follow the STL interface. It is commonly used and function names are known to C++ developers. They are probably expected here too.

More important: Enforce const correctness. Container classes that not do this are unpleasant to use, as they require many const_casts by their users.

How is the user supposed to iterate over the container items?
Eternl Knight
Posts: 313
Joined: Tue Nov 01, 2005 5:01 am

Post by Eternl Knight »

Actually Baal, some tests I have done show that STL can be slower in debug mode than the Irrlicht containers - at least for std::vector compared to core::array.

It does not seem to have any effect on framerate performance, but heavy usage of the [] operator in setup (for example creating tiled terrain) is about twice as slow using STL than Irrlicht. The reason being that Microsoft STL creates two iterators for each [] operator call (by calling begin() & adding the offset to it). Irrlicht just checks the index is ok and then accesses the internal array directly.

Release builds seem to be about equal though.

I personally am in favour of integrating STL into Irrlicht. There are enough free & compliant versions to make it a non-issue. For example, there is STLPort (though non-US users may dislike being forced to agree to US laws by it's license) and STDCXX (nice Apache license).

Also, I have already implemented a core::map class using Red-Black trees. As mentioned in a prior thread, I am waiting on an email from Niko on a possible STL integration before releasing it though.

--EK
Baal Cadar
Posts: 377
Joined: Fri Oct 28, 2005 10:28 am
Contact:

Post by Baal Cadar »

Debug mode performance comparisons are rather pointless in my opinion. :)
As you will release your software in release version anyway. Debug mode affects STL extremely, not just MS' implementation, but STLPort too. My project with another 3d engine for instance (didn't yet make a debug/release comparison in irrlicht) is about four times slower in debug mode and this is mainly due to the heavy STL usage in it.

But yeah, such comparisons can't be seen out of context and simple loops of container code won't help too. If one really wants to know the effect, one had to replace core::array/list with STL counterparts and try then. But I don't expect the difference to be significant. The advantages of the STL lie elsewhere.

Using begin()+offset should not be slower than [] btw, as all STL function calls are inlined and so this is a pointer + offset, which is just what [] does. Only thing in debug is maybe some superfluous constructor call, which is optimised away in release. But that's just a half-educated ad-hoc theory. 8)
Eternl Knight
Posts: 313
Joined: Tue Nov 01, 2005 5:01 am

Post by Eternl Knight »

Well, the thing is - I HAVE replaced all core::array, core::list, and core::string instances with their STL counterparts in a version of IrrSpintz. This is where I got my results from :) Due to the simplicity of the switch - I have asked Niko if he would accept a patch for it... however, he has yet to give me a positive/negative response on that.

As for the "begin() + idx" - you are actually incorrect here. It is NOT pointer plus offset. Iterators are full classes with member variables, and as such require allocation of memory (generally on the stack). While not a major time issue (I mean, you're allocating a few bytes on the stack and initializing them with defined values) - inlined or not, it does add up.

I agree 100% that the advantages of STL are not in speed alone (even a completely optimised version is only going to be as fast as an array access). Which is why I am trying to convince the Irrlicht boss man into switching :)

--EK
Guest

Post by Guest »

I have seen that Scene manager kips track of its resources in core::array with I think it should be changes with core::lists with are fare less stressfull when adding and/or deleting items. Take a look at the way core::array dose those processes.

Code: Select all

	        //! render pass lists
		core::array<ISceneNode*> LightAndCameraList;
		core::array<ISceneNode*> ShadowNodeList;
		core::array<ISceneNode*> SkyBoxList;
		core::array<DefaultNodeEntry> SolidNodeList;
		core::array<TransparentNodeEntry> TransparentNodeList;

		core::array<IMeshLoader*> MeshLoaderList;
		core::array<ISceneNode*> DeletionList;
When I will have time I'll send an e-mail to Niko.
terefang
Posts: 48
Joined: Tue Jun 21, 2005 9:56 am

Post by terefang »

@Eternl Knight:

there is a reason to keep away from STL -- Exceptions !

especially since they do not work across dll boundaries
on SOME platform/compiler combinations.

cheers,
terefang
nVidia 7800GT/256, AMD64-X2 4k2, Latest Fedora/CentOS
Eternl Knight
Posts: 313
Joined: Tue Nov 01, 2005 5:01 am

Post by Eternl Knight »

True, but I would think that exceptions should be handled in the DLL itself really. Combine that with the fact that you are not immune to them in the Irrlicht case either (how else are you going to handle invalid indices in core::array, etc?).

I personally do not like exceptions much (I think they're overused), but if that's the primary reason for not using STL - I remain unconvinced. I have had feedback from Niko stating that he is NOT going to use STL in any case (his choice, his engine - so no bad feelings at all).

Which basically means two things. Firstly, that I'll be forking my engine to use the STL (currently converted both Irrlicht 0.14 & IrrSpintz 0.14 to STL without ill effects) and secondly - I'll have to copy out the core::map implementation out of my old engine for you guys :)

--EK
Baal Cadar
Posts: 377
Joined: Fri Oct 28, 2005 10:28 am
Contact:

Post by Baal Cadar »

Eternl Knight, anything you can report on Nico's rationale behind his decision?
Eternl Knight
Posts: 313
Joined: Tue Nov 01, 2005 5:01 am

Post by Eternl Knight »

Actually, no. I made my arguments for moving to STL, he listened, noted the memory ownership problem I mentioned, and then decided to stick with Irrlicht containers anyway.

As I said, he said he wanted to avoid STL and stick with his container classes. Being the sole developer (or sole "authorising" developer) of Irrlicht - that would seem reason enough I guess.

As I said, there will be an STL branch anyway as I'll be doing it. Though given the fact I like IrrSpintz (due to more bugs being fixed each version) - I am more likely to be maintaining an IrrSpintz fork than anything else. Currently there is no issue as I have already converted Irrlicht 0.14, but we'll have to see the workload involved when 0.15 comes out as to whether I will be maintaining two versions (one of which I'm not likely to use)...

--EK
Guest

Post by Guest »

I think its a good idea to stick with irrlicht containers, since they are already there - there is no reason not to use them. nico wants to keep irrlicht as easy to use, he provides a compiled dll which you can use with mingw, vc etc. - if irrlicht would use STL instead this wont be that easy (since all compilers have their own stl implementation, and especially when using DLLs this can be a mess, believe me!)
Eternl Knight
Posts: 313
Joined: Tue Nov 01, 2005 5:01 am

Post by Eternl Knight »

Well, in my case there IS a reason to use STL over Irrlicht's containers. I'll give you a rundown on why I prefer STL over Irrlicht containers and you can decide for yourself. It doesn't fuss me if you disagree or find my reasons unconvincing - what matters to me is that I have reasons for changing the containers over. One of the wonders of open source :)

"Why I prefer STL over Irrlicht Containers"
  • String Classes: Irrlicht String classes have way less functionality than that provided by the STL string class (and it's supporting cast of string streams). The only benefit the Irrlicht classes have is the fact that char* & wchar_t* string classes can accept either form of pointer array to construct them. However, there is a more robust method of converting them available in STL (which I implemented as a set of template functions).
  • Memory Ownership: Irrlicth containers (such as the core::array) have problems with constructing & freeing memory. For example, if I create an array in the application EXE, pass that array into the Irrlicht DLL, which then tries to delete the memory buffer (say, because it needed to extend the size of the array - which is done by allocating a new memory buffer, copying the contents, and then deleting the old buffer) - it will crash. Replacing the container with STL's std::vector fixed this issue.
  • Flexibility and Code Re-use: I have a lot of geometric & physics simulation code written using STL classes. To port them into Irrlicht would require alot of work. If instead I convert Irrlicht to STL, an entire body of work becomes available for integration. This not only goes for my previous work - but for alot of other open-source libraries as well (such as Boost, & STLPlus - both of which come in very handy)

    Combine that with the fact that STL is not only container classes, but also a variety of commonly used algorthms. Sorts, searchs, etc are now not only available to the core::array, but to arrays, lists, maps, etc.
  • Irrlicht Classes for Irrlicht Functionality: Irrlicht only implements the classes it needs. This thread (about the addition of a core::map template) is an example of that. However, one of the benefits of open source (& Irrlicht in particular) is the ability to EXTEND the original library with new functionality. For example, I am adding the ability to edit meshes in different ways directly to my branch of Irrlicht. As such, I need MORE functionality than offered by just a loading/rendering solution (Irrlicht's primary functions revolve around this). STL provides me with this extra functionality whereas Irrlicht's containers are currently only implemented to the extent that is required by Irrlicht. I have the choice of either adding to/patching the Irrlicht containers everytime I need new functionality (such as adding a core::map class or implementing proper "allocators" such as Niko has mentioned he will be doing to fix the memory ownership issue) or I can use a common cross-platform framework that has solved these issues already. I'm a lazy programmer - what do you think I'm going to choose :P
--EK
Spintz
Posts: 1688
Joined: Thu Nov 04, 2004 3:25 pm

Post by Spintz »

terefang wrote:@Eternl Knight:

there is a reason to keep away from STL -- Exceptions !

especially since they do not work across dll boundaries
on SOME platform/compiler combinations.

cheers,
The only compilers, I know of that have this problem are Visual Studio 6 ( for which their is a patch to fix it ), Visual Studio .Net 2002 ( for which there is a patch to fix it ) and gcc in versions before 3.x. Even still, gcc is free, and if you're still using 2.96 or anything under 3.x, you have other problems besides STL. And between all the patches for old versions, and now, Visual C++ Express 2005 being free, it makes absolute sense to use STL. I'm kind of boggled by the decision by Niko, however, IrrSpintz will be moving over to using STL, once the updates for Vertex/Index Buffers, OpenGL rendering fixes and other things are done, thanks to EK.
Image
wolfgke

Post by wolfgke »

@Eternl Knight

Since I already wrote some data structures (which normally implement very special things that I need for my application) I would be interested to get an explanation by what the "Memory ownership" problem is caused and how it can be fixed. Thanks in advance
wolfgke
terefang
Posts: 48
Joined: Tue Jun 21, 2005 9:56 am

Post by terefang »

Spintz wrote: The only compilers, I know of that have this problem are ...
(ok this will get offtopic, but ...)

the main reason i keep away from exceptions is that tcpip sockets
become unusable (on win32) if receiving signals/exceptions during
blocking operations.

since i write a lot of networking code, you could consider me a burned child.

cheers,
terefang
nVidia 7800GT/256, AMD64-X2 4k2, Latest Fedora/CentOS
Post Reply