Date post: | 17-Feb-2017 |
Category: |
Technology |
Upload: | nimmi-rashinika |
View: | 247 times |
Download: | 4 times |
1 | P a g e
Data Structures and Algorithms IN 2110
Reference:
http://coltech.vnu.edu.vn/~sonpb/DSA/Data%20Structures%20and%20Algorithms%20in%20Java,%206t
h%20Edition,%202014.pdf
Lesson 01: Introduction
Why do we need data structures?
Their impact is broad and far reaching
o Internet
o Biology
o Computers
o Computer graphics
o Security
o Physics etc
Old roots, new opportunities
To solve problems that could not otherwise be addressed
o Ex: network connectivity
For intellectual stimulation
o Great algorithms are poetry of computation
To become a proficient programmer
o To organize data
o To create more efficient computer programs
They may unlock secrets of life and of the universe
o Ex: machine learning algorithms, data mining algorithms
For fun and profit
Measurements for Efficiency of an algorithm/solution
1. Space
o Memory usage of a data structure operation should be little as possible.
2. Time
o Running time or execution time of operations of data structure should be small as
possible
Cost of an algorithm/solution (what we have to sacrifice)
2 | P a g e
1. Space (for storing each data item)
2. Time (to perform each operation)
3. Effort (for programming)
*note: each problem has constrains on available space and time.
There are two types of data structures.
1. Abstract data structures
Normally called abstract data types (ADT)
2. Concrete data structures
Generally the term ‘Data structures’ refers to concrete data structures
Watch: https://www.youtube.com/watch?v=CCq5qvmuStY
Abstract Data Structures Also called Abstract data types (ADT)
Abstract data type is a collection of data with a set of operations (such as insert, delete, search etc )
supported to manipulate the structure.
This is a mathematical/ logical model which looks at the abstract view of data.
Collection of data items
Operations supported on them
Types of parameters of the operations
This applies the concept of abstraction to design of data types.
Examples: list, set, stack, queue, priority queue, dictionary
Lists and Sets The difference between list and set: list allows duplicity
Some operations a List data type supports:
add(element)
clear()
copy():set
difference(set):set
remove(element)
intersection(set):set
isDisjoint():boolean
isSubset():boolean
3 | P a g e
Stack and Queue
Figure 1: Stack and Queue
Stack
Figure 2: Push (put into stack) and Pop (pull out from stack)
Last In First Out (LIFO)
Some operations a stack data type supports:
push(new_item: item_type)
o Adding a new item
pop():item_type
o Remove the most recently pushed item and return it
top():item_type
o Returns the last pushed item
is_empty(): Boolean
o Returns true if there are no items in the stack
is_full(): Boolean
o Returns true if stack is full
get_size(): Integer
o Returns the number of elements in the stack
4 | P a g e
Queue
Figure 3: Enqueue and Dequeue in a queue
First In First Out (FIFO)
Some operations a queue data type supports:
enqueue(new_item: item_type)
o Adding a new item
dequeue():item_type
o Removes the item from the front of the queue and return it
front(): item_type
o Returns the item at the front of the queue
is_empty(): Boolean
o Returns true if there are no items in the queue
is_full(): Boolean
o Returns true if queue is full
get_size(): integer
o Returns the number of elements in the queue
*note: Data structures such as Arrays, linked lists could be used to implement these ADTs.
Refer: https://en.wikibooks.org/wiki/Data_Structures/Stacks_and_Queues#Queues
Concrete Data structures Generally the term ‘Data structures’ refers to concrete data structures
Concrete Data structures are the implementation of abstract data structures.
Examples: arrays, linked lists, trees, heaps, hash table
From here onwards I use the term ADT to Abstract data structures and the generic term data structures
to Concrete data structures.
*note: Further Understanding of ADT and Data Structures
5 | P a g e
ADT is a logical description and data structure is concrete.
ADT is the logical picture of the data and the operations to manipulate the component element
of the data.
Data structure is the actual representation of the data during the implementation and the
algorithms to manipulate the data elements.
ADT is in logical level and data structure is in implementation level.
Example: List is what you want, so it is the ADT. You thought of using an array to implement this
in the program. So that array is the data structure.
Types of Data Structures Based on memory allocation and representation
Types Based on memory allocation:
1. Static (fixed sized) data structures
Ex: Arrays
2. Dynamic (variable sized) data structures
Ex: Linked Lists
Types based on representation:
1. Linear Data structures
Elements form a sequence
Can reach only one element directly from another
Very easy to implement, since the memory of the computer is also organized in a linear
fashion.
Ex: Arrays, Linked Lists
An arrays is a collection of data elements where each element could be identified using
an index.
A linked list is a sequence of nodes, where each node is made up of a data element and
a reference to the next node in the sequence.
2. Non-Linear Data Structures
Data items are not arranged in a sequential structure
Each element may be connected with two or more other items or elements
Removing one of the links could divide data structure into two disjoint pieces
Ex: Multi dimensional arrays, Trees, graphs
A multidimensional array is simply a collection of one-dimensional arrays.
A tree is a data structure that is made up of a set of linked nodes, which can be used to
represent a hierarchical relationship among data elements.
A graph is a data structure that is made up of a finite set of edges and vertices. Edges
represent connections or relationships among vertices that stores data elements.
6 | P a g e
Implementation of ADT using Data structures The operations such as Insertion, Deletion, Searching, and Sorting could be done using algorithms. The
actual storage of data items in computer memory has to considered, i.e data structures.
Operations:
Sorting
o Operation of arranging data in some given order such as increasing or decreasing.
Searching
o Finding the location of the record with a given key value, or finding the locations of all
records, which satisfy one or more conditions
Inserting
o Adding a new record to the structure
Traversing
o Accessing each record exactly once so that certain items in the record may be
processed.
Removing
o Removing a record from structure
Programs consist of Data structures and Algorithms. We have to use the most appropriate data
structure and the most appropriate algorithm when implementing ADTs.
Example: Someone is looking for a data structure with following properties.
1. High efficiency for searching
2. Moderate efficiency for insertion
3. No need to care deletion
Now what is the best data structure and what is the best algorithm to use with that particular data
structure? In few weeks we can learn the answer for the above problem.
*note:
When implementing ADT (such as stack, queue, bag, priority queue etc) it can be implemented using
different data structures as above mentioned in this document.
Therefore in programming we use an interface to declare ADT (ex: Stack) and different classes (ex:
ArrayStack, LinkedArray etc) that implements the ADT interface to write method bodies declared in the
interface using different data structures.
7 | P a g e
Example Code:
//Stack interface //E is used to show the type of elements in stack ex: int, String, Dog etc public interface Stack<E> { int size(); boolean isEmpty(); void push(E e); E peek(); //sometimes referred to as top() E pop(); }
//implement ADT Stack using array data structure public class ArrayStack<E> implements Stack<E>{ private int capacity; private E[] dataStack; private int top = -1; public ArrayStack(int capacity){ this.capacity=capacity; //dataStack=new E[this.capacity]; //cannot create a generic array E[] dataStack=(E[])new Object[this.capacity];
} public int size(){ //code goes here } public boolean isEmpty(){ //code goes here } public void push(E e){ //code goes here } public E peek(){ //code goes here } public E pop(){ //code goes here } }
The above code segment implements Stack using arrays.
The following code segment implements Stack using linked lists.
This is the way how we serve different clients requesting for same ADT with different requirements, i.e
Arrays or linked lists or etc.
8 | P a g e
//implement ADT Stack using linked list data structure //class SinglyLinkedStack library can be found in net public class LinkedStack<E> implements Stack<E>{ private SinglyLinkedList<E> list = new SinglyLinkedList<>(); public int size(){ //code goes here } public boolean isEmpty(){ //code goes here } public void push(E e){ //code goes here } public E peek(){ //code goes here } public E pop(){ //code goes here } }
It is clear that there is a separation between interface and implementation of ADTs using Data
structures.
Benefits of this separation:
Client can’t know details of implementation => Client has many implementation from which to
choose.
Implementation can’t know details of client needs => Many clients can re-use the same
implementation.
Design: creates modular reusable libraries
Performance: use optimized implementation where it matters
Terminology:
Client: program using operations defined in interface
Implementation: actual code implementing operations
Interface: description of data type, basic operations
*note that the above implementations are not the only way. There can be several ways.
We have discussed Data structures up to a certain level. Now let’s see what Algorithms are.
9 | P a g e
Algorithms Step by step recipe for performing a task within a fine period of time.
Algorithms often operate on a collection of data, which is stored in a data structure.
A problem can be solved by many algorithms.
Sorting problems can be solved using following algorithms:
Insertion sort
Bubble sort
Selection sort
Shell sort
Merge sort
Characteristics of a good algorithm:
Finiteness
o Terminates after a finite number of steps and each step should be executable in finite
amount of time
No ambiguity
o Each step of an algorithm must be precisely defined
Input
o Algorithm should have a finite number of inputs
Output
o An algorithm has one or more outputs(at least one output)
o Can be proved to produce the correct output for a given input
Effectiveness
o Steps should be simple and basic
Algorithm creation techniques
1. Flow chart
2. Pseudo code
3. Programming language
Numerical factors for measuring the goodness and effectiveness of an algorithm
1. Running time
2. Total memory usage
10 | P a g e
Lesson 02: Stacks and Queues Reference: Chapter 6, Data Structures and Algorithms in Java 6th edition by Goodrich
Stacks A collection of objects that are inserted and removed according to the Last In First Out (LIFO) principle
that can access or remove the most recently inserted object
Figure 4: Items are added (push) and removed (pop) at top of the stack
Examples:
Internet browsers store the addresses of recently visited sites on a stack.
o Each time a user visits a page,
push(address_of_the_visited_page_just_now)
o When user hits the “back” button,
pop():address_of_the_recently_visited_page
Text editors store text changes in a stack.
o “undo” mechanism use this stack
Implementing function calls (ex: revise recursive functions)
Dijkstra’s two-stack algorithm
Methods (operations) of a stack:
1. Update methods
push(new_item: item_type)
o Adding a new item
pop():item_type
o Remove the most recently pushed item and return it
o Returns null if the stack is empty
2. Accessor methods
top():item_type
o Returns the last pushed item
11 | P a g e
o Returns null if the stack is empty
isEmpty(): Boolean
o Returns true if there are no items in the stack
isFull(): Boolean
o Returns true if the stack is full
size(): Integer
o Returns the number of elements in the stack
Exercise:
Assume that initially stack is empty.
Method Return Value Stack content
push(5) (5)
push(3) (5,3)
size() 2 (5,3)
pop() 3 (5)
isEmpty() false (5)
pop() 5 ()
isEmpty() true ()
pop() null ()
push(6) (6)
push(8) (6,8)
top() / peek() 8 (6,8)
A simple Array-based Stack implementation You can directly use java.util.Stack interface without defining a new interface called Stack.
//Stack.java //Stack interface //E is used to show the type of elements in stack ex: int, String, Dog etc public interface Stack<E> { int size(); boolean isEmpty(); void push(E e); E peek(); E pop(); }
12 | P a g e
//ArrayStack.java //implement ADT Stack using array data structure public class ArrayStack<E> implements Stack<E>{ private int capacity; private E[] dataStack;
//dataStack was made private becoz you cannot reach array elements using //indexes. only the top element that you can reach.
private int top = -1; public ArrayStack(int capacity){ this.capacity=capacity; //dataStack=new E[this.capacity]; //cannot create a generic array E[] dataStack=(E[])new Object[this.capacity]; } public int size(){ return top+1; } public boolean isEmpty(){ return (top==-1); } public void push(E e){ if(size()==capacity) System.out.println("Sorry, stack is full."); else dataStack[++top] = e; } public E peek(){ //sometimes also referred to as top() return dataStack[top]; } public E pop(){ if(isEmpty()) return null; else return dataStack[top--]; } }
13 | P a g e
//ArrayStack.java public class Test { public static void main(String[] args) { ArrayStack<Integer> stack = new ArrayStack<>(10); stack.push(10); System.out.println(stack.peek()); stack.push(12); stack.push(11); System.out.println(stack.pop()); stack.push(15); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.isEmpty()); //false System.out.println(stack.pop()); System.out.println(stack.isEmpty()); //true System.out.println(stack.pop()); stack.push(23); stack.push(65); stack.push(19); stack.push(11); stack.push(10); stack.push(20); stack.push(41); stack.push(67); System.out.println(stack.size()); stack.push(11); System.out.println(stack.size()); stack.push(17); System.out.println(stack.size()); //10 stack.push(15); } }
//Output /* 10 11 15 12 false 10 true null 8 9 10 Sorry, stack is full. You cannot insert further. */
14 | P a g e
In this example, we declare the parameterized type of the stack as the Integer wrapper class. This causes
the signature of the push method to accept an Integer instance as a parameter, and for the return type
of both top and pop to be an Integer. Of course, with Java’s autoboxing and unboxing a primitive int can
be sent as a parameter to push.
Drawbacks of the above Array-based implementation:
Overflow
o Array size is fixed
o Capacity exceeds from pushing items into the stack.
o Use resizing array for array implementation.
Loitering
o Holding a reference to an object when it is no longer needed.
o This happens in java.
o We just reduce the variable ‘top’ by 1, we do not delete the element after a pop().
Therefore that item is still in the array though we don’t use. i.e. holding a reference to
an object when it is no longer needed.
public E pop(){ if(isEmpty()) return null; else return dataStack[top--]; }
o So we can set top=null at appropriate place in the code as a solution. However it is
not possible all the time.
Corrected code without loitering is as follows.
public E pop(){ if(isEmpty()){ return null; }else{ E answer = dataStack[top]; dataStack[top]=null; top--; return answer; }
}
o Our reason for returning the cell to a null reference is to assist Java’s garbage collection
mechanism, which searches memory for objects that are no longer actively referenced
and reclaims their space for future use.
If the application needs much less space than the reserved capacity, memory is wasted.
15 | P a g e
Resizing Array Implementation There are so many approaches to resizing. Below shows a one approach among them.
Push: double size of the array when array is full
Pop: halve size of the array when array is one quarter full
Your code ArrayStack.java has to be changed as follows when implementing resizing property. Let’s
implement this feature in a new java file ResizingArrayStack.java. This will give the same output
public ResizingArrayStack(){ dataStack = (E[])new Object[1]; } public void push(E e){ if(size()==dataStack.length) resize(2*dataStack.length); dataStack[++top] = e; } private void resize(int newCapacity){ E[] copy = (E[])new Object[newCapacity]; for(int i=0;i<size();i++) copy[i]=dataStack[i]; dataStack=copy; } public E peek(){ return dataStack[top]; } public E pop(){ if(isEmpty()){ return null; }else{ E answer = dataStack[top]; //this step is essential here dataStack[top]=null; //this step is not essential here if(top==dataStack.length/4) resize(dataStack.length/2); top--; //this step is essential here return answer; } }
16 | P a g e
Implementing Stack with a Linked List Linked List is also a concrete data structure, or simply a data structure.
What is a linked list?
A linked list is a linear data structure where each element is a separate object.
Each element (we will call it a node) of a list is comprising of two items,
1. The data
2. A reference to the next node
The code for a node is as follows;
private class Node<E>{ E item; Node<E> next;
}
The last node has a reference to null. The entry point into a linked list is called the head of
the list.
Figure 5: Linked List
Using java class SinglyLinkedList is very easy, but for learning purposes let’s try to implement our own
Link list.
*note
Resizing array vs Linked list:
Linked list implementation:
Every operation takes constant time in the worst case
Use extra time and space to deal with the links (have to go from item to item, linear time)
Resizing array implementation:
Every operation takes constant amortized time
Less time, Less wasted space (constant time)
17 | P a g e
//LinkedArray.java //implement ADT Stack using linked list data structure public class LinkedStack<E> implements Stack<E>{ private Node<E> first = null; //inner class private class Node<E>{ E item; Node<E> next; } public int size(){ //this is my code, there may
//be a more feasible method //than this
if(first==null){ return 0; }else{ count=1; return isNull(first); } } private int count; //my variable private int isNull(Node x){ //my method if(x.next!=null){ //there is a better method in page 33 count++; return isNull(x.next); }else return count; } public boolean isEmpty(){ return first==null; } public void push(E e){ Node oldFirst = first; first = new Node<>(); first.item = e; first.next = oldFirst; } public E peek(){ return first.item; } public E pop(){ if(isEmpty()) return null; else{ E item = first.item; first=first.next; return item; } } }
18 | P a g e
Reversing an array using a stack int[] array = {1,4,2,3,4,6,2,3,9}; LinkedStack<Integer> stack = new LinkedStack<>(); for(int i = 0;i<array.length;i++) stack.push(array[i]); for(int i = 0;i<array.length;i++) array[i]=stack.pop(); for(int i = 0;i<array.length;i++) System.out.println(array[i]);
Addition of numbers using stacks
Figure 6: addition of two numbers using stacks
Matching Parenthesis and HTML Tags This involves testing for pairs of delimiters.
19 | P a g e
Queues A collection that keeps objects in a sequence, where element access and deletion are restricted to the
first element in the queue, and element insertions is restricted to the back of the sequence. i.e. First in
First Out (FIFO) principle.
Figure 7: Items are added to the back (enqueue) and removed from font (dequeue)
Examples:
Calls to a customer service center
Wait- list at a restaurant
Web server responding to requests
Network printer responding to requests
Methods (operations) of a queue:
1. Update methods
enqueue(new_item: item_type)
o Adding a new item to the back of the queue
dequeue():item_type
o Remove the item at the front of the queue and return it
o Returns null if the stack is empty
2. Accessor methods
first():item_type
o Returns the first element
o Returns null if the stack is empty
size(): Integer
o returns the number of elements in the queue
isEmpty(): Boolean
o returns true if the queue is empty
20 | P a g e
Exercise:
Assume that initially queue is empty.
Method Return Value Queue content
enqueue(5) (5)
enqueue (3) (5,3)
size() 2 (5,3)
dequeue() 5 (3)
isEmpty() false (3)
dequeue () 3 ()
isEmpty() true ()
dequeue () null ()
enqueue (6) (6)
enqueue (8) (6,8)
first() 6 (6,8)
Four types of queues:
1. Simple queue
Insertion occurs at the rear of the queue
Deletion occurs at the front of the queue
2. Circular queue
All nodes are treated as circular such that the first node follows the last node
Here we can move first element to the back of the queue using rotate() method
3. Priority queue
Contain items that have same preset priority
When removing elements, items with the highest priority is removed first
4. Deque (Double-Ended queue )
Insertion and deletion occur at both ends i.e. front and rear of the queue
21 | P a g e
Array-Based implementation of a Queue This method is also called realization of a queue by an array. You can directly use java.util.Queue
without using a new interface.
//Queue.java public interface Queue<E> { int size(); boolean isEmpty(); void enqueue(E e); E first(); E dequeue(); }
//ArrayQueue.java public class ArrayQueue<E> implements Queue<E> { private int maxSize; //array size private E[] dataQueue; private int front; //front locator private int rear; //rear loactor private int n; //number of items public ArrayQueue(int s){ maxSize = s; dataQueue = (E[])new Object[maxSize]; front = 0; rear = -1; n = 0; } public int size(){ return n; } public boolean isEmpty(){ return n==0; } public boolean isFull(){ return (n==maxSize); } public void enqueue(E e){ if(isFull()==false){ rear=(rear+1)%maxSize; dataQueue[rear]=e; n++; }else System.out.println("Sorry you cannot add more"); } public E dequeue(){ if(isEmpty()==false){ n--; E answer = dataQueue[front]; front=(front+1)%maxSize; return answer; }else return null;
22 | P a g e
} public E first(){ return dataQueue[front]; } }
//Test.java public class Test { public static void main(String[] args) { ArrayQueue<Integer> queue = new ArrayQueue(5); queue.enqueue(5); queue.enqueue(3); System.out.println(queue.size()); System.out.println(queue.dequeue()); System.out.println(queue.isEmpty()); System.out.println(queue.dequeue()); System.out.println(queue.isEmpty()); System.out.println(queue.dequeue()); queue.enqueue(6); queue.enqueue(8); System.out.println(queue.first()); } }
//output
/* 2 5 false 3 true null 6 */
*note
There is confusion in the implementation of a simple array and a circular array. Clear it from Ms.
Supunmali.
23 | P a g e
Linked List-Based implementation of a Queue Also called realization of a queue using a linked list
//LinkedQueue.java public class LinkedQueue<E> implements Queue<E>{ private Node<E> first = null; private Node<E> last = null; //inner class private class Node<E>{ E item; Node next; } public int size() { if(first==null){ return 0; }else{ count=1; return isNull(first); } } private int count; //my variable private int isNull(Node x){ //my method if(x.next!=null){ count++; return isNull(x.next); }else return count; } public boolean isEmpty() { return first==null; } public void enqueue(E e) { if(isEmpty()){ first = new Node<E>(); first.item = e; first.next = null; last = first; }else{ Node newLast = new Node<E>(); newLast.item = e; newLast.next = null; last.next = newLast; last = newLast; } } public E first() { return first.item; }
24 | P a g e
public E dequeue() { if(isEmpty()){ return null; }else{ E item = first.item; first=first.next; return item; } } }
Double-Ended Queues (Deque) A queue where insertion and deletion could be done at both front and the back
Figure 8: Deque
Deque:
More general than both the stack and queue
Richer than both the stack and the queue ADTs
Provides a symmetrical abstraction
Examples:
When modeling any kind of real-world waiting line: entities (bits, people, cars, words, particles,
whatever) arrive with a certain frequency to the end of the line and are serviced at a different
frequency at the beginning of the line. While waiting some entities may decide to leave the
line.... etc. The point is that you need "fast access" to insert/deletes at both ends of the line,
hence a deque.
25 | P a g e
Methods (operations) of a deque:
1. Update methods
addFirst (new_item: item_type)
o Adding a new item to the front of the queue
addLast (new_item: item_type)
o Adding a new item to the back of the queue
removeFirst():item_type
o Remove the item at the front of the queue and return it
o Returns null if the queue is empty
removeLast():item_type
o Remove the item at the back of the queue and return it
o Returns null if the queue is empty
2. Accessor methods
first():item_type
o Returns the first element
o Returns null if the queue is empty
last():item_type
o Returns the last element
o Returns null if the queue is empty
size(): Integer
o returns the number of elements in the queue
isEmpty(): Boolean
o returns true if the queue is empty
Exercise:
Assume that initially Deque is empty.
Method Return Value Deque content
addLast(5) (5)
addFirst (3) (3,5)
addFirst(7) (7,3,5)
first() 7 (7,3,5)
removeLast() 5 (7,5)
size() 2 (7,5)
removeLast () 5 (7)
removeFirst() 7 ()
addFirst (6) (6)
last() 6 (6)
addFirst (8) (8,6)
isEmpty() false (8,6)
last() 6 (8,6)
26 | P a g e
Implementation of Deque Java.uitl.Deque is an interface in java that you can use directly when implementing a Deque.
//Deque.java public interface Deque<E> { int size(); boolean isEmpty(); E first(); E last(); void addFirst(E e); void addLast(E e); E removeFirst(); E removeLast(); }
Priority Queues A queue which stores the priority additionally with the items in the queue
Priority Queue:
Items are ordered by a priority value, at the insertion.
Item with the largest priority is always at the front.
Remove the element from the queue that has the highest priority and return it.
Methods (operations) of a priority queue:
1. Update methods
insert(key,value)
1. if the no: of items in the queue is maxsize, then no more items can be inserted
(queue is full), else step 2.
2. If initially there are no more elements, insert new item at first position zero (0)
index, otherwise if new item is larger than the existing one’s shift those
elements upward one by one till the larger one is found.
3. Insert item to that new location.
removeMin():[key,value]
1. if the number of items in the queue is zero then no more items to be deleted,
Quit, else step 2.
2. Remove the front element.
2. accessors
min():[key,value]
if priority queue is not empty, return a queue entry (key, value) having minimal
key, Else returns null.
size():integer
isEmpty():Boolean
27 | P a g e
Exercise:
Assume that initially Priority Queue is empty.
Method Return value Priority queue content
insert(5,A) {(5,A)}
insert(9,c) {(5,A),(9,C)}
insert(3,B) {(3,B),(5,A),(9,C)}
min() (3,B) {(3,B),(5,A),(9,C)}
removeMin() (3,B) {(5,A),(9,C)}
size() 2 {(5,A),(9,C)}
insert(7,D) {(5,A),(7,D),(9,C)}
removeMin() (5,A) {(7,D),(9,C)}
removeMin() (7,D) {(9,C)}
removeMin() (9,C) {}
removeMin() null {}
isEmpty() true {}
Task:
Assume that you have a set of numbers like 5,4,3,7,1,6.
Consider the priorities of these numbers are equivalent to number itself.
Hint: Assume 1 has higher priority than 2.
Implement a priority queue with inserting operation to illustrate the above scenario.
28 | P a g e
Lesson 03: Linked Lists A linear data structure where each element is a separate object
Issues/ limitations of arrays:
Changing size of the array requires a new array and copy all data from old sized array to new
sized array and then continue.
Data in the array are next to each other sequentially in memory, so to insert an item inside the
array; we need to shift some data.
To overcome the above limitations, we need Linked structures.
Linked data structures:
A data structure which consists of a set of data records (nodes) linked together and organized by
references (links or pointers). The link between data can also be connector.
Major differences between Array data structure and Linked data structures:
Linked data structures Array data structure
Easier to grow organically Needs to be known the size in advance, or have to re-create when it needs to grow
references are compared with equality, no need to do arithmetic operations on references (pointers)
have perform arithmetic operations on references when referring
Types of linked data structures:
Linked lists
Search trees
Expression trees
*note
Linked data structures are also key building blocks for many efficient algorithms, such as topological sort
and set union-find.
In this lesson we study about link lists.
Traversing/ link hopping/ pointer hopping:
Starting at the head and moving from one node to another node by following each node’s next
reference.
29 | P a g e
Linked list A linear collection of data elements called ‘nodes’ pointing to the next node by means of pointer
The principle benefit of a linked list over a conventional array is that the list elements can be
easily be inserted or removed without reallocation or reorganization of the entire structure
becoz data items need not be stored contiguously in memory or on disk.
The major limitation of linked lists is that inability to random access to the data due to absence
of a efficient indexing.
Node:
Composed of data and a reference
Figure 9: How Nodes are connected in a linked list
Types of linked lists:
1. Singly linked list
Figure 10: Singly linked list
o Each node is divided into two parts. i.e. INFO field(data) and LINK field (pointer)
1. The first part contains the information of the element and is called ‘INFO Field’.
2. The second part contains the address of the next node and is called ‘LINK Field’
or ‘NEXT Pointer Field’.
Implementation of singly linked list node in java:
This is implemented as a class in java, and struct in c.
class Node<E>{ E item; //data (INFO) Node<E> next; //reference to the next node (successor)
}
*note
In this lesson our interested area is the singly linked list.
30 | P a g e
2. Doubly linked list
Figure 11: Doubly linked list
o Here each node is divided into three parts:
1. The first part is ‘PREV’ part. It is previous pointer field. It contains the address of
the node which is before the current node.
2. The second part is the ‘INFO’ part. It contains the information of the element.
3. The third part is ‘NEXT’ part. It is next pointer field. It contains the address of
the node which is after the current node.
Implementation of singly linked list node in java:
This is implemented as a class, and struct in c.
class Node<E>{ E item; //data (INFO) Node<E> next; //reference successor Node<E> prev; //reference predecessor }
3. Circular linked list
Figure 12: Circular linked list
o Here the last node does not contain NULL pointer. Instead the last node contains a
pointer that has the address of first node and thus points back to the first node.
o The structure and implementation of the node is as same as the node of a singly linked
list.
31 | P a g e
Singly Linked list
Features of Singly linked list
Only one variable is used to access any node in the list.
o Ex: if the first node is ‘p’
Then the second node is ‘p.next’
The third node id ‘p.next.next’ etc
Last node can be recognized by null reference field.
Figure 13 : Inserting an element at the Head of a Singly Linked List
Figure 14 : Inserting an element at the Tail of a Singly Linked List
32 | P a g e
Figure 15 : Removing an element from the Head of the linked list
//SLLNode.java (NODE) public class SLLNode<E> { public E data; public SLLNode next; public SLLNode(E e){ this(e,null); } public SLLNode(E e,SLLNode n){ data = e; next = n; } }
//SinglyLinkedList.java public class SinglyLinkedList <E>{ private SLLNode head, tail; private int n = 0; public SinglyLinkedList(){ head = tail = null; } public boolean isEmpty(){ return head == null; } public void addToHead(E e){ head = new SLLNode(e, head);
33 | P a g e
if(tail==null){ tail = head; } n++; }
public void addToTail(E e){ if(!isEmpty()){ tail.next = new SLLNode(e); tail = tail.next; }else head = tail = new SLLNode(e); n++; } public E deleteFromHead(){ E e = (E) head.data; if(head==tail) //if only one in the list head = tail = null; else head = head.next; n--; return e; } public E deleteFromTail(){ E e = (E) tail.data; if(head==tail) //if only one in the list head = tail = null; else{ SLLNode temp; for(temp = head; temp.next!=null; temp=temp.next); tail = temp; tail.next = null; } n--; return e; } public void delete(E e){ //deletes the first object matches if(!isEmpty()){ if(head==tail && e==head.data){ head = tail = null; n--; } else if(e==head.data){ head = head.next; n--; } else{ SLLNode pred, temp; for(pred=head,temp=head.next; temp!=null && temp.data!=e; pred = pred.next, temp=temp.next
34 | P a g e
); if(temp!=null){ //if e was found pred.next = temp.next; if(temp==tail) //if e is in the last node tail = pred; n--; } } } } public int size(){ return n; } public E getHead(){ return (E) head.data; } public E getTail(){ return (E) tail.data; } } //Test.java import java.util.Scanner; public class Test { public static void main(String[] args) { SinglyLinkedList<Integer> sll = new SinglyLinkedList<>(); sll.addToHead(10); sll.addToHead(10); sll.addToHead(12); sll.addToTail(20); System.out.println(sll.getTail()); //20 System.out.println(sll.getHead()); //12 System.out.println(sll.deleteFromHead()); //12 System.out.println(sll.size()); //3 System.out.println(sll.getHead()); //10 sll.delete(10); System.out.println(sll.getHead()); //10 } }
*note
In singly linked list, deleteFromTail() and addToTail() lead to an issue scanning the entire list to stop right
in front of the tail. Solution: Doubly linked list
35 | P a g e
Doubly Linked List
Doubly linked list node consists of three fields
1. Data/ INFO field
2. Next field (reference field to successor)
3. Prev field (reference field to predecessor)
Figure 16: Doubly Linked list
Implementation:
//inserting to the tail public void addToTail(E e){ if(!isEmpty()){ tail = new DLLNode(e,null,tail); tail.prev.next = tail; }else head = tail = new DLLNode(e); n++; } //deleting from the tail public E deleteFromTail(){ E e = (E) tail.data; if(head==tail) //if only one in the list head = tail = null; else{ tail = tail.prev; tail.next = null; } n--; return e; }
*note
You can use java.util.LinkedList class. All the methods we implemented our self could be found here.
java.util.LinkedList<Integer> ll = new java.util.LinkedList<>(); ll.add(90); System.out.println(ll.getFirst()); ll.addFirst(7); System.out.println(ll.size()); System.out.println(ll.removeLast()); System.out.println(ll.size());
36 | P a g e
Usage of Doubly Linked list:
A great way to represent a deck of cards in a game.
The browser cache which allows you to hit the BACK button (a linked list of URLs)
Applications that have a Most Recently Used (MRU) list (a linked list of file names)
A stack, hash table, and binary tree can be implemented using a doubly linked list.
Undo functionality in Photoshop or Word (a linked list of state)
Circular Linked Lists A linked list, which is essentially a singularly linked list in which the next reference of the tail node is set
to refer back to the head of the list (rather than null).
Figure 17: Circular Linked list
We use this model to design and implement a new CircularlyLinkedList alss, which supports all of the
public behaviors of our SinglyLinkedlist class and one additional update method, i.e. rortate() which
moves the first element to the end of the Linked list.
Figure 18: rotate() method
(a) before the rotation; representing sequence { LAX, MSP, ATL, BOS }
(b) after the rotation; representing sequence { MSP, ATL, BOS, LAX }
37 | P a g e
Figure 19: Adding a new data item
Usage of circular linked lists:
Round Robin scheduling (how each process will come to ready queue and get chance to execute
in CPU)
Multiplayer board game (how players are getting chance to play)
Round Robin Scheduling
A round-robin scheduler could be implemented with a traditional linked list, by repeatedly performing
the following steps on linked list L.
process p = L.removeFirst( ); Give a time slice to process p; L.addLast(p);
With this new operation in circular linked list rotate(), round-robin scheduling can be efficiently
implemented by repeatedly performing the following steps on a circularly linked list C:
Give a time slice to process C.first( ); C.rotate( );
38 | P a g e
Lesson 04: Implementation of Stack and Queues using Linked Lists
//SLLNode.java public class SLLNode<E> { public E data; public SLLNode next; public SLLNode(E e){ this(e,null); } public SLLNode(E e,SLLNode n){ data = e; next = n; } }
*note sometimes we use a private inner class inside the LinkedStack or LinkedQueue to hide the
implementation of the SLLNode class.
In the following codes of implementation I have used a provate Node class instead of public SLLNode
class.
Implementation of Stack (LIFO) Elements are added to head or the first element; elements are removed from the head or the first
element.
//LinkedStack.java public class LinkedStack<E> implements Stack<E>{ private Node<E> first = null; private int n = 0; //inner class private class Node<E>{ E item; Node<E> next; } public int size(){ return n; } public boolean isEmpty(){ return first==null; }
39 | P a g e
public void push(E e){ //add to front Node oldFirst = first; first = new Node<>(); first.item = e; first.next = oldFirst; n++; } public E peek(){ return first.item; } public E pop(){ //remove from front if(isEmpty()) return null; else{ E item = first.item; first=first.next; n--; return item; } }
Implementation of Queue (FIFO) Elements are added to the rear of the linked list or to the last element; elements are removed from the
head or the first element.
//LinkedQueue.java public class LinkedQueue<E> implements Queue<E>{ private Node<E> first = null; private Node<E> last = null; private int n = 0; //inner class private class Node<E>{ E item; Node next; } public int size() { return n; } public boolean isEmpty() { return first==null; }
40 | P a g e
public void enqueue(E e) { if(isEmpty()){ first = new Node<E>(); first.item = e; first.next = null; last = first; }else{ Node newLast = new Node<E>(); newLast.item = e; newLast.next = null; last.next = newLast; last = newLast; } n++; } public E first() { return first.item; } public E dequeue() { if(isEmpty()){ return null; }else{ E item = first.item; first=first.next; n--; return item; } } }
41 | P a g e
Lesson 05: Recursion A programming technique in which a method/ function make one or more calls to itself during execution
Characteristics of a recursive function:
The method calls it self
At some point, this recursion should be terminated
o Otherwise it leads to an infinite loop
o Those points are called base cases.
Base case and the recursive case:
Let’s consider an example;
𝑛! = {1 𝑛 = 0𝑛. (𝑛 − 1)! 𝑛 ≥ 1
This definition is typical of many recursive definitions of functions.
First we have one or more base case stating that 𝑛! = 1 for 𝑛 = 0.
Second we have one or more recursive cases, which define the function in terms of
itself.
Examples of the use of recursion:
The factorial function
The Fibonacci function
English ruler structure
Binary search
o When the sequence is unsorted, the standard approach to search for a target value is to
use a loop to examine every element, until either finding the target or exhausting the
data set. This algorithm is known as linear search, or sequential search, and it runs in
O(n) time (i.e., linear time) since every element is inspected in the worst case.
File system
42 | P a g e
Example: Factorial of n
𝑛! = {1 𝑛 = 0𝑛. (𝑛 − 1)! 𝑛 ≥ 1
Implement with using a for loop:
int factorial(int n){ int fact = 1; if(n==1) return fact; else{ for(int i=1;i<=n;i++) fact *= i; return fact; } }
Implement using a recursive function:
int factorial(int n){ if(n==0) return 1; else return n*factorial(n-1); }
Execution model for factorial:
Calling the recursion function factorial(5)
5*factorial(4)
5*{4*factorial(3)}
S*{4*{3*factorial(2)}}
5*{4*{3*{2*factorial(1)}}}
Returning values after the base case
5*{4*{3*{2*1}}}
5*{4*{3*2}}
5*{4*6}
5*24
120
43 | P a g e
Example: Fibonacci Series
𝑓𝑖𝑏(𝑛) = {0 𝑛 = 01 𝑛 = 1𝑓𝑖𝑏(𝑛 − 1) + 𝑓𝑖𝑏(𝑛 − 2) 𝑛 > 1
The Fibonacci series is as follows:
0, 1, 1, 2, 3, 5, 8,…
Implementing using a for loop:
int fib(int n){ int old1 = 0; int old2 = 1; if(n==1) return old1; else if(n==2) return old2; else{ int fib = 0; for(int i=3;i<=n;i++){ fib = old1 + old2; old1 = old2; old2 = fib; } return fib; } }
Implement using a recursive function:
int fib(int n){ if (n==1) return 0; else if(n==2) return 1; else return fib(n-1)+fib(n-2); }
Invocation tree:
A tree structure which shows the invoking precedence of a recursive function
Invocation tree of Fibonacci function if n=4:
Figure 20: Invocation tree for fib(4)
44 | P a g e
Categorization of Recursive functions Recursive functions can be categorize in different ways, we examine 3 of them here.
1. Tail recursive and non tail recursive
2. Direct recursion and indirect recursion
3. Linear recursion, binary recursion and multiple recursion
Tail recursive Vs Non tail recursive A recursive function is said to be tail recursive if there is nothing to do after the methods returns expect
return value. Otherwise it is called non-tail recursive
Example for non-tail Recursive function:
factorial()
When returning back from a recursive call, there is still one pending operation, multiplication.
Therefore, factorial() is a non-tail recursive function.
Example for tail Recursive function:
void tailrec(int i){ if(i>0) System.out.println("i = "+i); tailrec(i-1); }
When returning back from athe above recursive call, there is still no pending operation,
Therefore, it is a tail recursive function.
*note that the following example is not a tail recursion.
void prog(int i){ if(i>0){ prog(i-1); System.out.println("i = "+i); prog(i-1); } }
Converting a non tail recursive function to a tail recursion
int tailFactorial(int n, int sofar){ if(n==1) return sofar; else return tailFactorial(n-1,sofar*n); }
45 | P a g e
Indirectly/ Mutually Recursion
Direct recursion:
If X() makes a recursive call to X() itself, it is called direct recursion,
void X(){ X(); }
Indirect recursion:
If recursion methods call them indirectly through calling other methods, it is called indirect
recursion. In general, indirect recursion is a circular sequence of two or more recursive calls
g()->f()->…->g()
void g(){ f(); } void f(){ g(); }
void main(){ g(); }
Linear recursive Vs Binary recursive Vs Multiple recursive
Linear Recursion:
If a recursive call starts at most one other, we call it a linear recursion.
Ex: factorial(int n), power(double x, int n)
public double power(double x, int n) { if (n == 0) return 1; else { double partial = power(x, n/2); double result = partial * partial; if (n % 2 == 1) result *= x; return result; } }
46 | P a g e
Figure 21: Recursion trace for an execution of power(2, 13)
Binary Recursion:
If a recursive call may start others, we call it a binary recursion.
Ex: fib(int n), binarySum(int[ ] data, int low, int high)
public static int binarySum(int[ ] data, int low, int high) { if (low > high) // zero elements in subarray return 0; else if (low == high) // one element in subarray return data[low]; else { int mid = (low + high) / 2; return binarySum(data, low, mid) + binarySum(data, mid+1, high); } }
Figure 22: Recursion trace for the execution of binarySum(data, 0, 7)
47 | P a g e
Multiple Recursion:
If a recursion call may start three or more others, we call it a multiple recursion.
Figure 23: Recursion trace for an execution of PuzzleSolve(3, S, U)
Exercise:
What the Tower of Hanoi algorithm?
Implement it using Java.
Refer:
http://www.tutorialspoint.com/data_structures_algorithms/tower_of_hanoi.htm
https://www.youtube.com/watch?v=MbybmBZVjWk
http://www.tutorialspoint.com/data_structures_algorithms/tower_of_hanoi_in_c.htm
Figure 24: The Tower of Hanoi
48 | P a g e
The mission is to move all the disks from source tower to the destination tower without violating the
sequence of arrangement.
Rules:
Only one disk can be moved among the towers at any given time.
Only the top disk can be removed.
No large disk can sit over a small disk.
*note Tower of Hanoi puzzle with n disks can be solved in minimum 2n-1 steps.
Algorithm:
Disks are numbered from top to bottom from I to n.
Our ultimate goal is to move disks n from source to destination and then put all the other (n-1) disks
onto it.
Step 1 – Move n-1 disks from source to aux
Step 2 – Move nth disk from source to destination
Step 3 – Move n-1 disks from aux to destination
Now we can imagine to apply the same recursive way for all given set of disks.
A recursive algorithm for tower of Hanoi can be driven as follows.
START
Procedure Hanoi (disk, source, destination, aux)
IF disk == 0, THEN
Move disk from source to destination
ELSE
Hanoi(disk-1, source, aux, destination) //step 1
Move disk from source to destination //step 2
Hanoi(disk-1, aux, destination, source) //step 3
END IF
END Procedure
STOP
49 | P a g e
Lesson 06: Trees A non linear data structure that represents nodes connected by edges. Tree provides a natural
organization data. Tree can be a ADT or a data structure depending on the implementation.
Components of a tree:
1. Nodes
2. edges
Figure 25: A tree with 17 nodes representing the organization of a fictitious corporation.
Refer:
http://www.tutorialspoint.com/data_structures_algorithms/tree_data_structure.htm
Examples where trees are used:
Organizational hierarchy of a company
Hierarchical relationship between files and directories in a computer’s file system
Components of a structured document such as a report, book etc.
Binary Tree:
A special data structure used for data storage purposes. A binary tree has a special condition
that each node can have only two children at maximum.
50 | P a g e
Terms related to Tree Data Structure
Figure 26: Terms related to Tree
Root: Element at the top of the hierarchy
Child: Node below a given node connected by its edge downward
Grandchild: Elements next in the hierarchy
Siblings: The nodes belongs to the same parent
Ancestors: Nodes situated in the path from a given node to the root node
Descendents: Nodes that are descendents of a given node
Leaf: Node which does not have any child nodes
Sub-tree: Descendents of a given node
Levels: Generation of the node
Visiting: Checking value of a node when control is on the node
Traversing: Passing through nodes in a specific order
Key: represents a value of a node based on which a search operation is to be carried out for a node
51 | P a g e
Figure 27: An Example tree
Leaves = {Mike, Ai, Sue, Chris}
Parent(Mary) = Joe
Grandparent(Sue) = Mary
Siblings(Mary) = {Ann, John}
Ancestors(Mike) = {Ann, Joe}
Descendents(Mary) = {Mark, Sue}
Node degree: Number of children a given node has
Tree degree : Maximum of node degrees
Node degrees of nodes in the Figure 27 are as follows:
NodeDegree(Joe) = 3 <= Tree degree
NodeDegree(Ann) = 2
NodeDegree(Mary) = 1
NodeDegree(John) = 1
NodeDegree(Mike) = 0
NodeDegree(Ai) = 0
NodeDegree(Mark) = 1
NodeDegree(Sue) = 0
NodeDegree(Chris) = 0
Level 3
Level 2
Level 1
Level 0 Joe
Ann
Mike Ai
Mary
Mark
Sue
John
Chris
52 | P a g e
Path: if n1, n2,…nk is a sequence of nodes in a tree such that ni is the parent of ni+1 for 1<=i<=k, then this
sequence is called a path from node n1 to nk.
Length of the path = number of nodes in the path – 1
*note Node to Node: length of path = 0
Height of a node: length of the longest path from a node to a leaf
Height of a tree: height of the root node
Depth of a node: length of the path from the root to the given node
Recursive Definition of a Tree:
A single node by itself is a tree. This node is also the root of this tree.
Let t1, t2,…tk be disjoint trees with roots r1, r2,…rk respectively, and let R be another node. We
can get a new tree by making R the parent of the nodes r1, r2,…rk.
Tree Traversal Methods of visiting (processing) each node in the tree exactly one time
Methods of traversal:
1. Breadth First
2. Depth First
a. Preorder traversal
b. Postorder traversal
c. Inorder traversal (for binary trees only)
53 | P a g e
PreOrder Traversal of a General Tree
Traverse a tree in node-left-right sequence
Figure 28: Preorder traversal of an ordered tree
Paper, Title, Abstract, $ 1, $ 1.1, $ 1.2, $ 2, $ 2.1, $ 2.2, $2.3, $ 3, $ 3.1, $3.2, References
Algorithm for PreOrder Traversal:
Procedure perOrder(root) if(root is not null) process(root) preOrder(leftSubtree) preOrder(rightSubtree) End if End Procedure
Processing order:
Figure 29: Processing Order in PreOrder Traversal
54 | P a g e
PostOrder Traversal of a General Tree
Traverse a tree in left-right-node sequence
Figure 30: Postorder traversal of the ordered tree
Title, Abstract, $ 1.1, $ 1.2, $ 1, $ 2.1, $ 2.2, $ 2.3, $ 2, $ 3.1, $ 3.2, $ 3, Reference
Algorithm for PostOrder Traversal:
Procedure PostOrder (root) if(root is not null)
PostOrder (leftSubtree) PostOrder (rightSubtree) process(root) End if End Procedure
Processing order:
Figure 31: Processing Order in PostOrder Traversal
55 | P a g e
InOrder Traversal of a Binary Tree
Traverse a tree in left- node-right- sequence
Figure 32: InOrder traversal of a binary tree
(((2 + 1) x 3)/ (9 – 5) + 2 ) – ((3 x (7 - 4) + 6)
Algorithm for inOrder Traversal:
Procedure inOrder (root) if(root is not null)
inOrder (leftSubtree) process(root)
inOrder (rightSubtree) End if End Procedure
Processing order:
Figure 33: Processing Order in inOrder Traversal
56 | P a g e
Euler Tour We can unify the tree traversal algorithms into a single framework known as a Euler tour traversal. The
Euler tour traversal of a tree T can be informally defined as a walk round T, where we start by going
from root forward its leftmost child, viewing the edge of T as being walls that we always keep to our left.
Figure 34: Euler Tour Traversal
Lets us consider an example;
Figure 35: PreOrder Traversal A, B, C, D, E, F
Figure 36: PostOrder Traversal C, D, B, F, E, A
Figure 37: InOrder Traversal C, B, D, A, E, F
57 | P a g e
Breadth First
Visit all the positions at depth d before visit the positions at depth d+1. This is a level by level approach
from layer 0 (root layer) to upwards.
Figure 38: Breadth first traversal of the ordered tree
Paper, Title, Abstract, $ 1, $ 2, $ 3, References, $ 1.1, $ 1.2, $ 2.1, $ 2.2, $ 2.3, $ 3.1, $ 3.2
58 | P a g e
Lesson 07: Binary Trees A data structure, in which a record is linked to two successor records, could be either a ADT or a data
structure
Types of Binary trees:
Full binary tree (proper binary tree or 2 - tree)
Complete binary tree
Balanced binary tree
Full binary tree:
A binary tree in which every node other than leaves has two children
Figure 39: A full binary tree
*note leaves are in blue color.
Complete binary tree
A binary tree in which every level, except possibly the last, is completely filled and all nodes are as far
left as possible. i.e. all nodes of the last layer should be filled from left to right
Figure 40: A complete binary tree
*note last level is in blue colour.
59 | P a g e
Examples:
Exercise:
Identify whether the following Binary tree is a full tree, a complete tree or a full and complete tree or
none of above mentioned.
Answer: this is not full, not complete, therefore none of above mentioned types
Balanced Binary tree
Height of the left and the right sub tress can vary by one level at most (maximum)
Figure 41: (a) non-balanced, (b) balanced
60 | P a g e
Implementation of Binary tree ADT Arrays based implementation and linked lists based implementation is discussed here.
Methods (operations) of a Binary Tree:
addRoot(value)
addLeft(position, value)
addRight(position, value)
set(position, value)
o replace existing element of the given position
attach(position, T1, T2)
o Attaches the trees of T1 and T2 as left and right sub trees of leaf position p
remove(position): value
find(value): node
Array based implementation For every position p of tree T, let f(p) be the integer defined as follows.
If p is the root of T, then f(p) = 0
If p is the left child of position q, then f(p) = 2f(q)+1
If p is the right child of position q, then f(p) = 2f(q)+2
The numbering function f is known as a level numbering of the position in a binary tree T, for it numbers
the positions on each level of T in increasing order from left to right.
*note Level numbering is based on potential positions within a tree, not actual shape of a specific tree,
so they are not necessarily consecutive.
Figure 42: Positions of the nodes when implemented using a array
61 | P a g e
Example:
*note The space usage of an array based representation depends greatly on the shape of the tree.
Linked List based implementation A natural way to realize a binary tree T is to use a linked structure, with a node that maintains
references to the elements stored at a position p and to the nodes associated with the children and
parent of p.
If p is the root element, then the parent node reference is null.
If p is a leaf node, both children references are null.
Figure 43: (a) Node structure (b) Example Binary tree
*note
62 | P a g e
root variable is a Node reference that keeps reference for the root node of the tree.
size variable is a int variable that keeps the track of the total number of nodes in the tree.
Node class of the linked structure used to implement the Binary tree ADT
class Node<E>{ E element; Node<E> parent; Node<E> leftChild; Node<E> rightChild; }
*note Sometimes in the Node class the reference to the parent node is absent.
class Node<E>{ E element; Node<E> leftChild; Node<E> rightChild; }
Binary Search Tree (BST): A BST is a binary tree in symmetric order.
Symmetric Order:
Each node contains one key (also known as data).
The keys in the left sub tree are less than the key in its parent node.
The keys in the right sub tree are greater than the key in its parent node.
Duplicate keys are not allowed.
A Binary Search Tree is where each node has a comparable key and an associated value and satisfies the
restriction that the key in any node is larger than the keys in all the nodes in that node’s left sub tree
and smaller than the keys in all nodes in that node’s right sub tree.
Other names for BST:
Ordered tree
Sorted binary tree
A node in a BST comprised of four fields:
key
value
left sub tree
right sub tree
63 | P a g e
Node Class of a Binary Search Tree:
class Node<Key extends Comparable<Key>,Value>{ Key key; Value val; //sometimes a tree do not have a value field Node leftChild; Node rightChild; Node(Key key, Value val){ this.key = key; this.val = val; } }
Figure 44: Binary Search tree
Figure 45: Order of growth of the running time for Ordered linked list and a binary tree
Binary Search:
If less, go left;
If greater, go right;
If equal, search hit
Binary Insert:
If less, go left;
If greater, go right;
If null, insert a new node, else if the key already exists reset existing value
64 | P a g e
Binary Search Tree Implementation using a Linked Structure
//This is a implementation of Map ADT using a BST. //I say this is a map since there are entries with a key and a value in each entry. public class BST <Value>{ private Node root; private class Node<Value>{ Integer key; //key is always a integer value in this example Value val; Node leftChild; Node rightChild; Node(Integer key, Value val){ this.key = key; this.val = val; } } public void put(Integer key, Value val){ root = put(root, key, val); } private Node put(Node x, Integer key, Value val){ if(x==null) return new Node(key,val); if(key<x.key) x.leftChild = put(x.leftChild,key,val); else if(key>x.key) x.rightChild = put(x.rightChild,key,val); else x.val = val; return x; } public Value get(Integer key){ Node x = root; while(x != null){ if(key<x.key) x = x.leftChild; else if(key>x.key) x = x.rightChild; else return (Value) x.val; //search hit } return null; //unsuccessful search } public void delete(Integer key){ //this is some what complex, you can try it later }
65 | P a g e
}
*note
In the above implementation of binary insertion, many BSTs correspond to same set of keys depending
on the order that we insert. If we insert keys according to an order, that leads to a worst case.
Number of compares for search/insert is equal to 1+ depth of node.
Figure 46: Different BSTs for same set of keys
public Node get(int p, int k){ if (p<0) return p; //unsuccessful search else if(k== key(p)) return p; //successful search else if (k< key(p)) //recur on left sub tree return get(left(p), k); else //recur on right sub tree if k>key(p) return get(right(p), k); }
*note
The above code segment is a recursive function for binary search.
Binary Deletion:
There are three cases that we have to pay attention.
o Case 1: Deleting a leaf node
Set null the pointer to that node
o Case 2: Deleting a node having only one child
Let the single child replace the parent
o Case 3: Deleting a node has two children
Find the leftmost node of its right sub tree (inOrder successor)
Let successor replaces node to be deleted
Or find the right most node of the left sub tree (inOrder predecessor)
Let predecessor replaces node to be deleted
66 | P a g e
AVL (Adelson, Velski & Landis) Trees A BST where the height of the left and right sub trees of each node differ by at most 1.
Balance factor = height(left sub tree) – height(right sub tree)
Accepted balance factors for a BST to become a AVL tree are -1, 0 and 1.
*note
What happens of input to binary search tree comes in sorted ascending order and descending manner?
Figure 47: Balanced not balanced not balanced
In second tree, the left sub tree of C has height 2 and right sub tree has height 0, so the difference is 2.
In third tree, the right sub tree of A has height 2 and left is missing, so it is 0, and the difference is 2
again. AVL tree permits difference balance factor to be only 1.
To make itself balanced, an AVL tree may perform 4 kinds of rotations:
1. Left rotation
2. Right rotation
3. Left – Right rotation
4. Right – Left rotation
Refer:
http://www.tutorialspoint.com/data_structures_algorithms/avl_tree_algorithm.htm
Figure 48: Left rotation
67 | P a g e
Figure 49: Right rotation
Figure 50: Example for a AVL tree
However there are many different varieties if trees and Binary trees.
Ex: tries, red black trees, etc
68 | P a g e
Lesson 08: Maps An ADT designed to store and retrieve values based upon a uniquely identifying search key for each.
Key value pairs of (k,v) are called entries where k for Key and v for Value of the entry.
Keys are required to be unique.
Maps are known as associative arrays, because the key serves somewhat like an index into the map, in
that it assists the map in efficiently locating the associated entry. Unlike a standard array, a key of a map
need not to be numeric, and it does not directly designates a position within the structure.
Common applications of maps:
A university’s information system relies on some form of a student ID as a key that is mapped to
that student’s associated record serving as the value.
The Domain-Name System (DNS) maps a host name to an IP address.
A social media site typically relies on a (nonnumeric) username as a key that can be efficiently
mapped to a particular user’s associated information.
Methods (operations) of a Map:
size():integer
isEmpty():Boolean
get(key:data_type_of_key):data_type_of_value
put(key:data_type_of_key, value: data_type_of_value):data_type_of_value
o return the existed value if there already an entry existing from that key
remove(key:data_type_of_key)
keySet():data_type_of_key[]
o Returns an iterable collection containing all the keys stored
Values():data_type_of_value[]
o Returns an iterable collection containing all the values of entries stored
entrySet():all key-value entries
o Returns an iterable collection containing all the key-value entries in Map
In java, we can use java.util.Map interface to implement a map.
69 | P a g e
Exercise:
Assume that initially Deque is empty.
Method Return Value Map content
isEmpty() true {}
put(5,A) null {(5,A)}
put(7,B) null {(5,A),(7,B)}
put(2,C) null {(5,A),(7,B),(2,C)}
put(8,D) null {(5,A),(7,B),(2,C),(8,D)}
put(2,E) C {(5,A),(7,B),(2,E),(8,D)}
get(7) B {(5,A),(7,B),(2,E),(8,D)}
get(4) null {(5,A),(7,B),(2,E),(8,D)}
get(2) E {(5,A),(7,B),(2,E),(8,D)}
size() 4 {(5,A),(7,B),(2,E),(8,D)}
remove(5) A {(7,B),(2,E),(8,D)}
remove(2) E {(7,B),(8,D)}
get(2) null {(7,B),(8,D)}
remove(2) null {(7,B),(8,D)}
isEmpty() false {(7,B),(8,D)}
entrySet() {(7,B),(8,D)} {(7,B),(8,D)}
keySet() {7,8} {(7,B),(8,D)}
values() {B,D} {(7,B),(8,D)}
Map implementation
//Map interface Map.java public interface Map<K,V> { int size(); boolean isEmpty(); V get(K key); V put(K key, V value); V remove(K key); Iterable<K> keySet(); Iterable<V> values(); Iterable<Entry<K,V>> entrySet(); }
Implementation of the above Map interface could be done in many ways
Array (ex: ArrayList in java)
Linked List
BST
Hash Table
70 | P a g e
The following code segment shows the structure of a entry(node) in linked list implementation of the
Map
class MapEntry<K,V>{ //equivalent to Node class K key; V value; MapEntry next; MapEntry prev }
Hash Tables One of the most efficient data structures for implementing a map
A map M supports the abstraction of using keys as addresses that help locate an entry. As a mental
warm-up, consider a restricted setting in which a map with n entries uses keys that are known to be
integers in a range from 0 to N-1. In this case we can represent the map using a lookup table of length N.
Figure 51: A lookup table with length 11 for a map containing entries (1,D), (3,Z), (6,C), and (7,Q)
Basic Map operations get, put, and remove can be implemented in O(1) worst-case time.
The challenges in extending this framework to the more general setting of a map:
If new keys that are not in the range (ex: 11) appear to put into the map, the lookup table
should be lengthen
Unnecessarily allocating space
Only integer values can be used as keys
The solution is the use of a Hash function to map general keys to corresponding indices in a table.
The keys will be distributed in the range from 0 to N-1 by a hash function, but in practice there may be
two or more distinct keys that get mapped to the same index. So we declare our table as a Bucket array.
Exercise:
Assume that you have following entries to enter in a hash table with 11 buckets indexed from 0 to 10.
(1,D), (25,C), (3,F), (14,Z), (6,A), (39,C) and (7,Q)
Use the following hash function
H(key) = key % 11
71 | P a g e
Figure 52: A bucket array of capacity 11 with entries (1,D), (25,C), (3,F), (14,Z), (6,A), (39,C) and (7,Q) using a simple hash function
Hash function is used to decide which bucket a key, value pair should be placed. The most simple hash
function is the modulus.
In the above exercise,
H(key) = key % 11
is the hash function.
Here 11 is the number of buckets in the bucket array. Therefore the output of any key value through
hash function lies between 0 and 10.
Collision:
Collisions occur if you have more than one item in a bucket. If you have perfect hash function,
there should be a very less number of collisions.
The problem is how you are going to distribute entries within the range of indices of the bucket
array.
Hash function consists of two parts
1. Hash code
2. Compression function
The advantage of separating hash function into two such components is that the hash code portion of
that computation is independent of a specific hash table size. This allows the development of a general
hash code for each object that can beused for a hash table of any size; only the compression function
depends upon the table size
72 | P a g e
Figure 53: Two parts of a hash function
Key
(ex: A to Z)
Hash Code
An integer
Compression function
Bucket number
0 to (N-1)
73 | P a g e
Lesson 09: Sorting Algorithms Sorting means to put elements of a list in a certain order.
There are 5 popular algorithms for sorting:
Simple sorting:
1. Bubble sort
2. Selection sort
3. Insertion sort
Advance sorting:
4. Merge sort
5. Quick sort
Classification of sorting:
1. Internal sorting
o The amount of data to be sorted is sufficient to process and the process is carried out in
the computer RAM.
2. External sorting
o The amount of data to be sorted is too much and process can’t carry out in the
computer RAM at once.
o Therefore data are stored on a secondary storage device.
74 | P a g e
Bubble Sort (Exchange sort) Reference: Lecture Slides
Bubble search focuses on successive adjacent pairs of elements in the list, compares them, and either
swaps them or not.
( 5 1 4 2 8 ) -> ( 1 5 4 2 8 ) -> ( 1 4 5 2 8 ) -> ( 1 4 2 5 8) -> ( 1 4 2 5 8 )
( 1 4 2 5 8 ) -> ( 1 4 2 5 8 ) -> ( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 )
Implementation of bubble sort
public class Test { static int[] bubbleSort(int[] array){ for (int i = 0; i < array.length-1; i++) { for (int j = 0; j < array.length-1-i; j++) { if(array[j]>array[j+1]){ int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } return array; } public static void main(String[] args) { int[] array = new int[]{1,2,3,4,3,29,8,2}; array = bubbleSort(array); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } }
75 | P a g e
Selection Sort The basic operation of the selection sort is to identify the smallest element from the sequence of
elements.
As the first step scan the elements from the beginning to end and find the smallest and swap it with the
first one etc.
( 5 1 4 2 8 ) -> ( 1 5 4 2 8 ) Smallest value is 1, swap 1 and 5
( 1 5 4 2 8 ) -> ( 1 2 4 5 8 ) Next smallest value is 2, swap 2 and 5
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 ) Next smallest value is 4, no change
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 ) Next smallest value is 5, no change
( 1 2 4 5 8 ) Done
Implementation of insertion sort
public class Test {
static int[] selectionSort(int[] array){ for (int i = 0; i < array.length-1; i++){ int minIndex = i; for(int j = i+1; j<array.length; j++){ if(array[minIndex]>array[j]){ minIndex = j; } } int temp = array[i]; array[i] = array[minIndex]; array[minIndex] = temp; } return array; } public static void main(String[] args) { int[] array = new int[]{1,2,3,4,3,29,8,2}; array = selectionSort(array); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } }
76 | P a g e
Insertion Sort The basic operation is insertion of a single element into a sequence of sorted elements.
Generally, suppose that we have already sorted first i-1 number of elements. Then we take ith element
and scan through the sorted list to see where to insert item i.
( 5 1 4 2 8 ) -> ( 1 5 4 2 8 ) Fix 5 and take 1 and see where to put. Put it.
( 1 5 4 2 8 ) -> ( 1 4 5 2 8 ) Fix 1,5 and take 4 and see where to put. Put it.
( 1 4 5 2 8 ) -> ( 1 2 4 5 8 ) Fix 1,4,5 and take 2 and see where to put. Put it.
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 ) Fix 1,2,4,5 and take 8 and see where to put. Put it.
( 1 2 4 5 8 ) Done
Implementation of insertion sort
public class Test { static int[] insertionSort(int[] array){ for (int i = 1; i < array.length; i++){ int j=i-1; int number = array[i]; while(number<array[j]){ array[j+1] = array[j]; j--; } array[j+1] = number; } return array; } public static void main(String[] args) { int[] array = new int[]{1,2,3,4,3,29,8,2}; array = insertionSort(array); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } } //best case if array is already in ascending order, //worst case if the array is in descending order.
77 | P a g e
Classic Sorting Algorithms (Advanced Sorting Algorithms) Critical components in the world’s computational infrastructure:
Full scientific understanding of their properties has enabled us to develop them into practical
system sorts.
Quicksort honored as one of the top 10 algorithms of 20th century in Science and engineering.
Classic sorting algorithms:
Shellsort
o Embedded systems
Mergesort
o Java sort for objects
Quicksort
o Java sort for primitive types
Divide and Conquer
Merge sort and quick sort use recursion in an algorithmic design pattern called divide and conquer.
Three steps of divide and conquer pattern
1. Divide
If the input size is smaller than a certain threshold (one or two elements), solve the
problem directly using a straightforward method and return the solution so obtained.
Otherwise, divide the input data into two or more disjoint subsets.
2. Conquer
Recursively solve the subproblems associated with the subsets.
3. Combine
Take the solution to the subproblems and merge them into a solution to the original
problem.
Merge-Sort We can visualize an execution of the merge-sort algorithm by means of a binary tree T, called merge-
sort tree. Each mode of T represents a recursive invocation (or call) of the merge-sort algorithm.
The following two figures summarizes the execution of the merge-sort algorithm for unsorted array of
{ 85, 24, 63, 45, 17, 31, 96, 50}.
The array is divided until it become a single element.
Then it is sorted backwards and keep on merging.
78 | P a g e
Figure 54: Merge-sort recursive invocation tree
Figure 55: Merge-sort returning procedure
Implementation of Merge-sort
When talk about the implementation of the merge-sort algorithm, we have to use separate functions for
merging and sorting.
79 | P a g e
public class Test { private static void merge(Integer[] array,Integer[] aux,int lo,int mid,int hi){ for(int k = lo; k<=hi; k++) aux[k] = array[k]; int i = lo; int j = mid + 1; for(int k = lo; k<=hi; k++){ if(i>mid) array[k] = aux[j++]; else if(j>hi) array[k] = aux[i++]; else if(aux[j]<aux[i]) array[k] = aux[j++]; else array[k] = aux[i++]; } } private static void sort(Integer[] array,Integer[] aux, int lo, int hi){ if (hi<=lo) return; int mid = (lo + hi)/2; sort(array,aux,lo,mid); sort(array,aux,mid+1,hi); merge(array,aux,lo,mid,hi); } static Integer[] mergeSort(Integer[] array){ Integer[] aux = new Integer[array.length]; sort(array,aux,0,array.length-1); return aux; } public static void main(String[] args) { Integer[] array = new Integer[]{1,2,3,4,3,29,8,2}; array = mergeSort(array); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } }