0% found this document useful (0 votes)
24 views84 pages

Basic Concepts: Introduction To Data Structures

Uploaded by

K. Vishal
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)
24 views84 pages

Basic Concepts: Introduction To Data Structures

Uploaded by

K. Vishal
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/ 84

Basic Concepts: Introduction to Data Structures:

A data structure is a way of storing data in a computer so that it can be used efficiently and it will allow
the most efficient algorithm to be used. The choice of the data structure begins from the choice of an
abstract data type (ADT). A well-designed data structure allows a variety of critical operations to be
performed, using as few resources, both execution time and memory space, as possible. Data structure
introduction refers to a scheme for organizing data, or in other words it is an arrangement of data in
computer's memory in such a way that it could make the data quickly available to the processor for
required calculations.

A data structure should be seen as a logical concept that must address two fundamental concerns.

1. First, how the data will be stored, and


2. Second, what operations will be performed on it.
As data structure is a scheme for data organization so the functional definition of a data structure should
be independent of its implementation. The functional definition of a data structure is known as ADT
(Abstract Data Type) which is independent of implementation. The way in which the data is organized
affects the performance of a program for different tasks. Computer programmers decide which data
structures to use based on the nature of the data and the processes that need to be performed on that
data. Some of the more commonly used data structures include lists, arrays, stacks, queues, heaps, trees,
and graphs.

Classification of Data Structures:

Data structures can be classified as

 Simple data structure


 Compound data structure
 Linear data structure
 Non linear data structure

[Fig 1.1 Classification of Data Structures]

Simple Data Structure:

Simple data structure can be constructed with the help of primitive data structure. A primitive data
1
structure used to represent the standard data types of any one of the computer languages. Variables,
arrays, pointers, structures, unions, etc. are examples of primitive data structures.
Compound Data structure:

Compound data structure can be constructed with the help of any one of the primitive data structure and
it is having a specific functionality. It can be designed by user. It can be classified as

 Linear data structure


 Non-linear data structure
Linear Data Structure:

Linear data structures can be constructed as a continuous arrangement of data elements in the memory.
It can be constructed by using array data type. In the linear Data Structures the relationship of adjacency
is maintained between the data elements.

Operations applied on linear data structure:

The following list of operations applied on linear data structures

1. Add an element
2. Delete an element
3. Traverse
4. Sort the list of elements
5. Search for a data element
For example Stack, Queue, Tables, List, and Linked Lists.

Non-linear Data Structure:

Non-linear data structure can be constructed as a collection of randomly distributed set of data item
joined together by using a special pointer (tag). In non-linear Data structure the relationship of
adjacency is not maintained between the data items.

Operations applied on non-linear data structures:

The following list of operations applied on non-linear data structures.


1. Add elements
2. Delete elements
3. Display the elements
4. Sort the list of elements
5. Search for a data element
For example Tree, Decision tree, Graph and Forest

Abstract Data Type:

An abstract data type, sometimes abbreviated ADT, is a logical description of how we view the data
and the operations that are allowed without regard to how they will be implemented. This means that
we are concerned only with what data is representing and not with how it will eventually be
constructed. By providing this level of abstraction, we are creating an encapsulation around the data.
The idea is that by encapsulating the details of the implementation, we are hiding them from the user’s
view. This is called information hiding. The implementation of an abstract data type, often referred to
as a data structure, will require that we provide a physical view of the data using some collection of
2
programming constructs and primitive data types.

3
[Fig. 1.2: Abstract Data Type (ADT)]

Algorithms:

Structure and Properties of Algorithm:

An algorithm has the following structure

1. Input Step

2. Assignment Step

3. Decision Step

4. Repetitive Step

5. Output Step

An algorithm is endowed with the following properties:

1. Finiteness: An algorithm must terminate after a finite number of steps.

2. Definiteness: The steps of the algorithm must be precisely defined or unambiguously specified.

3. Generality: An algorithm must be generic enough to solve all problems of a particular class.

4. Effectiveness: the operations of the algorithm must be basic enough to be put down on pencil and
paper. They should not be too complex to warrant writing another algorithm for the operation.

5. Input-Output: The algorithm must have certain initial and precise inputs, and outputs that may be
generated both at its intermediate and final steps.

Different Approaches to Design an Algorithm:

An algorithm does not enforce a language or mode for its expression but only demands adherence to its
properties.

Practical Algorithm Design Issues:

4
1. To save time (Time Complexity): A program that runs faster is a better program.

2. To save space (Space Complexity): A program that saves space over a competing program is

5
considerable desirable.

Efficiency of Algorithms:

The performances of algorithms can be measured on the scales of time and space. The performance of
a program is the amount of computer memory and time needed to run a program. We use two
approaches to determine the performance of a program. One is analytical and the other is experimental.
In performance analysis we use analytical methods, while in performance measurement we conduct
experiments.

Time Complexity: The time complexity of an algorithm or a program is a function of the running time
of the algorithm or a program. In other words, it is the amount of computer time it needs to run to
completion.

Space Complexity: The space complexity of an algorithm or program is a function of the space needed
by the algorithm or program to run to completion.

The time complexity of an algorithm can be computed either by an empirical or theoretical approach.
The empirical or posteriori testing approach calls for implementing the complete algorithms and
executing them on a computer for various instances of the problem. The time taken by the execution of
the programs for various instances of the problem are noted and compared. The algorithm whose
implementation yields the least time is considered as the best among the candidate algorithmic
solutions.

Analyzing Algorithms

Suppose M is an algorithm, and suppose n is the size of the input data. Clearly the complexity f(n) of M
increases as n increases. It is usually the rate of increase of f(n) with some standard functions. The most
common computing times are

O(1), O(log2 n), O(n), O(n log2 n), O(n2), O(n3), O(2n)

Need of Data Structures

As applications are getting complexed and amount of data is increasing day by day, there may arise the
following problems:

Processor speed: To handle very large amount of data, high speed processing is required, but as the data is
growing day by day to the billions of files per entity, processor may fail to deal with that much amount of data.

Data Search: Consider an inventory size of 106 items in a store, If our application needs to search for a
particular item, it needs to traverse 106 items every time, results in slowing down the search process.

Multiple requests: If thousands of users are searching the data simultaneously on a web server, then there are
the chances that a very large server can be failed during that process

In order to solve the above problems, data structures are used. Data is organized to form a data structure in
such a way that all items are not required to be searched and required data can be searched instantly.

1] Arrays –

6
An array is a collection of similar data elements stored at contiguous memory locations. It is the simplest data
structure where each data element can be accessed directly by only using its index number.

2] Linked List –

Linked list is a linear data structure which is used to maintain a list-like structure in the computer memory. It is
a group of nodes that are not stored at contiguous locations. Each node of the list is linked to its adjacent node
with the help of pointers.

3] Stack –

Stack is a linear data structure that follows a specific order during which the operations are performed. The
order could be FILO (First In Last Out) or LIFO (Last In First Out).

The basic operations performed in stack are as follows :

· Push – Adds an item within the stack.

7
· Pop – Deletes or removes an item from the stack.

· Top – Returns the topmost element of the stack.

· IsEmpty – Returns true if the stack is empty.

4] Queue –

Queue is a linear data structure in which elements can be inserted from only one end which is known as rear
and deleted from another end known as front. It follows the FIFO (First In First Out) order.

· Deque – Adds an element to the queue.

· Enqueue – Deletes or removes an element from the queue.

· IsFull – Returns true if the queue is full.

· IsEmpty – Returns true if the queue is empty.

Reference:

https://youtu.be/DFpWCl_49i0

8
LINEAR DATA STRUCTURES

Stacks Primitive Operations:

A stack is a container of objects that are inserted and removed according to the last-in first-out (LIFO)
principle. In the pushdown stacks only two operations are allowed: push the item into the stack, and pop
the item out of the stack. A stack is a limited access data structure - elements can be added and removed
from the stack only at the top. Push adds an item to the top of the stack, pop removes the item from the
top. A helpful analogy is to think of a stack of books; you can remove only the top book, also you can
add a new book on the top.

A stack may be implemented to have a bounded capacity. If the stack is full and does not contain enough
space to accept an entity to be pushed, the stack is then considered to be in an overflow state. The pop
operation removes an item from the top of the stack. A pop either reveals previously concealed items or
results in an empty stack, but, if the stack is empty, it goes into underflow state, which means no items
are present in stack to be removed.

Stack (ADT) Data Structure:

Stack is an Abstract data structure (ADT) works on the principle Last In First Out (LIFO). The last
element add to the stack is the first element to be delete. Insertion and deletion can be takes place at one
end called TOP. It looks like one side closed tube.
 The add operation of the stack is called push operation
 The delete operation is called as pop operation.
 Push operation on a full stack causes stack overflow.
 Pop operation on an empty stack causes stack underflow.
 SP is a pointer, which is used to access the top element of the stack.
 If you push elements that are added at the top of the stack;
 In the same way when we pop the elements, the element at the top of the stack is deleted.

9
Operations of stack:

There are two operations applied on stack they are


1. push
2. pop.
While performing push & pop operations the following test must be conducted on the stack.
1) Stack is empty or not
2) Stack is full or not
Push:

Push operation is used to add new elements in to the stack. At the time of addition first check the stack is
full or not. If the stack is full it generates an error message "stack overflow".
Pop:

Pop operation is used to delete elements from the stack. At the time of deletion first check the stack is
empty or not. If the stack is empty it generates an error message "stack underflow".
Representation of a Stack using Arrays:

Let us consider a stack with 6 elements capacity. This is called as the size of the stack. The number of
elements to be added should not exceed the maximum size of the stack. If we attempt to add new
element beyond the maximum size, we will encounter a stack overflow condition. Similarly, you cannot
remove elements beyond the base of the stack. If such is the case, we will reach a stack underflow
condition.

When a element is added to a stack, the operation is performed by push().

10
When an element is taken off from the stack, the operation is performed by pop().

11
Source code for stack operations, using array:

STACK: Stack is a linear data structure which works under the principle of last in first out. Basic
operations: push, pop, display.

1. PUSH: if (top==MAX), display Stack overflow else reading the data and making stack [top]
=data and incrementing the top value by doing top++.

2. POP: if (top==0), display Stack underflow else printing the element at the top of the stack and
decrementing the top value by doing the top.

3. DISPLAY: IF (TOP==0), display Stack is empty else printing the elements in the stack from
stack [0] to stack [top].

# Python program for implementation of stack

# import maxsize from sys module


# Used to return -infinite when stack is empty from sys import maxsize

# Function to create a stack. It initializes size of stack as 0


def createStack():
stack = []
return stack

# Stack is empty when stack size is 0


def isEmpty(stack):
return len(stack) == 0

# Function to add an item to stack. It increases size by 1


def push(stack, item):
stack.append(item)
print("pushed to stack " + item)

# Function to remove an item from stack. It decreases size by 1


def pop(stack):
if (isEmpty(stack)):
print("stack empty")

12
return str(-maxsize -1) #return minus infinite

return stack.pop()

# Driver program to test above functions


stack = createStack()
print("maximum size of array is",maxsize)
push(stack, str(10))
push(stack, str(20))
push(stack, str(30))
print(pop(stack) + " popped from
stack") print(pop(stack) + " popped
from stack") print(pop(stack) + "
popped from stack") print(pop(stack) +
" popped from stack") push(stack,
str(10))
push(stack, str(20))
push(stack, str(30))
print(pop(stack) + " popped from stack")
Linked List Implementation of Stack:

We can represent a stack as a linked list. In a stack push and pop operations are performed at one end
called top. We can perform similar operations at one end of list using top pointer.

Source code for stack operations, using linked list:

# Python program for linked list implementation of stack

# Class to represent a node


class StackNode:

# Constructor to initialize a node

13
def init (self, data):
self.data = data

14
self.next = None

class Stack:

# Constructor to initialize the root of linked list


def init (self):
self.root = None

def isEmpty(self):
return True if self.root is None else False

def push(self, data):


newNode = StackNode(data)
newNode.next = self.root
self.root = newNode
print ("%d pushed to stack" %(data))

def pop(self):
if (self.isEmpty()):
return float("-inf")
temp = self.root
self.root = self.root.next
popped = temp.data
return popped

def peek(self):
if self.isEmpty():
return float("-inf")
return self.root.data

# Driver program to test above class


stack = Stack()
stack.push(10)
stack.push(20)
stack.push(30)

print ("%d popped from stack" %(stack.pop()))


print ("Top element is %d " %(stack.peek()))
Stack Applications:

1. Stack is used by compilers to check for balancing of parentheses, brackets and braces.

2. Stack is used to evaluate a postfix expression.

3. Stack is used to convert an infix expression into postfix/prefix form.

15
4. In recursion, all intermediate arguments and return values are stored on the processor‟s stack.

5. During a function call the return address and arguments are pushed onto a stack and on return
they are popped off.

6. Depth first search uses a stack data structure to find an element from a graph.

In-fix- to Postfix Transformation:

Procedure:

Procedure to convert from infix expression to postfix expression is as follows:

1. Scan the infix expression from left to right.

2. a) If the scanned symbol is left parenthesis, push it onto the stack.

b) If the scanned symbol is an operand, then place directly in the postfix expression
(output).

c) If the symbol scanned is a right parenthesis, then go on popping all the items from
the stack and place them in the postfix expression till we get the matching left
parenthesis.

d) If the scanned symbol is an operator, then go on removing all the operators from the
stack and place them in the postfix expression, if and only if the precedence of the
operator which is on the top of the stack is greater than (or equal) to the precedence
of the scanned operator and push the scanned operator onto the stack otherwise,
push the scanned operator onto the stack.

Convert the following infix expression A + B * C – D / E * H into its equivalent postfix expression.

Symbol Postfix string Stack Remarks

A A

+ A +

B AB +

* AB +*

C ABC -

- ABC*+ -

D ABC*+D -

/ ABC*+D -/

E ABC*+DE -/

* ABC*+DE/ -*

16
H ABC*+DE/H -*

End of The input is now empty. Pop the output symbols from the
string ABC*+DE/H*- stack until it is empty.

Source Code:

# Python program to convert infix expression to postfix

# Class to convert the expression


import string
class Conversion:

# Constructor to initialize the class variables


def init (self, capacity):
self.top = -1
self.capacity = capacity
# This array is used a stack
self.array = []
# Precedence setting
self.output = []
self.precedence = {'+':1, '-':1, '*':2, '/':2, '^':3}

# check if the stack is empty


def isEmpty(self):
return True if self.top == -1 else False

# Return the value of the top of the stack


def peek(self):
return self.array[-1]

# Pop the element from the stack


def pop(self):
if not self.isEmpty():
self.top -= 1
return self.array.pop()
else:
return "$"

# Push the element to the stack


def push(self, op):
self.top += 1
self.array.append(op)

# A utility function to check is the given character


# is operand
def isOperand(self, ch):
return ch.isalpha()

# Check if the precedence of operator is strictly


17
# less than top of stack or not

18
def notGreater(self, i):
try:
a = self.precedence[i]
b = self.precedence[self.peek()]
return True if a <= b else False
except KeyError:
return False

# The main function that converts given infix expression


# to postfix expression
def infixToPostfix(self, exp):

# Iterate over the expression for conversion


for i in exp:
# If the character is an operand,
# add it to output
if self.isOperand(i):
self.output.append(i)

# If the character is an '(', push it to stack


elif i == '(':
self.push(i)

# If the scanned character is an ')', pop and


# output from the stack until and '(' is found
elif i == ')':
while( (not self.isEmpty()) and self.peek() != '('):
a = self.pop()
self.output.append(a)
if (not self.isEmpty() and self.peek() !=
'('): return -1
else:
self.pop()

# An operator is encountered
else:
while(not self.isEmpty() and self.notGreater(i)):
self.output.append(self.pop())
self.push(i)

# pop all the operator from the stack


while not self.isEmpty():
self.output.append(self.pop())

result= "".join(self.output)
print(result)
# Driver program to test above function
exp = "a+b*(c^d-e)^(f+g*h)-i"
obj = Conversion(len(exp))
obj.infixToPostfix(exp)

19
Evaluating Arithmetic Expressions:

Procedure:

The postfix expression is evaluated easily by the use of a stack. When a number is seen, it is pushed onto
the stack; when an operator is seen, the operator is applied to the two numbers that are popped from the
stack and the result is pushed onto the stack.

Evaluate the postfix expression: 6 5 2 3 + 8 * + 3 + *

Symbol Operand 1 Operand 2 Value Stack Remarks

6 6

5 6, 5

2 6, 5, 2

The first four symbols are


3 6, 5, 2, 3
placed on the stack.

Next a „+‟ is read, so 3 and 2


+ 2 3 5 6, 5, 5 are popped from the stack and
their sum 5, is pushed

8 2 3 5 6, 5, 5, 8 Next 8 is pushed

Now a „*‟ is seen, so 8 and 5


* 5 8 40 6, 5, 40 are popped as 8 * 5 = 40 is
pushed

Next, a „+‟ is seen, so 40 and


+ 5 40 45 6, 45 5 are popped and 40 + 5 = 45
is pushed
3 5 40 45 6, 45, 3 Now, 3 is pushed

Next, „+‟ pops 3 and 45 and


+ 45 3 48 6, 48
pushes 45 + 3 = 48 is pushed

Finally, a „*‟ is seen and 48


* 6 48 288 288 and 6 are popped, the result 6
* 48 = 288 is pushed

Source Code:

# Python program to evaluate value of a postfix expression

# Class to convert the expression


class Evaluate:

# Constructor to initialize the class variables


def init (self, capacity):
20
self.top = -1

21
self.capacity = capacity
# This array is used a stack
self.array = []

# check if the stack is empty


def isEmpty(self):
return True if self.top == -1 else False

# Return the value of the top of the stack


def peek(self):
return self.array[-1]

# Pop the element from the stack


def pop(self):
if not self.isEmpty():
self.top -= 1
return self.array.pop()
else:
return "$"

# Push the element to the stack


def push(self, op):
self.top += 1
self.array.append(op)

# The main function that converts given infix expression


# to postfix expression
def evaluatePostfix(self, exp):

# Iterate over the expression for conversion


for i in exp:

# If the scanned character is an operand


# (number here) push it to the stack
if i.isdigit():
self.push(i)

# If the scanned character is an operator,


# pop two elements from stack and apply it.
else:
val1 = self.pop()
val2 = self.pop()
self.push(str(eval(val2 + i + val1)))

return int(self.pop())

# Driver program to test above function


exp = "231*+9-"
obj = Evaluate(len(exp))
print ("Value of {0} is {1}".format(exp, obj.evaluatePostfix(exp)))

22
Basic Queue Operations:

A queue is a data structure that is best described as "first in, first out". A queue is another special kind of
list, where items are inserted at one end called the rear and deleted at the other end called the front. A
real world example of a queue is people waiting in line at the bank. As each person enters the bank, he
or she is "enqueued" at the back of the line. When a teller becomes available, they are "dequeued" at the
front of the line.

Representation of a Queue using Array:

Let us consider a queue, which can hold maximum of five elements. Initially the queue is empty.

0 1 2 3 4
Q u e u e E mp t y
F RO NT = RE A R = 0

F R

Now, insert 11 to the queue. Then queue status will be:

0 1 2 3 4
RE A R = RE A R +
1 = 1 F RO NT = 0
11

F R

Next, insert 22 to the queue. Then the queue status is:

0 1 2 3 4
RE A R = RE A R + 1
= 2 F RO NT = 0
11 22

F R

23
Again insert another element 33 to the queue. The status of the queue is:

24
0 1 2 3 4
RE A R = RE A R +
11 22 33 1 = 3 F RO NT = 0

F R

Now, delete an element. The element deleted is the element at the front of the queue. So the status of the
queue is:

0 1 2 3 4
RE A R = 3

22 33 F RO NT = F R O NT + 1 = 1

F R

Again, delete an element. The element to be deleted is always pointed to by the FRONT pointer. So, 22
is deleted. The queue status is as follows:

0 1 2 3 4
RE A R = 3

33 F RO NT = F R O NT + 1 = 2

F R

Now, insert new elements 44 and 55 into the queue. The queue status is:

F R
0 1 2 3 4

33 44 55

25
RE A R =
5 F RO NT
= 2

Next insert another element, say 66 to the queue. We cannot insert 66 to the queue as the rear crossed the
maximum size of the queue (i.e., 5). There will be queue full signal. The queue status is as follows:

26
0 1 2 3 4
RE A R =
33 44 55 5 F RO NT
= 2

Now it is not possible to insert an element 66 even though there are two vacant positions in the linear
queue. To over come this problem the elements of the queue are to be shifted towards the beginning of
the queue so that it creates vacant position at the rear end. Then the FRONT and REAR are to be
adjusted properly. The element 66 can be inserted at the rear end. After this operation, the queue status is
as follows:

0 1 2 3 4
33 44 55 66
RE A R = 4 F RO NT = 0

F R

This difficulty can overcome if we treat queue position with index 0 as a position that comes after
position with index 4 i.e., we treat the queue as a circular queue.

Procedure for Queue operations using array:

In order to create a queue we require a one dimensional array Q(1:n) and two variables front and rear.
The conventions we shall adopt for these two variables are that front is always 1 less than the actual
front of the queue and rear always points to the last element in the queue. Thus, front = rear if and only if
there are no elements in the queue. The initial condition then is front = rear = 0.

The various queue operations to perform creation, deletion and display the elements in a queue are as
follows:

1. insertQ(): inserts an element at the end of queue Q.

2. deleteQ(): deletes the first element of Q.

3. displayQ(): displays the elements in the queue.

Linked List Implementation of Queue: We can represent a queue as a linked list. In a queue data is
deleted from the front end and inserted at the rear end. We can perform similar operations on the two
27
ends of a list. We use two pointers front and rear for our linked queue implementation.

28
Source Code:

front = 0

rear = 0

mymax = 3

# Function to create a stack. It initializes size of stack as 0

def createQueue():

queue = []

return queue

# Stack is empty when stack size is 0

def isEmpty(queue):

return len(queue) == 0

# Function to add an item to stack. It increases size by 1

def enqueue(queue,item):

queue.append(item)

# Function to remove an item from stack. It decreases size by 1

def dequeue(queue):

if (isEmpty(queue)):

return "Queue is empty"

item=queue[0]

del queue[0]

return item

# Driver program to test above functions

queue = createQueue()

while True:

print("1 Enqueue")

print("2 Dequeue")

print("3 Display")

print("4 Quit")

29
ch=int(input("Enter choice"))

if(ch==1):

if(rear < mymax):

item=input("enter item")

enqueue(queue, item)

rear = rear + 1

else:

print("Queue is full")

elif(ch==2):

print(dequeue(queue))

elif(ch==3):

print(queue)

else:

break

Applications of Queues:

1. It is used to schedule the jobs to be processed by the CPU.

2. When multiple users send print jobs to a printer, each printing job is kept in the printing queue.
Then the printer prints those jobs according to first in first out (FIFO) basis.

3. Breadth first search uses a queue data structure to find an element from a graph.

Disadvantages of Linear Queue:

There are two problems associated with linear queue. They are:

 Time consuming: linear time to be spent in shifting the elements to the beginning of the queue.

 Signaling queue full: even if the queue is having vacant position.

Round Robin Algorithm:

The round-robin (RR) scheduling algorithm is designed especially for time-sharing systems. It is similar
to FCFS scheduling, but pre-emption is added to switch between processes. A small unit of time, called
a time quantum or time slices, is defined. A time quantum is generally from 10 to 100 milliseconds. The
ready queue is treated as a circular queue. To implement RR scheduling

 We keep the ready queue as a FIFO queue of processes.

 New processes are added to the tail of the ready queue.

30
 The CPU scheduler picks the first process from the ready queue, sets a timer to interrupt after
1 time quantum, and dispatches the process.

 The process may have a CPU burst of less than 1 time quantum.

o In this case, the process itself will release the CPU voluntarily.

o The scheduler will then proceed to the next process in the ready queue.

 Otherwise, if the CPU burst of the currently running process is longer than 1 time quantum,

o The timer will go off and will cause an interrupt to the OS.

o A context switch will be executed, and the process will be put at the tail of the ready
queue.

o The CPU scheduler will then select the next process in the ready queue.

The average waiting time under the RR policy is often long. Consider the following set of processes that
arrive at time 0, with the length of the CPU burst given in milliseconds: (a time quantum of 4
milliseconds)

Burst Waiting Turnaround

Process Time Time Time

24 6 30

3 4 7

3 7 10

Average - 5.66 15.66

Using round-robin scheduling, we would schedule these processes according to the following chart:

31
DEQUE(Double Ended Queue):

A double-ended queue (dequeue, often abbreviated to deque, pronounced deck) generalizes a queue,
for which elements can be added to or removed from either the front (head) or back (tail).It is also often
called a head-tail linked list. Like an ordinary queue, a double-ended queue is a data structure it
supports the following operations: enq_front, enq_back, deq_front, deq_back, and empty. Dequeue can
be behave like a queue by using only enq_front and deq_front , and behaves like a stack by using only
enq_front and deq_rear.

The DeQueue is represented as follows.

DEQUE can be represented in two ways they are

1) Input restricted DEQUE(IRD)

2) output restricted DEQUE(ORD)

The output restricted DEQUE allows deletions from only one end and input restricted DEQUE allow
insertions at only one end. The DEQUE can be constructed in two ways they are

1) Using array

2)Using linked list

Operations in DEQUE

1. Insert element at back

2. Insert element at front

3. Remove element at front

4. Remove element at back

32
33
Applications of DEQUE:

1. The A-Steal algorithm implements task scheduling for several processors


(multiprocessor scheduling).

2. The processor gets the first element from the deque.

3. When one of the processor completes execution of its own threads it can steal a thread from
another processor.

4. It gets the last element from the deque of another processor and executes it.

Circular Queue:

Circular queue is a linear data structure. It follows FIFO principle. In circular queue the last node is
connected back to the first node to make a circle.

 Circular linked list fallow the First In First Out principle

 Elements are added at the rear end and the elements are deleted at front end of the queue

 Both the front and the rear pointers points to the beginning of the array.

 It is also called as “Ring buffer”.

 Items can inserted and deleted from a queue in O(1)

time. Circular Queue can be created in three ways they are

1. Using single linked list

2. Using double linked list

3. Using arrays
34
Representation of Circular Queue:

Let us consider a circular queue, which can hold maximum (MAX) of six elements. Initially the queue is
empty.

F R

5 0

1 Q u e u e E mp t y
4 MAX = 6

2
F RO NT = RE
3
AR =0COU
NT = 0

C irc u lar Q u e u e

Now, insert 11 to the circular queue. Then circular queue status will be:

5 0

11 R

1
F RO NT = 0
4 RE A R = ( RE A R + 1) % 6 = 1
3 2 C O U NT = 1

C irc u lar Q u e u e

Insert new elements 22, 33, 44 and 55 into the circular queue. The circular queue status is:

35
F
R

0
5
11

55
1 22 F RO NT = 0, RE A R = 5
4 RE A R = RE A R % 6 = 5
44 33
C O U NT = 5
3 2

C irc u lar Q u e u e

Now, delete an element. The element deleted is the element at the front of the circular queue. So, 11 is
deleted. The circular queue status is as follows:

36
R

0
5

55 221

F RO NT = (F R O NT + 1) % 6 = 1
4 44 33 RE A R = 5
3 2
C O U NT = C O U NT - 1 = 4

C irc u lar Q u e u e

Again, delete an element. The element to be deleted is always pointed to by the FRONT pointer. So, 22
is deleted. The circular queue status is as follows:

0
5

55 1 F RO NT = (F R O NT + 1) % 6 = 2
4 RE A R = 5
44 33 C O U NT = C O U NT - 1 = 3
3 2

C irc u lar Q u e u e

Again, insert another element 66 to the circular queue. The status of the circular queue is:

37
0
5
66

4 55 1 F RO NT = 2

RE A R = ( RE A R + 1)
44 33
% 6 = 0 C O U NT = C
3 2
O U NT + 1 = 4

C irc u lar Q u e u e

Now, insert new elements 77 and 88 into the circular queue. The circular queue status is:

38
0
5

66 77

4 55 88 1
F RO NT = 2, RE A R
= 2 RE A R = RE A R
% 6 = 2 C O U NT =
6
3 44 33

2
R
F
C irc u lar Q u e u e

Now, if we insert an element to the circular queue, as COUNT = MAX we cannot add the element to
circular queue. So, the circular queue is full.

39
UNIT-3 LINKED LISTS

Linked lists and arrays are similar since they both store collections of data. Array is the most common
data structure used to store collections of elements. Arrays are convenient to declare and provide the easy
syntax to access any element by its index number. Once the array is set up, access to any element is
convenient and fast.
The disadvantages of arrays are:

• The size of the array is fixed. Most often this size is specified at compile time. This makes the
programmers to allocate arrays, which seems "large enough" than required.
• Inserting new elements at the front is potentially expensive because existing elements need to be
shifted over to make room.
• Deleting an element from an array is not possible. Linked lists have their own strengths and
weaknesses, but they happen to be strong where arrays are weak.
 Generally array's allocates the memory for all its elements in one block whereas linked lists use
an entirely different strategy. Linked lists allocate memory for each element separately and only
when necessary.
Linked List Concepts:

A linked list is a non-sequential collection of data items. It is a dynamic data structure. For every data
item in a linked list, there is an associated pointer that would give the memory location of the next data
item in the linked list. The data items in the linked list are not in consecutive memory locations. They
may be anywhere, but the accessing of these data items is easier as each data item contains the address of
the next data item.
Advantages of linked lists:

Linked lists have many advantages. Some of the very important advantages are:
1. Linked lists are dynamic data structures. i.e., they can grow or shrink during the execution of a
program.
2. Linked lists have efficient memory utilization. Here, memory is not preallocated. Memory is
allocated whenever it is required and it is de-allocated (removed) when it is no longer needed.
3. Insertion and Deletions are easier and efficient. Linked lists provide flexibility in inserting a data
item at a specified position and deletion of the data item from the given position.
4. Many complex applications can be easily carried out with linked lists.

Disadvantages of linked lists:

1. It consumes more space because every node requires a additional pointer to store address of the
next node.
2. Searching a particular element in list is difficult and also time consuming.
Types of Linked Lists:

Basically we can put linked lists into the following four items:
1. Single Linked List.
2. Double Linked List.

40
3. Circular Linked List.

41
4. Circular Double Linked List.
A single linked list is one in which all nodes are linked together in some sequential manner. Hence, it is
also called as linear linked list.
A double linked list is one in which all nodes are linked together by multiple links which helps in
accessing both the successor node (next node) and predecessor node (previous node) from any arbitrary
node within the list. Therefore each node in a double linked list has two link fields (pointers) to point to
the left node (previous) and the right node (next). This helps to traverse in forward direction and
backward direction.
A circular linked list is one, which has no beginning and no end. A single linked list can be made a
circular linked list by simply storing address of the very first node in the link field of the last node.
A circular double linked list is one, which has both the successor pointer and predecessor pointer in the
circular manner.

42
Applications of linked list:

1. Linked lists are used to represent and manipulate polynomial. Polynomials are expression containing
terms with non zero coefficient and exponents. For example: P(x) = a0 Xn + a1 Xn-1 + …… + an-1
X + an
2. Represent very large numbers and operations of the large number such as addition, multiplication and
division.
3. Linked lists are to implement stack, queue, trees and graphs. 4. Implement the symbol table in
compiler construction.
Single Linked List:

A linked list allocates space for each element separately in its own block of memory called a "node". The
list gets an overall structure by using pointers to connect all its nodes together like the links in a chain.
Each node contains two fields; a "data" field to store whatever element, and a "next" field which is a
pointer used to link to the next node. Each node is allocated in the heap using malloc(), so the node
memory continues to exist until it is explicitly de-allocated using free(). The front of the list is a pointer to
the “start” node.

A single linked list


The beginning of the linked list is stored in a "start" pointer which points to the first node. The first node
contains a pointer to the second node. The second node contains a pointer to the third node, ... and so on.
The last node in the list has its next field set to NULL to mark the end of the list. Code can access any
node in the list by starting at the start and following the next pointers.
The start pointer is an ordinary local pointer variable, so it is drawn separately on the left top to show that
it is in the stack. The list nodes are drawn on the right to show that they are allocated in the heap.
The basic operations in a single linked list are:

• Creation.
• Insertion.
• Deletion.
• Traversing.
Creating a node for Single Linked List:

43
Creating a singly linked list starts with creating a node. Sufficient memory has to be allocated for creating
a node. The information is stored in the memory.

44
Insertion of a Node:

One of the most primitive operations that can be done in a singly linked list is the insertion of a node.
Memory is to be allocated for the new node (in a similar way that is done while creating a list) before
reading the data. The new node will contain empty data field and empty next field. The data field of the
new node is then stored with the information read from the user. The next field of the new node is
assigned to NULL. The new node can then be inserted at three different places namely:
• Inserting a node at the beginning.
• Inserting a node at the end.
• Inserting a node at intermediate position.

 Inserting a node at the beginning.

 Inserting a node at the end:

45
 Inserting a node into the single linked list at a specified intermediate position other than
beginning and end.

Deletion of a node:

Another primitive operation that can be done in a singly linked list is the deletion of a node. Memory is
to be released for the node to be deleted. A node can be deleted from the list from three different places
namely.
• Deleting a node at the beginning.
• Deleting a node at the end.
• Deleting a node at intermediate position.
Deleting a node at the beginning:

46
Deleting a node at the end:

Deleting a node at Intermediate position:

The following steps are followed, to delete a node from an intermediate position in the list (List must
contain more than two node).

Traversal and displaying a list (Left to Right):


To display the information, you have to traverse (move) a linked list, node by node from the first node,
until the end of the list is reached.
Traversing a list involves the following steps:

• Assign the address of start pointer to a temp pointer.


• Display the information from the data field of each node. The function traverse () is used for traversing
and displaying the information stored in the list from left to right.
Source Code for creating and inserting the Implementation of Single Linked List:

class Item:
'''An Item that stores the data of a Linked List'''
def init (self, data):
'''Initializes the data of the Item'''
self._content = data # non-public data
self.next = None

class SingleLikedList:
47
'''Single Linked List Implementation'''
def init (self):
self._start = None

48
self._count = 0

def getItemAtIndex(self, pos):


if self._start == None:
return None
i=0
cursor = self._start
while cursor != None:
if i == pos:
return cursor
i += 1
cursor = cursor.next

return None

def insert(self, data, pos = 0):


item = Item(data)

if self._start == None:
self._start = item
else:
cursor = self.getItemAtIndex(pos)
item.next = cursor.next
item.next = cursor.next
cursor.next = item

self._count += 1
def display(self):
cursor = self._start
while cursor != None:
print(cursor._content, end=' ')
cursor = cursor.next
l = SingleLikedList()
l.insert(10)
l.insert(20)

49
l.insert(30)
l.insert(40)
l.insert(50, 3)
l.display()

Source Code for creating , inserting ,deleting the Implementation of Single Linked List:

class Node(object):
def init (self, data=None, next_node=None):
self.data = data
self.next_node = next_node
def get_data(self):
return self.data
def get_next(self):
return self.next_node
def set_next(self, new_next):
self.next_node = new_next

class LinkedList(object):
def init (self, head=None):
self.head = head
def insert(self, data):
new_node = Node(data)
new_node.set_next(self.head)
self.head = new_node
def size(self):
current = self.head
count = 0
while current:
count += 1
current = current.get_next()
return count
def search(self, data):
current = self.head
found = False

50
while current and found is False:
if current.get_data() == data:
found = True
else:
current = current.get_next()
if current is None:
raise ValueError("Data not in list")
return current
def delete(self, data):
current = self.head
previous = None
found = False
while current and found is False:
if current.get_data() == data:
found = True
else:
previous = current
current = current.get_next()
if current is None:
raise ValueError("Data not in list")
if previous is None:
self.head = current.get_next()
else:
previous.set_next(current.get_next())

Source Code for creating , inserting ,deleting the Implementation of Single Linked List:

import sys
import os.path
sys.path.append(os.path.join(os.path.abspath(os.pardir), "/home/satya/PycharmProjects/DataStractures"))
from LinkedList2 import LinkedList
import unittest
class TestLinkedList(unittest.TestCase):
def setUp(self):
self.list = LinkedList()

51
def tearDown(self):

52
self.list = None
def test_insert(self):
self.list.insert("David")
self.assertTrue(self.list.head.get_data() == "David")
self.assertTrue(self.list.head.get_next() is None)
def test_insert_two(self):
self.list.insert("David")
self.list.insert("Thomas")
self.assertTrue(self.list.head.get_data() ==
"Thomas") head_next = self.list.head.get_next()
self.assertTrue(head_next.get_data() == "David")
def test_nextNode(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
self.assertTrue(self.list.head.get_data() ==
"Rasmus") head_next = self.list.head.get_next()
self.assertTrue(head_next.get_data() == "Pallymay")
last = head_next.get_next()
self.assertTrue(last.get_data() == "Jacob")
def test_positive_search(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
found = self.list.search("Jacob")
self.assertTrue(found.get_data() == "Jacob")
found = self.list.search("Pallymay")
self.assertTrue(found.get_data() == "Pallymay")
found = self.list.search("Jacob")
self.assertTrue(found.get_data() == "Jacob")
def test_searchNone(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
# make sure reg search works
found = self.list.search("Jacob")

53
self.assertTrue(found.get_data() == "Jacob")
with self.assertRaises(ValueError):
self.list.search("Vincent")
def test_delete(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
# Delete the list head
self.list.delete("Rasmus")
self.assertTrue(self.list.head.get_data() == "Pallymay")
# Delete the list tail
self.list.delete("Jacob")
self.assertTrue(self.list.head.get_next() is None)
def test_delete_value_not_in_list(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
with self.assertRaises(ValueError):
self.list.delete("Sunny")
def test_delete_empty_list(self):
with self.assertRaises(ValueError):
self.list.delete("Sunny")
def test_delete_next_reassignment(self):
self.list.insert("Jacob")
self.list.insert("Cid")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
self.list.delete("Pallymay")
self.list.delete("Cid")
self.assertTrue(self.list.head.next_node.get_data() == "Jacob")
Source Code for creating , inserting ,deleting the Implementation of Single Linked List:

class Node(object):

def init (self, data, next):

54
self.data = data

55
self.next = next
class SingleList(object):
head = None
tail = None
def show(self):

print "Showing list data:"


current_node = self.head
while current_node is not None:
print current_node.data, " -> ",
current_node = current_node.next
print None

def append(self, data):


node = Node(data, None)
if self.head is None:
self.head = self.tail = node

else:

self.tail.next = node
self.tail = node
def remove(self, node_value):
current_node = self.head
previous_node = None
while current_node is not None:

if current_node.data == node_value:
# if this is the first node (head)

if previous_node is not None:


previous_node.next = current_node.next
else:

self.head = current_node.next

# needed for the next iteration


previous_node = current_node
current_node = current_node.next

56
s = SingleList()
s.append(31)
s.append(2)

57
s.append(3)
s.append(4)
s.show()
s.remove(31)
s.remove(3)
s.remove(2)
s.show()

Using a header node:

A header node is a special dummy node found at the front of the list. The use of header node is an
alternative to remove the first node in a list. For example, the picture below shows how the list with data
10, 20 and 30 would be represented using a linked list without and with a header node:

Note that if your linked lists do include a header node, there is no need for the special case code given
above for the remove operation; node n can never be the first node in the list, so there is no need to check
for that case. Similarly, having a header node can simplify the code that adds a node before a given node
n.
Note that if you do decide to use a header node, you must remember to initialize an empty list to contain
one (dummy) node, you must remember not to include the header node in the count of "real" nodes in the
list.
It is also useful when information other than that found in each node of the list is needed. For example,
imagine an application in which the number of items in a list is often calculated. In a standard linked list,
the list function to count the number of nodes has to traverse the entire list every time. However, if the
current length is maintained in a header node, that information can be obtained very quickly. 3.5. Array
based linked lists: Another alternative is to allocate the nodes in blocks. In fact, if you know the
58
maximum size of a list a head of time, you can pre-allocate the nodes in a single array. The result is a
hybrid structure – an array based linked list.
shows an example of null terminated single linked list where all the nodes are allocated contiguously in
an array.

Double Linked List: A double linked list is a two-way list in which all nodes will have two links. This
helps in accessing both successor node and predecessor node from the given node position. It provides bi-
directional traversing. Each node contains three fields:
 Left link.
 Data.
 Right link.
The left link points to the predecessor node and the right link points to the successor node. The data field
stores the required data.
Many applications require searching forward and backward thru nodes of a list. For example searching
for a name in a telephone directory would need forward and backward scanning thru a region of the whole
list.
The basic operations in a double linked list are:

 Creation.
 Insertion.
 Deletion.
 Traversing.

A double linked list is shown in figure

59
The beginning of the double linked list is stored in a "start" pointer which points to the first node. The
first node‟s left link and last node‟s right link is set to NULL.
Creating a node for Double Linked List:

Creating a double linked list starts with creating a node. Sufficient memory has to be allocated for
creating a node. The information is stored in the memory.
Double Linked List with 3 nodes:

Inserting a node at the beginning:

60
Inserting a node at the end:

61
Inserting a node at an intermediate position:

Deleting a node at the beginning:

Deleting a node at the end:

62
Deleting a node at Intermediate position:

63
Traversal and displaying a list (Left to Right):

To display the information, you have to traverse the list, node by node from the first node, until the end
of the list is reached. The function traverse_left_right() is used for traversing and displaying the
information stored in the list from left to right.
Traversal and displaying a list (Right to Left):

To display the information from right to left, you have to traverse the list, node by node from the first
node, until the end of the list is reached. The function traverse_right_left() is used for traversing and
displaying the information stored in the list from right to left.

1. Source Code for the Implementation of Double Linked List:


class DList:
def init (self):
self.head = None
self.size = 0
def insert(self, data):
self.size += 1
if self.head is None:
self.head = Node(data)
else:
p = Node(data)
p.next = self.head
self.head.previous = p
self.head = p
def remove(self, index):
if self.head is None:
raise ValueError('Removing off an empty
list') if index < 0 or index >= self.size:

64
raise IndexError("Index is either negative or greater than the list size.")

65
current = self.head
if index == 0:
self.head = self.head.next
self.head.previous = None
else:
for _ in range(index -1):
current = current.next
p = current.next.next
if p is None:
current.next = None
else:
current.next = p
p.previous = current
def sizeof (self):
return self.size
def repr (self):
res = '[ '
current = self.head
while current is not None:
res += str(current.data)
res += ' '
current =
current.next res += ']'
return res
class Node:
def init (self, data):
if data is None:
raise ValueError('Node value cannot be None')
self.data = data
self.previous = None
self.next = None

Source Code for the Implementation of Double Linked List:


class Node(object):

def init (self, data, prev, next):


66
self.data = data
self.prev = prev
self.next = next

class DoubleList(object):
head = None
tail = None
def append(self, data):
new_node = Node(data, None, None)
if self.head is None:

self.head = self.tail = new_node


else:

new_node.prev = self.tail
new_node.next = None
self.tail.next = new_node
self.tail = new_node
def remove(self, node_value):
current_node = self.head

while current_node is not None:

if current_node.data == node_value:
# if it's not the first element

if current_node.prev is not None:


current_node.prev.next = current_node.next
current_node.next.prev = current_node.prev

else:

# otherwise we have no prev (it's None), head is the next one, and prev becomes None

self.head = current_node.next
current_node.next.prev = None

current_node = current_node.next

def show(self):
67
print "Show list data:"
current_node = self.head

68
while current_node is not None:

print current_node.prev.data if hasattr(current_node.prev, "data") else None,


print current_node.data,

print current_node.next.data if hasattr(current_node.next, "data") else None

current_node = current_node.next
print "*"*50
d = DoubleList()
d.append(5)
d.append(6)
d.append(50)
d.append(30)
d.show()
d.remove(50)
d.remove(5)
d.show()

Circular Single Linked List:

It is just a single linked list in which the link field of the last node points back to the address of the first
node. A circular linked list has no beginning and no end. It is necessary to establish a special pointer
called start pointer always pointing to the first node of the list. Circular linked lists are frequently used
instead of ordinary linked list because many operations are much easier to implement. In circular linked
list no null pointers are used, hence all pointers contain valid address.
Creating a circular single Linked List with „n‟ number of nodes:

The basic operations in a circular single linked list are:

• Creation.
• Insertion.
• Deletion.
69
• Traversing.

70
Inserting a node at the beginning:

Inserting a node at the end:

Deleting a node at the beginning:

Deleting a node at the end:

71
Source Code for Circular Single Linked List:

72
from enum import Enum
class NodeConstants(Enum):
FRONT_NODE = 1
class Node:
def init (self, element=None, next_node=None):
self.element = element
self.next_node = next_node
def str (self):
if self.element:
return self.element. str ()
else:
return 'Empty Node'
def repr (self):
return self. str ()
class CircularLinkedList:
def init (self):
self.head = Node(element=NodeConstants.FRONT_NODE)
self.head.next_node = self.head
def size(self):
count = 0
current = self.head.next_node
while current != self.head:
count += 1
current = current.next_node
return count
def insert_front(self, data):
node = Node(element=data, next_node=self.head.next_node)
self.head.next_node = node
def insert_last(self, data):
current_node = self.head.next_node
while current_node.next_node != self.head:
current_node = current_node.next_node
node = Node(element=data, next_node=current_node.next_node)
current_node.next_node = node
def insert(self, data, position):

73
if position == 0:
self.insert_front(data)
elif position == self.size():
self.insert_last(data)
else:
if 0 < position < self.size():
current_node = self.head.next_node
current_pos = 0
while current_pos < position - 1:
current_pos += 1
current_node = current_node.next_node
node = Node(data, current_node.next_node)
current_node.next_node = node
else:
raise IndexError
def remove_first(self):
self.head.next_node = self.head.next_node.next_node
def remove_last(self):
current_node = self.head.next_node
while current_node.next_node.next_node != self.head:
current_node = current_node.next_node
current_node.next_node = self.head
def remove(self, position):
if position == 0:
self.remove_first()
elif position == self.size():
self.remove_last()
else:
if 0 < position < self.size():
current_node = self.head.next_node
current_pos = 0
while current_pos < position - 1:
current_node = current_node.next_node
current_pos += 1
current_node.next_node = current_node.next_node.next_node

74
else:
raise IndexError
def fetch(self, position):
if 0 <= position < self.size():
current_node = self.head.next_node
current_pos = 0
while current_pos < position:
current_node = current_node.next_node
current_pos += 1
return current_node.element
else:
raise IndexError
import unittest
from random import randint
class TestCircularLinkedList(unittest.TestCase):
names = ['Bob Belcher',
'Linda Belcher',
'Tina Belcher',
'Gene Belcher',
'Louise Belcher']
def test_init(self):
dll = CircularLinkedList()
self.assertIsNotNone(dll.head)
self.assertEqual(dll.size(), 0)
def test_insert_front(self):
dll = CircularLinkedList()
for name in TestCircularLinkedList.names:
dll.insert_front(name)
self.assertEqual(dll.fetch(0), TestCircularLinkedList.names[4])
self.assertEqual(dll.fetch(1), TestCircularLinkedList.names[3])
self.assertEqual(dll.fetch(2), TestCircularLinkedList.names[2])
self.assertEqual(dll.fetch(3), TestCircularLinkedList.names[1])
self.assertEqual(dll.fetch(4), TestCircularLinkedList.names[0])

def test_insert_last(self):

75
dll = CircularLinkedList()
for name in TestCircularLinkedList.names:
dll.insert_last(name)
for i in range(len(TestCircularLinkedList.names) - 1):
self.assertEqual(dll.fetch(i), TestCircularLinkedList.names[i])
def test_insert(self):
dll = CircularLinkedList()
for name in TestCircularLinkedList.names:
dll.insert_last(name)
pos = randint(0, len(TestCircularLinkedList.names) - 1)
dll.insert('Teddy', pos)
self.assertEqual(dll.fetch(pos), 'Teddy')
def test_remove_first(self):
dll = CircularLinkedList()
for name in TestCircularLinkedList.names:
dll.insert_last(name)
for i in range(dll.size(), 0, -1):
self.assertEqual(dll.size(),
i) dll.remove_first()
def test_remove_last(self):
dll = CircularLinkedList()
for name in TestCircularLinkedList.names:
dll.insert_last(name)
for i in range(dll.size(), 0, -1):
self.assertEqual(dll.size(),
i) dll.remove_last()
def test_remove(self):
dll = CircularLinkedList()
for name in TestCircularLinkedList.names:
dll.insert_last(name)
dll.remove(1)
self.assertEqual(dll.fetch(0), 'Bob Belcher')
self.assertEqual(dll.fetch(1), 'Tina Belcher')
self.assertEqual(dll.fetch(2), 'Gene Belcher')
self.assertEqual(dll.fetch(3), 'Louise Belcher')

76
if name == ' main ':
unittest.main()

Circular Double Linked List:

A circular double linked list has both successor pointer and predecessor pointer in circular manner. The
objective behind considering circular double linked list is to simplify the insertion and deletion operations
performed on double linked list. In circular double linked list the right link of the right most node points
back to the start node and left link of the first node points to the last node.
A circular double linked list is shown in figure

Creating a Circular Double Linked List with „n‟ number of nodes


The basic operations in a circular double linked list are:

• Creation.
• Insertion.
• Deletion.
• Traversing.

Inserting a node at the beginning:

77
Inserting a node at the end:

78
Inserting a node at an intermediate position:

Deleting a node at the beginning:

Deleting a node at the end:

79
Deleting a node at Intermediate position:

Comparison of Linked List Variations:

The major disadvantage of doubly linked lists (over singly linked lists) is that they require more space
(every node has two pointer fields instead of one). Also, the code to manipulate doubly linked lists needs
to maintain the prev fields as well as the next fields; the more fields that have to be maintained, the more
chance there is for errors.
The major advantage of doubly linked lists is that they make some operations (like the removal of a given
node, or a right-to-left traversal of the list) more efficient.
The major advantage of circular lists (over non-circular lists) is that they eliminate some extra-case code
for some operations (like deleting last node). Also, some applications lead naturally to circular list
representations. For example, a computer network might best be modeled using a circular list.

Polynomials:

A polynomial is of the form: i n i ∑ ci


Where, ci is the coefficient of the ith term and
n is the degree of the polynomial
Some examples are:

80
5x2 + 3x + 1
12x3 – 4x
5x4 – 8x3 + 2x2 + 4x1 + 9x0

81
It is not necessary to write terms of the polynomials in decreasing order of degree. In other words the two
polynomials 1 + x and x + 1 are equivalent.

The computer implementation requires implementing polynomials as a list of pairs of coefficient and
exponent. Each of these pairs will constitute a structure, so a polynomial will be represented as a list of
structures.

A linked list structure that represents polynomials 5x4 – 8x3 + 2x2 + 4x1 + 9x0

Addition of Polynomials:

To add two polynomials we need to scan them once. If we find terms with the same exponent in the two
polynomials, then we add the coefficients; otherwise, we copy the term of larger exponent into the sum
and go on. When we reach at the end of one of the polynomial, then remaining part of the other is copied
into the sum.
To add two polynomials follow the following steps:

• Read two polynomials


• Add them.
• Display the resultant polynomial.

Asymptotic Notations:

It is often used to describe how the size of the input data affects an algorithm’s usage of computational
resources. Running time of an algorithm is described as a function of input size n for large n.

Big oh(O): Definition: f(n) = O(g(n)) (read as f of n is big oh of g of n) if there exist a positive integer
n0 and a positive number c such that |f(n)| ≤ c|g(n)| for all n ≥ n 0 . Here g(n) is the upper bound of the
function f(n).

82
Omega(Ω): Definition: f(n) = Ω(g(n)) ( read as f of n is omega of g of n), if there exists a positive
integer n0 and a positive number c such that |f(n)| ≥ c |g(n)| for all n ≥ n 0. Here g(n) is the lower bound
of the function f(n).

Theta(Θ): Definition: f(n) = Θ(g(n)) (read as f of n is theta of g of n), if there exists a positive integer
n0 and two positive constants c1 and c2 such that c1 |g(n)| ≤ |f(n)| ≤ c2 |g(n)| for all n ≥ n0. The function
g(n) is both an upper bound and a lower bound for the function f(n) for all values of n, n ≥ n0 .

83
84

You might also like