DS Unit 2
DS Unit 2
List ADT – array-based implementations – linked list implementations – singly linked lists –
circularly linked lists – doubly linked lists – applications of lists – Stack ADT – Queue ADT –
double ended queues
Data Structure
Organizing, managing and storing data is important as it enables easier access and efficient
modifications. Data Structures allows you to organize your data in such a way that enables you
to store collections of data, relate them and perform operations on them accordingly.
A data structure is classified into two categories:
● Linear data structure
● Non-linear data structure
The types of linear data structures are Array, Queue, Stack, Linked List.
LIST ADT
Python’s list structure is a mutable sequence container that can change size as items are added
or removed. It is an abstract data type that is implemented using an array structure to store the
items contained in the list.
When the list() constructor is called, an array structure is created to store the items contained
in the list. The array is initially created bigger than needed, leaving capacity for future
expansion. The values stored in the list comprise a subarray in which only a contiguous subset
of the array elements are actually used.
Appending Items
pyList.append( 50 )
If there is room in the array, the item is stored in the next available slot of the array and the
length _eld is incremented by one. The result of appending 50 to pyList is illustrated in Figure
2.3.
What happens when the array becomes full and there are no free elements in which to add a
new list item? For example, consider the following list operations:
pyList.append( 18 )
pyList.append( 64 )
pyList.append( 6 )
After the second statement is executed, the array becomes full and there is no available space
to add more values
By definition, a list can contain any number of items and never becomes full. Thus, when the
third statement is executed, the array will have to be expanded to make room for value 6. From
the discussion in the previous section, we know an array cannot change size once it has been
created. To allow for the expansion of the list, the following steps have to be performed:
Extending A List
A list can be appended to a second list using the extend() method as shown in the following
example:
pyListA = [ 34, 12 ]
pyListB = [ 4, 6, 31, 9 ]
pyListA.extend( pyListB )
Inserting Items
An item can be inserted anywhere within the list using the insert() method. In the following
example
pyList.insert( 3, 79 )
we insert the value 79 at index position 3. Since there is already an item at that position, we
must make room for the new item by shifting all of the items down one position starting with
the item at index position 3. After shifting the items, the value 79 is then inserted at position 3
Removing Items
An item can be removed from any position within the list using the pop() method. Consider the
following code segment, which removes both the first and last items from the sample list:
pyList.pop( 0 ) # remove the first item
pyList.pop() # remove the last item
List Slice
Slicing is an operation that creates a new list consisting of a contiguous subset of elements
from the original list.
aSlice = theVector[2:3]
Complexity of List Operations
If an element is appended to a list at a time when the underlying array is full, we perform the
following steps:
• Allocate a new array B with larger capacity.
• Set B[i] = A[i], for i Æ 0, ...,n1, where n denotes current number of items.
• Set A = B, that is, we henceforth use B as the array supporting the list.
• Insert the new element in the new array.
import ctypes
class DynamicArray(object):
def __init__(self):
self.n = 0 # Count actual elements (Default is 0)
self.capacity = 1 # Default Capacity
self.A = self.make_array(self.capacity)
def __len__(self):
return self.n
def insertAt(self,item,index):
if index<0 or index>self.n:
print("please enter appropriate index..")
return
if self.n==self.capacity:
self._resize(2*self.capacity)
for i in range(self.n-1,index-1,-1):
self.A[i+1]=self.A[i]
self.A[index]=item
self.n+=1
def delete(self):
if self.n==0:
print("Array is empty deletion not Possible")
return
self.A[self.n-1]=0
self.n-=1
def removeAt(self,index):
if self.n==0:
print("Array is empty deletion not Possible")
return
if index<0 or index>=self.n:
return IndexError("Index out of bound....deletion not possible")
if index==self.n-1:
self.A[index]=0
self.n-=1
return
for i in range(index,self.n-1):
self.A[i]=self.A[i+1]
self.A[self.n-1]=0
self.n-=1
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.
A linked list allocates space for each element separately in its own block of memory called a
"node". 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.
The basic operations in a single linked list are:
● Creation.
● Insertion.
● Deletion.
● Traversing.
● Searching
Creating a node for Single Linked List: 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.
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 into the single linked list at a specified intermediate position other than
beginning and end.
Algorithm -
Create a New node
New.value = element
If position < size of list
Then,
If size of list = 0
Then,
Head -> New node
Else If position = 0
Then,
New.next -> Head
Head -> New
Else If position < size
Then,
Temp = Head
For i in range of 0 to position – 1
/*position - 1 is used to reach the element after which the new node is to be
inserted */
Do,
Temp -> Temp.next // traversal using next pointer
New.next -> Temp.next
Temp.next -> New
Increment count
Else
Invalid insertion // position greater than size
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.
Algorithm -
If list is not Empty and position < size
Then,
temp = head
for i in range of 0 to position - 1
Do,
temp -> temp.next
res = temp.next.value
temp.next -> temp.next.next
Decrement count
return res
else
return 0 or a delimiter to specify List is empty
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.
Algorithm –
If list is not Empty and position < size
Then,
i=0
temp -> head
for i in range 0f 0 to position
Do,
If temp -> temp.next
Return temp.next.value
Else
Return 0
Searching Operation
The pointer traverse across the list until the element is found or end is reached.
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
bidirectional 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 through 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.
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 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:
Delete Algorithm:
If list is not empty and position<size
Then
Temp=head
For i in range 0 to position
Do
Temp-> temp.next
Temp1=temp.previous
Res=temp.value
Temp=temp.next
Temp1.next->temp
Temp1.previous->temp1
Decrement count
Return res
Else
Return 0
STACK ADT
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.
Algorithm
Search in a Stack
Algorithm -
found_flag =0
While found_flag is not 1 or Original_stack is not empty
Do,
If(Original_stack.top() is equal to required element)
Then
Found_flag = 1
Else
Temporary_stack.push(Original_stack.pop())
While Temporary_stack is not empty
Do,
Original_stack.push(Temporary_stack.pop())
Return found_flag
Sorting a Stack
Algorithm –
While Original_stack is not empty
Do,
Temp = Original_stack.pop()
If(Temp is less than sorted_stack.top()
Then
Sorted_stack.push(Temp)
Else
While sorted_stack.top() is less than Temp or Sorted_stack is not empty()
Do, Original_stack.push(sorted_stack.pop())
Sorted_stack.push(Temp)
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/prefix expression.
3. Stack is used to convert an infix expression into postfix/prefix form.
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
Complexity:
All the 3 basic operations of the stack are of constant complexity O(1). Some Complex
operations like Searching and Sorting will have a complexity of O(n).
Queue ADT
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.
Types:
Single Ended Queue
Circular Array Based Queue
Double Ended Queue (Dequeue)
Single Ended Queue
It is a one way queue, where the elements get inside in the rear end and move out from the
front end. Opposite movement is restricted.
Enqueue - The operation of adding an element into the queue, which happens at the rear end.
Algorithm- Enqueue
If Queue is empty
Then,
Assign data in queue[0]
Front ->queue[0]
Rear ->queue[1]
Increment size
Else
Assign data to rear
Rear++
Size++
Dequeue - The operation of removing an element into the queue, which happens at the front
end.
Algorithm:
If queue is not empty
Then,
Temp=value at front
Front++
Size - -
Return Temp
Else
Return 0
Algorithm
Sort a Queue
Algorithm –
Sorted_queue.enqueue(Original_queue.dequeue)
While original queue is not empty
Do,
temp = original_queue.dequeue
for i in range of 0 to sorted_queue.size
Do,
If sorted_queue.front is less than temp
Then,
sorted_queue.enqueue(sorted_queue.dequeue)
Else
Sorted_queue.enqueue(temp)
temp = sorted_queue.dequeue
sorted_queue.enqueue(temp)
Operations in DEQUE
1. Insert element at back (Push Back)
2. Insert element at front (Push Front)
3. Remove element at front (Pop Front)
4. Remove element at back (Pop Back)
Algorithm:
Insert element at front (Push Front)
If Queue is empty
Then,
Assign data in Q[0]
Front ->Q[0]
Rear -> Q[1]
Size++
Else
Front - -
Assign data to front
Size + +