I'm fairly new to coding in c and have stumbled upon a problem. The code below is what I have so far.
The trouble I'm running into is the syntax, more specifically on how to loop through the list. I've spent the past week looking at different resources but none explained how to work with a similar struct data type. The way I've attempted to loop through the list results in an error, and I'm having trouble adding the job in the right place since the list is in increasing order - based on the reference number.
The other problem I've run into is within function 5. I'm not entirely too sure of how to return both an integer and character value through a char pointer, unless you return just the entire job_node. As of now, I have casted the value of the node to be a char.
Since I haven't coded much in c - any other suggestions on how to make the code more efficient without changing the values of the functions would be extremely helpful.
A linked list is a container of nodes, and each node has some data payload. Often each data record has a key, a value used to compare two records. In students' assignments in general the data is a simple letter or a number. In your case is a pointer to a "job post".
Below I will show you an alternative way to write his. But first...
It is important to note that the list is a container of nodes, not a list of job posts. A list is not a node, a node is not a list, and the data is not a node.
Mixing these things like you did in your program leads only to problems: you will not be able to use the list to contain other data, for example. You will have trouble changing the implementation of the list without changing the data. You will need to change the list functions if you change a field in the job post. Only problems.
example
bool job_insert(int refNum, char job_name[], struct job_list * list);
should be like
int insert_l( Job* job_post ,List* the_list ,int(*compare_function)(void*,void*));
Why?
Using the former way you can use just a pointer to Job --- in general you should use a void* so you can use anything inside the data record without the need to change the arguments list.
this way if you add a field to Job or change perhaps the reference number to a char[] to add a check-digit you do not need to change a thing here
compare_function() compare two Job and return -1 if the 1st is greater, 1 if the 2nd is greater, or 0. This way you can change the sorting criteria at any time, just writing a new 10-line function, and have records inserted by new criteria. C qsort() uses this, C++ sort() uses this, Borland Turbo Pascal used this in the 80;s. It is simple and flexible.
the function can be as simples as
int compare_reference(void* a, void* b)
{
int first = ((Job*) a)->reference_number;
int second = ((Job*) b)->reference_number;
if (first < second ) return 1;
if (first > second ) return -1;
return 0;
};
and used like this
int res = insert_l( one_job, new_jobs, compare_reference );
and if res is 0 one_job was inserted in new_jobs list at the correct position. It is also easier to read: insert record into list according to criteria.
And if at any time you need to use descending order or sort by another criteria you just need to pass a new function. This means that you can use the same List-related functions to contain any data. This is the reason to use only void pointers to the data element also...
Back to the program
This could be a test file to drive your tests:
1 engineer
2 magician
3 astronaut
4 actress
5 truck driver
6 writer
The structures
typedef struct
{
int reference_number;
char* job_name;
} Job;
typedef struct node_
{
Job* info; // pointer to data record
struct node_ *next;
struct node_ *prev;
} Node;
typedef struct
{
unsigned size; // actual size
unsigned capacity; // limit in # of nodes
Node* head;
Node* tail;
} List;
And you have a List that is a collection of Node. Each Node has a pointer info that point to a Job, that is the data record. Inside a Job you have the reference number and the job's name. reference_number is called key because it is the field used to compare Job records.
The List has no reference to Job, only to Node. Node has no data, just a pointer. So to use List to contain songs or cars or books like the next assignment in line is easy ;)
the functions
List* create_l(unsigned); // return new List
List* delete_l(List*); // erase one
int insert_l( Job*,List*,int(*)(void*,void*)); // insert based on function
List* merge_l(List*,List*,int(*)(void*,void*) ); // merge 2 lists into a 3rd
int remove_by_ref_l(int,List*); // delete a record
char* search_job_l(int,List*); // search for job description for a reference#
I believe that now you see some added convenience:
insert() receives just a pointer to a Job and to a List, and the name of a function to compare 2.
create() returns a pointer to a new List with the limit set to a max # of nodes
remove() gets a reference number and if it is found in List removes the record and returns 0
search() works the same way
I suggest you write this way. If you need help I can provide more code. I had no time today to write a full example.
Related
I am working on a shared library (aka dll) implementation that can be consumed by other C programs. As a data contract, I wish to be able to return a list of variable length from one of my methods. Say, the structure I wish to return is my_data_type, what should I put as a contract data structure?
I can think of something like this:
struct data_type_list
{
my_data_type* data_list;
int count;
};
where the consumer knows there are count elements in the list and consumer terminates the array at data_list[count-1].
Any other ideas?
It really depends on how the user of your library utilizes the returned data. I would probably go with your solution if the data is pretty much read-only (in a sense that the user doesn't want to write to it but just iterates over every element pointed to by data_type_list.data_list). If the user wants to do extensive modifications like adding to or deleting data, a linked list would be the preferred data structure to use, I think:
/* example of a node in a singly-linked list */
struct list_node {
void *data;
struct list_node *next;
}
A NULL pointer indicates the end of the list while data can be accessed using the data member of every node. You could of course add a struct list_node *prev member that points to the previous node, too (yielding a doubly-linked list).
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have a few questions on a data-structure called a hash table (also know as associative array) and how it is implemented in C.
How do you make a hash table in C?
What is a hashtable and how do you implement it?
Why would I want to use a hash table rather than an array?
NOTE:
I know this is a really broad question which will require a large answer but, I made this because I had some people asking me what it all was. so I put it on here to fully explain it and help anyone else out.
Prerequisites
For this answer I'm going to assume you know how to use pointers, structs, and have a basic understanding of the C language.
Also if you don't know. When talking about the speed of algorithms and data structures you should know the terms:
O() = (it's pronounced "Big-oh") Big-oh or O() refers to the "worst-case-scenario" runtime. Similarly, in math, it's big O notation and describes the limiting behavior of a function. If somethings O(1) that's constant time "really good". If somethings O(n) that means if the list is a million long. It is at worst going to run a million time. O() is generally the one used to determine how fast something runs because that's how fast it'll run in it's worst case.
Ω = (greek letter Omega) refers to it's best case scenario. It's not used that as much as O() so I won't go into too much detail about it. But just know that if somethings Ω(1), in it's best case scenario it'll take just one time.
Θ = (greek letter theta) is unique in that it is only used when the O() and Ω() runtime are the same. So like in the case of the recursive sorting algorithm merge sort. It's run time is Θ(n(log(n))). Which means that it's O(n(log(n))) and it's Ω(n(log(n))).
What is a Hash table?
A hash table or associative array is a popular data structure used in programming. A hash table is just a linked list (I'll get to what a linked list is later on) with a hash function. A hash function basically just takes things and puts them in different "baskets". Each "basket" is just another linked list or something else depending on how you implement it. I'll explain more details on hash tables when I show you how to implement one.
Why would I want to use a hash table rather than an array?
An array is very easy to use and simple to make, but it also has its downsides. For this example, let's say we have a program and in which we want to keep all its users in an array.
That's pretty simple. Let's just say we plan on this program having no more than 100 users and fill that array with our users
char* users[100];
// iterate over every user and "store" their name
for (int i = 0; i < userCount; i++)
{
users[i] = "New username here";
}
So that works all well and fine and really fast too. That's O(1) right there. We can access any user in constant time.
But let's now assume that our program gets really popular. It now has over 80 users. Uh-Oh! We better increase the size of that array or else we'll get a buffer overflow.
So how do we do that? Well we're gonna have to make a new array that's bigger and copy over the contents of the old array into the new array.
That's very costly and we don't want to do that. We want to think cleverly and not use a something that has a fixed size. Well we already know how to use pointers to our advantage and we can bundle information into a struct if we wanted to.
So we could create a struct to store the username and then have it point (via a pointer) to a new struct. Voila! We now have a data structure that is expandable. It's a list of bundled information that's linked together by pointers. Thus the name linked list.
Linked Lists
So let's create that linked list. First we're gonna need a struct
typedef struct node
{
char* name;
struct node* next;
}
node;
Alright so we have a string name and a... Wait a sec... I've never heard of a data type called a struct node. Well for our convenience I typedef a new "data type" called node that also happens to be our struct called node.
So now that we have our node for our list, what do we need next? Well we need to create a "root" to our list so we can traverse it (I'll explain what I mean by traverse later). So let's assign a root. (remember that node data type I typdefed earlier)
node* first = NULL;
So now that we have our root all we need to do is make a function to insert new usernames into our list.
/*
* inserts a name called buffer into
* our linked list
*/
void insert(char* buffer)
{
// try to instantiate node for number
node* newptr = malloc(sizeof(node));
if (newptr == NULL)
{
return;
}
// make a new ponter
newptr->name = buffer;
newptr->next = NULL;
// check for empty list
if (first == NULL)
{
first = newptr;
}
// check for insertion at tail
else
{
// keep track of the previous spot in list
node* predptr = first;
// because we don't know how long this list is
// we must induce a forever loop until we find the end
while (true)
{
// check if it is the end of the list
if (predptr->next == NULL)
{
// add new node to end of list
predptr->next = newptr;
// break out of forever loop
break;
}
// update pointer
predptr = predptr->next;
}
}
}
So there you go. We have a basic linked list and now we can keep adding users all we want and we don't have to worry about running out of room. But this does come with down sides. The big problem with this is that every node or "user" in our list is "anonymous". We don't know were they are at or even how many users we have with this. (of course there are ways of making this much better -- I just want to show a very basic linked list) We have to traverse the entire list to add a user because we cannot access the end directly.
It's like we are in a huge dust storm and you can't see anything and we need to get to our barn. We can't see where our barn is but we have a solution. There are people standing our there (our nodes) and they are all holding two ropes (our pointers). Each person only owns one rope but that rope is being held at the other end by someone else. Just like our struct, the rope acts as a pointer to where they are. So how do we get to our barn? (for this example the barn is the last "person" in the list). Well we have no idea how big our line of people are or where they go. In fact, all we see is a fence post with a rope tied to it. (Our root!) that fence post will never change so we can grab the post and start moving along until we see our first person. That person is holding two ropes (the post's pointer and their pointer).
So we keep traveling along the rope until we reach a new person and grab onto their rope. Eventually, we get to the end and find our barn!
So that is a linked list in a nutshell. Its benefits are that it can expand as much as you want but its runtime depends on how big the list is, namely O(n). So if there are 1 million users, it would have to run 1 million times to insert a new name! Wow that seems really wasteful just to insert 1 name.
Luckily, we are clever and can create a better solution. Why don't we, instead of having just one linked list, have a few linked lists. An array of linked lists if you will. Why don't we make an array of size 26. So we can have a unique linked list for every letter of the alphabet. Now instead of a run time of n. We can reasonably say that our new run time will be n/26. Now that won't make much of a difference at all if you have a list 1 million big. But we're just going to keep it simple for this example.
So we have an array of linked lists but how are we going to sort our users into the array. Well... why don't we make a function that decides which user should go where. This function will "hash" the users if you will into an array or "table". So let's create this "hashed" linked list. Thus the name hash table
Hash Table
As I just said, our hash table will be an array of linked lists and will be hashed by the first letter of their username. A will go to position 0, B to 1, and so on.
The struct for the this hash table will be the same as the struct for our previous linked list
typedef struct node
{
char* name;
struct node* next;
}
node;
Now just like our linked list, we need a root for our hash table
node* first[26] = {NULL};
The root will be an array the size of the alphabet and all positions in it will be initialized to NULL. (Remember: the last element in a linked list always has to point to NULL or else we wouldn't know it was the end)
Let's make a main function. It takes a username we are going to hash then insert.
int main(char* name)
{
// hash the name into a spot
int hashedValue = hash(name);
// insert the name in table with hashed value
insert(hashedValue, name);
}
So here's our hash function. It's pretty simple. All we want to do is look at the first letter in the word and give a value from 0 - 25 based on what letter it is
/*
* takes a string and hashes it into the correct bucket
*/
int hash(const char* buffer)
{
// assign a number to the first char of buffer from 0-25
return tolower(buffer[0]) - 'a';
}
So now all we need is to create our insert function. It's going to look just like our insert function before except every time we reference our root, we're going to reference it as an array.
/*
* takes a string and inserts it into a linked list at a part of the hash table
*/
void insert(int key, const char* buffer)
{
// try to instantiate node to insert word
node* newptr = malloc(sizeof(node));
if (newptr == NULL)
{
return;
}
// make a new pointer
strcpy(newptr->name, buffer);
newptr->next = NULL;
// check for empty list
if (first[key] == NULL)
{
first[key] = newptr;
}
// check for insertion at tail
else
{
node* predptr = first[key];
while (true)
{
// insert at tail
if (predptr->next == NULL)
{
predptr->next = newptr;
break;
}
// update pointer
predptr = predptr->next;
}
}
}
So that's basics of a hash table. It's pretty simple if you know how to use pointers and structs. I know that was a pretty simple example of a hash table with only an insert function, but you can make it a lot better and get more creative with your hashing function. You can also make the array as big as you want or even a use multi-dimensional array.
I'm trying to find a specific location in a linked list and then be able to access its attributes. I know how to sort through the linked list but I can not figure out how to access the name attribute of the Locations.
I define my Location * structure as (These locations are stored into the list later):
#ifndef NESW_STRUCT
#define NESW_STRUCT
typedef struct location{
char *name;
char *longer;
char *shorter;
char *tip;
char *north;
char *south;
char *east;
char *west;
char *logic;
int visited;
char *items[20];
} Location;
#endif
My instructor provides us with a module to create a linked list as well as various functions to manipulate the list. The linked list is comprised of Node * which I believe hold the Locations as well as point to the next node in the list.
typedef struct node
{
Location *loc;
struct node *next;
} Node;
So in my game loop I create a global variable 'world' that is my linked list of (I think) Locations:
Node *world;
and
extern Node* world;
In other modules that also access it.
I then run a simple while loop in my main that creates a Location structure and then joins it to the Linked list(excluded from this post), world, using join(location,world) with the following functions my instructor provided, modified by me to work with Locations rather than void objects. I don't initialize world to anything before joining the first location to it, I think I may need to, but since its a core dump and crashes either way, I can't tell if it makes a difference/is necessary:
Node *
newNode(Location *place,Node *next)
{
Node *n = malloc(sizeof(Node));
if (n == 0)
{
fprintf(stderr,"newNode: out of memory!\n");
exit(1);
}
n->loc = place;
n->next = next;
return n;
}
Node *
join(Location *s,Node *rest)
{
return newNode(s,rest);
}
This all works perfectly fine so far and I create my list successfully. However, elsewhere in my program I created a function that maps through the world list and find the location that has a matching name to whichever name I pass to the function, which, logically, works correctly . I created a temp list that is equal to 'world', and then compared the name attribute of the head of the list to the name of the location I was looking for using, strcmp, returning that location if it matches, and setting the list = to the tail of the list if it doesn't.
Head and Tail are defined here, again provided in the module from my instructor:
Location *
head(Node *items)
{
return items->loc;
}
Node *
tail(Node *items)
{
return items->next;
}
If I understand these functions correctly, using head(list) should return a Location right, not a pointer? Then I should be able to just use 'location->name' to access the name? Apparently not though...
To save time of running through all the game logic just get to the part where it needs to compare the names, I tried writing some temporary code similar to how it would be in the mapping function, to test getting a location from the list and then accessing the attributes.
The probably wrong code I'm using to try and test accessing the list is:
Location *test = 0; //creating an empty location, (not sure if it needs to be initialized to 0 before assigning the desired value but I think I remember a mention of that during class)
test = head(world); //I would like to believe this sets test equal to the location of the head of the list world, but I am fairly certain this is where my error occurs because what is getting assigned to test really isn't a location
printf("%s",test->name); //basic print of the name attribute, I know this works logically because I use it elsewhere when dealing with locations not accessed through world, however this is what causes the core dump because I think I'm trying to access a garbage value so to speak
The program compiles with no errors and successfully reads all the the locations based on a debugging print statement I added. Any help, advice, or tips are greatly welcomed. I know people hate on kids that post here because they think they are trying to get their work done for free, but this is a very small part of an immersive project and once I figure this out, the game is essentially done other than content. I'm at a minor roadblock that is a major inhibitor and have tried everything my friend and I could think of and have even just started changing random data types in the Node struct and join/newNode functions as well as the Location struct hoping to either get lucky or figure out a solution through different error messages that occurred but as you can guess, no luck.
Solution by OP.
Because each new item is joined to the front of the list, the very last Node read into (which is the first node read when going through the list because the list is filled backwards) is the null pointer.
Example: Because the join function used stores a pointer to a Location and then points to the next Node in the list, the list is reversed after reading in.
Say you want to read alpha, bravo, charlie, delta epsilon into the list and you read them in that order, the list in memory looks like this with null being the head in the list:
null<-epsilon<-delta<-charlie<-bravo<-alpha
Therefore when I was trying to print with
Node *spot = world;
Location *loc = 0;
loc = head(spot);
printf("%s",loc->name);
I was attempting the print the name for the location which was really just a null pointer and obviously doesn't exist...So a very easy fix was to set spot equal to the tail before actually working with any of the Nodes in the list.
Node *spot = world;
Location *loc = 0;
spot = tail(spot);
loc = head(spot);
printf("%s",loc->name);
I'm studying linked lists from this lesson.
The writer (and all other coders on every single tutorial) goes through creating node type pointer variables, then allocates memory to them using typecasting and malloc. It seems kinda unnecessary to me (Offourse I know I'm missing something), why can't we implement the same using this?
struct node
{
int data;
struct node *next;
};
int main()
{
struct node head;
struct node second;
struct node third;
head.data = 1;
head.next = &second;
second.data = 2;
second.next = &third;
third.data = 3;
third.next = NULL;
getchar();
return 0;
}
I've created nodes and the next pointers points towards the addresses of the next nodes...
Let's say you create a variable of type node called my_node:
struct node my_node;
You can access its members as my_node.data and my_node.next because it is not a pointer. Your code, however, will only be able to create 3 nodes. Let's say you have a loop that asks the user for a number and stores that number in the linked list, stopping only when the user types in 0. You don't know when the user will type in 0, so you have to have a way of creating variables while the program is running. "Creating a variable" at runtime is called dynamic memory allocation and is done by calling malloc, which always returns a pointer. Don't forget to free the dynamically allocated data after it is no longer needed, to do so call the free function with the pointer returned by malloc. The tutorial you mentioned is just explaining the fundamental concepts of linked lists, in an actual program you're not going to limit yourself to a fixed number of nodes but will instead make the linked list resizable depending on information you only have at runtime (unless a fixed-sized linked list is all you need).
Edit:
"Creating a variable at runtime" was just a highly simplified way of explaining the need for pointers. When you call malloc, it allocates memory on the heap and gives you an address, which you must store in a pointer.
int var = 5;
int * ptr = &var;
In this case, ptr is a variable (it was declared in all its glory) that holds the address of another variable, and so it is called a pointer. Now consider an excerpt from the tutorial you mentioned:
struct node* head = NULL;
head = (struct node*)malloc(sizeof(struct node));
In this case, the variable head will point to data allocated on the heap at runtime.
If you keep allocating nodes on the heap and assigning the returned address to the next member of the last node in the linked list, you will be able to iterate over the linked list simply by writing pointer_to_node = pointer_to_node->next. Example:
struct node * my_node = head; // my_node points to the first node in the linked list
while (true)
{
printf("%d\n", my_node->data); // print the data of the node we're iterating over
my_node = my_node->next; // advance the my_node pointer to the next node
if (my_node->next == NULL) // let's assume that the 'next' member of the last node is always set to NULL
{
printf("%d\n", my_node->data);
break;
}
}
You can, of course, insert an element into any position of the linked list, not just at the end as I mentioned above. Note though that the only node you ever have a name for is head, all the others are accessed through pointers because you can't possibly name all nodes your program will ever have a hold of.
When you declare 'struct node xyz;' in a function, it exists only so long as that function exists. If you add it to a linked list and then exit the function, that object no longer exists, but the linked list still has a reference to it. On the other hand, if you allocate it from the heap and add it to the linked list, it will still exist until it is removed from the linked list and deleted.
This mechanism allows an arbitrary number of nodes to be created at various times throughout your program and inserted into the linked list. The method you show above only allows a fixed number of specific items to be placed in the list for a short duration. You can do that, but it serves little purpose, since you could have just accessed the items directly outside the list.
Of course you can do like that. but how far ? how many nodes are you going to create ? We use linkedlists when we don't know how many entries we need when we create the list. So how can you create nodes ? How much ?
That's why we use malloc() (or new nodes).
But what if you had a file containing an unknown number of entries, and you needed to iterate over them, adding each one to the linked list? Think about how you might do that without malloc.
You would have a loop, and in each iteration you need to create a completely new "instance" of a node, different to all the other nodes. If you just had a bunch of locals, each loop iteration they would still be the same locals.
Your code and approach is correct as long as you know the number of nodes that you need in advance. In many cases, though, the number of nodes depends on user input and is not known in advance.
You definitely have to decide between C and C++, because typecasting and malloc belong in C only. Your C++ linked list code won't be doing typecasting nor using malloc precisely because it's not C code, but C++ code.
Say you are writing an application such as a text editor. The writer of the application has no idea how big a file a user in the future may want to edit.
Making the editor always use a large amount of memory is not helpful in multi-tasking environments, especially one with a large number of users.
With malloc() an editing application can take additional amounts of memory from the heap as required, with different processes using different amounts of memory, without large amounts of memory being wasted.
You can, and you can exploit this technique to create cute code like this, to use the stack as a malloc in a way:
The code below should be safe enough assuming there are no tail optimizations enabled.
#include <stdio.h>
typedef struct node_t {
struct node_t *next;
int cur;
int n;
} node_t;
void factorial(node_t *state, void (*then)(node_t *))
{
node_t tmp;
if (state->n <= 1) {
then(state);
} else {
tmp.next = state;
tmp.cur = state->n * state->cur;
tmp.n = state->n - 1;
printf("down: %x %d %d.\n", tmp);
factorial(&tmp, then);
printf("up: %x %d %d.\n", tmp);
}
}
void andThen(node_t *result)
{
while (result != (node_t *)0) {
printf("printing: %x %d %d.\n", *result);
result = result->next;
}
}
int main(int argc, char **argv)
{
node_t initial_state;
node_t *result_state;
initial_state.next = (node_t *)0;
initial_state.n = 6; // factorial of
initial_state.cur = 1; // identity for factorial
factorial(&initial_state, andThen);
}
result:
$ ./fact
down: 28ff34 6 5.
down: 28ff04 30 4.
down: 28fed4 120 3.
down: 28fea4 360 2.
down: 28fe74 720 1.
printing: 28fe74 720 1.
printing: 28fea4 360 2.
printing: 28fed4 120 3.
printing: 28ff04 30 4.
printing: 28ff34 6 5.
printing: 0 1 6.
up: 28fe74 720 1.
up: 28fea4 360 2.
up: 28fed4 120 3.
up: 28ff04 30 4.
up: 28ff34 6 5.
factorial works differently than usual because we can't return the result to caller because the caller will invalidate it with any single stack operation. a single function call will destroy the result, so instead, we must pass it to another function that will have its own frame on top of the current result, which will not invalidate the arbitrary number of stack frames it's sitting on top of that hold our nodes.
I imagine there are many ways for this to break other than tail call optimizations, but it's really elegant when it doesn't, because the links are guaranteed to be fairly cache local, since they are fairly close to each other, and there is no malloc/free needed for arbitrary sized consecutive allocations, since everything is cleaned as soon as returns happen.
Lets think you are making an Application like CHROME web browser, then you wanna create link between tabs created by user at run time which can only possible if you use Dynamic Memory Allocation.
That's why we use new, malloc() etc to apply dynamic memory allocation.
☺:).
I have to write a program that reads a .txt file into the tree and then it allows to perform specific operations with it. I'm stuck on the part where I need to sort tree by names and search by name as well, any input would be awesome.
So, my input file is in the format :
3800 Lee, Victor; 2.8
3000 Brown, Joanne; 4.0
So, my binary tree is in the format of:
typedef struct
{
int id;
char name[MAX_NAME_LEN];
float gpa;
} STUDENT;
typedef struct node
{
STUDENT* dataPtr;
struct node* left;
struct node* right;
} NODE;
typedef struct
{
int count;
int (*compare) (void* argu1, void* argu2); // Was provided by teacher, not really sure how this works
NODE* root;
} BST_TREE;
Read file and insert functions are working just fine, but I dont know how to implement search by name(string). Will strcmp work? If so, how would I use it? I have a working search function, but it's optimized to search for id(integer) and it doesn't work with strings.
Here is a part of search function:
/* ===================== _retrieve =====================
Searches tree for node containing requested key
and returns its data to the calling function.
Pre _retrieve passes tree, dataPtr, root
dataPtr is pointer to data structure
containing key to be located
Post tree searched; data pointer returned
Return Address of data in matching node
If not found, NULL returned
*/
static void* _retrieve (BST_TREE* tree,
void* dataPtr, NODE* root)
{
if (root){
if (tree->compare(dataPtr, root->dataPtr) < 0)
return _retrieve(tree, dataPtr, root->left);
else if (tree->compare(dataPtr, root->dataPtr) > 0)
return _retrieve(tree, dataPtr, root->right);
else
// Found equal key
return root;
} // if root
else
// Data not in tree
return NULL;
}// _retrieve
Also, how do I sort the BST? Especially how would I sort it by name which is string and consists of 2 parts (first and last name)? Should I sort it only by the first character? I was thinking of dropping the last name part somehow and making it easier to look only by first names since my teacher didn't really specify how she wants this done. She never told us about sorting BST by non-integer values, therefore I'm lost.
One more thing is that this tree would need to be printed by: level(queue), as indented list and by leaves only.
Example of indented printed list:
1.50
2.70
3.80
3.90
2.60
3.30
I would really appreciate any suggestions on implementing those tasks.
Thank You
No, strcmp() won't work directly, because the comparator is passed two STUDENT * values (disguised as void *. So you will have to write a comparator that unpackages the pointers and then calls strcmp():
int cmp_by_name(const void *v1, const void *v2)
{
const STUDENT *s1 = (STUDENT *)v1;
const STUDENT *s2 = (STUDENT *)v2;
return strcmp(s1->name, s2->name);
}
There will be those who say the casts are not absolutely necessary. There will be those who observe that the variables could be omitted by using slightly more complex expressions in the call to strcmp(). However, if you decide that you need to compare on GPA or ID number too, then having the local variables will be cleaner. The compiler will probably eliminate the variables anyway as a routine optimization, so doing that manually at the cost of clarity is a case of 'premature optimization'.
Because the templates you are working with don't include const in the declaration of the comparator type, you may have to omit const from the function definition line.
You don't need to sort the BST; the data is already stored in a sorted order. You can print it out in sorted order with an in-order traversal of the tree.
You simply set the compare element of the BST_TREE to cmp_by_name:
BST_TREE root = { 0, cmp_by_name, 0 };
You then call your functions with &root as the root of the tree. This is instead of the comparison function you were provided with. You should build and search the tree with a single comparison function; using different comparators at different times will cause chaos.
You don't need to zap the comma if you're using an ISO 8859 code set such as 8859-15, or if you're using Unicode. The comma sorts earlier than any letters, so these names are in order:
Lee, James
Lee, Kirk
Leeds, Shaw
Left, Right
The only time you'd have problems is if the data is not consistent:
Lee James
Lee, Brady
That's the sorted order; you'd probably want the order reversed.