0% found this document useful (0 votes)
8 views28 pages

DS Notes

An Abstract Data Type (ADT) is a mathematical model for data types defined by its behavior, including possible values and operations. Examples of ADTs include Stack, Queue, and List, each with specific operations like push, pop, insert, and delete. The document also discusses asymptotic notations (Big O, Omega, and Theta) used for analyzing algorithm efficiency and complexity, providing mathematical representations and examples for each notation.

Uploaded by

pushpamohanraj93
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views28 pages

DS Notes

An Abstract Data Type (ADT) is a mathematical model for data types defined by its behavior, including possible values and operations. Examples of ADTs include Stack, Queue, and List, each with specific operations like push, pop, insert, and delete. The document also discusses asymptotic notations (Big O, Omega, and Theta) used for analyzing algorithm efficiency and complexity, providing mathematical representations and examples for each notation.

Uploaded by

pushpamohanraj93
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 28

Abstract Data Type

In computer science, an abstract data type (ADT) is a mathematical model for data types,
defined by its behavior (semantics) from the point of view of a user of the data, specifically
in terms of possible values, possible operations on data of this type, and the behavior of these
operations.

Some examples of ADT are Stack, Queue, List etc.


 Stack −
o isFull(), This is used to check whether stack is full or not
o isEmpry(), This is used to check whether stack is empty or not
o push(x), This is used to push x into the stack
o pop(), This is used to delete one element from top of the stack
o peek(), This is used to get the top most element of the stack
o size(), this function is used to get number of elements present into the stack
 Queue −
o isFull(), This is used to check whether queue is full or not
o isEmpry(), This is used to check whether queue is empty or not
o insert(x), This is used to add x into the queue at the rear end
o delete(), This is used to delete one element from the front end of the queue
o size(), this function is used to get number of elements present into the queue

 List −
o size(), this function is used to get number of elements present into the list
o insert(x), this function is used to insert one element into the list
o remove(x), this function is used to remove given element from the list
o get(i), this function is used to get element at position i
o replace(x, y), this function is used to replace x with y value

Asymptotic Notations

Asymptotic Notations The main idea of asymptotic analysis is to have a measure of


efficiency of algorithms that doesn’t depend on machine specific constants, and doesn’t
require algorithms to be implemented and time taken by programs to be compared.
Asymptotic notations are mathematical tools to represent time complexity of algorithms for
asymptotic analysis. The following three asymptotic notations are mostly used to represent
time complexity of algorithms.
The Big O Notation:
The Big O notation defines an upper bound of an algorithm, it
bounds a function only from above. For example, consider the
case of Insertion Sort. It takes linear time in best case and
quadratic time in worst case. We can safely say that the time
complexity of Insertion sort is O(n2). Note that O(n2) also covers
linear time.

The Big O notation is useful when we only have upper bound on


time complexity of an algorithm. Many times we easily find an
upper bound by simply looking at the algorithm.

For a given function g(n), we denote by O(g(n)) the set of functions.

O(g(n)) = { f(n): there exist positive constants c and n0, such that 0 ≤
f(n)≤ c×g(n) for all n ≥ n0}
Example

f(n) = 2n + 3

2n + 3 ≤ 10 n ∀ n ≥ 1
Here, c=10, n0=1,
g(n)=n
=> f(n) = O(n)

3n 2n + 3 ≤ 5 n ∀ n ≥
Also, 2n + 3 ≤ 2 n +

And, 2n + 3 ≤ 2n2 +
3n2 2n + 3 ≤ 5n2
=> f(n) = O(n2)

O(1) < O(log n) < O(√ n) < O(n) < O(n log n) < O(n2) < O(n3) < O(2n) < O(3n) < O(nn)
The Omega (Ω) notation:

Just as Big O notation provides an asymptotic upper


bound on a function, Ω notation provides an asymptotic
lower bound.

Ω notation can be useful when we have lower bound on


time complexity of an algorithm. As discussed in the
previously, the best case performance of an algorithm is
generally not useful, the omega notation is the least used
notation among all three.

