0% found this document useful (0 votes)
45 views37 pages

IT2311 DS-A Notes Unit-1

Abstract Data Types (ADTs) are defined by their operations and constraints, allowing for various implementations across programming languages. Data structures, including primitive and non-primitive types, are essential for organizing and manipulating data efficiently, with applications in areas like operating systems and database management. Linked lists, a type of non-primitive data structure, offer dynamic memory allocation and flexibility in size, contrasting with static arrays.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
45 views37 pages

IT2311 DS-A Notes Unit-1

Abstract Data Types (ADTs) are defined by their operations and constraints, allowing for various implementations across programming languages. Data structures, including primitive and non-primitive types, are essential for organizing and manipulating data efficiently, with applications in areas like operating systems and database management. Linked lists, a type of non-primitive data structure, offer dynamic memory allocation and flexibility in size, contrasting with static arrays.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 37

Abstract Data Types (ADTs)

An abstract data type is defined only by the operations that may be performed on it and by
mathematical pre-conditions and constraints on the effects (and possibly cost) of those operations.

For example, an abstract stack, which is a last-in-first-out structure, could be defined by three
operations: push, that inserts some data item onto the structure, pop, that extracts an item from it, and
peek or top, that allows data on top of the structure to be examined without removal.

An abstract queue data structure, which is a first-in-first-out structure, would also have three
operations, enqueue to join the queue; dequeue, to remove the first element from the queue; and front,
in order to access and serve the first element in the queue.

An ADT may be implemented by specific data types or data structures, in many ways
and in many programming languages; or described in a formal specification language. ADTs are often
implemented as modules: the module's interface declares procedures that correspond to the ADT
operations, sometimes with comments that describe the constraints. This information hiding strategy
allows the implementation of the module to be changed without disturbing the client programs.

Defining an abstract data type (ADT)

An abstract data type is defined as a mathematical model of the data objects that make up a data
type as well as the functions that operate on these objects. There are no standard conventions for
defining them. A broad division may be drawn between "imperative" and "functional" definition styles.

Modules:
The programs can be spit in to smaller sub programs called modules. Each module is a logical
unit and does a specific job or process. The size of the module is kept small by calling other modules.

Modularity has several advantages:

 Modules can be compiled separately which makes debugging process easier.


 Several modules can be implemented and executed simultaneously.
 Modules can be easily enhanced.

An ADT is a set of operations and is an extension of modular design. A useful tool for specifying
the logical properties of a data type is the abstract data type. ADT refers to the basic mathematical
concept that defines the data type. Eg. Objects such as list, set and graph along their operations can be
viewed as ADT's. (or) Abstract Data Type is some data, associated with a set of operations and
mathematical abstractions just as integer, Boolean etc.,
 Basic idea

 Write once, use many.


 Any changes are transparent to the other modules.
.

INTRODUCTION TO DATA STRUCTURES:

As computers become faster and faster the need for programs that can handle large amount of
inputs become more acute which in turn requires choosing suitable algorithm with more efficiency,
therefore the study of data structures became an important task which describes, method of organizing
large amounts of data and manipulating them in a better way.

DATA:

A collection of facts, concepts, figures, observations, occurences or instructions in a formalized


manner.

INFORMATION:

The meaning that is currently assigned to data by means of the conventions applied to those
data(i.e. processed data)

RECORD:

Collection of related fields.

DATATYPE:

Set of elements that share common set of properties used to solve a program.

DATASTRUCTURE:

A way of organizing, storing, and retrieving data and their relationship with each other.

ALGORITHM:

An algorithm is a logical module designed to handle a specific problem relative to a particular


data structure.

DESIRABLE PROPERTIES OF AN EFFECTIVE ALGORITHM:

o it should be clear / complete and definite.


o it should be efficient.
o it should be concise and compact.
o it should be less time consuming.
o effective memory utilization.

APPLICATION OF DATA STRUCTURES:

Some of the applications of data structures are:

 operating systems
 compiler design
 statistical and numerical analysis
.  database management systems
 expert systems
 network analysis

List Abstract Data Type

Data Structure is a systematic way of organizing, storing and retrieving data and their
relationship with each other.

Classification of Data Structure:

There are two main types of Data Structure classification.

1. Primitive Data Structure.


2. Non-primitive Data Structure.

Data Structures

Primitive Data Structure. Non - primitive Data Structure.

Eg. Int, char, float, pointer etc. Linear DS Non- Linear DS

Eg. Array, Linked list, Stack, queue etc. Eg. Trees, Graphs etc.

Primitive Data Structure: It is the basic Data structure which can be directly operated by the machine
instruction.

Non-Primitive Data Structure: It is the Data structure which emphasizes on structuring of a group of
homogenous or heterogeneous data items. It is further classified into two types. They are:

 Linear Data Structures.


 Non- Linear Data Structures.

Linear Data Structures: It is a data structure which contains a linear arrangement of elements in the
memory.

Non-Linear Data Structure: It is a data structure which represents a hierarchical arrangement of


elements.

 Write once, use many.


 Any changes are transparent to the other modules.
List ADT:

A. list is a dynamic data structure. The no. of nodes on a list may vary dramatically as elements are
inserted and removed. The dynamic nature of list is implemented using linked list and the static nature
of list is implemented using array.

In general the list is in the form of elements A1, A2, …, AN, where N is the size of the list associated
with a set of operations:

 Insert: add an element e.g. Insert(X,5)-Insert the element X after the position 5.
 Delete: remove an element e.g. Delete(X)-The element X is deleted.
 Find: find the position of an element (search) e.g. Find(X)-Returns the position of X
 FindKth: find the kth element e.g. Find(L,6)- Returns the element present in the given position
of list L.
 PrintList: Display all the elements from the list.
 MakeEmpty: Make the list as empty list.

Implementation of List:

There are two methods

1. Array
2. Linked list

Array Implementation of list:

Array: A set of data elements of same data type is called array. Array is a static data structure i.e., the
memory should be allocated in advance and the size is fixed. This will waste the memory space when
used space is less than the allocated space. An array implementation allows the following operations.

The basic operations are:

a. Creation of a List.
b. Insertion of a data in the List
c. Deletion of a data from the List
d. Searching of a data in the list

Global Declaration:

int list[25], index=-1;

Note: The initial value of index is -1.

Create Operation:

Procedure:

void create()

int n,i;
printf("\nEnter the no.of elements to be added in the list");

.
scanf("%d",&n);

if(n<=maxsize)

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

scanf("%d",&list[i]);

index++;

else

printf("\nThe size is limited. You cannot add data into the list");

Explanation:

 The list is initially created with a set of elements.


 Get the no. of elements (n) to be added in the list.
 Check n is less than or equal to maximum size. If yes, add the elements to the list.
 Otherwise, give an error message.

Diagram:
.
Insert Operation:

Procedure:

void insert()

int i,data,pos;

printf("\nEnter the data to be inserted");

scanf("%d",&data);

printf("\nEnter the position at which element to be inserted");

scanf("%d",&pos);

if(index<maxsize)

for(i=index;i>=pos-1;i--)

list[i+1]=list[i];

index++;

list[pos-1]=data;

else

printf("\nThe list is full");

Explanation:

 Get the data element to be inserted.


 Get the position at which element is to be inserted.
 If index is less than or equal to maxsize, then Make that position empty by altering the position
of the elements in the list.
 Insert the element in the poistion.
 Otherwise, it implies that the list is empty.
Diagram:

Deletion Operation:

Procedure:

void del()

int i,pos;

printf("\nEnter the position of the data to be deleted");

scanf("%d",&pos);

printf("\nThe data deleted is %d",list[pos-1]);

for(i=pos;i<=index;i++)

list[i-1]=list[i];

index--;

Explanation:

 Get the position of the element to be deleted.


 Alter the position of the elements by performing an assignment operation, list[i-1]=list[i],
where i value ranges from position to the last index of the array.
Diagram:

Display Operation:

Procedure:

void display()

int i;

for(i=0;i<=index;i++)

printf("\t%d",list[i]);

Explanation:

 Formulate a loop, where i value ranges from 0 to index (index denotes the index of the last
element in the array.
 Display each element in the array.

Limitation of array implementation

 An array size is fixed at the start of execution and can store only the limited number of elements.
 Insertion and deletion of array are expensive. Since insertion is performed by pushing the entire
array one position down and deletion is performed by shifting the entire array one position up.

A better approach is to use a Linked List.


.
Linked data structure

Linked data structure is 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 called a
connector.

In linked data structures, the links are usually treated as special data types that can only be
dereferenced or compared for equality. Linked data structures are thus contrasted with arrays and other
data structures that require performing arithmetic operations on pointers. This distinction holds even
when the nodes are actually implemented as elements of a single array, and the references are actually
array indices: as long as no arithmetic is done on those indices, the data structure is essentially a linked
one.

Linking can be done in two ways - Using dynamic allocation and using array index linking.

Linked lists

A linked list is a collection of structures ordered not by their physical placement in memory but
by logical links that are stored as part of the data in the structure itself. It is not necessary that it should
be stored in the adjacent memory locations. Every structure has a data field and an address field. The
Address field contains the address of its successor.

A list is a linear structure. Each item except the first (front, head) has a unique predecessor. Each
item except the last (end, tail) has a unique successor. First item has no predecessor, and last item has
no successor. An item within a list is specified by its position in the list.

Linked list can be singly, doubly or multiply linked and can either be linear or circular.

Singly Linked List

Singly linked list is the most basic linked data structure. In this the elements can be placed
anywhere in the heap memory unlike array which uses contiguous locations. Nodes in a linked list
are linked together using a next field, which stores the address of the next node in the next field of
the previous node i.e. each node of the list refers to its successor and the last node contains the
NULL reference. It has a dynamic size, which can be determined only at run time.

10 40 20 30 Null

Basic operations of a singly-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.
Algorithm:

. The node of a linked list is a structure with fields data (which stored the value of the node)
and*next (which is a pointer of type node that stores the address of the next node).
Two nodes *start (which always points to the first node of the linked list) and *temp (which is
used to point to the last node of the linked list) are initialized.
Initially temp = start and temp->next = NULL. Here, we take the first node as a dummy node.
The first node does not contain data, but it used because to avoid handling special cases in insert and
delete functions.

Functions –

1. Insert – This function takes the start node and data to be inserted as arguments. New node is
inserted at the end so, iterate through the list till we encounter the last node. Then, allocate
memory for the new node and put data in it. Lastly, store the address in the next field of the new
node as NULL.
2. Delete - This function takes the start node (as pointer) and data to be deleted as arguments.
Firstly, go to the node for which the node next to it has to be deleted, If that node points to
NULL (i.e. pointer->next=NULL) then the element to be deleted is not present in the
list.Else, now pointer points to a node and the node next to it has to be removed, declare a
temporary node (temp) which points to the node which has to be removed.

Store the address of the node next to the temporary node in the next field of the node
pointer (pointer->next = temp->next). Thus, by breaking the link we removed the node which is
next to the pointer (which is also temp). Because we deleted the node, we no longer require the
memory used for it, free() will deallocate the memory.
3. Find - This function takes the start node (as pointer) and data value of the node (key) to be
found as arguments. First node is dummy node so, start with the second node. Iterate through
the entire linked list and search for the key.Until next field of the pointer is equal to NULL,
check if pointer->data = key. If it is then the key is found else, move to the next node and
search (pointer = pointer -> next). If key is not found return 0,else return 1.
4. Print - function takes the start node (as pointer) as an argument. If pointer = NULL, then there
is no element in the list. Else, print the data value of the node (pointer->data) and move to the
next node by recursively calling the print function with pointer->next sent as an argument.

Performance:

1. The advantage of a singly linked list is its ability to expand to accept virtually unlimited
number of nodes in a fragmented memory environment.
2. The disadvantage is its speed. Operations in a singly-linked list are slow as it uses
sequential search to locate a node.

Declaration of Linked List

void insert(int X,List L,position P);


void find(List L,int X);
void delete(int x , List L);

typedef struct node *position;


position L,p,newnode;
struct node
. {
int data;
position next;
};
Routine to insert an element in list

Void insert(int X,List L,position p)


{
position newnode;
newnode=malloc(sizeof(struct node));
If(newnode==NULL)
Factal error(“Out of Space”);
else
{
newnode->data=x;
newnode-next=p-next;
p->next=newnode;
}
}

Routine to Insert an Element in the List

Null

Insert(Start,10) - A new node with data 10 is inserted and the next field is updated to NULL. The
next field of previous node is updated to store the address of new node.

10 Null

Insert(Start,20) - A new node with data 20 is inserted and the next field is updated to NULL. The
next field of previous node is updated to store the address of new node.

10 20 Null

L
Insert(Start,30) - A new node with data 30 is inserted and the next field is updated to NULL. The
next field of previous node is updated to store the address of new node.
10 20 30 Null
.

L
P

Insert(Start,40) - A new node with data 40 is inserted and the next field is updated to NULL. The
next field of previous node is updated to store the address of new node.

10 40 20 30 Null

L
P

Routine to check whether a list is Empty

int IsEmpty(List L)
{
if (L->next==NULL)
return(1);
}

Null

if the answer is 1 result is true

if the answer is 0 result is false

Routine to check whether the current position is last in the List

int IsLast(List L , position p)

if(p->next==NULL)

return(1);

}
.

10 40 20 30 Null

L
P
if the answer is 1 result is true

if the answer is 0 result is false

Routine to Find the Element in the List:

position find(List L,int X)

position p;

p=L->next;

while(p!=NULL && p->data!=X)

p=p->next;

return(p);

Find(start,10) - To find an element start from the first node of the list and move to the next with the
help of the address stored in the next field.

10 40 20 30 Null

X
Find Previous

It returns the position of its predecessor.


position find previous (int X,list L)
.
{

position p;

p=L;

while(p->next!=NULL && p->next->data!=X)

p=p->next;

return P;

Routine to Count the Element in the List:

void count(list L)

p=L->next;

while(p!=NULL)

count++;

p=p->next;

print count;

10 40 20 30 Null

L
Routine to Delete an Element in the List:

Delete(start,20) - A node with data 20 is found and the next field is updated to store the NULL
value. The next field of previous node is updated to store the address of node next to the deleted node.
it delete the first occurrence of element X from the list L
.
void delete(int x , List L)
{
position p,Temp;
p=find previous(X,L);
if(!IsLast(p,L))
{
temp=p->next;
p->next=temp->next;
free(temp);
}
}

10 40 20 30 Null

P Temp
After Deletion
mpmp mpmp

10 40 30 Null

Start and Temp

Routine to Delete the List

Deletet(start->next) - To print start from the first node of the list and move to the next
with the help of the address stored in the next field.
void delete list(List L)

.{

position P,temp;

P=L->next;

L->next=NULL;

while(P!=NULL)

temp=P->next;

free(P);

P=temp;

Routine to find next Element in the List

void findnext(int X, List L)

position P;

P=L->next;

while(P!=NULL && P->data!=X)

P->next;

return P->next;

it returns its position of successor.


Doubly-Linked List

. Doubly-linked list is a more sophisticated form of linked list data structure. Each node of the
list contain two references (or links) – one to the previous node and other to the next node. The
previous link of the first node and the next link of the last node points to NULL. In comparison to
singly-linked list, doubly-linked list requires handling of more pointers but less information is
required as one can use the previous links to observe the preceding element. It has a dynamic size,
which can be determined only at run time.

10 20 30 40

NULL L NULL

Basic operations of a singly-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.

Algorithm:

The node of a linked list is a structure with fields data (which stored the value of the node),
*previous (which is a pointer of type node that stores the address of the previous node) and *next
(Which is a pointer of type node that stores the address of the next node)?

Two nodes *start (which always points to the first node of the linked list) and *temp (which is
used to point to the last node of the linked list) are initialized.
Initially temp = start, temp->prevoius = NULL and temp->next = NULL. Here, we take
the first node as a dummy node. The first node does not contain data, but it used because to avoid
handling special cases in insert and delete functions.

Functions –

1. Insert – This function takes the start node and data to be inserted as arguments. New node is
inserted at the end so, iterate through the list till we encounter the last node. Then, allocate
memory for the new node and put data in it. Lastly, store the address of the previous node in
the previous field of the new node and address in the next field of the new node as NULL.

2. Delete - This function takes the start node (as pointer) and data to be deleted as arguments.
Firstly, go to the node for which the node next to it has to be deleted, If that node points to
NULL (i.e. pointer->next=NULL) then the element to be deleted is not present in the list.
Else, now pointer points to a node and the node next to it has to be removed, declare a
temporary node (temp) which points to the node which has to be removed.
Store the address of the node next to the temporary node in the next field of the node
pointer (pointer->next = temp->next) and also link the pointer and the node next to the node
to be deleted (temp->prev = pointer). Thus, by breaking the link we removed the node which
is next to the pointer (which is also temp). Because we deleted the node, we no longer require
the memory used for it, free() will deallocate the memory.
.

3. Find - This function takes the start node (as pointer) and data value of the node (key) to be
found as arguments. First node is dummy node so, start with the second node. Iterate through
the entire linked list and search for the key.
Until next field of the pointer is equal to NULL, check if pointer->data = key. If it is then the
key is found else, move to the next node and search (pointer = pointer -> next). If key is not
found return 0, else return 1.

4. Print - function takes the start node (as pointer) as an argument. If pointer = NULL, then there
is no element in the list. Else, print the data value of the node (pointer->data) and move to the
next node by recursively calling the print function with pointer->next sent as an argument.

Performance:

1. The advantage of a singly linked list is that we don‟t need to keep track of the previous
node for traversal or no need of traversing the whole list for finding the previous node.
2. The disadvantage is that more pointers needs to be handled and more link need to
updated.

General declaration routine:

typedef struct node *position;


struct node
{
int data;
position prev;
position next;
};

Initially

NULL

L
Routine for insert an element:
void insert (int x,List L,position p)
{
.
position newnode;
newnode = malloc(sizeof(struct node));
if(newnode==NULL)
Fatal error (“out of space”);
else
{
newnode ->data = x;
newnode ->next =p->next;
p->next ->prev = newnode;
p->next= newnode;
newnode ->prev =p;
}

Insert(Start,10) - A new node with data 10 is inserted, the next field is updated to NULL and the
previous field is updated to store the address of the previous node. The next field of previous node
is updated to store the address of new node.

10

NULL L

Insert(Start,20) - A new node with data 20 is inserted, the next field is updated to NULL and the
previous field is updated to store the address of the previous node. The next field of previous node is
updated to store the address of new node.

10 20

NULL L NULL

Insert(Start,30) - A new node with data 30 is inserted, the next field is updated to NULL and the
previous field is updated to store the address of the previous node. The next field of previous node is
updated to store the address of new node.

10 20 30

NULL L NULL
Insert(Start,40) - A new node with data 40 is inserted, the next field is updated to NULL and the
previous field is updated to store the address of the previous node. The next field of previous node is
updated to store the address of new node.

.
10 20 30

NULL NULL P
L NULL

10 40 20 30

NULL L NULL NULL

Routine to displaying an element:

void display(List L)
{
p=L->next;
while (p!=NULL)
{
print p->data;
p=p->next;
}
print NULL
}

Print(start) 10, 40, 20, 30


To print start from the first node of the list and move to the next with the help of the address stored
in the next field.

10 40 20 30

L NULL NULL
Routine for deleting an element:
.
Void delete (int x ,List L)
{
Position p , temp;
p=find(x,L);
If(isLast(p,L))
{
temp =p;
p->prev->next=NULL;
free(temp);
}
else
{
temp = p;
p->prev->next=p-next;
p-next-prev = p->prev;
free(temp);
}
Delete(start,10) - A node with data 10 is found, the next and previous fields are updated to store the
NULL value. The next field of previous node is updated to store the address of node next to the
deleted node. The previous field of the node next to the deleted one is updated to store the address of
the node that is before the deleted node.

10 40 20 30

NULL L NULL

40 20 30

NULL L
NULL

Find(start,10) „Element Not Found‟

To print start from the first node of the list and move to the next with the help of the address stored
in the next field.
.

40 20 30

NULL L
NULL

Circular Linked List

Circular linked list is a more complicated linked data structure. In this the elements can be
placed anywhere in the heap memory unlike array which uses contiguous locations. Nodes in a linked
list are linked together using a next field, which stores the address of the next node in the next field of
the previous node i.e. each node of the list refers to its successor and the last node points back to the
first node unlike singly linked list. It has a dynamic size, which can be determined only at run time.

Basic operations of a singly-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.

Algorithm:

The node of a linked list is a structure with fields data (which stored the value of the node)
and*next (which is a pointer of type node that stores the address of the next node).

Two nodes *start (which always points to the first node of the linked list) and *temp (which is used
to point to the last node of the linked list) are initialized.
Initially temp = start and temp->next = start. Here, we take the first node as a dummy

node. The first node does not contain data, but it used because to avoid handling special cases in
insert and delete functions.

Functions –

1. Insert – This function takes the start node and data to be inserted as arguments. New node is
inserted at the end so, iterate through the list till we encounter the last node. Then, allocate
memory for the new node and put data in it. Lastly, store the address of the first node (start) in
the next field of the new node.
2. Delete - This function takes the start node (as pointer) and data to be deleted as arguments.
Firstly, go to the node for which the node next to it has to be deleted, If that node points to
NULL (i.e. pointer->next=NULL) then the element to be deleted is not present in the list. Else,
now pointer points to a node and the node next to it has to be removed, declare a temporary
node (temp) which points to the node which has to be removed. Store the address of the node
next to the temporary node in the next field of the node pointer (pointer->next = temp->next).
. Thus, by breaking the link we removed the node which is next to the pointer (which is also
temp). Because we deleted the node, we no longer require the memory used for it, free() will
deallocate the memory.
3. Find - This function takes the start node (as pointer) and data value of the node (key) to be
found as arguments. A pointer start of type node is declared, which points to the head node
of the list (node *start = pointer). First node is dummy node so, start with the second node.
Iterate through the entire linked list and search for the key.
Until next field of the pointer is equal to start, check if pointer->data = key. If it is then the key
is found else, move to the next node and search (pointer = pointer -> next). If key is not found
return 0, else return 1.
4. Print - function takes the start node (as start) and the next node (as pointer) as arguments.
If pointer = start, then there is no element in the list. Else, print the data value of the node
(pointer->data) and move to the next node by recursively calling the print function with
pointer->next sent as an argument.

Performance:

1. The advantage is that we no longer need both a head and tail variable to keep track of
the list. Even if only a single variable is used, both the first and the last list elements can be
found in constant time. Also, for implementing queues we will only need one pointer namely
tail, to locate both head and tail.
2. The disadvantage is that the algorithms have become more complicated.

Example:

Initially

Insert(Start,2) - A new node with data 2 is inserted and the next field is updated to store the
address of start node. The next field of previous node is updated to store the address of new node.

start temp
Insert(Start,6) - A new node with data 6 is inserted and the next field is updated to store the
.address of start node. The next field of previous node is updated to store the address of new
node.

Start temp

Insert(Start,1) – A new node with data 1 is inserted and the next field is updated to store the address
of start node. The next field of previous node is updated to store the address of new node.

Start temp

Insert(Start,10) – A new node with data 6 is inserted and the next field is updated to store the address
of start node. The next field of previous node is updated to store the address of new node.

Start temp

Insert(Start,8) - A new node with data 6 is inserted and the next field is updated to store the address
of start node. The next field of previous node is updated to store the address of new node.

Start temp

Print(start->next) 2 ,6 ,1 ,10 ,8

Start temp
To print start from the first node of the list and move to the next with the help of the address stored
.
in the next field.
Delete(start,1) - A node with data 1 is found and the next field is updated to store the NULL value.
The next field of previous node is updated to store the address of node next to the deleted node.

Start Pointer temp

Start temp

Find(start,10) „Element Found‟

Start temp

To find, start from the first node of the list and move to the next with the help of the address stored
in the next field.
. Applications of List

Polynomial Manipulation

Polynomial manipulations such as addition, subtraction & differentiation etc.. can be performed
using linked list

Declaration for Linked list implementation of Polynomial ADT

struct poly
{
int coeff;
int power;
struct poly * Next;
}*list1,*list2,*list3;

Creation of the Polynomial

poly create(poly*head1,poly*newnode1)
{
poly *ptr;
if(head1==NULL)
{
head1=newnode1;
return (head1);
}
else
{
ptr=head1;
while(ptr->next!=NULL)
ptr=ptr->next;
ptr->next=newnode1;
}
return(head1);
}

Addition of two polynomials

void add()
{
poly*ptr1,*ptr2,*newnode;
ptr1=list1;
ptr2=list2;
while(ptr1!=NULL&&ptr2!=NULL)
{
newnode=malloc(sizeof(struct poly));
if(ptr1->power==ptr2->power)
{
. newnode->coeff=ptr1-coeff+ptr2->coeff;
newnode->power=ptr1->power;
newnode->next=NULL;
list3=create(list3,newnode);
ptr1=ptr1->next;
ptr2=ptr2->next;
}
else
{
if(ptr1-power>ptr2-power)
{
newnode->coeff=ptr1->coeff;
newnode->power=ptr1->power;
newnode->next=NULL;
list3=create(list3,newnode);
ptr1=ptr1->next;
}
else
{
newnode->coeff=ptr2->coeff;
newnode->power=ptr2->power;
newnode->next=NULL;
list3=create(list3,newnode);
ptr2=ptr2->next;
}
}
}

Subtraction of two polynomial

void sub()
{
poly*ptr1,*ptr2,*newnode;
ptr1=list1;
ptr2=list2;
while(ptr1!=NULL&&ptr2!=NULL)
{

newnode=malloc(sizeof(struct poly));

if(ptr1->power==ptr2->power)
{
newnode->coeff=(ptr1-coeff)-(ptr2->coeff);
newnode->power=ptr1->power;
newnode->next=NULL;
. list3=create(list3,newnode);
ptr1=ptr1->next;
ptr2=ptr2->next;
}
else
{
if(ptr1-power>ptr2-power)
{
newnode->coeff=ptr1->coeff;
newnode->power=ptr1->power;
newnode->next=NULL;
list3=create(list3,newnode);
ptr1=ptr1->next;
}
else
{
newnode->coeff=-(ptr2->coeff);
newnode->power=ptr2->power;
newnode->next=NULL;
list3=create(list3,newnode);
ptr2=ptr2->next;
}
}

}
Step Count Method
The step count method is one of the methods to analyze the Time complexity of an
algorithm. In this method, we count the number of times each instruction is executed. Based
on that we will calculate the Time Complexity.The step Count method is also called as
Frequency Count method. Let us discuss step count for different statements:
1. Comments:
 Comments are used for giving extra meaning to the program. They are not executed
during the execution. Comments are ignored during execution.
 Therefore the number of times that a comment executes is 0.
2. Conditional statements:
 Conditional statements check the condition and if the condition is correct then the
conditional subpart will be executed. So the execution of conditional statements
happens only once. The compiler will execute the conditional statements to check
whether the condition is correct or not so it will be executed one time.
 In if-else statements the if statement is executed one time but the else statement will
be executed zero or one time because if the “if” statement is executed then the else
statement will not execute.
 In switch case statements the starting switch (condition) statement will be executed
one time but the inner case statements will execute if none of the previous case
statements are executed.
 In nested if and if else ladder statements also the initial if statement is executed at
least once but inner statements will be executed based on the previous statements’
execution.
3. Loop statements:
 Loop statements are iterative statements. They are executed one or more times based
on a given condition.
 A typical for(i = 0; i ≤ n; i++) statement will be executed “n+1” times for the first n
times the condition is satisfied and the inner loop will be executed and for the
(n+1)th time the condition is failed and the loop terminates.
 While: The statement is executed until the given condition is satisfied.
 Do while: The statement will repeat until the given condition is satisfied. The do-
while statement will execute at least once because for the first time it will not check
the condition.
4. Functions:
 Functions are executed based on the number of times they get called. If they get called
n times they will be executed n times. If they are not called at least once then they
will not be executed. Other statements like BEGIN, END and goto statements will be
executed one time.
Illustration of Step Count Method:
Analysis of Linear Search algorithm
Let us consider a Linear Search Algorithm.
Linearsearch(arr, n, key)
{
i = 0;
for(i = 0; i < n; i++)
{
if(arr[i] == key)
{
printf(“Found”);
IT2311 - DATA STRUCTURES AND ALGORITHMS

}
}
Where,
i = 0, is an initialization statement and takes O(1) times.
for(i = 0;i < n ; i++), is a loop and it takes O(n+1) times .
if(arr[i] == key), is a conditional statement and takes O(1) times.
printf(“Found”), is a function and that takes O(0)/O(1) times.
Therefore Total Number of times it is executed is n + 4 times. As we ignore lower
exponents in time complexity total time became O(n).
Time complexity: O(n).
Auxiliary Space: O(1)
Linear Search in Matrix
Searching for an element in a matrix
AlgoMatrixsearch(mat[][], key)
{
// number of rows;
r := len(mat)
// number of columns;
c :=len(mat[0])
for(i = 0; i < r; i++)
{
for(j = 0; j < c; j++)
{
if(mat[i][j] == key)
{
printf(“Element found”);
}
}
}
}
Where,
r = len(mat), takes O(1) times.
c = len(mat[0]), takes O(1) times
for(i = 0; i < r; i++), takes O(r + 1) times
for(j = 0; j < c; j++), takes O(( c + 1 ) ) for each time the outer loop is satisfied. So
total r times the loop is executed.
if(mat[i][j] == key), takes O(1) times
printf(“Element found”), takes O(0)/O(1) times.
Therefore Total Number of times it is executed is (1 + 1 + (r + 1) + (r) * (c + 1) +
1) times. As we ignore the lower exponents, total complexity became O(r * (c + 1)).
the mat is an array so it takes n*n words , k, c, r, i, j will take 1 word.
Time Complexity: O(n2).
Auxiliary Space: O(n2)
Advantages of using this method over others:
 Easy to understand and implement.
 We will get an exact number of times each statement is executed.
Introduction to Algorithms and Asymptotic analysis

1 Algorithm: Design

An algorithm is a finite sequence of logically related instructions to solve a computational problem.


The following problems are well defined and are considered to be computational problems.

– Given an integer x, test whether x is prime or not.


– Given a program P , check whether P runs into an infinite loop.

Any algorithm must have an input provided by the user and must produce an output. Computa-
tional instructions in the algorithm must involve only basic algebraic (arithmetic) operations and it
should terminate after a finite number of steps. Finally, any algorithm must involve unambiguous
instructions and produce the desired output. Mathematically, an algorithm can be represented as
a function, F : I → O, where I is the set of inputs and O is the set of outputs generated by the
algorithm. The word algorithm comes from the name of Persian author “Abu Jafar Mohammad ibn
Musa al Khawarizmi ”.

Note: Although, the phrase algorithm is associated with computer science, the notion compu-
tation (algorithm) did exist for many centuries.

The definition of algorithm sparks natural fundamental questions;

– How to design an algorithm for a given problem?


– Is every problem algorithmically solvable? If so, how many algorithms can a problem have and
how to find the efficient one.

We shall address these questions in this lecture. Let us consider an example of finding a maximum
element in an array of size n. An algorithm is typically described using pseudo code as follows:

Example : Finding a maximum element in an array

Algo Max-array(A,n)
{
Max = A[1];
for i = 2 to n do
if ( A[i] > Max ) then Max = A[i];

return Max;
}
Note: The maximum can also be computed by sorting the array in an increasing order (decreasing
order) and picking the last element (first element). There are at least five different algorithms to
find a maximum element and therefore, it is natural ask for an efficient algorithm. This calls for
the study of analysis of algorithms.

1.1 Types of Algorithm


There are two ways to write an algorithm, namely, top-down approach (Iterative algorithm) and
bottom-up approach (Recursive algorithm). For example, the iterative and recursive algorithm for
finding a factorial of a given number n is given below:
1. Iterative :
Fact(n)
{
for i = 1 to n
fact = fact * i;
return fact;
}

Here the factorial is calculated as 1 × 2 × 3 × . . . × n.

2. Recursive :
Fact(n)
{
if n = 1
return 1;
else
return n * fact(n-1);
}

Here the factorial is calculated as n × (n − 1) × . . . × 1.

2 Algorithm: Analysis

The analysis of algorithms involves the following measures and are listed in the order of priority.
1. Correctness: For any algorithm, a proof of correctness is important which will exhibit the
fact that the algorithm indeed output the desired answer. Often, discovering the underlying
combinatorics is quite challenging.
2. Amount of work done (time complexity) : For a problem, there may exist many algo-
rithms. Comparison of two algorithms is inevitable to pick the best of two. By analysis we mean,
the amount of time and space required to execute the algorithm. A computational problem can
have many algorithms but the estimation of time and space complexity provide an insight into
reasonable directions of search for finding the efficient algorithm. The time Complexity does
not refer to the actual running time of an algorithm in terms of millisec (system time). The
actual running time depends on the system configuration. An algorithm taking 100µs on Intel
machine may take 10µs on an AMD machine. So, the time complexity must be defined indepen-
dent of the system configuration. For each algorithm, we focus on step count: the number
of times each statement in the algorithm is executed, which in some sense reflects the
time complexity. The step count focuses on primitive operations along with basic operations.
Moreover, this number increases with the problem size. Therefore, we express the step count
(time complexity) as a function of the input size. The notion input size and primitive operations
vary from one problem to another. The popular ones are;

Common Problems Associated Primitive Input Size


Operations
(Search Problem) Find x in an Comparison of x with other ele- Array size.
array A ments of A
Multiply 2 matrices A and B Multiplication and Addition Dimension of the matrix.
(Arithmetic on Matrices)
Sorting (Arrangement of ele- Comparison Array size.
ments in some order)
Graph Traversal Number of times an edge is tra- the number of vertices and
versed edges.
Any Recursive Procedure Number of recursive calls + time Array size (for array related
spent on each recursive call problems).
Finding Factorial Multiplication the input number.
Finding LCM(a,b) Basic arithmetic (division, subtrac- the number of bits needed to
tion) represent a and b.
3. Note: The number of primitive operations increases with the problem size. Therefore, we express
the step count (time complexity) as a function of the input size. Space complexity is a related
complexity measure that refers to the amount of space used by an algorithm. Here again,
the analysis focuses on the number of words required leaving the system details. It is good
to highlight that this measure is considered less significant compared to the time complexity
analysis.
4. Optimality: For a given combinatorial problem, there may exist many algorithms. A natural
question is to find the best algorithm (efficient), i.e., one might be interested in discovering best
ever possible. This study also helps us to understand the inherent complexity of the problem.

2.1 Step-count Method and Asymptotic Notation


In this section, we shall look at analysis of algorithms using step count method. Time and space
complexity are calculated using step count method. Some basic assumptions are;
– There is no count for { and } .
– Each basic statement like ’assignment’ and ’return’ have a count of 1.
– If a basic statement is iterated, then multiply by the number of times the loop is run.
– The loop statement is iterated n times, it has a count of (n + 1). Here the loop runs n times
for the true case and a check is performed for the loop exit (the false condition), hence the
additional 1 in the count.
Examples for Step-Count Calculation:

1. Sum of elements in an array

Step-count (T.C) Step-count (Space)


Algorithm Sum(a,n)
{ 0
sum = 0; 1 1 word for sum
for i = 1 to n do n+1 1 word each for i and n
sum = sum + a[i]; n n words for the array a[]
return sum; 1
} 0

Total: 2n+3 (n+3) words

2. Adding two matrices of order m and n

Algorithm Add(a, b, c, m, n) Step Count


{
for i = 1 to m do ---- m + 1
for j = 1 to n do ---- m(n + 1)
c[i,j] = a[i,j] + b[i,j] ---- m.n
} -------------
Total no of steps= 2mn + 2m + 2

Note that the first ’for loop’ is executed m + 1 times, i.e., the first m calls are true calls during
which the inner loop is executed and the last call (m + 1)th call is a false call.

3. Fibonacci series

algorithm Fibonacci(n) Step Count


{
if n <= 1 then ---- 1
output ’n’
else
f2 = 0; ---- 1
f1 = 1; ---- 1

for i = 2 to n do ---- n
{
f = f1 + f2; ---- n - 1
f2 = f1; ---- n - 1
f1 = f; ---- n - 1
}
output ’f’ ---- 1
} --------
Total no of steps= 4n + 1
Note that if n ≤ 1 then the step count is just two and 4n + 1 otherwise.

4. Recursive sum of elements in an array


algorithm RecursiveSum(a, n) Step Count
{
if n <= 0 then ---- 1
return 0; ---- 1
else
return RecursiveSum(a, n-1) + a[n]; ---- 2 + Step Count of recursive call
}
Note that step count of two is added at each recursive call. One count for making a ’recursive call’
with (n − 1) size input and the other count is for addition when the recursion bottoms out. Let the
Step count of array of size n be denoted as T(n), then the step-count for the above algorithm is
– T(n) = 3 + T(n-1), n > 0 /* one count for the ’if’ statement and two counts at
’return statement’ + count on recursive call */
– T(n) = 2, n ≤ 0
Solving, the above equation yields T (n) = 3n + 2.

2.2 Order of Growth


Performing step count calculation for large algorithms is a time consuming task. A natural way is
to upper bound the time complexity instead of finding the exact step count. Order of Growth or
Rate of Growth of an algorithm gives a simple characterization of the algorithm’s efficiency by
identifying relatively significant term in the step count. (e.g.) For an algorithm with a step count
2n2 +3n+1, the order of growth depends on 2n2 for large n. Other terms are relatively insignificant
as n increases. Asymptotic analysis is a technique that focuses analysis on the ’significant term’. In
the next section, we shall look at some of the commonly used asymptotic notations in the literature.
The popular ones are;
1. Big-oh Notation (O) - To express an upper bound on the time complexity as a function of the
input size.
2. Omega (Ω) - To express a lower bound on the time complexity as a function of the input size.
3. Theta (Θ) - To express the tight bound on the time complexity as a function of the input size.

2.3 Asymptotic upper bounds


Big-oh notation
The function f (n) = O(g(n)) if and only if there exist positive constants c, n0 such that
f (n) ≤ cg(n), ∀n ≥ n0 . Big-oh can be used to denote all upper bounds on the time complexity of
an algorithm. Big-oh also captures the worst case analysis of an algorithm.
Examples:
1. 3n + 2
3n + 2 ≤ 4n, c = 4 ∀n ≥ 2
⇒ 3n + 2 = O(n)
Note that 3n + 2 is O(n2 ), O(n3 ), O(2n ), O(10n ) as per the definition. i.e., it captures all upper
bounds. For the above example, O(n) is a tight upper bound whereas the rest are loose upper
bounds. Is there any notation which captures all the loose upper bounds?
2. 100n + 6 = O(n)
100n + 6 ≤ 101.n, c = 101 ∀n ≥ 6. One can also write 100n + 6 = O(n3 ).
3. 10n2 + 4n + 2 = O(n2 )
10n2 + 4n + 2 ≤ 11.n2 , c = 11 ∀n ≥ 5
4. 6.2n + n2 = O(2n )
6.2n + n2 ≤ 7.2n , c = 7 ∀n ≥ 7
5. 3n + 3 = O(2n )
3n + 3 ≤ 10.2n , c = 10 ∀n ≥ 5
6. n3 + n + 5 = O(n3 )
n3 + n + 5 ≤ 7.n3 , c = 7, ∀n ≥ 0

Remark:

– Note that 3n + 2 6= O(1). i.e., there does not exist c and n0 such that, 3n + 2 ≤ c.1 for all n
beyond n0 . However large the c is, there exist n beyond which 3n + 2 > c.1.
– 10n2 + 4n + 2 6= O(n).
– 3n 6= O(2n ). We shall prove this by contradiction. Suppose 3n = O(2n ), then by definition,
3n ≤ c2n . This implies that 1.5n ≤ c where c is a positive constant, which is a contradiction.
– n! ≤ c.nn for c =√1 and ∀n ≥ 2 i.e., n! = O(nn ). Moreover, from Stirling’s approximation it
follows that n! ≈ 2nπ.nn e−n

Next we present the notation which precisely captures the loose upper bounds.

o-notation
The asymptotic upper bound provided by O-notation may or may not be asymptotically tight.
The bound 2n2 =O(n2 ) is asymptotically tight, but the bound 2n=O(n2 ) is not. We use o-notation
(Little oh) to denote an upper bound that is not asymptotically tight. We formally define as
f (n) = o(g(n)) if for any positive constant c > 0, there exists a positive constant n0 > 0 such that
0 ≤ f (n) < c.g(n) for all n ≥ n0 .
Note that in the definition the inequality works for any positive constant c > 0. This is true because
g(n) is a loose upper bound, and hence g(n) is polynomially larger than f (n) by n ,  > 0. Due to
this n , the contribution of c to the inequality is minimal which is why the quantifier in o notation
is universal whereas in O is existential.
For example,

1. 2n = o(n2 ), but 2n2 6= o(n2 ). Note that here n2 is polynomially larger than 2n by n ,  = 1.
2. 100n + 6 = o(n1.2 ) Here n1.2 is polynomially larger than 100n + 6 by n ,  = 0.2. For any positive
constant c, there exist n0 such that ∀n ≥ n0 , 100n + 6 ≤ c.n1.2
3. 10n2 + 4n + 2 = o(n3 ) Here n3 is polynomially larger than 10n2 + 4n + 2 by n ,  = 1
4. 6.2n + n2 = o(3n ) Note that 3n is 1.5n × 2n . So for any c > 0, 2n ≤ c.3n . The value of c is
insignificant as 1.5n dominates any c > 0.
5. 3n + 3 = o(n1.00001 ) Here n1.00001 is polynomially larger than 3n + 3 by n ,  = 0.00001
6. n3 + n + 5 = o(n3.1 ) Here n3.1 is polynomially larger than n3 + n + 5 by n ,  = 0.1

Intuitively, in o-notation, the function f (n) becomes insignificant relative to g(n) as n approaches
f (n)
infinity; that is, lim =0
n→∞ g(n)
Having looked at the upper bounds, in the similar way we shall now see asymptotic lower bounds.
2.4 Asymptotic lower bounds

Omega notation
The function f (n) = Ω(g(n)) if and only if there exist positive constants c, n0 such that f (n) ≥
c.g(n), ∀n ≥ n0 . Omega can be used to denote all lower bounds of an algorithm. Omega notation
also denotes the best case analysis of an algorithm.
Examples:

1. 3n + 2
3n + 2 ≥ n, ∀n ≥ 1
⇒ 3n + 2 = Ω(n)
2. 10n2 + 4n + 2 = Ω(n2 )
10n2 + 4n + 2 ≥ n2 , c = 1 ∀n ≥ 1
3. n3 + n + 5 = Ω(n3 )
n3 + n + 5 ≥ n3 , c = 1, ∀n ≥ 0
4. 2n2 + nlogn + 1 = Ω(n2 )
2n2 + nlogn + 1 ≥ 2.n2 , c = 2, ∀n ≥ 1
5. 6.2n + n2 = Ω(2n ) = Ω(n2 ) = Ω(n) = Ω(1)
6.2n + n2 ≥ 2n , c = 1 ∀n ≥ 1
Of the above, Ω(2n ) is a tight lower bound, while all others are loose lower bounds.

Remark:

– 3n2 + 2 6= Ω(n3 ). Reason: There does not exist a positive constant c such that 3n2 + 2 ≥ c.n3
for every n ≥ n0 as we cannot bound n by a constant. That is, on the contrary if 3n2 + 2 ≥ c.n3 ,
then n ≤ 1c is a contradiction.
– 3.2n 6= Ω(n!).
– 5 6= Ω(n).

ω-notation
We use ω-notation to denote a lower bound that is not asymptotically tight.
We define ω(g(n)) (little-omega) as
f (n) = ω(g(n)) if for any positive constant c > 0, there exists a positive constant n0 > 0 such that
0 ≤ c.g(n) < f (n) for all n ≥ n0 .
For example,

1. n2 = ω(n) but n2 6= ω(n2 ).


2. 3n + 2 = ω(log(n))
3. 10n3 + 4n + 2 = ω(n2 )
4. 5n6 + 7n + 9 = ω(n3 )
5. 2n2 + nlogn + 1 = ω(n1.9999999 )
6. 15 × 3n + n2 = ω(2n ) = ω(n2 ) = ω(n) = ω(1)

f (n)
The relation f (n) = ω(g(n)) implies that lim = ∞, if the limit exists. That is, f (n) becomes
n→∞ g(n)
arbitrarily large relative to g(n) as n approaches infinity.

You might also like