Turbo Pascal for DOS Tutorial by Glenn Grotzinger Part 19: Binary Trees copyright (c) 1995-96 by Glenn Grotzinger OK. I stated no real problem that needed solving earlier, so we will start getting into some new material. This is an extension to part 18, in a small way, since it involves pointered structures similar to the linked lists, but the procedures for handling them are much different, so I am covering them in a seperate section. What is a Binary Tree? ====================== A binary tree is a set of nodes with 2 possible paths for each node, left or right. Hence, a binary tree node would look something like this: type nodeptr = ^node; node = record ourinfo: integer; left, right: nodeptr; end; Conceptually (my best low ASCII rendering, trust me!), a binary tree would look something like this. We can see from below that the possible paths of those two directions are any numerous. As with the linked lists, nil ends the path (knowing, it is probably garbled, but....). NODE _________/ \_________ / \ NODE NODE ____/ \____ ____/ \____ / \ / \ NODE nil nil NODE ____/ \____ ____/ \____ / \ / \ nil nil NODE nil ____/ \____ / \ NODE nil ____/ \____ / \ nil nil Rules of a Binary Tree ====================== Now that we see what the basic idea of a binary tree is, we will now study the rules for it. The basic rules of a binary tree is the following for any node in a binary tree commonly used for the purpose that they are used for a lot of times -- as a binary search tree or BST. Any program code in this section will make use of BSTs...: 1) values smaller than the value currently stored in a questioned node goes to the left. 2) values larger than the value currently stored in a questioned node goes the right. 3) values which are equal to other values in the tree are not placed into the tree. Let us see this by manually examining how we might build a small binary tree. Example of a Binary Tree Manual Build ===================================== Let us examine how we would build a binary tree for a set of values and how a program would traverse the tree with a very short example. 1) Let us start examining a tree build with a value set data of 27, 54, 32, 18, and 5. a) 27 is naturally the first or root node of the tree. There is no issue there. b) 54 > 27 so 54 would go to the right. Our resultant tree looks now like this: 27 \ 54 c) 32 > 27. So we would move to the right. Now we see 54 as the main node 32 < 54, so we would go to the left. So the resultant tree after this addition looks like this: 27 \ 54 / 32 d) 18 < 27. So we would move to the left. So our tree looks like this: 27 / \ 18 54 / 32 e) 5 < 27. So we would move to the left. Now we see 18 as the current node. 5 < 18. Then we would move to the left again. So the final binary tree for those 5 values looks like this: 27 / \ 18 54 / / 5 32 This is a reasonable binary tree structure. Any paths not used are set to nil in the process. We should see now how binary trees are constructed. 2) Now lets study traversal of the tree. It would depend on if we visit the actual node first or last. (I will give a detailed explanation of that last statement later.) Normally, as a standard, it's good to use the left side first. Therefore, lets look at the tree built in #1. The order the numbers would come up in is 5-18-27-32-54 on a basis that we handle or visit the head node of the tree last in moving through the binary tree. If you remember the discussion of the array binary search, we had to sort the data to accomplish it. Here it is taken care of already for us. Basic Idea of Programming for Binary Trees ========================================== You may have figured out already from our preliminary discussions, that recursion is a _major_ staple of binary tree programming (read ALWAYS use it!). Note that a binary tree can be divided up into smaller and smaller trees, hence our ability to use recursion. Any functions involved almost always ends up using some order of the following pseudocode, assuming a name of PROCEDURE for the procedure: PERFORM PROCEDURE ON LEFT SIDE OF TREE PERFORM ACTION ON ROOT NODE (the top node of the tree) PERFORM PROCEDURE ON RIGHT SIDE OF TREE We will see this idea be paramount in all the code we use. If you have noticed in your previous work about recursion, there is always a control statement present. Here, it will always be searching for the nil nodes. *ALWAYS* remember to replace the nil nodes if they come up....binary search trees are the perferred method generally, for setting up searches. program binary_tree_example; type nodeptr = ^node; node = record somedata: integer; left, right: nodeptr; end; var tree: nodeptr; afile: text; i: integer; procedure deletetree(var tree: nodeptr); { This procedure is a function designed to remove a binary tree from memory } begin { Given the background information, and other procedures as examples, you should be able to come up with this one } end; procedure inserttree(anumber: integer; var tree: nodeptr); { This procedure is a function designed to create a binary tree in memory by inserting a node in the proper position in the tree } begin if tree = nil then begin new(tree); tree^.somedata := anumber; tree^.left := nil; tree^.right := nil; end else begin if anumber > tree^.somedata then inserttree(anumber, tree^.right); if anumber < tree^.somedata then inserttree(anumber, tree^.left); end; end; procedure searchthrutree(anumber: integer; tree: nodeptr); { This procedure is designed to search through a binary tree for a particular value given in the variable anumber. } begin { Given the background information and other procedures as examples, you should be able to come up with this one. } end; procedure writeouttree(tree: nodeptr); { This procedure outputs a binary tree. } begin if tree <> nil then begin writeouttree(tree^.left); write(afile, tree^.somedata, ','); writeouttree(tree^.right); end; end; begin randomize; tree := nil; { the last statement is required for the nil markers set up via the BST procedures listed above } assign(afile, 'BTEST.TXT'); rewrite(afile); writeln(memavail, ' bytes available before tree creation.'); for i := 1 to 10 do inserttree(random(5)+1, tree); {This creates the tree. } writeln(memavail, ' bytes available after tree created.'); for i := 1 to 10 do searchthrutree(random(5)+1, tree); writeln(afile); writeln(afile); writeln(afile, 'I will now output the binary tree for the example.'); writeouttree(tree); writeln; close(afile); writeln('Now deleting tree out of memory'); deletetree(tree); writeln(memavail, ' bytes available after tree deletion.'); end. This is a whole program for working with a binary tree. I purposely left out 2 of the procedures, because the background information I gave earlier, plus pointer logic you should have learned from part 18 should allow you to logically come up with it. Do like I recommended in part 18 and draw it out. Example code is often not available for many problems, but the information _is_ there to be used. To program it is a needed skill to be able to figure out things on your own given some information. Code should not be handed out readily, IMO, unless there is no other way. Practice Programming Problem #19 ================================ One noted thing I have noticed with the binary search tree method especially, is that searches can be done very quickly with any kind of data -- working with arrays have a small way of being kludgy. Here we will see an example. You need to create a binary data file named SOMEWRDS.DAT which should contain 50 non-duplicated words, randomly defined (do not alphabetize them) out of 70 total words in the file. Allow users to type words into the keyboard, until they type the word "quit". Case-sensitivity should not matter in any usage of words. Write out to the user whether the word they typed was found in your database or not. Do not be concerned with reporting of run-time errors. Do be concerned, though, with the removal of the final binary-tree you use. Also for the delete code you write for binary trees, explain why your code does what it is supposed to do. Note ---- 1) Exercise prudent practice as programmers. In the event it was not covered, accuracy is a programmer's #1 concern, followed by efficiency in speed, memory usage, and disk usage. 2) This program should be friendly to the user, letting the user know precisely what is going on in all cases, even if a common error occurs, and cleaning up anything that may be of a problematic nature from those errors. You should have enough experience from previous parts to be able to determine what those errors are you would commonly encounter here. 3) The goal here for this program is to create a high-quality application. Final Statements for this Part ============================== I know many people may wish to someday release their creations to the public, or make programs for themselves or other's general usage. This is why I am being so vague now about describing programs, and will be from now on. Creativity and neatness is one of the major qualities that need to be developed by those who program. Also for all who program: The way to solve the problem is not always handed to you. You have to identify problems that are encountered, sit down and solve them. I estimate I'm probably 4-6 parts away from completing this whole tutorial, and 2 parts away from completing the parts of the tutorial regarding conventional Pascal programming. By now, most, if not all the tools have been presented for you to do this, and not need me to give you clues, and point you in the direction of what to do to solve the problem. Most if not all problems you will encounter in normal programming practice anywhere are normally presented in the form that this one is in -- a statement of what the program is expected to do. In short, I feel those who have been new programmers that have been following my tutorial from part 1 are about ready for the *REAL* world of programming. Next Time --------- I will cover several short random topics, which some of which may be suggested by those who e-mail me. I know I will cover hooking interrupts, calling an interrupt procedure, and linking an OBJ into Pascal code, incorporating assembler code into pascal code, and including inline statements in pascal program code. Is there any more people would like to see? E-mail me. Also, I haven't gotten any comments from anyone about how I am doing. Is anyone interested? Email ggrotz@2sprint.net. Note: I am sorry if I offended anyone by the "Final Statements for This Part" section. I am apologizing in advance if I come off too harsh.