1 cop 3540 data structures with oop chapter 8 - part 1 binary trees

36
1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

Upload: dortha-mosley

Post on 27-Dec-2015

218 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

1

COP 3540 Data Structures with OOP

Chapter 8 - Part 1Binary Trees

Page 2: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

2

Why Trees? Trees are one of the fundamental data structures. Many real-world phenomena cannot be

represented w/data structures we’ve had so far.

Think of arrays:

Easy to search, especially if ordered.

• O(log2n) performance! Great. (binary search)

Inserting; Deleting? Horrible if ordered. Must find item or place before actions.

Page 3: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

3

Why Trees?

How about Linked Lists? Inserts and deletes? Great. Take O(1) time –

the best you can get! (if inserting / deleting from one end)

Searching? Search to insert / delete/ change? Not nearly as good as O(1) or even O(log2n)!

• On average, must search n/2 items!• Process requires O(n) time. • Ordering the linked list may help, as we

must still search to find.

Page 4: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

4

Trees

First, trees in general.

Consists of nodes connected by edges.

Trees have indegree <=1, no cycles;

• Implies one ‘path’ to a node.

Trees with indegree > 1 and cycles = graphs.

• More later on graphs.

Page 5: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

5

Trees

General Tree

Edges connect nodes.Only way to get to one node is along an indicated path (edges) and these are downward.

Edges are represented in a program by ‘references;’ nodes as likely primitives or objects.

One node at top of tree called the root. Can only have one root.

Binary Trees have ‘outdegree’ <= 2 for ALL nodes.

Multi-way tree can have outdegree >= 2 for at least one node (see above).

root

edge

node

A

B

E

C D

F

H

G

Page 6: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

6

Trees

General Tree

Parent: All nodes have exactly one parent.Child: Any node may have one or more lines coming from it: children

Binary tree has at most two children emanating from a node.Leaf: node with no childrenSubtree: any node may be considered to be a root of a subtree, even leaves.Visiting: term used to indicate that a node is visited under program control – usually to process the data at that node. Merely passing over a node does not constitute a visit.Traversing: refers to visiting all nodes in a tree in a prescribed manner.Levels: start with level 0.Keys: normally what is displayed on the tree, like A, B, C, … above.

root

edge

node

A

B

E

C D

F

H

G

Page 7: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

7

Trees

Binary Tree

Binary Tree: every node has no more than two children.

A child node is called a left child or right child, but may, in turn, be the root of a subtree!

A node in a binary tree may have no children.

We normally talk in terms of binary search trees.

In theory (logical; abstraction), can exist to any number of levels; in practice (i.e. implementation), can run out of memory space.

root

edge

node

A

B

E

D

F

H

G

Page 8: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

8

How Do Binary Search Trees Work?

Need to carry out basic tree operations such as finding a node, traversing a tree (get around in the tree), adding a node, deleting a node, etc.

This is what this chapter is all about.

Page 9: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

9

Unbalanced Trees

root A

D

F

H

G

Note: tree is ‘unbalanced.’This means nodes are mostly on one side or the other.Tree may be nearly balanced until certain levelsThen, it may become quite unbalanced. Skewed.

Trees can become unbalanced due to the way they were created.Generally, they are more balanced, if randomly developed.

Greatly prefer balanced trees. Have very welcome properties

Unbalanced trees present problems in efficiently processing them.Red-Black trees address unbalanced trees (further ahead).

Page 10: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

10

Trees in Java Code

So, how do we implement binary trees in Java?

Normally, we will store the nodes at unrelated places in memory with references to children as we are accustomed to do.

Can also represent a tree in memory as an array, with children located in specific positions within this array. Will look later at this.

Let’s look at some code segments now.

Page 11: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

11

We need a class of node objects.These will contain appropriate data and up to two references to children (can have MORE, as we shall see later…)

The Node Class

class Node{

int iData; // data used as key valuedouble fData // other dataNode leftChild; // this node’s left childNode rightChild; // this node’s right child.

public void displayNode(){

// whatever…}// end display()

} // end class Node.

Page 12: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

12

Sometimes the data might be objects rather than primitives, and better simply referenced in the Node itself.

class Node{

Person p; // reference to a person object Node leftChild; // this node’s left childNode rightChild; // this node’s right child.

public void displayNode(){

// whatever…}// end display()

} // end class Node.

class Person{

int iData;double fData; …

} // end class person

Page 13: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

13

The Tree Class

Need a tree class from which a tree object can be instantiated.

Will call class Tree with one field: a Node variable that references the root.

Identical to ‘first’ and ‘last’ for linked lists…(get started)

Also, since we do not allocate the entire structure at one time (like an array), we can ONLY have a pointer to the first element or root.

Consider the basic format of a Tree class:

Page 14: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

14

class Tree {

private Node root; // only data field in Tree; but key!

public void find (int key){ // stub: not showing details of this method here}// end find()

public void insert (int id, double dd){ // stub; placeholder}// end insert()

pubic void delete (int id){ // stub}// end delete()

} // end class Tree.

Tree Class

Page 15: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

15

class TreeApp {

public static void main (String [ ] args){Tree theTree = new Tree; // make a tree. // creates an object of type Tree. (previous slide)

theTree.insert (50, 1.5); //insert three nodestheTree.insert(25, 1.7); // invoking tree methods…theTree.insert(75, 1.9); // no implementations shown yet.

Node found = theTree.find(25); // find node with key 25if (found != null) // So what does this do???

System.out.println (“Found the node with key 25”);else

System.out.pirntln(“ Could not find node with key 25”);// end main()

} // end class TreeApp.

The TreeApp Class

Page 16: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

16

Insert and delete a bit later. Start with finding a node Remember, nodes have values.

In building a binary search tree, we ‘assume’ nodes are built in an order; that is the values to the left of the root are smaller than the parent or root, while values to the right are larger.

Assume the binary search tree is already built:

Java Code for Finding a Node

Page 17: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

17

Sample Binary Tree

Note the left and right, less than, greater than relationships.

50

30 60

20 40

Page 18: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

18

public Node find (int key) { // assumes non-empty tree

Node current = root; // start at root

while (current.iData != key) // if no match{

if (key < current.iData)current = current.leftChild;// recall: current = current.next??

elsecurrent = current.rightChild;

If (current == null)return null; // not found; boundary condition

} // end whilereturn current; // returns reference to node

} // end find()

Discuss the code:

Page 19: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

19

Java Code for Inserting a Node Must find the place where to insert new node.

Follow a path to parent where we can insert child

Connect new Node to parent as left or right child

This depends on whether the new node is greater than or less than the value of the parent.

First: create a new node. Then, use similar to ‘find’ to locate new node. Ignore dupes at this time

Page 20: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

20

public void insert(int id, double dd) // we are within TreeApp.java file… { Node newNode = new Node(); // make new node newNode.iData = id; // insert data // create new node; move in its data newNode.dData = dd; if(root==null) // no node in root. root = newNode; // if true, we are done. else // root occupied { // else not root Node current = root; // start at root Current is a pointer to a node and it points to root. Node parent; // creating a reference to a parent (think singly-linked list!!! Needed ‘previous’) while(true) // (exits internally) // here is our search to find the right spot. { parent = current; if (id < current.iData) // go left? { current = current.leftChild; // But maybe there IS no left child. So: if(current == null) // means there is no left child of current {

parent.leftChild = newNode; // insert on left . Link this new node in. We are done. return; // we are done if we get here. }// end if else // go right // current > current.iData? If so, go right? { current = current.rightChild; if(current == null)

{ // if end of the line insert on right parent.rightChild = newNode; // link in and we are done again. return; // we are done if we get here. }// end if } // end else } // end while }// end else not root } // end insert()

Note: we use ‘parent’ to keep track of where we are…Parent is used to keep track of the last non-null node.

Page 21: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

21

Traversing the Tree

Traversing the tree means ‘visiting’ all the nodes is some kind of specified order.

Not particularly fast (unless you use recursion).

Three basic ways (there are several others…) Preorder traversal (NLR scan or traversal) Inorder traversal (LNR traversal) Postorder traversal (LRN traversal)

Most common is inorder.

Page 22: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

22

Inorder Traversal – Binary Tree (LNR Traversal)

Results in ascending scan based on key values.

Simplest way to traverse a tree is via recursion

Start with ‘a’ node as an argument (start with root)

Recursive routine will Call itself to traverse the node’s left subtree

Visit the node (implies do something with it!)

Call itself to traverse the node’s right subtree.

Page 23: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

23

Java Code for Traversing – Inorder Recursive…

Traversals typically take a total of three statements, if executed recursively. Here’s the LNR traversal:

private void inOrder (Node localRoot) initially called with root,

{ // as in inOrder(root);if (localRoot != null){

inOrder (localRoot.leftChild)System.out.print(“localRoot.iData + “ “);inOrder (localRoot.rightChild);

}// end if} // end inOrder()

It continues until there are no more nodes to visit. All nodes visited!

Displays value of the data via System.out.print statement.

Page 24: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

24

50

30 60

20 40

private void inOrder (node localRoot) { (initially called with root as in inOrder(root);) if (localRoot != null) { inOrder (localRoot.leftChild) System.out.print(“localRoot.iData + “ “); inOrder (localRoot.rightChild); }// end if} // end inOrder() // inOrder means priority Left!Start with 50 (root) - Check left subchild L; not nullRecursive call with 30 as root; check with left subchild L; not null;Recursive call with 20 as root; check with left subchild; L null. Visit 20 (print) N (System.out.print above…)

Recursive call with right subchild; R null – completed most recent call to the leftVisit 30 (print) N (System.out.print above…)Recursive call with right subchild (40)Recursive call with left subchild of 40; It is null LVisit 40 (print) N

Recursive call with right subchild of 40. It is null – completed another call on rightVisit 50 N (System.out.print above)Recursive call with right subtree (60) (not null)Recursive call with left subtree of N (60) It is null LVisit 60.Recursive call with right subtree of N (60) It is null Done!

Execute the algorithm: LNR Traversal (inOrder)

Page 25: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

25

Priority: L-N-R Inorder traversal (LNR) means

Left. Go to the left if at all possible; Continue going to the left as much as possible. THEN:process that node

Node Next, the root (node) N; process it

Right. Lastly, go to the right, R. But then try to go left if at all possible, then N, and process it, then Right… process the Right if there is nothing to t left. Note: the Right then becomes N.

But even when you go to the right, before you process it, you must determine if you can go to the left again, as much as possible…recursively…

Page 26: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

26

Inorder, preorder, postorder get their names from position of N in LNR, NLR, and LRN.

In traversing the tree, the first letter in the scan is the priority, second letter is second priority, and third letter is last priority.

Page 27: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

27

Pre-Order (NLR) and Post-Order (LRN) Traversals

NLR and LRN traversals. Very important.

Same statements in algorithm: Change order!!

NLR, visit first and then go left, which would be considered the node, N, of the left subtree (if present and not null).

Process this node (visit the node).

Go left. If not null, this node is now the new root, N, of another subtree. We process that…etc.

Simple priority: N – L – R and

Page 28: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

28

Can see only the order of recursive calls is changed.

inOrder (localRoot.leftChild) // LNR recursive scan inOrder (localRoot.leftChild)

System.out.print(“localRoot.iData + “ “); inOrder (localRoot.rightChild);

preOrder (localRoot.leftChild) // NLR recursive scan System.out.print(“localRoot.iData + “ “);

inOrder (localRoot.leftChild) inOrder (localRoot.rightChild);

postOrder (localRoot.leftChild) // LRN recursive scan inOrder (localRoot.leftChild) inOrder (localRoot.rightChild);

System.out.print(“localRoot.iData + “ “);

Page 29: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

29

Pre-Order and Post-order Traversals

Are specific applications for these traversals.

A binary tree (not a binary search tree) can be used to represent algebraic expressions that involve binary operators.

The root holds the operator and the other nodes hold a variable (operands) or another operator.

Each subtree is a valid algebraic expression.

Consider the following:

Page 30: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

30

*

+ C

A B

Pre-Order Traversal: (NLR)

This represents the expression (A+B)*CThis is called ‘infix’ notation – which we are used to.

For preorder traversals, NLR, we have the algorithm:visit the nodecall itself to traverse the node’s left subtreecall itself to traverse the nodes’s right subtree See the priority???

Above, preorder would be: *+ABCThis is also called ‘prefix’ notation. (sometimes called Polish prefix)Advantage: parentheses are never required.Starting with the left, the operator is applied to the next to operands: So, (A+B)*C (operator needs to operands. A+B is temp and considered an operand.

Of course, there are more advanced parse trees! (Different one in book)

Page 31: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

31

Post-Order Traversal – LRN. You can guess the execution sequence:

Call itself to traverse the node’s left subtreeCall itself to traverse the node’s right subtreeVisit the node, N

Will use the tree in the book. Priority: L R N in that order. So, ABC+*Note: you will only pop after visiting a node…

My words: Start with the root. Go left. Get A Can I go to the left. No. Can I go to the right? No. Therefore visit AGo back to previous node (root here) Have gone left. Then go right. (I’m at +)But before I visit +, any chance to go left. Yes! Go left. (I’m at B)Any more to the left? No (I’m at B). Anything to the Right? No. So visit B.Back to the node, +. Next priority is to the right or C.But before I ‘visit’ C can I go to the left? NO. Can I go to the right? NO Ergo, Visit CBack to the node, +. Have visited L and R. So N is left. Visit N, ( + ).Now recurse to it’s parent. Have gone to the left; have gone to the right. So visit that node, *. We are done.

Priority: Go left first, then right, then the node (visit) L – R – N.Note this is the postfix notation!!!!! This gives us: abc+* which is postfix notation!!

*

A +

B C

Page 32: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

32

Evaluating a Postfix Notation – postorder traversal…

So, how would you evaluate an expression in postfix notation?

Create tree using postfix notation as input. Once tree is built, look for operators.

Play with this…you will see these again.

Page 33: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

33

Enter: Pseudo Code for Iterative Scans:

Page 34: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

34

One GREAT way to approach these (as a few of you have emailed me) is to design the modules first; that is, use pseudo-code to draft out your logic,You all have progressed to the point where punching away at code is extremely frustrating and very error prone.  You can easily get to this point that you might mess up more than you fix.  So

it really is time to hone your skills to the next level and try to design the logic before trying to implement your logic in code.  

Please consider the following as an example: For inorder scan (LNR)

Inorder Scan: (LNR)            Clear stack   // since we use the stack in three separate iterative scans, clear it prior to each one.            Set current to root node             // need this to get me started into the tree.            Loop while true                        if current is not null                        {                                    Push current onto stack                                    Set current to left-child of current     // remember:  “L” is top dog.  Go to the left as // much as possible                        }  // end if                                                    //      before ‘visiting’ the node.

else                        {                                    If stack is empty           // nothing left to pop; boundary condition.                                                return                                   Pop stack and set current equal to object popped from stack                                   Visit this node               // in this app, print it out                                   Set current  to right child of current                        } // end else            End loopEnd inorder (LNR) scan.

Page 35: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

35

Pre-Order (NLR) Iterative ScanCreate a stack (or use current one, but clear it) // Stack myStack =

new Stack();// of course, Stack must have a push() pop(), isEmpty() isFull()

etc. and// these routines must adjust the stack index as expected…

Could pass the root as a parameter and pass it, as in// NLR-Iterative-Scan (Node current) or something like this…

Pre-Order NLR Iterative Scan:If current is not null // start at root. Make root current node.{

Create a node for the StackPush onto Stack // push node onto stackLoop while stack is not empty{

Pop Stack // maybe current = (Node) myStack.pop()

Visit popped object // display the node in our application

If current.right child is not null // this sequence is critical.

Push this object onto the stack // myStack.push(the object)

If current.left child is not null // note right child pushed before

left child. // Why?Push this object onto the stack

} // end loop} // end if

} // end NLR iterative preorder scan

Will leave iterative LRN scan for you…. (not as obvious…)

Page 36: 1 COP 3540 Data Structures with OOP Chapter 8 - Part 1 Binary Trees

36

Finding Maximum and Minimum Values

Pretty easy to do in a binary search tree. Do the LNR scan. When you encounter an

L with no left subtree, ‘that’ is the minimum.

Maximum? Go to the right. Same song.