Since nobody was able to successfully complete the B-tree assignment, I am giving everyone a second chance to get it right. In every case, the work submitted did represent significant progress toward completion. In every case, I have added extensive comments to your work to point out what you need to do to correct your mistakes and things that you still need to do to reach a successful conclusion. Although your approach may differ in some ways from the approach I used for my own solution, everyone's strategy for solving the problem was workable and noone should have to rework their program from the ground up.
Since many people made the same mistakes, here are some general suggestions for everyone.
I strongly recommend that you implement a visualization scheme for your BTree class. Being able to see what the tree looks like as you insert and delete items is a valuable aid for catching subtle logic errors. Here is the source code for methods I use to emit Mathematica code from my program that can be used to visualize the tree.
My BTree class contains this method:
template <class T,int M>
void BTree<T,M>::snapShot(ostream& out) {
out << "TreeForm[";
if(root != 0)
root->snapShot(out);
out << "]";
}
and my BTreeNode class contains this method:
template <class T,int M>
void BTreeNode<T,M>::snapShot(ostream& out) {
out << '\"';
int n;
for(n = 0;n < keyTally - 1;n++)
out << keys[n] << ':';
out << keys[n] << '\"';
if(!leaf) {
out << '[';
for(n = 0;n < keyTally;n++) {
pointers[n]->snapShot(out);
out << ',';
}
pointers[n]->snapShot(out);
out << ']';
}
}
Please use these methods to help in debugging.
Another general suggestion I have for everyone is to use additional methods to help break major tasks down into small, more manageable pieces. The general rule I try to follow is that any method that takes up more than one screen should be broken into smaller methods.
Here is a listing of the methods I put in my BTreeNode class to help me do some of the common tasks needed to solve this problem. You should give some thought to breaking your longer methods down into similar short methods to ease the task of writing complex logic.
// Helper function for the splitting process. Shoves a key and // child up into a parent node void insertKey(const T& el,BTreeNode* child); // Handles the special case of splitting the root void rootSplit(); // Split an overfull node that is not the root void split(); // Remove a key from a leaf node void removeKeyFromLeaf(const T& el); // Return the rightmost descendent node of this node BTreeNode* rightmost(); const T& lastKey(); // Which slot is this node a child of? int parentSlot(BTreeNode* child); // One of the two children at this slot is undersized - fix things up void rebalanceAround(int slot); // Consolidate the root by pulling its two children up into the root void consolidateRoot(); // Do a normal consolidation at some slot by moving everything from // the right child of this slot into the left. void consolidate(int slot); // Rotate right around a slot void rotateRight(int slot); // Rotate left around a slot void rotateLeft(int slot);
Below is the code I used in my main program to test my BTree. The test code inserts a number of integers chosen at random, emits a snapshot after every insertion, and then proceeds to delete some randomly chosen integers with a snapshot after each deletion. Feel free to use this test code to test your BTree class both with M = 4 and with larger values of M.
#include <fstream>
#include <stdlib.h>
#include "BTree.h"
using namespace std;
void main() {
BTree<int,4> x;
ofstream out("tree.txt");
for(int n = 0;n < 100;n++) {
int k = rand()%100;
if(!x.contains(k)) {
x.insert(k);
x.snapShot(out);
out << endl;
}
}
out << endl;
for(int n = 0;n < 80;n++) {
int k = rand()%100;
if(x.contains(k)) {
x.remove(k);
x.snapShot(out);
out << endl;
}
}
out.close();
}