For a given function g(n), we denote by Ω(g(n)) the set of


functions.

Ω (g(n)) = { f(n): there exist positive constants c and n0 such that 0 ≤


c×g(n) ≤ f(n) for all n ≥ n0 }.

Let us consider the same insertion sort example here. The time complexity of
insertion sort can be written as Ω(n), but it is not a very useful information
about insertion sort, as we are generally interested in worst case and sometimes
in average case.

Example :

f(n) = 2n + 3

2n + 3 ≥ n ∀ n ≥ 1
Here, c=1, n0=1,
g(n)=n
=> f(n) = Ω(n)
Also, f(n) = Ω(log n)
f(n) = Ω(√n)

The Theta (Θ) notation:

The theta notation bounds a functions from above and


below, so it defines the exact asymptotic behaviour. A
simple way to get theta notation of an expression is to
drop low order terms and ignore leading constants. For
example, consider the following expression.
3n3 + 6n2 + 6000 = Θ(n3)

Dropping lower order terms is always fine because there will always be a n0
after which Θ(n3) has higher values than Θ(n2) irrespective of the constants
involved. For a given function g(n), we denote Θ(g(n)) as the following set of
functions.

Θ(g(n)) = {f(n): there exist positive constants c1, c2 and n0 such that 0
≤ c1×g(n) ≤ f(n) ≤ c2×g(n) for all n ≥ n0}
The above definition means, if f(n) is theta of g(n), then the value f(n)
is always between c1×g(n) and c2×g(n) for large values of n (n ≥ n0).
The definition of theta also requires that f(n) must be non-negative for
values of n greater than n0.

Example

f(n) = 2n + 3

1 * n ≤ 2n + 3 ≤
5n

∀ n ≥ 1 Here,
c1=1, c2 = 5,
n0=1, g(n)=n
=> f(n) = Θ(n)

Example 7.5:

f(n) = 2n2 + 3n + 4

2n2 + 3n + 4 ≤ 2n2 + 3n2 + 4n2


2n2 + 3n
+4≤
9n2 f(n)
= O (n2)

also, 2n2 + 3n
+ 4 ≥ 1 * n2 f(n)
= Ω (n2)

9n2 ∀ n ≥ 1 Here, c1=1, c2


=>1 * n2 ≤ 2n2 + 3n + 4 ≤

= 9, n0=1, g(n)= n2
=> f(n) = Θ(n2)

Example
f(n) = n2 log n + n

n2 log n ≤ n2 log n + n
≤ 10 n2 log n Ω (n2 log
n) Θ(n2 log n) O(n2 log
n)

Example
f(n) = n!
=1×2×3×4×…×n
1×1×1×…×1≤1×2×3×4×…×n ≤
n × n × n × … × n 1 ≤ n! ≤ nn
Ω (1) O (nn) ( Here we cannot find the average or tight bound Θ)

Complexity Analysis of Data Structures and Algorithms


Complexity analysis is defined as a technique to measure how long an algorithm
would take to complete given an input of size n; independent of the machine,
language, and compiler. It is used for evaluating the variations of execution time
on different algorithms.
Why Complexity Analysis is Required?

 It gives you an estimated time and space required to execute a program.


 It is used for comparing different algorithms for different input sizes.
 It helps to determine the difficulty of a problem.

Asymptotic Notations for Determining Complexities


Your algorithm doesn't need to behave in the same way for different input sizes. Its
performance may vary. The study of the variations in the performance of the
algorithm with the change in the order of the input size is called Asymptotic
Analysis.
Asymptotic notations are mathematical notations to describe the running time of
an algorithm when the input tends towards a particular value or a limiting value. In
other words, it defines the mathematical limits of its run-time performance. Using
the asymptotic analysis, we can easily conclude the average-case, best-case, and
worst-case scenario of an algorithm.

There are mainly three asymptotic notations for the complexity analysis of
algorithms. They are as follows:

1. Big-O notation
2. Omega notation
3. Theta notation
1. Big O notation (O-notation)
Big O notation symbolizes the upper bound of the running time of an algorithm or
the algorithm's longest amount of time to complete its operation. Therefore, it
gives the worst-case complexity of an algorithm.

Mathematical Representation of Big-O Notation

O(g(n)) = { f(n): there exist positive constants c and n0


such that 0 ≤ f(n) ≤ cg(n) for all n ≥ n0 }
In the above expression, a function f(n) belongs to the set O(g(n)) if there exists a
positive constant c such that it lies between 0 and cg(n), for sufficiently large n.
For any value of n, the running time of an algorithm does not cross the time
provided by O(g(n)).
We will look at the Big O notation in much detail in the next section, Big O
Notation in Data Structures.

2. Omega notation (Ω-notation)


Omega notation symbolizes the lower bound of the running time of an algorithm.
Thus, it provides the best-case complexity of an algorithm. It determines the
fastest time that an algorithm can run.
Mathematical Representation of Omega Notation

Ω(g(n)) = { f(n): there exist positive constants c and n0


such that 0 ≤ cg(n) ≤ f(n) for all n ≥ n0 }
In the above expression, a function f(n) belongs to the set Ω(g(n)) if there exists a
positive constant c such that it lies above cg(n), for sufficiently large n. For any
value of n, the minimum time required by the algorithm is given by Omega
Ω(g(n)).
3. Theta Notation (Θ-notation)
Theta notation symbolizes the upper and the lower bound of the running time of an
algorithm. In real-world problems, an algorithm does not perform worst or best, it
fluctuates between the worst-case and best-case, and this gives us the average case
complexity of an algorithm.
Mathematical Representation of Theta Notation

Θ(g(n)) = { f(n): there exist positive constants c1, c2 and n0


such that 0 ≤ c1g(n) ≤ f(n) ≤ c2g(n) for all n ≥ n0 }
In the above expression, a function f(n) belongs to the set Θ(g(n)) if there exist
positive constants c1 and c2 such that it can be sandwiched
between c1g(n) and c2g(n), for sufficiently large n. If a function f(n) lies anywhere
in between c1g(n) and c2g(n) for all n ≥ n0, then f(n) is said to be asymptotically
tight bound.

Parameters to Measure Complexities


1. Time Complexity
Time complexity is the amount of time taken by an algorithm to execute each
statement of the code till its completion. The time taken here is for the function of
the length of the input and not the actual execution time of the machine on which
the algorithm is running. The time complexity of algorithms is commonly
expressed using the Big O notation.
To calculate the time complexity, total the cost of each fundamental instruction
and the number of times the instruction is executed.

 There are statements with basic operations like comparisons, return


statements, assignments, and reading a variable. Let's assume that each
statement takes constant time O(1).

Example

int a=5; // reading a variable


if( a==5) return true; // return statement
int x= 4>5 ? 1:0; // comparison
bool flag=true; // Assignment
Total time taken, T(n) = t(statement1) + t(statement2) + ... + t(statementN);
T(n)=O(1) i.e. constant complexity

Here, n is the input size, and t represents the amount of time that a statement
or collection of statements takes to execute.

 To calculate the time taken by any loop, find out the runtime of the loop
block and multiply it by the number of times the program will repeat the
loop.

Example

for (int i = 0; i < n; i++) {


cout << “ScholarHat” << endl;
}

In the above code, the loop will execute n times, and it will
print ScholarHat N times.

Total time taken, T(N) = n *( t(cout statement))


= n * O(1)
= O(n) i.e. Linear complexity.

2. Space Complexity
Space complexity is the amount of memory space an algorithm or a problem takes
during the execution of that particular problem/algorithm. Here, the space used
includes both, the variables and the input values. Space complexity is the
combination or sum up of the auxiliary space and the space used by input values.
Auxiliary space is the temporary space required by an algorithm/problem during
the execution of that algorithm/problem.
Space Complexity = Auxiliary Space + Space used for input values. It is
frequently represented using Big O notation, just like time complexity, to describe
the maximum amount of memory utilization. To get the space complexity, one
must know the amount of memory used by different types of datatype variables,
program instructions, constant values, and things like function calls, recursion
stacks, etc.
Let's calculate the space complexity for the code of Addition of Numbers

