COSC 600: Advanced Data structures and Algorithm Analysis Dr.Yanggon Kim Chapter 3-Lecture Notes Lists, Stacks and Queues Abstract Data Types(ADTs): An ADT is a set of objects together with a set of operations. These data types are mathematical abstractions and nowhere in ADT’s definition, there is no description of how the set of operations are implemented. Abstract Data types can be two types based on their access to the data. 1. Linear ADT: Linear ADTs can be two types again ● Direct Access Linear ADT which can be either a homogenous or a heterogenous one EX: Arrays, Sets ● Sequential Access Linear ADT EX: List(stacks-FILO , Queues-FIFO) 2. Non- Linear ADT. NOTE: When the relationships between the elements is logical then is called a linear relationship. The following are the few other examples of the ADTs. 1. Graphs which have vertices and edges. 2. Tree (it is a speciality graph and it does not have cycle). 3. Binary tree is a speciality case of Tree 4. Binary search tree is a speciality of Binary tree 5. Balanced binary search tree is a speciality of Binary Search tree. LISTS: List is a homogenous collection of elements with linear relationship between the elements. Lists can be of two types. ● ● Sorted Lists Unsorted Lists Operations that can be performed on Lists are as follows: ● Printlist() ● makeempty() ● find()/Search()/ ● Insert() ● Delete/remove() IMPLEMENTATION OF THE OPERATIONS ON LIST: Lists can be implemented in using the following data types 1. 2. Arrays Linked lists (single or double) Implementing the lists using arrays: All the operations on the list can be performed using arrays. All the capacity of the arrays is fixed , we can create the arrays with double the capacity when needed. The maximum size estimate for the array is not required in java or any other modern programming language. 1. Arrays become a good option to implement lists where the lists are build up by insertions at the high end. 2. If insertions and deletions occur throughout the list then array is not a good option. Operation Sorted Array Unsorted array Printlist() O(N) O(N) Find() O(logN) O(N) Insert() O(N) O(1) Delete() O(N) O(N) Implementing the lists using the Linked lists: Linked lists consists of series of nodes which are not necessarily adjacent in memory. Each node contains the element and a link to the next node. The last cell next reference would be null. Linked lists help in reducing the time complexity for the insert and delete operations. Operation sorted linked list unsorted linked list print() O(N) O(N) find() O(N) O(N) insert() O(1) O(1) delete() O(N)/O(1) O(N)/O(1) Note : If the previous node information is present then the time complexity for the delete option becomes O(1). STACKS(LIFO): A stack is a list where the insertions and deletions can be performed at one position, that is at the end of the list, called the top. They are Last In First Out lists. Operations performed on a Stack: 1. 2. 3. push - equivalent to insert pop - equivalent to delete top- which finds the element just inserted. In stack, only the top element is visible and that is the only element accessible to perform any operation on stack. All the stack operations are constant time operations. Since stacks are lists , they can either be implemented by arraylists or linked lists. Linked List implementation of Stacks: It uses single linked lists. 1. 2. 3. 4. 5. Push - inserts an element at the front of the list Pop- deletes an element at the front of the list top - examines the element at the front of the list IsEmpty()-returns boolean Isfull()-returns boolean. Array implementation of the stacks: Here, the operations are performed at a very fast constant time. Each stack array has theArray() and the topOfstack which is -1 for the empty stack. To insert some element into the stack, we incerement the topOfstack and then set the thearray[topOfstack]=x. To pop, we set the return value to the theArray[topOfArray] and then decrement the topOfstack. Applications of Stacks: Compilers, game theory, all machines and back-tracking[maze problem] Also converting the infix arithmetic expressions to the postfix arithmetic expressions uses stack ADT. Priorities of the operators:(decreasing order of priority) 1. 2. 3. 4. 5. 6. 7. unary operator(--,++) *,/,%,div +,==,!=,<,>,<=,>= Boolean and/OR ( Algorithm to convert an infix expression to a post fix expression: 1. 2. Initialize a stack While (error/endof the stack) ● read the token ● if the token is operand display it. ● if the token is ( push it into the stack ● if the token is ) pop and display the stack element until ( is encounered. ● if the token is an operator ❖ if the stack is empty or the token as higher priority, then pop the top stack element out , and push the token into the stack ❖ else(otehrwise) pop and display the top item. (the above 2 steps should be done repeatedly) 3. At the end of the expression, pop and display stack items until stack is empty. Example: (((A-B)-C)-D*E/F+(G-H)) this is an infix expression AB-C-DE*F/-GF-+ Post fix expression. Below figure shows how the algorithm is implemented to convert the expression from infix to post fix. STEP:1 First token is (, its an operator so , its pushed into the stack - ( ( ( ( ( ( ( * + _ ( ( ( ( ( ( ( ( .Step 2: Second token is again (, priority is compared with the elements in the stack. if its equal or less its pushed into the stack. Step 3: Third token is again (, again priority is matched and its pushed into the stack Step 4: Fourth token is an operand , so it is displayed. A Step 5: Fifth token is -, its an operator and its priority is higher than elements in stack and its pushed into the stack Step 6: token 6 is operand B, so its displayed AB Step 7: token 7 is ), now all the elements in the stack are popped out until an ( is encountered and its deleted from the stack ABStep 8: 8th token is operator -, so its priority is checked and pushed into stack Step 9: 9th token is C, its an operand and its displayed AB-C Step 10: Next token is ), so the elements in the stack are check and all are displayed untill (, is encountered. so the expression becomes AB-CStep 11: Next token is D, an operand so it is displayed AB-C-D Step 12: Next token is * , an operand so priority is compared and since its higher, push into the stack Step 13: Next token is E , its an operand so display it. AB-C-DE Step 14: Next token is /, it has same priority as *, pop the top element out and then display this operator also.. AB-C-DE* Step 15: Next token is F , its operand so display it AB-C-DE*F Step 16: Next token is +, compared to the existing elements and \ is popped out AB-C-DE*F/ Step 17: Next token is (, so it should be pushed into the stack and - is popped out. AB-C-DE*F/Step 18: next token is G , and its displayed AB-C-DE*F/-G Step 19 : Next token is - , its operator and pushed into the stack Step 20: Next token is H , its operand and displayed. AB-C-DE*F/-GH Step 21: Next token is ), so the elements in the stack are popped out until ( is encountered. AB-C-DE*F/-GH-+ When the above procedure is followed for the entire infix expression, it gets converted to postfix expression. STACKS(LIFO): With a queue, however, insertion is done at one end and deletion is done at the other end. Operations: Enqueue ………….. O(1) Dequeue ………….. O(1) IsEmpty …………... O(1) IsFull ……………….O(1) Queue Dequeue <---------------- enqueue <------------------- Enqueue : which inserts an element at the end of the list called the rear. Dequeue: which deletes the element at the start of the list called as front. Array implementation of Queues: Array vs Single Linked list Like stacks, both the linked list and array implementation gives O(1) running times for every operation. Example: For each queue data structure we keep an array “thearray” positions “front” and “back” which represents the ends of the queue number of elements in the queue “currentsize” To enqueue an element “X”, increment currentsize and back then set thearray[back]= X To dequeue an element , set the return value to thearray[front], decrement currentsize and then increment front There appears a problem with this implementation, after several enqueues the queue will be full since back is now at the last array index, the next queue would be in a non existing position. The simple solution to this problem is that whenever front or back gets to the end of array, it is wrapped around to the beginning, known as circular array implementation. Initial state 2 4 ↑ front ↑ back After enqueue(1) 1 ↑ back 2 ↑ front 4 After enqueue(3) 1 3 ↑ back 2 ↑ front 4 2 4 ↑ front 2 4 2 4 2 4 After dequeue, which returns 2 1 3 ↑ back After dequeue, which returns 4 1 3 ↑ ↑ front back After dequeue, which returns 1 1 3 ↑ back front After dequeue, which returns 3 and makes queue empty 1 3 ↑ ↑ back front How to distinguish queue is empty to queue is full? For array, use upto n-1 items Use “counter” to keep track of # number of items in a queue If counter=0 array is full If counter=1 array is empty Applications of Queues: There are many algorithms to give efficient running times. 1)Virtually every real life line is a queue. For instance, lines at ticket counters are queues, becoz it is first come first served 2)calls to large companies are generally placed on a queue when all operators are busy 3)Jobs sent to a line printer are placed on a queue