Chapter 12, Continued Objectives: 1. Trees 2. Stacks Trees: Linked list, stacks, and queues are linear data structures. A tree is a nonlinear, two dimensional data structure with special properties. Tree nodes contain two or more links. The most common type of tree is a binary-tree, which uses exactly two nodes, a right node and a left node. The root node is the first node in a tree. Each link in the root node refers to a child. The left child is the first node in the left sub-tree, and the right child is the first node in the right sub-tree. The children of the nodes are called the children. Computer trees are usually drawn from the top down, exactly the opposite of real trees. We will discuss binary-search trees. A binary search tree has the characteristic that the values in the left sub-tree are less than the value in its parent node, and the values in any right sub-tree are greater than the value in its parent node. Binary trees have two main functions, insert and delete. We will add two additional ones, one for searching and one for displaying the list. Binary search trees have all of the benefits of a binary search against an array and the dynamic memory ability of a linked list, making it the best of both worlds. Inserting a node: If ptrTree is NULL, create a new node. Call malloc assign the allocated memory to *ptrTree. Memcpy the structure data. Set the right and left pointers of the new node to NULL. Else If ptrTree is NOT NULL If the name to insert is less than the name ptrTree Recursively call Insert with the address of ptrTree->Left Else Recursively call Insert with the address of ptrTree->Right End if End if 1 of 13 C-Code: typedef struct { char lname[41]; /* 40 spaces for last name plus the \0 string terminator. */ char fname[41]; /* 40 spaces for first name plus the \0 string terminator. */ char phone[15]; /*15 spaces for phone # plus the \0 string terminator */ } PERSON; typedef struct lnkPerson { struct lnkPerson ptrLeft; PERSON demographics; struct lnkPerson ptrRight; } lnkPERSON; /* Right Child */ /* Left Child */ * Notice our link structure now has two pointers, one for the left and one for the right. void Insert (PERSON *ptrPerson, lnkPERSON **lnkPerson) { lnkPERSON *lnkTemp, *lnkPrior, *lnkTop = *lnkPerson; if (*lnkPerson == NULL) { *lnkPerson = (lnkPERSON *) malloc (sizeof (lnkPERSON)); memcpy (&((*lnkPerson)->demographics), ptrPerson, sizeof (PERSON)); (*lnkPerson)->ptrLeft = NULL; (*lnkPerson)->ptrRight = NULL; return; } if (strcmp (ptrPerson->lname, (*lnkPerson)->demographics.lname) < 0) Insert (ptrPerson, &((*lnkPerson)->ptrLeft)); else Insert (ptrPerson, &((*lnkPerson)->ptrRight)); return; } 2 of 13 Deleting a Node: Removing a node is by far the most difficult of the task because you have to reconnect the remaining sub-trees to form a valid tree. Before attempting to program this task, it’s a good idea to develop a visual picture of what is to be done. The simplest case is where the node to be deleted has no children. Such a node is called a leaf node. All that has to be done in this case is to reset a pointer in the parent node to NULL and to use the free() function to reclaim the memory used by the deleted node. Next in complexity is deleting a node with one chile. Deleting the node leaves the child sub-tree separated from the rest of the tree. To fix this, the address of the child subtree needs to be stored in the parent node at the location formerly occupied by the address of the deleted node. Finally, you have deleting a node with two subtrees. One subtree, say the left can be attached to where the deleted node was formerly attached. But where should the remaining subtree go? Keep in mind the basic design of a tree. Every item in a left 3 of 13 subtree precedes the item in the parent node. This means that every item in the right subtrees comes after every item in the left subtree. Also, because the right subtree once was part of the subtree headed by the deleted node, every item in the right subtree comes before the parent node of the deleted node. Imagine coming down the tree looking for where to place the head of the right sub-tree. It comes before the parent node, so you haveto go down the left sub-tree from there. However it comes after every item in the left sub-tree, so you have to take the right branch of the left sub-tree and see whether it has an opening for a new node. If not, you must go down the right side of the left subtree until you do find an opening. Now we are ready to begin planning the necessary functions, separating the job into two tasks. One is associating a particular item with the node to be delted, and the second is actually deleting the node. One point to note is that all the cases involve modifying a pointer in the parent node. Which has two important consequences. 1. The program has to identify the node to be deleted. 2. To modify the pointer, the code must pass the address of that pointer to the deleting function. The first function will act much like our find function, except that because we will modify the pointer address, we must receive the address of the pointer. Mean while the pointer to be modified is itself of type Node* or pointer to Node. Because the function argument is the address of the pointer, the argument will be of type Node** or pointer-topointer-to-Node. Assuming you have the proper address available, you can write the deletion function as the following. C-Code: void DeleteItem (char strLname[], lnkPERSON **lnkPerson) { int intCompare; if (lnkPerson != NULL) { intCompare = strcmp (strLname, (*lnkPerson)->demographics.lname); printf ("comparing %s to %s, result is %d\n", strLname, (*lnkPerson)->demographics.lname, intCompare); if (intCompare == 0) Delete (lnkPerson); else if (intCompare < 0) DeleteItem (strLname, &((*lnkPerson)->ptrLeft)); 4 of 13 else DeleteItem (strLname, &((*lnkPerson)->ptrRight)); } else printf ("Find is NULL\n"); } void Delete (lnkPERSON **lnkPerson) { lnkPERSON *lnkTemp; if ((*lnkPerson)->ptrLeft == NULL) { lnkTemp = *lnkPerson; *lnkPerson = (*lnkPerson)->ptrRight; free (lnkTemp); } else if ((*lnkPerson)->ptrRight == NULL) { lnkTemp = *lnkPerson; *lnkPerson = (*lnkPerson)->ptrLeft; free (lnkTemp); } else /* deleted node has two children */ { /* Find where to reattach right subtree */ for (lnkTemp = (*lnkPerson)->ptrLeft; lnkTemp->ptrRight != NULL; lnkTemp = lnkTemp->ptrRight) continue; lnkTemp->ptrRight = (*lnkPerson)->ptrRight; lnkTemp = *lnkPerson; *lnkPerson = (*lnkPerson)->ptrLeft; free (lnkTemp); } } 5 of 13 The Find Function: The find function will traverse the binary tree to find the person we want. Binary Search Trees are very fast, similar to a binary search on an array. On average it will take only 20 hits to find a record in a one million node tree. If lnkPerson IS NULL Then The list either empty or we reached the end of the list without finding match. Return NULL Else If Person we are searching for matches the person at this node Then Return the address of this node. Else If Person we are search for is less than person at this node Then Recursively call Find with the Left node. Else If Person we are search for is less than person at this node Then Recursively call Find with the Right node. End If End If C-Code: lnkPERSON* Find (char strLname[], lnkPERSON *lnkPerson) { int intCompare; if (lnkPerson != NULL) { intCompare = strcmp (strLname, lnkPerson->demographics.lname); printf ("comparing %s to %s, result is %d\n", strLname, lnkPerson->demographics.lname, intCompare); if (intCompare == 0) return lnkPerson; else if (intCompare < 0) Find (strLname, lnkPerson->ptrLeft); else Find (strLname, lnkPerson->ptrRight); } else printf ("Find is NULL\n"); } 6 of 13 Displaying the list in order: Traversing a tree is more involved than traversing a linked list because each node has two branches to follow. This branching nature makes divide and conquer recursion a natural choice for handling the problem. At each node, the function should to the following Process the left sub-tree with a recursive call. Process the item in the node. Process the right sub-tree with a recursive call. C-Code: void Display (lnkPERSON *lnkPerson) { if (lnkPerson != NULL) { Display (lnkPerson->ptrLeft); printf("[%s %s Phone is %s]\n", lnkPerson->demographics.fname, lnkPerson->demographics.lname, lnkPerson->demographics.phone); Display (lnkPerson->ptrRight); } return; } Stacks: A stack is a constrained version of a linked list. New nodes can be added to a stack and removed from a stack only at the top. For this reason, a stacked is referred to as a last-in, first-out (LIFO) data structure. A stack is referenced via a pointer to the top element of the stack. The link member in the last node of the stack is set to NULL to indicate the bottom of the stack. The primary functions used to manipulate a stack are push and pop. Functions push creates a new node and places it on top of the stack. Function pop removes a node from the top of the stack, frees the memory that was allocated to the popped node and returns the popped value. Lab Assignment: Redo the link list lab, this time using the binary tree example below. 7 of 13 Full Source Code for Binary Tree Example: /* lab chapter 14b */ #include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct { char lname[41]; /* 40 spaces for last name plus the \0 string terminator. */ char fname[41]; /* 40 spaces for first name plus the \0 string terminator. */ char phone[15]; /*15 spaces for phone # plus the \0 string terminator */ } PERSON; typedef struct lnkPerson { struct lnkPerson *ptrLeft; PERSON demographics; struct lnkPerson *ptrRight; } lnkPERSON; void Insert (PERSON *ptrPerson, lnkPERSON **lnkPerson); lnkPERSON* Find (char strLname[], lnkPERSON *lnkPerson); void Display (lnkPERSON *lnkPerson); void Delete (lnkPERSON **lnkPerson); void DeleteItem (char strLname[], lnkPERSON **lnkPerson); int main (void) { PERSON MyFriend; lnkPERSON *ptrCurrent = NULL, *ptrTop = NULL; int iCounter = 0, intChoice; FILE *stream; if ((stream = fopen("myfriends.dat", "r")) == NULL) { fprintf(stderr, "Cannot open output file.\n"); return 1; } while (fread (&MyFriend, sizeof(PERSON), 1, stream) != NULL) 8 of 13 Insert (&MyFriend, &ptrTop); do { printf ("1. Display List\n" "2. Insert Into List\n" "3. Find from list\n" "4. Delete from list\n" "5. Quit\n\n" "Enter Choice:"); scanf ("%d", &intChoice); fflush (stdin); switch (intChoice) { case 1: Display (ptrTop); break; case 2: printf ("\nEnter Last Name: "); gets (MyFriend.lname); printf ("\nEnter First Name: "); gets (MyFriend.fname); printf ("\nEnter Phone #: "); gets (MyFriend.phone); Insert (&MyFriend, &ptrTop); break; case 3: printf ("\nEnter Last Name: "); gets (MyFriend.lname); if (strlen(MyFriend.lname) > 0) { ptrCurrent = Find (MyFriend.lname, ptrTop); if (ptrCurrent != NULL) printf ("%s %s is %s\n", ptrCurrent->demographics.fname, ptrCurrent->demographics.lname, ptrCurrent->demographics.phone); } break; case 4: printf ("\nEnter Last Name: "); 9 of 13 gets (MyFriend.lname); if (strlen(MyFriend.lname) > 0) DeleteItem (MyFriend.lname, &ptrTop); break; } } while (intChoice != 5); fclose (stream); return 0; } void Display (lnkPERSON *lnkPerson) { if (lnkPerson != NULL) { Display (lnkPerson->ptrLeft); printf("[%s %s Phone is %s]\n", lnkPerson->demographics.fname, lnkPerson->demographics.lname, lnkPerson->demographics.phone); Display (lnkPerson->ptrRight); } return; } void Insert (PERSON *ptrPerson, lnkPERSON **lnkPerson) { lnkPERSON *lnkTop = *lnkPerson; if (*lnkPerson == NULL) { *lnkPerson = (lnkPERSON *) malloc (sizeof (lnkPERSON)); memcpy (&((*lnkPerson)->demographics), ptrPerson, sizeof (PERSON)); (*lnkPerson)->ptrLeft = NULL; (*lnkPerson)->ptrRight = NULL; return; } 10 of 13 if (strcmp (ptrPerson->lname, (*lnkPerson)->demographics.lname) < 0) Insert (ptrPerson, &((*lnkPerson)->ptrLeft)); else Insert (ptrPerson, &((*lnkPerson)->ptrRight)); return; } void DeleteItem (char strLname[], lnkPERSON **lnkPerson) { int intCompare; if (lnkPerson != NULL) { intCompare = strcmp (strLname, (*lnkPerson)->demographics.lname); printf ("comparing %s to %s, result is %d\n", strLname, (*lnkPerson)->demographics.lname, intCompare); if (intCompare == 0) Delete (lnkPerson); else if (intCompare < 0) DeleteItem (strLname, &((*lnkPerson)->ptrLeft)); else DeleteItem (strLname, &((*lnkPerson)->ptrRight)); } else printf ("Find is NULL\n"); } void Delete (lnkPERSON **lnkPerson) { lnkPERSON *lnkTemp; if ((*lnkPerson)->ptrLeft == NULL) { lnkTemp = *lnkPerson; *lnkPerson = (*lnkPerson)->ptrRight; free (lnkTemp); } 11 of 13 else if ((*lnkPerson)->ptrRight == NULL) { lnkTemp = *lnkPerson; *lnkPerson = (*lnkPerson)->ptrLeft; free (lnkTemp); } else /* deleted node has two children */ { /* Find where to reattach right subtree */ for (lnkTemp = (*lnkPerson)->ptrLeft; lnkTemp->ptrRight != NULL; lnkTemp = lnkTemp->ptrRight) continue; lnkTemp->ptrRight = (*lnkPerson)->ptrRight; lnkTemp = *lnkPerson; *lnkPerson = (*lnkPerson)->ptrLeft; free (lnkTemp); } } lnkPERSON* Find (char strLname[], lnkPERSON *lnkPerson) { int intCompare; if (lnkPerson != NULL) { intCompare = strcmp (strLname, lnkPerson->demographics.lname); printf ("comparing %s to %s, result is %d\n", strLname, lnkPerson->demographics.lname, intCompare); if (intCompare == 0) return lnkPerson; else if (intCompare < 0) Find (strLname, lnkPerson->ptrLeft); else Find (strLname, lnkPerson->ptrRight); } else printf ("Find is NULL\n"); 12 of 13 return NULL; } 13 of 13