Example

{
int sum = x + y;
return sum;
}
In the above code, we have 3 integer variables sum, x, and y. All the variables will
take 4 bytes of space, and an extra 4-byte for the return value sum. Hence, the total
space complexity = 4*4 + 4 = 20 bytes. This is the fixed complexity and because
of the same type of inputs, such space complexities are considered as constant
space complexities or so-called O(1) space complexity.

How to optimize an Algorithm?


We can optimize a solution using both time and space optimization. To
optimize a program,
 Reduce the time taken to run the program and increase the
space occupied.
 Reduce the memory usage of the program and increase its total
run time.
 Reduce both time and space complexity by deploying relevant
algorithms.

Common Asymptotic Notations


Type of Complexity Asymptotic Notation

constant ?(1)

linear ?(n)

logarithmic ?(log n)

n log n ?(n log n)

exponential 2?(n)

cubic ?(n3)

polynomial n?(1)

quadratic ?(n2)

Complexity Analysis Of Algorithms


Algorithm Complexity
Linear Search Algorithm O(N)

Binary Search O(LogN)

Bubble Sort O(N^2)

Insertion Sort O(N^2)

Selection Sort O(N^2)

QuickSort O(N^2)

Merge Sort O(N log(N))

Radix Sort O((n+b) * logb(k)).

Breadth First Search O(V+E)

Depth First Search O(V+E)

Worst-case time complexity of


different data structures for
different operations
Data structure Access Search Insertion Deletion
Array O(1) O(N) O(N) O(N)

Stack O(N) O(N) O(1) O(1)

Queue O(N) O(N) O(1) O(1)

Singly Linked List O(N) O(N) O(N) O(N)

Doubly Linked List O(N) O(N) O(1) O(1)

Hash Table O(N) O(N) O(N) O(N)

Binary Search Tree O(N) O(N) O(N) O(N)

AVL Tree O(log N) O(log N) O(log N) O(log N)

Binary Tree O(N) O(N) O(N) O(N)

Red Black Tree O(log N) O(log N) O(log N) O(log N)

Linked Lists:
A Linked list is an ordered collection of elements. Each element
in the list is referred as a node. Each node contains two fields
namely,
Data field-The data field contains the actual data of the
elements to be stored in the list Next field- The next field
contains the address of the next node in the list

DATA NEXT
A Linked list node

Null 10 40 20 30

L
Advantages of Linked list:
1. Insertion and deletion of elements can be done efficiently
2.It uses dynamic memory allocation
3.Memory utilization is efficient compared to arrays
Disadvantages of linked list:
1. Linked list does not support random access
2.Memory is required to store next field
3.Searching takes time compared to arrays
Types of Linked
List
1. Singly Linked List or One Way List
2. Doubly Linked List or Two-Way Linked List
3. Circular Linked List
Dynamic allocation

The process of allocating memory to the variables during


