You are on page 1of 18

Stacks and Queues

Two of the more common data objects found in computer algorithms are stacks and queues. Both of these objects are special cases of the more general data object, an ordered list. A stack is an ordered list in which all insertions and deletions are made at one end, called the top. A queue is an ordered list in which all insertions take place at one end, the rear, while all deletions take place at the other end, the front. Given a stack S=(a[1],a[2],.......a[n]) then we say that a[1] is the bottommost element and element a[i]) is on top of element a[i-1], 1<i<=n. When viewed as a queue with a[n] as the rear element one says that a[i+1] is behind a[i], 1<i<=n.

7/29/12

7/29/12

Adding into stack procedure add(item : items); {add item to the global stack stack; top is the current top of stack and n is its maximum size} begin if top = n then stack full; top := top+1; stack(top) := item; end: {of add} Deletion in stack Click to delete(var item : items); procedure edit Master subtitle style {remove top element from the stack stack and put it in the item} begin if top = 0 then stack empty; item := stack(top); top := top-1; end; {of delete}

Procedure delete actually combines the functions TOP and DELETE, stackfull and stackempty are procedures which are left unspecified since they will depend upon the particular application. Often a stackfull condition will signal that more storage needs to be allocated and the program re-run. Stackempty is often a meaningful condition.

7/29/12

Addition into a queue procedure addq (item : items); {add item to the queue q} begin if rear=n then queuefull else begin rear :=rear+1; q[rear]:=item; end; end;{of addq} Deletion in a queue procedure deleteq (var item : items); {delete from the front of q and put into item} begin if front = rear then queueempty else begin front := front+1 item := q[front]; end; end; {of deleteq}
7/29/12

Linked Lists Simple data structures such as arrays, sequential mappings, have the property that successive nodes of the data object are stored a fixed distance apart. These sequential storage schemes proved adequate given the functions one wished to perform (access to an arbitrary node in a table, insertion or deletion of nodes within a stack or queue). However, when a sequential mapping is used for ordered lists, operations such as insertion and deletion of arbitrary elements become expensive. For example, consider the following list of all of the three letter English words ending in AT: (BAT, CAT, EAT, FAT, HAT, JAT, LAT, MAT, OAT, PAT, RAT, SAT, TAT, VAT, WAT)

7/29/12

To make this list complete we naturally want to add the word GAT. If we are using an array to keep this list, then the insertion of GAT will require us to move elements already in the list either one location higher or lower. We must either move HAT, JAT, LAT,..., WAT or else move BAT, CAT, EAT, FAT. If we have to do many such insertions into the middle, then neither alternative is attractive because of the amount of data movement. Or suppose we decided to move the word LAT. Then again, we have to move many elements so as to maintain the sequential representation of the list. When our problem called for several ordered lists of varying sizes, sequential representation again proved to be inadequate. By storing each list in a different array of maximum size, storage may be wasted. By maintaining the lists in a single array a potentially large 7/29/12 amount of data movement is needed.

"Ordered lists" reduce the time needed for arbitrary insertion and deletion which are explained in this section. Sequential representation is achieved by using linked representations. Unlike a sequential representation where successive items of a list are located a fixed distance apart, in a linked representation these items may be placed anywhere in memory. Another way of saying this is that in a sequential representation the order of elements is the same as in the ordered list, while in a linked representation these two sequences need not be the same. To access elements in the list in the correct order, with each element we store the address or location of the next element in that list. Thus associated with each data item in a linked representation is a pointer to the next item. This pointer is often referred to as a link. In general a node is a collection of data, data(1), ... ,data(n) and links link(1), ... , link(m). Each item in a node is called a field. A field contains either a 7/29/12 data item or a link.

7/29/12

The elements of the list are stored in a one dimensional array called data. But the elements of the list no longer occur in sequential order, BAT before CAT before EAT, etc. Instead we relax this restriction and allow them to appear anywhere in the array and in any order. In order to remind us of the real order, a second array link is added. The values in this array are pointers to elements in the data array. Since the list starts at data[8] = BAT, let us set a variable f=8. link[8] has the value 3, which means it points to data[3] which contains CAT. The third element of the list is pointed at by link[3] which is EAT. By continuing in this way we can list all the words in the proper order. We recognize that we have come to the end when link has a value of zero.

It is customary to draw linked lists as an ordered sequence of nodes with links being represented by arrows. We shall use the name of the pointer variable that points to the list as the name of the entire list. Thus the list we consider is the list f. Notice that we do not explicitly put in the values of the pointers but simply draw arrows to indicate they are there. This is so that we reinforce in our own mind the facts that (i) the nodes do not actually reside in sequential locations, and that (ii) the locations of nodes may change on different runs. Therefore, when we write a program which works with lists, we almost never look for a specific address except when we test for zero. It is much more easier to make an arbitrary insertion or deletion using a linked list rather than a sequential list.
7/29/12

To insert the data item GAT between FAT and HAT the following steps are adequate: get a node which is currently unused; let its address be x; set the data field of this node to GAT; set the link field of x tp point the node after FAT which contains HAT; set the link field of the node containing FAT to x. The important thing is that when we insert GAT we do not have to move any other elements which are already in the list. We have overcome the need to move data at the expense of the storage needed for the second field, link. Now suppose we want to delete GAT from the list. All we need to do is find the element which immediately precedes GAT, which is FAT, and set link[9] to the position of HAT which is 1. Again, there is no need to move the data around. Even 7/29/12 though the link field of GAT still contains a pointer to HAT, GAT is no longer in the list.

