C++ Decision Tree Implementation Question: Think In Code - c++

I've been coding for a few years but I still haven't gotten the hang of pseudo-coding or actually thinking things out in code yet. Due to this problem, I'm having trouble figuring out exactly what to do in creating a learning Decision Tree.
Here are a few sites that I have looked at and trust me there were plenty more
Decision Tree Tutorials
DMS Tutorials
Along with several books such as Ian Millington's AI for Games which includes a decent run-down of the different learning algorithms used in decision trees and Behavioral Mathematics for Game Programming which is basically all about Decision Trees and theory. I understand the concepts for a decision tree along with Entropy, ID3 and a little on how to intertwine a genetic algorithm and have a decision tree decide the nodes for the GA.
They give good insight, but not what I am looking for really.
I do have some basic code that creates the nodes for the decision tree, and I believe I know how to implement actual logic but it's no use if I don't have a purpose to the program or have entropy or a learning algorithm involved.
What I am asking is, can someone help me figure out what I need to do to create this learning decision tree. I have my nodes in a class of their own flowing through functions to create the tree, but how would I put entropy into this and should it have a class, a struct I'm not sure how to put it together. Pseudo-code and an Idea of where I am going with all this theory and numbers. I can put the code together if only I knew what I needed to code. Any guidance would be appreciated.
How Would I Go About This, Basically.
Adding a learning algorithm such as ID3 and Entropy. How should it be set up?
Once I do have this figured out on how to go about all this,I plan to implement this into a state machine that goes through different states in a game/simulation format. All of this is already set up, I just figure this could be stand-alone and once I figure it out I can just move it to the other project.
Here is the source code I have for now.
Thanks in advance!
Main.cpp:
int main()
{
//create the new decision tree object
DecisionTree* NewTree = new DecisionTree();
//add root node the very first 'Question' or decision to be made
//is monster health greater than player health?
NewTree->CreateRootNode(1);
//add nodes depending on decisions
//2nd decision to be made
//is monster strength greater than player strength?
NewTree->AddNode1(1, 2);
//3rd decision
//is the monster closer than home base?
NewTree->AddNode2(1, 3);
//depending on the weights of all three decisions, will return certain node result
//results!
//Run, Attack,
NewTree->AddNode1(2, 4);
NewTree->AddNode2(2, 5);
NewTree->AddNode1(3, 6);
NewTree->AddNode2(3, 7);
//Others: Run to Base ++ Strength, Surrender Monster/Player,
//needs to be made recursive, that way when strength++ it affects decisions second time around DT
//display information after creating all the nodes
//display the entire tree, i want to make it look like the actual diagram!
NewTree->Output();
//ask/answer question decision making process
NewTree->Query();
cout << "Decision Made. Press Any Key To Quit." << endl;
//pause quit, oh wait how did you do that again...look it up and put here
//release memory!
delete NewTree;
//return random value
//return 1;
}
Decision Tree.h:
//the decision tree class
class DecisionTree
{
public:
//functions
void RemoveNode(TreeNodes* node);
void DisplayTree(TreeNodes* CurrentNode);
void Output();
void Query();
void QueryTree(TreeNodes* rootNode);
void AddNode1(int ExistingNodeID, int NewNodeID);
void AddNode2(int ExistingNodeID, int NewNodeID);
void CreateRootNode(int NodeID);
void MakeDecision(TreeNodes* node);
bool SearchAddNode1(TreeNodes* CurrentNode, int ExistingNodeID, int NewNodeID);
bool SearchAddNode2(TreeNodes* CurrentNode, int ExistingNodeID, int NewNodeID);
TreeNodes* m_RootNode;
DecisionTree();
virtual ~DecisionTree();
};
Decisions.cpp:
int random(int upperLimit);
//for random variables that will effect decisions/node values/weights
int random(int upperLimit)
{
int randNum = rand() % upperLimit;
return randNum;
}
//constructor
//Step 1!
DecisionTree::DecisionTree()
{
//set root node to null on tree creation
//beginning of tree creation
m_RootNode = NULL;
}
//destructor
//Final Step in a sense
DecisionTree::~DecisionTree()
{
RemoveNode(m_RootNode);
}
//Step 2!
void DecisionTree::CreateRootNode(int NodeID)
{
//create root node with specific ID
// In MO, you may want to use thestatic creation of IDs like with entities. depends on how many nodes you plan to have
//or have instantaneously created nodes/changing nodes
m_RootNode = new TreeNodes(NodeID);
}
//Step 5.1!~
void DecisionTree::AddNode1(int ExistingNodeID, int NewNodeID)
{
//check to make sure you have a root node. can't add another node without a root node
if(m_RootNode == NULL)
{
cout << "ERROR - No Root Node";
return;
}
if(SearchAddNode1(m_RootNode, ExistingNodeID, NewNodeID))
{
cout << "Added Node Type1 With ID " << NewNodeID << " onto Branch Level " << ExistingNodeID << endl;
}
else
{
//check
cout << "Node: " << ExistingNodeID << " Not Found.";
}
}
//Step 6.1!~ search and add new node to current node
bool DecisionTree::SearchAddNode1(TreeNodes *CurrentNode, int ExistingNodeID, int NewNodeID)
{
//if there is a node
if(CurrentNode->m_NodeID == ExistingNodeID)
{
//create the node
if(CurrentNode->NewBranch1 == NULL)
{
CurrentNode->NewBranch1 = new TreeNodes(NewNodeID);
}
else
{
CurrentNode->NewBranch1 = new TreeNodes(NewNodeID);
}
return true;
}
else
{
//try branch if it exists
//for a third, add another one of these too!
if(CurrentNode->NewBranch1 != NULL)
{
if(SearchAddNode1(CurrentNode->NewBranch1, ExistingNodeID, NewNodeID))
{
return true;
}
else
{
//try second branch if it exists
if(CurrentNode->NewBranch2 != NULL)
{
return(SearchAddNode2(CurrentNode->NewBranch2, ExistingNodeID, NewNodeID));
}
else
{
return false;
}
}
}
return false;
}
}
//Step 5.2!~ does same thing as node 1. if you wanted to have more decisions,
//create a node 3 which would be the same as this maybe with small differences
void DecisionTree::AddNode2(int ExistingNodeID, int NewNodeID)
{
if(m_RootNode == NULL)
{
cout << "ERROR - No Root Node";
}
if(SearchAddNode2(m_RootNode, ExistingNodeID, NewNodeID))
{
cout << "Added Node Type2 With ID " << NewNodeID << " onto Branch Level " << ExistingNodeID << endl;
}
else
{
cout << "Node: " << ExistingNodeID << " Not Found.";
}
}
//Step 6.2!~ search and add new node to current node
//as stated earlier, make one for 3rd node if there was meant to be one
bool DecisionTree::SearchAddNode2(TreeNodes *CurrentNode, int ExistingNodeID, int NewNodeID)
{
if(CurrentNode->m_NodeID == ExistingNodeID)
{
//create the node
if(CurrentNode->NewBranch2 == NULL)
{
CurrentNode->NewBranch2 = new TreeNodes(NewNodeID);
}
else
{
CurrentNode->NewBranch2 = new TreeNodes(NewNodeID);
}
return true;
}
else
{
//try branch if it exists
if(CurrentNode->NewBranch1 != NULL)
{
if(SearchAddNode2(CurrentNode->NewBranch1, ExistingNodeID, NewNodeID))
{
return true;
}
else
{
//try second branch if it exists
if(CurrentNode->NewBranch2 != NULL)
{
return(SearchAddNode2(CurrentNode->NewBranch2, ExistingNodeID, NewNodeID));
}
else
{
return false;
}
}
}
return false;
}
}
//Step 11
void DecisionTree::QueryTree(TreeNodes* CurrentNode)
{
if(CurrentNode->NewBranch1 == NULL)
{
//if both branches are null, tree is at a decision outcome state
if(CurrentNode->NewBranch2 == NULL)
{
//output decision 'question'
///////////////////////////////////////////////////////////////////////////////////////
}
else
{
cout << "Missing Branch 1";
}
return;
}
if(CurrentNode->NewBranch2 == NULL)
{
cout << "Missing Branch 2";
return;
}
//otherwise test decisions at current node
MakeDecision(CurrentNode);
}
//Step 10
void DecisionTree::Query()
{
QueryTree(m_RootNode);
}
////////////////////////////////////////////////////////////
//debate decisions create new function for decision logic
// cout << node->stringforquestion;
//Step 12
void DecisionTree::MakeDecision(TreeNodes *node)
{
//should I declare variables here or inside of decisions.h
int PHealth;
int MHealth;
int PStrength;
int MStrength;
int DistanceFBase;
int DistanceFMonster;
////sets random!
srand(time(NULL));
//randomly create the numbers for health, strength and distance for each variable
PHealth = random(60);
MHealth = random(60);
PStrength = random(50);
MStrength = random(50);
DistanceFBase = random(75);
DistanceFMonster = random(75);
//the decision to be made string example: Player health: Monster Health: player health is lower/higher
cout << "Player Health: " << PHealth << endl;
cout << "Monster Health: " << MHealth << endl;
cout << "Player Strength: " << PStrength << endl;
cout << "Monster Strength: " << MStrength << endl;
cout << "Distance Player is From Base: " << DistanceFBase << endl;
cout << "Disntace Player is From Monster: " << DistanceFMonster << endl;
//MH > PH
//MH < PH
//PS > MS
//PS > MS
//DB > DM
//DB < DM
//good place to break off into different decision nodes, not just 'binary'
//if statement lower/higher query respective branch
if(PHealth > MHealth)
{
}
else
{
}
//re-do question for next branch. Player strength: Monster strength: Player strength is lower/higher
//if statement lower/higher query respective branch
if(PStrength > MStrength)
{
}
else
{
}
//recursive question for next branch. Player distance from base/monster.
if(DistanceFBase > DistanceFMonster)
{
}
else
{
}
//DECISION WOULD BE MADE
//if statement?
// inside query output decision?
//cout << <<
//QueryTree(node->NewBranch2);
//MakeDecision(node);
}
//Step.....8ish?
void DecisionTree::Output()
{
//take repsective node
DisplayTree(m_RootNode);
}
//Step 9
void DecisionTree::DisplayTree(TreeNodes* CurrentNode)
{
//if it doesn't exist, don't display of course
if(CurrentNode == NULL)
{
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//need to make a string to display for each branch
cout << "Node ID " << CurrentNode->m_NodeID << "Decision Display: " << endl;
//go down branch 1
DisplayTree(CurrentNode->NewBranch1);
//go down branch 2
DisplayTree(CurrentNode->NewBranch2);
}
//Final step at least in this case. A way to Delete node from tree. Can't think of a way to use it yet but i know it's needed
void DecisionTree::RemoveNode(TreeNodes *node)
{
//could probably even make it to where you delete a specific node by using it's ID
if(node != NULL)
{
if(node->NewBranch1 != NULL)
{
RemoveNode(node->NewBranch1);
}
if(node->NewBranch2 != NULL)
{
RemoveNode(node->NewBranch2);
}
cout << "Deleting Node" << node->m_NodeID << endl;
//delete node from memory
delete node;
//reset node
node = NULL;
}
}
TreeNodes.h:
using namespace std;
//tree node class
class TreeNodes
{
public:
//tree node functions
TreeNodes(int nodeID/*, string QA*/);
TreeNodes();
virtual ~TreeNodes();
int m_NodeID;
TreeNodes* NewBranch1;
TreeNodes* NewBranch2;
};
TreeNodes.cpp:
//contrctor
TreeNodes::TreeNodes()
{
NewBranch1 = NULL;
NewBranch2 = NULL;
m_NodeID = 0;
}
//deconstructor
TreeNodes::~TreeNodes()
{ }
//Step 3! Also step 7 hah!
TreeNodes::TreeNodes(int nodeID/*, string NQA*/)
{
//create tree node with a specific node ID
m_NodeID = nodeID;
//reset nodes/make sure! that they are null. I wont have any funny business #s -_-
NewBranch1 = NULL;
NewBranch2 = NULL;
}

Please correct me if I'm wrong but judging from the images at http://dms.irb.hr/tutorial/tut_dtrees.php and http://www.decisiontrees.net/?q=node/21 the actual decision logic should go in the nodes, not in the tree. You could model that by having polymorphic nodes, one for each decision there is to be made. With a bit of change to the tree construction and a minor minor modification for the decision delegation your code should be fine.

Basically you need to break everything down into stages and then modularise each part of the algorithm that you are trying to implement.
You can do this in pseudo-code or in code itself using functions/classes and stubs.
Each part of the algorithm you can then code up in some function and even test that function before adding it all together. You will basically end up with various functions or classes that are used for specific purposes in the algorithm implementation. So in your case for tree construction you will have one function that calculates entropy at a node, another that partitions the data into subsets at each node, etc.
I am talking in the general case here rather than specifically with respect to decision tree construction. Check out the book on Machine Learning by Mitchell if you need specific information on decision tree algorithms and the steps involved.

The pseudocode to implement a decision tree is as follows
createdecisiontree(data, attributes)
Select the attribute a with the highest information gain
for each value v of the attribute a
Create subset of data where data.a.val==v ( call it data2)
Remove the attribute a from the attribute list resulting in attribute_list2
Call CreateDecisionTree(data2, attribute_list2)
You will have to store nodes at each level with some code like
decisiontree[attr][val]=new_node

Related

Returning name of lowest node

First of all, this is part of a university course, so whilst a copy-paste solution would do, I'm looking for a bit more depth. I'll be seeing my supervisor tomorrow anyways though.
Now onto the problem. I am implementing Dijkstra's algorithm for 5 linked nodes, A-E, which have their associated costs and links stored in a vector;
struct Node
{
char nodeLink; //adjacent link
int cost; //cost of a link
}; //to use in Dijkstra algorithm
class HeadNode
{
public:
char Name;
bool Visited;
vector<Node> nodes;
HeadNode(char x) { Name = x; Visited = false; }
};
class Graph
{
char Start = 'A';
char StartNode;
char CurrentNode;
char Destination = 'E';
int TotalCost = 0;
vector<HeadNode> hnode;
vector<char> path;
vector<int> weight;
public:
Graph();
void createHeadNode(char X);
void createAdjMatrix();
char LeastDistance(char node);
void printAdjMatrix();
void Dijkstra(char StartNode);
char GetStartNode();
};
int main()
{
Graph graph;
graph.createHeadNode('A');
graph.createHeadNode('B');
graph.createHeadNode('C');
graph.createHeadNode('D');
graph.createHeadNode('E');
graph.createAdjMatrix();
//graph.printAdjMatrix();
graph.Dijkstra(graph.GetStartNode());
system("pause");
return 0;
}
Graph::Graph()
{
}
void Graph::createHeadNode(char x)
{
hnode.push_back(x);
}
In order to properly implement the algorithm, I have created a precursor function, LeastDistance(), within the class graph. I also have a function to get the start node, but that isn't particularly important here;
char Graph::LeastDistance(char node)
{
int smallest = 9999;
char smallestNode;
for (int i = 0; i < hnode.size(); i++)
{
for (int j = 0; j < hnode[i].nodes.size(); ++j)
{
if ((node == hnode[i].Name) && (hnode[i].nodes[j].cost <= smallest) && (hnode[i].Visited == false))
{
smallest = hnode[i].nodes[j].cost;
smallestNode = hnode[i].nodes[j].nodeLink;
}
else
{
hnode[i].Visited = true;
break;
}
}
}
TotalCost = TotalCost + smallest;
return(smallestNode);
}
void Graph::Dijkstra(char StartNode)
{
CurrentNode = StartNode;
if (CurrentNode == Destination)
{
cout << "the start is the destination, therefore the cost will be 0." << endl;
}
else
{
while(true)
{
if (CurrentNode != Destination)
{
CurrentNode = LeastDistance(StartNode);
cout << CurrentNode << "<-";
}
else if (CurrentNode == Destination)
{
cout << endl;
cout << "The total cost of this path is:" << TotalCost;
TotalCost = 0;//reset cost
break;
}
}
}
}
My problem is that the LeastDistance fucntion appears always to return node C, leading to it being printed over and over, so it fills the console. So far, I have tried to debug using visual studio 2017, but I cant make much sense out of the watches. I have also tweaked the order of the breaks around, and tried to make sure the visited flag is being set to true. whether any precedence of operations is affecting this I am not sure.
Thanks in advance.
I would contend that there are multiple problems with the way you implement this... but I think the one that's causing you the problem you describe is the statement right here:
if (CurrentNode != Destination)
{
CurrentNode = LeastDistance(StartNode);
cout << CurrentNode << "<-";
}
Think about what this does. Let's say your first node isn't the one you're looking for, then you call least distance and find the next smallest node. Then you print it. Then you iterate on the while loop again only to find that CurrentNode isn't the one you're looking for, so you call LeastDistance(StartNode) again, which will return the exactly same value. Thus, you'll keep printing the same result which apparently is c.
Assuming everything else is correct, I think you want:
CurrentNode = LeastDistance(CurrentNode);

c++ trying to find the distance between 2 nodes

I am currently working on a function that has 1 helper function, the main function takes in 2 strings and searches for the first one (which becomes a reference as if it was m_root) and a second one to be searched in the tree. once they are searched, my helper function is supposed to search for the 2nd city and count the distance it had to travel as if a truck was going towards that city.
int Stree::distance(string origin_city, string destination_city)
{
int total_distance = 0;
Node *new_root = m_root;
new_root = find_node(m_root, origin_city);
total_distance = get_distance(new_root, total_distance, destination_city);
return total_distance;
}
int Stree::get_distance(Node* cur, int distance, string destination)
{
Node *tmp = cur;
if(cur == NULL)
return 0;
if(cur->m_city == destination || tmp->m_city == destination)
{
//cout << distance + cur->m_parent_distance << endl;
return distance += cur->m_parent_distance;
}
if(tmp->m_left != NULL)
{
//cout << "checking left" << endl;
tmp = cur->m_left;
return get_distance(cur->m_left, distance, destination) ;
}
else
{
//cout << "checking right" << endl;
return get_distance(cur->m_right, distance, destination);
}
}
In a cursory glance, I don't see anywhere that you modify or increment distance, whether it be the distance variable or something like:
return 1 + get_distance(cur->m_right, distance, destination);
So I would make sure that in an algorithmic sense, each step walked is counted, otherwise it will certainly return 0 every time.

How to print the nodes in bst whose grandparent is a multiple of five?

I'm sorry that was my first time for asking question in stackoverflow. I just read the faq and knew I disobeyed the rules. I was not just coping and pasting the questions. I use an in-order traverse method to do the recursion and check whether the node is a multiple of five and I don't know what to do next. Should I use a flag to check something?
void findNodes(BSTNode<Key,E> *root) const
{
if(root==NULL) return;
else
{
if(root->key()%5==0)//I know it's wrong, but I don't know what to do
{
findNodes(root->left());
cout<<root->key()<<" ";
findNodes(root->right());
}
else
{
findNodes(root->left());
findNodes(root->right());
}
}
}
Printing nodes whose grandparent is a multiple of 5 is complicated as you have to look "up" the tree. It is easier if you look at the problem as find all the nodes who are a multiple of 5 and print their grandchildren, as you only have to go down the tree.
void printGrandChildren(BSTNode<Key,E> *root,int level) const{
if(!root) return;
if(level == 2){
cout<<root->key()<<" ";
return;
}else{
printGrandChildren(root->left(),level+1);
printGrandChildren(root->right(),level+1);
}
}
Then modify your findNodes to
void findNodes(BSTNode<Key,E> *root) const
{
if(root==NULL) return;
else
{
if(root->key()%5==0)
{
printGrandChildren(root,0);
}
else
{
findNodes(root->left());
findNodes(root->right());
}
}
}
Try this:
int arr[height_of_the_tree]; //arr[10000000] if needed;
void findNodes(BSTNode<Key,E> *root,int level) const {
if(root==NULL) return;
arr[level] = root -> key();
findNodes(root -> left(), level + 1);
if(2 <= level && arr[level - 2] % 5 == 0) cout << root->key() << " ";
findNodes(root -> right(), level + 1);
}
int main() {
...
findNodes(Binary_search_tree -> root,0);
...
}
Replace the following
cout<<root->key()<<" ";
with
if(root->left)
{
if(root->left->left)
cout<<root->left->left->key()<< " ";
if(root->left->right)
cout<<root->left->right->key()<< " ";
}
if(root->right)
{
if(root->right->left)
cout<<root->right->left->key()<< " ";
if(root->right->right)
cout<<root->right->right->key()<< " ";
}
If you're just trying to print our all child nodes which have an ancestor which has a key which is a multiple of 5, then one way would be to pass a bool to your findNodes function which stores this fact.
Something along the lines of:
void findNodes(BSTNode<Key,E>* node, bool ancesterIsMultOf5) const
{
if (node)
{
if (ancesterIsMultOf5)
std::cout << node->key() << std::endl;
ancesterIsMultOf5 |= (node->key() % 5 == 0);
findNodes(node->left(), ancesterIsMultOf5);
findNodes(node->right(), ancesterIsMultOf5);
}
}
Alternately, if you're trying to draw the tree, it has been answered before: C How to "draw" a Binary Tree to the console

Insertion error in Binary Search tree

void BST::insert(string word)
{
insert(buildWord(word),root);
}
//Above is the gateway insertion function that calls the function below
//in order to build the Node, then passes the Node into the insert function
//below that
Node* BST::buildWord(string word)
{
Node* newWord = new Node;
newWord->left = NULL;
newWord->right = NULL;
newWord->word = normalizeString(word);
return newWord;
}
//The normalizeString() returns a lowercase string, no problems there
void BST::insert(Node* newWord,Node* wordPntr)
{
if(wordPntr == NULL)
{
cout << "wordPntr is NULL" << endl;
wordPntr = newWord;
cout << wordPntr->word << endl;
}
else if(newWord->word.compare(wordPntr->word) < 0)
{
cout << "word alphabetized before" << endl;
insert(newWord,wordPntr->left);
}
else if(newWord->word.compare(wordPntr->word) > 0)
{
cout << "word alphabetized after" << endl;
insert(newWord, wordPntr->right);
}
else
{
delete newWord;
}
}
So my problem is this: I call the gateway insert() externally (also no problems with the inflow of data) and every time it tells me that the root, or the initial Node* is NULL. But that should only be the case before the first insert. Each time the function is called, it sticks the newWord right at the root.
To clarify: These functions are part of the BST class, and root is a Node* and a private member of BST.h
It's possible it is quite obvious, and I have just been staring too long. Any help would be appreciated.
Also, this is a school-assigned project.
Best
Like user946850 says, the variable wordPntr is a local variable, if you change it to point to something else it will not be reflected in the calling function.
There are two ways of fixing this:
The old C way, by using a pointer to a pointer:
void BST::insert(Node *newWord, Node **wordPntr)
{
// ...
*wordPntr = newWord;
// ...
}
You call it this way:
some_object.insert(newWord, &rootPntr);
Using C++ references:
void BST::insert(Node *newWord, Node *&wordPntr)
{
// Nothing here or in the caller changes
// ...
}
To help you understand this better, I suggest you read more about scope and lifetime of variables.
The assignment wordPntr = newWord; is local to the insert function, it should somehow set the root of the tree in this case.

Strcmp Error in Binary Search Tree Insertion (Recursion)?

i'm trying to implement some functions that allow me to add "Books" to a binary search tree for the "Student" class, but I'm getting a strange error:
msvcr100d.dll!strcmp(unsigned char * str1, unsigned char * str2) Line 83 Asm
The program is entirely in C/C++, so I'm not sure why its returning an assembly language error? My first thought is something is wrong with my use of strcmp, and the Call Stack shows Line 188 as the last executed statement (before the above error), which means I'm probably messing up my recursion somewhere. I am calling the insertBook() function of "Student", so here is my "Student" class. Any help? Thanks.
class Student : public Personnel { //inherit from Personnel
public:
Book *bookTree;
Book* searchBookTree(Book *bookNode, char *title) {
if ((strcmp(title, bookNode->title)) < 0) //***LINE 188
return searchBookTree(bookNode->left, title);
else if ((strcmp(title, bookNode->title)) > 0)
return searchBookTree(bookNode->right, title);
else
return bookNode;
}
void insertBook(Book *node) {
Book *newBook, *parent;
newBook = node;
newBook->left = NULL;
newBook->right = NULL;
if (bookTree == NULL) { //if bookTree is empty
bookTree = newBook;
}
else {
parent = searchBookTree(bookTree, newBook->title);
newBook->left = parent->left;
newBook->right = parent->right;
}
}
void printBooks(Book *top) {
Book *root = top;
if (root != NULL) {
printBooks(root->left);
cout << "BOOK LIST" << endl;
cout << "Title:\t\t" << root->title << endl;
cout << "URL:\t\t" << root->url << endl;
printBooks(root->right);
}
}
void display() {
Personnel::display();
cout << "STUDENT" << endl;
cout << "Level:\t\t" << getLevel() << endl;
printBooks(bookTree); cout << endl;
}
Student(char *cName, char *cBirthday, char *cAddress, char *cPhone, char *cEmail, level gradeLevel)
: Personnel(cName, cBirthday, cAddress, cPhone, cEmail)
{
bookTree = NULL;
setLevel(gradeLevel);
}
};
Book* searchBookTree(Book *bookNode, char *title) {
if ((strcmp(title, bookNode->title)) < 0) //***LINE 188
// What happens if bookNode->left == NULL ???
return searchBookTree(bookNode->left, title);
else if ((strcmp(title, bookNode->title)) > 0)
// What happens if bookNode->right== NULL ???
return searchBookTree(bookNode->right, title);
else
return bookNode;
}
you'll need a termination point in your search function. At the top, I'd first check if bookNode == NULL.
Your recursive search an important termination test missing! At some point, you hit the bottom of the tree without finding the item. And so your search function is called with a null pointer for the tree node! The problem is not in strcmp, but in the null pointer in one of the argument expressions.
You have only considered the case when the item exists in the tree and is eventually found, neglecting the not-found case.
Programmers are not to be measured by their ingenuity and their logic but by the completeness of their case analysis.
Alan J. Perlis, Epigram #32
Your insert routine has problems. I suggest you make your searchBookTree just return a null pointer when it doesn't find anything. Do not use that routine in the implementation of insertBook. Rather, you can write insertBook recursively also:
private:
// Inserts bookNode into tree, returning new tree:
Book *insertBookHelper(Book *tree, Book *bookNode) {
if (tree == NULL)
return bookNode; // bookNode becomes new tree
// no need to call strcmp twice!!!
int cmp = strcmp(title, bookNode->title);
if (cmp < 0) {
tree->left = insertBookHelper(tree->left, bookNode->title);
else if (cmp > 0)
tree->right = insertBookHelper(tree->right, bookNode->title);
else {
// Uh oh! Tree already contains that title, what to do?
// Answer: update!
// I don't know how to write this because I don't know
// how your Book class handles the memory for the strings,
// and what other members it has besides the title.
// this could be a possibility:
// bookNode->left = tree->left; // install same child pointers
// bookNode->right = tree->right; // into bookNode.
// *tree = *bookNode; // if Book has a sane copy constructor!!!
}
return tree;
}
public:
void insertBook(Book *node) {
tree = insertBookHelper(tree, node);
}
Do you see how the recursion works? It's a little different from the pure search. Each recursive level handles the insertion into the subtree and returns the new subtree. Often, the returned tree is exactly the same as the tree that went in! But when inserting into an empty tree, the returned tree is not the same: the tree that went in is a null pointer, but a non-null pointer comes out. This trick of pretending that we are making a new tree and returning it as a replacement for the old tree makes for smooth code.