execution of the program or at run time is known as dynamic
memory allocation. C language has four library routines which
allow this function.
Dynamic memory allocation gives best performance in
situations in which we do not know memory requirements in
advance. C provides four library routines to automatically
allocate memory at the run time.
To use dynamic memory allocation functions, you must include the
header file stdlib.h.
malloc()
The malloc function reserves a block of memory of
specified size and returns a pointer of type void. This
means that we can assign it to any type of pointer.
The general syntax of malloc()
is ptr =(cast-
type*)malloc(
byte- size);
where ptr is a pointer of type cast-type. malloc() returns a
pointer (of cast type) to an area of memory with size
byte-size.
calloc():
calloc() function is another function that reserves memory at
the run time. It is normally used to request multiple blocks of
storage each of the same size and then sets all bytes to zero.
calloc() stands for contiguous memory allocation and is
primarily used to allocate memory for arrays. The syntax of
calloc() can be given as:
ptr=(cast-type*) calloc(n,elem-size);
The above statement allocates contiguous space for n
blocks each of size elem-size bytes. The only difference
between malloc() and calloc() is that when we use
calloc(), all bytes are initialized to zero. calloc() returns a
pointer to the first byte of the allocated region. free():
The free() is used to release the block of memory.
Syntax:
The general
syntax of the
free()function is,
free(ptr);
where ptr is a pointer that has been created by using
malloc() or calloc(). When memory is deallocated using free(),
it is returned back to the free list within the heap.
realloc():
At times the memory allocated by using calloc() or
malloc() might be insufficient or in excess. In both the
situations we can always use realloc() to change the
memory size already allocated by calloc() and malloc().
This process is called reallocation of memory. The
general syntax for realloc() can be given as,
ptr = realloc(ptr,newsize);
variable ptr. It returns a pointer to the first byte of the memory
block. The allocated new block may be or may not be at the
same region. Thus, we see that realloc() takes two arguments.
The first is the pointer referencing the memory and the second is
the total number of bytes you want to reallocate.

Differences between Array based and Linked based


implementation

Array Linked List

Linked list is an ordered


Array is a collection of elements
collection of elements
Definition having same data type with
which are connected by
common name
links/pointers

Elements can be accessed


Access Sequential access
using index/subscript, random
access

Elements are stored in contiguous Elements are stored at


Memory structure
memory locations available memory space

Insertion and deletion takes more time Insertion and deletion are fast
Insertion & Deletion
in array and easy
Memory is allocated at run
Memory is allocated at compile time
Memory Allocation time i.e dynamic memory
i.e static memory allocation
allocation

Types 1D,2D,multi-dimensional SLL, DLL circular linked list

Each node is dependent


on each other as address
Dependency Each elements is independent
part contains address of
next node in the list

SINGLY LINKED LIST


A singly linked list is a linked list in which each node
contains only one link field pointing to the next node in
the list

SLL

SLL with a Header


Basic operations on a singly-linked list are:
1. Insert – Inserts a new node in the list.
2. Delete – Deletes any node from the list.
3. Find – Finds the position( address ) of any node in the
list.
4. FindPrevious - Finds the position( address ) of the
previous node in the list.
5. FindNext- Finds the position( address ) of the next
node in the list.
6. Display-display the date in the list
7. Search-find whether a element is present in the list or
not

SINGLY LINKED LIST OR ONE WAY CHAIN


Singly linked list can be defined as the collection of ordered
set of elements. The number of elements may vary according
to need of the program. A node in the singly linked list consist
of two parts: data part and link part. Data part of the node
stores actual information that is to be represented by the
node while the link part of the node stores the address of its
immediate successor.

One way chain or singly linked list can be traversed only in


one direction. In other words, we can say that each node
contains only next pointer, therefore we can not traverse the
list in the reverse direction.

Consider an example where the marks obtained by the


student in three subjects are stored in a linked list as shown
in the figure.

In the above figure, the arrow represents the links. The


data part of every node contains the marks obtained by the
student in the different subject. The last node in the list is
identified by the null pointer which is present in the address
part of the last node. We can have as many elements we
require, in the data part of the list.
Operations on Singly Linked List
There are various operations which can be performed on
singly linked list. A list of all such operations is given below.

Node Creation
1
.

s
t
r
u
c
t

n
o
d
e

2
.

