Futo - CSC 305 - Data Structure Lecture Note
Futo - CSC 305 - Data Structure Lecture Note
ALGORITHM
Linked List
Tree
Graph
1
What is Algorithm
An algorithm is a finite set of instructions or logic, written in order, to
accomplish a certain predefined task. Algorithm is not the complete code or
program, it is just the core logic(solution) of a problem, which can be expressed
either as an informal high level description as pseudocode or using
a flowchart.
An algorithm is said to be efficient and fast, if it takes less time to execute and
consumes less memory space. The performance of an algorithm is measured on
the basis of following properties :
1. Time Complexity
2. Space Complexity
Space Complexity
Its the amount of memory space required by the algorithm, during the course of
its execution. Space complexity must be taken seriously for multi-user systems
and in situations where limited memory is available.
An algorithm generally requires space for following components :
2
Instruction Space : Its the space required to store the executable version
of the program. This space is fixed, but varies depending upon the number
Data Space : Its the space required to store all the constants and variables
value.
Time Complexity
Time Complexity is a way to represent the amount of time needed by the
program to run to completion. We will study this in details in our section.
NOTE: Before going deep into data structure, you should have a good
knowledge of programming either in C or in C++ or Java.
3
Now the most common metric for calculating time complexity is Big O
notation. This removes all constant factors so that the running time can be
estimated in relation to N, as N approaches infinity. In general you can think of
it like this :
statement;
Above we have a single statement. Its Time Complexity will be Constant. The
running time of the statement will not change in relation to N.
for(i=0; i < N; i++)
{
statement;
}
The time complexity for the above algorithm will be Linear. The running time
of the loop is directly proportional to N. When N doubles, so does the running
time.
5
Types of Notations for Time Complexity
Now we will discuss and understand the various notations used for Time
Complexity.
2. Big Omega denotes "more than or the same as" <expression> iterations.
INTRODUCTION TO SORTING
6
Sorting is nothing but storage of data in sorted order, it can be in ascending or
descending order. The term Sorting comes into picture with the term Searching.
There are so many things in our real life that we need to search, like a particular
record in database, roll numbers in merit list, a particular telephone number, any
particular page in a book etc.
Sorting arranges data in a sequence which makes searching easier. Every record
which is going to be sorted will contain one key. Based on the key the record
will be sorted. For example, suppose we have a record of students, every such
record will have the following data:
Roll No.
Name
Age
Class
Here Student roll no. can be taken as key for sorting the records in ascending or
descending order. Now suppose we have to search a Student with roll no. 15,
we don't need to search the complete record we will simply search between the
Students with roll no. 10 to 20.
Sorting Efficiency
There are many techniques for sorting. Implementation of particular sorting
technique depends upon situation. Sorting techniques mainly depends on two
parameters. First parameter is the execution time of program, which means time
taken for execution of program. Second is the space, which means space taken
by the program.
1. Bubble Sort
2. Insertion Sort
7
3. Selection Sort
4. Quick Sort
5. Merge Sort
6. Heap Sort
Bubble Sorting
Bubble Sort is an algorithm which is used to sort N elements that are given in a
memory for eg: an Array with N number of elements. Bubble Sort compares all
the element one by one and sort them based on their values.
It is called Bubble sort, because with each iteration the smaller element in the
list bubbles up towards the first place, just like a water bubble rises up to the
water surface.
Sorting takes place by stepping through all the data items one-by-one in pairs
and comparing adjacent data items and swapping each pair that is out of order.
Hence we can insert a flag and can keep checking whether swapping of
elements is taking place or not. If no swapping is taking place that means the
array is sorted and can jump out of the for loop.
int a[6] = {5, 1, 6, 2, 4, 3};
int i, j, temp;
for(i=0; i<6; i++)
{
int flag = 0; //taking a flag variable
for(j=0; j<6-i-1; j++)
9
{
if( a[j] > a[j+1])
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
flag = 1; //setting flag as 1, if swapping occurs
}
}
if(!flag) //breaking out of for loop if no swapping takes place
{
break;
}
}
In the above code, if in a complete single cycle of j iteration(inner for loop), no
swapping takes place, and flag remains 0, then we will break out of the for
loops, because the array has already been sorted.
10
Best-case Time Complexity will be O(n), it is when the list is already sorted.
Insertion Sorting
It is a simple Sorting algorithm which sorts the array by shifting elements one
by one. Following are some of the important characteristics of Insertion Sort.
2. It is efficient for smaller data sets, but very inefficient for larger lists.
3. Insertion Sort is adaptive, that means it reduces its total number of steps if
5. Its space complexity is less, like Bubble Sorting, inerstion sort also
equal keys
11
How Insertion Sorting Works
int main() {
int array[5]= {5,4,3,2,1};
insertionSort(array,5);
return 0;
}
13
void insertionSort(int arr[], int length) {
int i, j ,tmp;
for (i = 1; i < length; i++) {
j = i;
while (j > 0 && arr[j - 1] > arr[j]) {
tmp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = tmp;
j--;
}
printArray(arr,5);
}
}
14
Average Time Complexity : O(n2)
Space Complexity : O(1)
Selection Sorting
Selection sorting is conceptually the most simplest sorting algorithm. These
algorithms first finds the smallest element in the array and exchanges it with the
element in the first position, then find the second smallest element and
exchange it with the element in the second position, and continues in this way
until the entire array is sorted.
In the first pass, the smallest element found is 1, so it is placed at the first
position, then leaving first element, smallest element is searched from the rest of
the elements, 3 is the smallest, so it is then placed at the second position. Then
we leave 1 nad 3, from the rest of the elements, we search for the smallest and
put it at third position and keep doing this, until array is sorted.
15
min = i; //setting min as i
for(j=i+1; j < size; j++)
{
if(a[j] < a[min]) //if element at j is less than element at min position
{
min = j; //then set min as j
}
}
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
Selection Sorting
Selection sorting is conceptually the most simplest sorting algorithm. This
algorithm first finds the smallest element in the array and exchanges it with the
element in the first position, then find the second smallest element and
16
exchange it with the element in the second position, and continues in this way
until the entire array is sorted.
In the first pass, the smallest element found is 1, so it is placed at the first
position, then leaving first element, smallest element is searched from the rest of
the elements, 3 is the smallest, so it is then placed at the second position. Then
we leave 1 nad 3, from the rest of the elements, we search for the smallest and
put it at third position and keep doing this, until array is sorted.
17
{
min = j; //then set min as j
}
}
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
2. Pivot element
19
{
if(p < r)
{
int q;
q = partition(a, p, r);
quicksort(a, p, q);
quicksort(a, q+1, r);
}
}
20
a[j] = temp;
}
else
{
return j;
}
}
}
Space required by quick sort is very less, only O(n log n) additional space
is required.
21
Merge Sort is quite fast, and has a time complexity of O(n log n). It is also a
stable sort, which means the "equal" elements are ordered in the same order in
the sorted list.
Like we can see in the above example, merge sort first breaks the unsorted list
into sorted sublists, and then keep merging these sublists, to finlly get the
complete sorted list.
22
Lets take a[5] = {32, 45, 67, 2, 7} as the array to be sorted.
23
}
else
{
b[k++] = a[j++];
}
}
while(i <= q)
{
b[k++] = a[i++];
}
while(j <= r)
{
b[k++] = a[j++];
}
24
Complexity Analysis of Merge Sort
Worst Case Time Complexity : O(n log n)
Best Case Time Complexity : O(n log n)
Average Time Complexity : O(n log n)
Space Complexity : O(n)
average and best) as merge sort always divides the array in two halves and
It requires equal amount of additional space as the unsorted list. Hence its
element from the heap, and inserting it into the array. The heap is
25
What is a Heap ?
Heap is a special tree-based data structure, that satisfies the following special
heap properties :
2. Heap Property : All nodes are either [greater than or equal to] or [less
than or equal to] each of its children. If the parent nodes are greater than
their children, heap is called a Max-Heap, and if the parent nodes are
26
How Heap Sort Works
Initially on receiving an unsorted list, the first step in heap sort is to create a
Heap data structure(Max-Heap or Min-Heap). Once heap is built, the first
element of the Heap is either largest or smallest(depending upon Max-Heap or
Min-Heap), so we put the first element of the heap in our array. Then we again
make heap using the remaining elements, to again pick the first element of the
heap and put it into the array. We keep on doing the same repeatedly untill we
have the complete sorted list in our array.
In the below algorithm, initially heapsort() function is called, which
calls buildheap() to build heap, which inturn uses satisfyheap() to build the
heap.
27
void heapsort(int[], int);
void buildheap(int [], int);
void satisfyheap(int [], int, int);
void main()
{
int a[10], i, size;
cout << "Enter size of list"; // less than 10, because max size of array is 10
cin >> size;
cout << "Enter" << size << "elements";
for( i=0; i < size; i++)
{
cin >> a[i];
}
heapsort(a, size);
getch();
}
28
a[0] = a[heapsize];
a[heapsize] = temp;
heapsize--;
satisfyheap(a, 0, heapsize);
}
for( i=0; i < length; i++)
{
cout << "\t" << a[i];
}
}
29
if(l <= heapsize && a[l] > a[i])
{
largest = l;
}
else
{
largest = i;
}
if( r <= heapsize && a[r] > a[largest])
{
largest = r;
}
if(largest != i)
{
temp = a[i];
a[i] = a[largest];
a[largest] = temp;
satisfyheap(a, largest, heapsize);
}
}
30
Space Complexity : O(n)
Heap sort is not a Stable sort, and requires a constant space for sorting a
list.
Linear Search
A linear search is the basic and simple search algorithm. A linear search
searches an element or value from an array till the desired element or value is
not found and it searches in a sequence order. It compares the element with all
the other elements given in the list and if the element is matched it returns the
value index else it return -1. Linear Search is applied on the unsorted or
unordered list when there are fewer elements in a list.
31
function findIndex(values, target)
{
for(var i = 0; i < values.length; ++i)
{
if (values[i] == target)
{
return i;
}
}
return -1;
}
//call the function findIndex with array and number to be searched
findIndex([ 8 , 2 , 6 , 3 , 5 ] , 5) ;
Binary Search
Binary Search is applied on the sorted array or list. In binary search, we first
compare the value with the elements in the middle position of the array. If the
value is matched, then we return the value. If the value is less than the middle
element, then it must lie in the lower half of the array and if it's greater than the
element then it must lie in the upper half of the array. We repeat this procedure
on the lower (or upper) half of the array. Binary Search is useful when there are
large numbers of elements in an array.
32
Example with Implementation
To search an element 13 from the sorted array or list.
Stacks
Stack is an abstract data type with a bounded(predefined) capacity. It is a simple
data structure that allows adding and removing elements in a particular order.
Every time an element is added, it goes on the top of the stack, the only element
that can be removed is the element that was at the top of the stack, just like a
pile of objects.
34
Basic features of Stack
3. push() function is used to insert new elements into the Stack and pop() is
used to delete an element from the stack. Both insertion and deletion are
Applications of Stack
The simplest application of a stack is to reverse a word. You push a given word
to stack - letter by letter - and then pop letters from the stack.
There are other uses also like : Parsing, Expression Conversion(Infix to
Postfix, Postfix to Prefix etc) and many more.
Implementation of Stack
Stack can be easily implemented using an Array or a Linked List. Arrays are
quick, but are limited in size and Linked List requires overhead to allocate, link,
unlink, and deallocate, but is not limited in size. Here we will implement Stack
using array.
35
/* Below program is written in C++ language */
Class Stack
{
int top;
public:
int a[10]; //Maximum size of Stack
Stack()
{
top = -1;
}
};
void Stack::push(int x)
{
if( top >= 10)
{
36
cout << "Stack Overflow";
}
else
{
a[++top] = x;
cout << "Element Inserted";
}
}
int Stack::pop()
{
if(top < 0)
{
cout << "Stack Underflow";
return 0;
}
else
{
int d = a[top--];
return d;
}
}
void Stack::isEmpty()
{
if(top < 0)
37
{
cout << "Stack is empty";
}
else
{
cout << "Stack is not empty";
}
}
-1 Stack is Empty
Analysis of Stacks
Below mentioned are the time complexities for various operations that can be
performed on the Stack data structure.
38
Push Operation : O(1)
39
Basic features of Queue
types.
3. Once a new element is inserted into the Queue, all the elements inserted
before the new element in the queue must be removed, to remove the new
element.
Applications of Queue
Queue, as the name suggests is used whenever we need to have any group of
objects in an order in which the first one coming in, also gets out first while the
others wait for there turn, like in the following scenarios :
scheduling etc.
2. In real life, Call Center phone systems will use Queues, to hold people
40
Implementation of Queue
Queue can be implemented using an Array, Stack or Linked List. The easiest
way of implementing a queue is by using an Array. Initially the head(FRONT)
and the tail(REAR) of the queue points at the first index of the array (starting
the index of array from 0). As we add elements to the queue, the tail keeps on
moving ahead, always pointing to the position where the next element will be
inserted, while the head remains at the first index.
When we remove element from Queue, we can follow two possible approaches
(mentioned [A] and [B] in above diagram). In [A] approach, we remove the
element at head position, and then one by one move all the other elements on
position forward. In approach [B] we remove the element from head position
and then move head to the next position.
In approach [A] there is an overhead of shifting the elements one position
forward every time we remove the first element. In approach [B] there is no
41
such overhead, but whener we move head one position ahead, after removal of
first element, the size on Queue is reduced by one space each time.
/* Below program is wtitten in C++ language */
public:
Queue()
{
rear = front = -1;
}
void enqueue(int x); //declaring enqueue, dequeue and display functions
int dequeue();
void display();
}
Enqueue : O(1)
Dequeue : O(1)
Size : O(1)
int dequeue();
44
}
45
int Queue :: dequeue() {
while(S1.isEmpty()) {
x = S1.pop();
S2.push();
}
return x;
}
They are a dynamic in nature which allocates the memory when required.
47
Disadvantages of Linked Lists
sequentially.
Linked lists let you insert elements at the beginning and end of the list.
Singly Linked List : Singly linked lists contain nodes which have a data
part as well as an address part i.e. next, which points to the next node in
sequence of nodes. The operations we can perform on singly linked lists are
48
Doubly Linked List : In a doubly linked list, each node contains two
links the first link points to the previous node and the next link points to the
Circular Linked List : In the circular linked list the last node of the list
contains the address of the first node and forms a circular chain.
49
We will also be adding some more useful methods like :
Before inserting the node in the list we will create a class Node. Like shown
below :
class Node {
public:
int data;
//pointer to the next node
node* next;
node() {
data = 0;
next = NULL;
}
node(int x) {
data = x;
next = NULL;
}
}
We can also make the properties data and next as private, in that case we will
need to add the getter and setter methods to access them. You can add the
getters and setter like this :
int getData() {
50
return data;
}
void setData(int x) {
this.data = x;
}
node* getNext() {
return next;
}
51
//function to add Node at front
int addAtFront(node *n);
//function to check whether Linked list is empty
int isEmpty();
//function to add Node at the End of list
int addAtEnd(node *n);
//function to search a value
node* search(int k);
//function to delete any Node
node* deleteNode(int x);
LinkedList() {
head = NULL;
}
}
2. When a new Linked List is instantiated, it just has the Head, which is
Null.
3. Else, the Head holds the pointer to the first Node of the List.
52
4. When we want to add any Node at the front, we must make the head point
to it.
5. And the Next pointer of the newly added Node, must point to the
6. The previous Head Node is now the second Node of Linked List, because
1. If the Linked List is empty then we simply, add the new Node as the
53
2. If the Linked List is not empty then we find the last node, and make it'
next to the new Node, hence making the new node the last Node.
If the Node to be deleted is the first node, then simply set the Next pointer
of the Head to point to the next element from the Node to be deleted.
55
If the Node is in the middle somewhere, then find the Node before it, and
56
}
else { return 0; }
}
Now you know a lot about how to handle List, how to traverse it, how to search
an element. You can yourself try to write new methods around the List.
If you are still figuring out, how to call all these methods, then below is how
your main() method will look like. As we have followed OOP standards, we
will create the objects of LinkedList class to initialize our List and then we will
create objects of Node class whenever we want to add any new node to the List.
int main() {
LinkedList L;
//We will ask value from user, read the value and add the value to our Node
int x;
cout << "Please enter an integer value : ";
cin >> x;
Node *n1;
//Creating a new node with data as x
n1 = new Node(x);
//Adding the node to the list
L.addAtFront(n1);
}
Similarly you can call any of the functions of the LinkedList class, add as many
Nodes you want to your List.
The real life application where the circular linked list is used is our
running applications are kept in a circular linked list and the OS gives a
fixed time slot to all for running. The Operating System keeps on iterating
over the linked list until all the applications are completed.
Another example can be Multiplayer games. All the Players are kept in a
Circular Linked List and the pointer keeps on moving forward as a player's
chance ends.
Queue we have to keep two pointers, FRONT and REAR in memory all the
58
Implementing Circular Linked List
Implementing a circular linked list is very easy and almost similar to linear
linked list implementation, with the only difference being that, in circular linked
list the last Node will have it's next point to the Head of the List. In Linear
linked list the last Node simply holds NULL in it's next pointer.
So this will be oue Node class, as we have already studied in the lesson, it will
be used to form the List.
class Node {
public:
int data;
//pointer to the next node
node* next;
node() {
data = 0;
next = NULL;
}
node(int x) {
data = x;
next = NULL;
}
}
59
Circular Linked List
Circular Linked List class will be almost same as the Linked List class that we
studied in the previous lesson, with a few difference in the implementation of
class methods.
class CircularLinkedList {
public:
node *head;
//declaring the functions
CircularLinkedList() {
head = NULL;
}
}
60
Insertion at the Beginning
Steps to insert a Node at beginning :
2. When a new Linked List is instantiated, it just has the Head, which is
Null.
3. Else, the Head holds the pointer to the fisrt Node of the List.
4. When we want to add any Node at the front, we must make the head point
to it.
5. And the Next pointer of the newly added Node, must point to the
6. The previous Head Node is now the second Node of Linked List, because
61
else {
n->next = head;
//get the Last Node and make its next point to new Node
Node* last = getLastNode();
last->next = n;
//also make the head point to the new first Node
head = n;
i++;
}
//returning the position where Node is added
return i;
}
1. If the Linked List is empty then we simply, add the new Node as the
2. If the Linked List is not empty then we find the last node, and make it'
next to the new Node, and make the next of the Newly added Node point to
63
Deleting a Node from the List
Deleting a node can be done in many ways, like we first search the Node
with data which we want to delete and then we delete it. In our approach, we
will define a method which will take the data to be deleted as argument, will
use the search method to locate it and will then remove the Node from the List.
To remove any Node from the list, we need to do the following :
If the Node to be deleted is the first node, then simply set the Next pointer
of the Head to point to the next element from the Node to be deleted. And
If the Node is in the middle somewhere, then find the Node before it, and
If the Node is at the end, then remove it and make the new last node point
to the head.
65