Binary Trees A binary tree is an important type of structure which occurs very often. It is characterized by the fact that any node can have at most two branches, i.e.,there is no node with degree greater than two. For binary trees we distinguish between the subtree on the left and on the right, whereas for trees the order of the subtreewas irrelevant. Also a binary tree may have zero nodes. Thus a binary tree is really a different object than a tree. Definition: A binary tree is a finite set of nodes which is either empty or consists of a root and two disjoint binary trees called the left subtree and the right subtree. We can define the data structure binary tree as follows: structure BTREE declare CREATE( ) --> btree ISMTBT(btree,item,btree) --> boolean MAKEBT(btree,item,btree) --> btree LCHILD(btree) --> btree DATA(btree) --> item RCHILD(btree) --> btree for all p,r in btree, d in item let ISMTBT(CREATE)::=true ISMTBT(MAKEBT(p,d,r))::=false LCHILD(MAKEBT(p,d,r))::=p; LCHILD(CREATE)::=error DATA(MAKEBT(p,d,r))::d; DATA(CREATE)::=error RCHILD(MAKEBT(p,d,r))::=r; RCHILD(CREATE)::=error 7/29/12 end

This set of axioms defines only a minimal set of operations on binary trees. Other operations can usually be built in terms of these. The distinctions between a binary tree and a tree should be analyzed. First of all there is no tree having zero nodes, but there is an empty binary tree. The two binary trees below are different. The first one has an empty right subtree while the second has an empty left subtree. If these are regarded as trees, then they are the same despite the fact that they are drawn slightly differently.

7/29/12

Binary Tree Representations A full binary tree of depth k is a binary tree of depth k having pow(2,k)-1 nodes. This is the maximum number of the nodes such a binary tree can have. A very elegant sequential representation for such binary trees results from sequentially numbering the nodes, starting with nodes on level 1, then those on level 2 and so on. Nodes on any level are numbered from left to right. This numbering scheme gives us the definition of a complete binary tree. A binary tree with n nodes and a depth k is complete iff its nodes correspond to the nodes which are numbered one to n in the full binary tree of depth k. The nodes may now be stored in a one dimensional array tree, with the node numbered i being stored in tree[i]. Lemma 5.3: If a complete binary tree with n nodes (i.e., depth=[LOG2N]+1) is represented sequentially as above then for any node with index i, 1 (i) parent(i) is at [i/2] if is not equal to 1. When i=1, i is the root and has no parent. 7/29/12 (ii) lchild(i) is at 2i if 2in, then i has no left child.

Proof: We prove (ii). (iii) is an immediate consequence of (ii) and the numbering of nodes on the same level from left to right. (i) follows from (ii) and (iii). We prove (ii) by induction on i. For i=1, clearly the left child is at 2 unless 2>n in which case 1 has no left child. Now assume that for all j, 1n in which case i+1 has no left child. This representation can clearly be used for all binary trees though in most cases there will be a lot of unutilized space. For complete binary trees the representation is ideal as no space is wasted. In the worst case a skewed tree of k will require pow(2,k)1 spaces. Of these only k will be occupied. While the above representation appears to be good for complete binary trees it is wasteful for many other binary trees. In addition, the representation suffers from the general inadequacies of sequential representations. Insertion or deletion of nodes from the middle of a tree requires the movement of potentially many nodes to reflect the change in level number of these nodes. These problems can be easily overcome through the use of a linked representation. Each node will have three fields leftchild, data and rightchild and is defined in Pascal as type treepointer = ^treerecord; treerecord = record 7/29/12 leftchild : treepointer;

Binary Tree Traversal There are many operations that we often want to perform on trees. One notion that arises frequently is the idea of traversing a tree or visiting each node in the three exactly once. A full traversal produces a linear order for the information in a tree. This linear order may be familiar and useful. When traversing a binary tree we want treat each node and its subtrees in the same fashion. If we let L, D, R stand for moving left, printing the data, and moving right when at a node then there are six possible combinations of traversal: LDR, LRD, DLR, DRL, RDL, and RLD. If we adopt the convention that we traverse left before right then only three traversals remain: LDR, LRD, and DLR. To these we assign the names inorder, postorder and preorder because there is a natural correspondence between these traversals and producing the infix, postfix and prefix forms of an expression. Inorder Traversal: informally this calls for moving down the tree towards the left untilyou can go no farther. Then you "visit" the node, move one node to the right and continue again. If you cannot move to the right, go back one more node. A precise way of describing this traversal is to write it as a recursive procedure.

7/29/12

procedure inorder(currentnode:treepointer); {currentnode is a pointer to a noder in a binary tree. For full tree traversal, pass inorder the pointer to the top of the tree} begin {inorder} if currentnode <> nil then begin inorder(currentnode^.leftchild); write(currentnode^.data); inorder(currentnode^.rightchild); end end; {of inorder}

7/29/12

Recursion is an elegant device for describing this traversal. A second form of traversal is preorder: procedure preorder(currentnode:treepointer); {currentnode is a pointer to a node in a binary tree. For full tree traversal, pass preorder the ponter to the top of the tree} begin {preorder} if currentnode <> nil then begin write(currentnode^.data); preorder(currentnode^.leftchild); preorder(currentnode^.rightchild); end {of if} end; {of preorder}

7/29/12

In words we would say "visit a node, traverse left and continue again. When you cannot continue, move right and begin again or move back until you can move right and resume. At this point it should be easy to guess the next thraversal method which is called postorder: procedure postorder(currentnode:treepointer); {currentnode is a pointer to a node in a binary tree. For full tree traversal, pass postorder the pointer to the top of the tree} begin {postorder} if currentnode<> nil then begin postorder(currentnode^.leftchild); postorder(currentnode^.rightchild); write(currentnode^.data); 7/29/12 end {of if}

You might also like