{
3. int data;
4.
6. struct node *head, *ptr;
7. ptr = (struct node *)malloc(sizeof(struct node *));
Insertion
The insertion into a singly linked list can be performed at
different positions. Based on the position of the new node
being inserted, the insertion is categorized into the following
categories.
SN Operation Description

1 Insertion at It involves inserting any element at the front of the list. We just
beginning need to a few link adjustments to make the new node as the
head of the list.

2 Insertion at end of It involves insertion at the last of the linked list. The new node
the list can be inserted as the only node in the list or it can be inserted
as the last one. Different logics are implemented in each
scenario.

3 Insertion after It involves insertion after the specified node of the linked list.
specified node We need to skip the desired number of nodes in order to reach
the node after which the new node will be inserted. .

Deletion and Traversing


The Deletion of a node from a singly linked list can be
performed at different positions. Based on the position of the
node being deleted, the operation is categorized into the
following categories.

SN Operation Description

1 Deletion at It involves deletion of a node from the beginning of the list. This
beginning is the simplest operation among all. It just need a few adjustments
in the node pointers.

2 Deletion at the It involves deleting the last node of the list. The list can either be
end of the list empty or full. Different logic is implemented for the different

scenarios.
3 Deletion after It involves deleting the node after the specified node in the list. we need to
specified node skip the desired number of nodes to reach the node after which the node
will be deleted. This requires traversing through the list.

4 Traversing In traversing, we simply visit each node of the list at least once in order to
perform some specific operation on it, for example, printing data part of
each node present in the list.

5 Searching In searching, we match each element of the list with the given element. If
the element is found on any of the location then location of that element is
returned otherwise null is returned. .

Advantages of SLL
1. The elements can be accessed using the next link
2. Occupies less memory than DLL as it has only one next field.
Disadvantages of SLL
1. Traversal in the backwards is not possible
2. Less efficient to for insertion and deletion.

DOUBLY-LINKED LIST
A doubly linked list is a linked list in which each node
has three fields namely Data, Next, Prev. Data-This
field stores the value of the element
Next-This field points to the successor node in the list Prev-This field
points to the predecessor node in the list

PREV DATA NEXT

DLL NODE
DOUBLY LINKED LIST

Basic operations of a doubly -linked list are:


1. Insert – Inserts a new element at the end of the list.
2. Delete – Deletes any node from the list.
3. Find – Finds any node in the list.
4. Print – Prints the list

Doubly linked list is a complex type of linked list in which a


node contains a pointer to the previous as well as the next
node in the sequence. Therefore, in a doubly linked list, a
node consists of three parts: node data, pointer to the next
node in sequence (next pointer) , pointer to the previous node
(previous pointer). A sample node in a doubly linked list is
shown in the figure.

A doubly linked list containing three nodes having numbers


from 1 to 3 in their data part, is shown in the following image.
In C, structure of a node in doubly linked list can be given as :

1
.

s
t
r
u
c
t

n
o
d
e

2
.

{
3. struct node *prev;
4. int data;
5.
The prev part of the first node and the next part of the last
node will always contain null indicating end in each direction.

In a singly linked list, we could traverse only in one direction,


because each node contains address of the next node and it
doesn't have any record of its previous nodes. However,
doubly linked list overcome this limitation of singly linked list.
Due to the fact that, each node of the list contains the
address of its previous node, we can find all the details about
the previous node as well by using the previous address
stored inside the previous part of each node.
Memory Representation of a doubly linked list
Memory Representation of a doubly linked list is shown in the
following image. Generally, doubly linked list consumes more
space for every node and therefore, causes more expansive
basic operations such as insertion and deletion. However, we
can easily manipulate the elements of the list since the list
maintains pointers in both the directions (forward and
backward).

In the following image, the first element of the list that is i.e.
13 stored at address 1. The head pointer points to the starting
address 1. Since this is the first element being added to the
list therefore the prev of the list contains null. The next
node of the list resides at address 4 therefore the first node
contains 4 in its next pointer.

We can traverse the list in this way until we find any node containing
null or -1 in its next part.
Operations
on doubly
linked list
Node
Creation

1
.

s
t
r
u
c
t

n
o
d
e

2
.

{
3. struct node *prev;
4. int data;
5.
7. struct node *head;
All the remaining operations regarding doubly linked list are
described in the following table.

SN Operation Description
1 Insertion at beginning Adding the node into the linked list at beginning.

You might also like