DSA-Lecture-Notes-UNIT-I
DSA-Lecture-Notes-UNIT-I
L-T-P-C 3-0-1-4
MR22-1CS0105
Lecture Notes
Data Structures and Its Applications
UNIT – I
Data Structures: Introduction, Difference between data type and data structure, Need
of Data structure, Classification of Data structures, Linear vs. Non-linear Data
Structures.
Types of Data Structures in Python: Built-in and user-defined data structures.
Static Linear Data Structure: Arrays- Characteristics of an Array, Applications of Arrays.
Arrays vs.List.
Searching: Linear Search, Binary search. Sorting: Merge Sort, Bubble Sort, Selection
Sort and Quick Sort.
UNIT – II
Dynamic Linear Data Structure: Stacks, Stack operations, Characteristics of a Stacks,
Applications of Stacks.
Dynamic Linear Data Structure: Queues, Characteristics of a Queues, Circular Queues,
Applications of Queue.
UNIT - III
Dynamic Linear Data Structure: Linked List, Characteristics of a Linked List, Types of
Linked List:
Singly linked, doubly linked and circular. Applications of Linked List.
Implementation of stacks and queue using linked list.
UNIT – IV
Non-linear Data Structure: Trees, Tree Terminologies, Characteristics of a Trees,
Operations on Binary Trees and Binary Search Trees: find, insert and delete.
Tree traversal techniques: Inorder, Preorder, Postorder Traversal, Applications of Trees.
UNIT -V
Non-linear Data Structure: Graphs, Characteristics of a Graphs,
Graph Traversals: Breadth First Search, Depth First Search, Applications of Graphs.
REFERENCE BOOKS:
Data Types
Numbers
Python supports three different numerical types
1 Integer (signed integers)
2 Float (floating point real variables)
3 Complex (complex variables)
All integers in python are represented as long integers, hence no separate long
int data type in python.
Ex: a = 5, b = 25.5, c = 2+3i
Strings
Boolean
The Python Boolean type is one of Python's built-in data types. It is used to
represent the truth value of an expression - True or False. It is denoted by a class
'bool'. True can be represented by any non-zero value or 'True' whereas false can
be represented by the 0 or 'False'.
Data Structure:
Once we have data in variables, we need some mechanism to manipulate that
data for solving our problems. Data Structure is a specific way of storing and
organizing data, so that it can be used more effectively. Data Structure is used,
in general, for organizing, processing, retrieving and storing of data.
General data structures includes arrays, files, linked lists, stacks, queues, trees,
graphs and so on.
Data structures are the building blocks of programming languages.
In addition to using data structures, it is crucial to select the best data structure
for each activity. A bad data structure selection could lead to slow runtimes or
unresponsive code. There are five things to think about while choosing a data
structure.
What kind of information will be stored?
How will that information be used?
Where should data persist, or be kept, after it is created?
What is the best way to organize the data?
What factors should be taken into account while managing Memory and
storage reservations?
Now let's have a brief look at both these data structures. What is the linear data
structure?
A linear data structure is a structure in which the elements are stored
sequentially, and the elements are connected to the previous and the next
element. As the elements are stored sequentially, so they can be traversed or
accessed in a single run. The implementation of linear data structures is easier
as the elements are sequentially organized in memory. The data elements in an
array are traversed one after another and can access only one element at a time.
The types of linear data structures are Array, Queue, Stack, Linked List.
Static data structure has a fixed memory size. It is easier to access the
elements in a static data structure. The content of the data structure can
be modified but without changing the memory space allocated to it.
Example: Arrays
In dynamic data structure, the size is not fixed. It's size can be randomly
updated during the runtime i.e., during the operations performed on it,
which may be considered efficient concerning to the memory (space) and
as well time complexity of the code.
Example: Queue, Stack
Advantages of Static Linear Data Structures
Fast access time: Static data structures offer fast access time because memory
is allocated at compile-time and the size is fixed. It is easier to access the
elements in a static data structure.
Predictable memory usage: Since the memory allocation is fixed at compile-
time, the programmer can precisely predict how much memory will be used by
the program, which is an important factor in memory-constrained environments.
Ease of implementation and optimization: Static data structures may be
easier to implement and optimize since the structure and size are fixed, and
algorithms can be optimized for the specific data structure, which reduces cache
misses and can increase the overall performance of the program.
Efficient memory management: Static data structures allow for efficient
memory allocation and management. Since the size of the data structure is fixed
at compile-time, memory can be allocated and released efficiently, without the
need for frequent reallocations or memory copies
Simplified code: Since static data structures have a fixed size, they can simplify
code by removing the need for dynamic memory allocation and associated error
checking.
Reduced overhead: Static data structures typically have lower overhead than
dynamic data structures, since they do not require extra bookkeeping to manage
memory allocation and de-allocation.
Comprehensions:
List:
List comprehensions provide a concise way to create lists. Common applications
are to make new lists where each element is the result of some operations applied
to each member of another sequence or iterable, or to create a subsequence of
those elements that satisfy a certain condition.
>>> list1=[]
>>> for x in range(10):
list1.append(x**2)
>>> list1
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
(or)
This is also equivalent to
>>> list1=list(map(lambda x:x**2, range(10)))
>>> list1
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
(or)
Which is more concise and redable.
>>> list1=[x**2 for x in range(10)]
>>> list1
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Tuple:
Tuple Comprehensions are special: The result of a tuple comprehension is
special. You might expect it to produce a tuple, but what it does is produce a
special "generator" object that we can iterate over.
For example:
>>> x = (i for i in 'abc') #tuple comprehension
>>> x
<generator object <genexpr> at 0x033EEC30>
>>> print(x)
<generator object <genexpr> at 0x033EEC30>
You might expect this to print as ('a', 'b', 'c') but it prints as <generator object
<genexpr> at 0x02AAD710> The result of a tuple comprehension is not a tuple:
it is actually a generator. The only thing that you need to know now about a
generator now is that you can iterate over it, but only once.
Set:
Similarly, to list comprehensions, set comprehensions are also supported:
>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}
>>> x= {3*x for x in range(10) if x>5}
>>> x
{24, 18, 27, 21}
Dictionary:
Dictionary comprehensions can be used to create dictionaries from arbitrary
key and value expressions:
>>> z={x: x**2 for x in (2,4,6)}
>>> z
{2: 4, 4: 16, 6: 36}
>>> dict11 = {x: x*x for x in range(6)}
>>> dict11
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Arrays
An array is a fundamental data structure available in most programming
languages. An array is a collection of items of the same variable type that are
stored at contiguous memory locations. It's one of the most popular and simple
data structures and is often used to implement other data structures. It allows
the processing of a large amount of data in a relatively short period.
Array Index: In an array, elements are identified by their indexes. Array index
starts with 0.
Array Element: Elements are items stored in an array and can be accessed by
their index.
Array Length: The length of an array is determined by the number of elements
it can contain. There are different operations possible in an array, like Searching,
Sorting, Inserting, Traversing, Reversing, and Deleting
Characteristics of Arrays
Arrays use an index-based data structure which helps to identify each of the
elements in an array easily using the index. If a user wants to store multiple
values of the same data type, then the array can be utilized efficiently. An array
can also handle complex data structures by storing data in a two-dimensional
array. An array is also used to implement other data structures like Stacks,
Queues, Heaps, Hash tables, etc.,
The search process in an array can be done very easily
Types of Arrays
One-dimensional array (1-D): You can imagine a 1d array as a row, where
elements are stored one after another.
Two-dimensional array (2-D): Multidimensional arrays can be considered as an
array of arrays or as a matrix consisting of rows and columns.
Three-dimensional array (3-D): A 3-D Multidimensional array contains three
dimensions, so it can be considered an array of two-dimensional arrays.
Operations on Arrays
Initialization: An array can be initialized with values at the time of declaration
or later using an assignment statement.
Accessing elements: Elements in an array can be accessed by their index, which
starts from 0 and goes up to the size of the array minus one.
Searching for elements: Arrays can be searched for a specific element using
linear search or binary search algorithms.
Sorting elements: Elements in an array can be sorted in ascending or
descending order using algorithms like bubble sort, selection sort, or quick sort.
Inserting elements: Elements can be inserted into an array at a specific
location, but this operation can be time-consuming because it requires shifting
existing elements in the array.
Deleting elements: Elements can be deleted from an array by shifting the
elements that come after it to fill the gap.
Updating elements: Elements in an array can be updated or modified by
assigning a new value to a specific index.
Traversing elements: The elements in an array can be traversed in order,
visiting each element once
Arrays vs List
Both list and array and list are used to store the data in Python. These data
structures allow us to indexing, slicing, and iterating. But they have little
dissimilarity with each other.
Python List
A list is a built-in, linear data structure of Python. It is used to store the data in
a sequence manner. We can perform several operations to list, such as indexing,
iterating, and slicing. The list comes with the following features.
The list elements are enclosed with a square bracket, and each element is
separated by a comma (,).
It is a mutable type which means we can modify the list items after their
creation.
The lists are ordered, which means items are stored in a specific order. We
can use indexing to access the list element.
We can store the items of different data types. We can combine strings,
integers, and objects in the same list.
Example 2
1. # creating a list containing elements
2. # belonging to different data types
3. list1 = [1,"Yash",['a','e']]
4. print(list1)
Output:
[1, 'Yash', ['a', 'e']
In the above list, the first element is an integer; the second is a string and third
is a list of characters.
Array in Python
An array is also a linear data structure that stores the data. It is also ordered,
mutable, and enclosed in square brackets. It can store the non-unique items.
But there are restrictions to store the different data type values.
To work with the Array in Python, we need to import either an array module or
a Numpy.
1. import array as arr
2. or
3. import numpy as np
Example - 1
1. Import array as arr
2. array_1 = arr.array("i", [31, 60,19, 12])
3. print(array_1)
4. print(type(array_1))
Output:
array('i', [31, 60, 19, 12])
<class 'array.array'>
Output:
['numbers' 'sunil' 'sachin' 'megha' 'deepti']
<class 'numpy.ndarray'>
We have specified the string type and stored the string value.
Searching
Binary Search
Both techniques are widely used to search an element in the given list.
Linear Search
Step 2: key and arr[0] are not the same. So make i = 1 and match key with
arr[1].
Step 3: arr[1] and key are different. Increment i and compare key with arr[2].
Compare key with index 2
Step 4: arr[2] is not the same with key. Increment i and compare key with
arr[3].
Step 5: key and arr[3] are different. Make i = 4 and compare key with arr[4].
Step 6: key and arr[4] are not same. Make i = 5 and match key with arr[5].
# Driver Code
if __name__ == "__main__":
arr = [2, 3, 4, 10, 40]
x = 10
N = len(arr)
# Function call
result = search(arr, N, x)
if(result == -1):
print("Element is not present in array")
else:
print("Element is present at index", result)
Explanation:
In the above code, we have created a function search(), which takes three
arguments - list1, length of the list, and number to search. We defined for loop
and iterate each element and compare to the key value. If element is found,
return the index else return -1 which means element is not present in the list.
Auxiliary Space: O(1) as except the variable to iterate through the list, no other
variable is used.
Linear search algorithm is suitable for smaller list (<100) because it check every
element to get the desired number. Suppose there are 10,000 element list and
desired element is available at the last position, this will consume much time by
comparing with each element of the list.
Binary Search:
1. Iteration Method
2. Recursive Method (The recursive method follows the divide and conquer
approach)
mid = l + (r - l) // 2
if arr[mid] == x:
return mid
elif arr[mid] > x:
return binarySearch(arr, l, mid-1, x)
else:
return binarySearch(arr, mid + 1, r, x)
else:
# Element is not present in the array
return -1
# Driver Code
arr = [2, 3, 4, 10, 40]
x = 10
# Function call
result = binarySearch(arr, 0, len(arr)-1, x)
if result != -1:
print("Element is present at index % d" % result)
else:
print("Element is not present in array")
Output:
Element is present at index 3
Time Complexity: O(log n)
Auxiliary Space: O(log n)
# Initial list1
list1 = [12, 24, 32, 39, 45, 50, 54]
n = 45
# Function call
result = binary_search(list1, n) 35.
if result != -1:
print("Element is present at index", str(result))
else:
print("Element is not present in list1")
Output:
Element is present at index 4
Explanation:
In the above program -
We have created a function called binary_search() function which
takes two arguments - a list to sorted and a number to be searched.
We have declared two variables to store the lowest and highest values
in the list. The low is assigned initial value to 0, high to len(list1) - 1
and mid as 0.
Next, we have declared the while loop with the condition that the
lowest is equal and smaller than the highest the while loop will
iterate if the number has not been found yet.
In the while loop, we find the mid value and compare the index value
to the number we are searching for.
Otherwise, decrease the mid value and assign it to the high. The
search moves to the right side.
If we reach at the end of the function, then the element is not present
in the list. We return -1 to the calling function.
Sorting
Bubble Sort:
It is a simple sorting algorithm which sorts ‘n’ number of elements in the list by
comparing the ach pair of adjacent items and swaps them if they are in wrong
order.
Algorithm:
1. Starting with the first element (index=0), compare the current
element with the next element of a list.
2. If the current element is greater (>) than the next element of the
list then swap them.
3. If the current element is less (<) than the next element of the list
move to the next element.
4. Repeat step 1 until it correct order is framed.
list1=[9,16,6,26,0]
print("unsorted list1 is", list1)
for j in range(len(list1)-1):
for i in range(len(list1)-1):
if list1[i]>list1[i+1]:
list1[i],list1[i+1]=list1[i+1],list1[i]
print(list1)
else:
print(list1)
print( )
print("sorted list is",list1)
Output:
unsorted list1 is [9, 16, 6, 26, 0]
[9, 16, 6, 26, 0]
[9, 6, 16, 26, 0]
[9, 6, 16, 26, 0]
[9, 6, 16, 0, 26]
[6, 9, 16, 0, 26]
[6, 9, 16, 0, 26]
[6, 9, 0, 16, 26]
[6, 9, 0, 16, 26]
[6, 9, 0, 16, 26]
[6, 0, 9, 16, 26]
[6, 0, 9, 16, 26]
[6, 0, 9, 16, 26]
[0, 6, 9, 16, 26]
[0, 6, 9, 16, 26]
[0, 6, 9, 16, 26]
[0, 6, 9, 16, 26]
sorted list is [0, 6, 9, 16, 26]
Output:
unsorted list1 is [9, 16, 6, 26, 0]
[9, 16, 6, 26, 0]
[9, 6, 16, 26, 0]
[9, 6, 16, 26, 0]
[9, 6, 16, 0, 26]
[6, 9, 16, 0, 26]
[6, 9, 16, 0, 26]
[6, 9, 0, 16, 26]
[6, 9, 0, 16, 26]
[6, 0, 9, 16, 26]
[0, 6, 9, 16, 26]
sorted list is [0, 6, 9, 16, 26]
# In a different way:
list1=[9,16,6,26,0]
print("unsorted list1 is", list1)
for j in range(len(list1)-1):
for i in range(len(list1)-1-j):
if list1[i]>list1[i+1]:
list1[i],list1[i+1]=list1[i+1],list1[i]
print(list1)
else:
print(list1)
print( )
print("sorted list is",list1)
Output:
unsorted list1 is [9, 16, 6, 26, 0]
[9, 16, 6, 26, 0]
[9, 6, 16, 26, 0]
[9, 6, 16, 26, 0]
[9, 6, 16, 0, 26]
[6, 9, 16, 0, 26]
[6, 9, 16, 0, 26]
[6, 9, 0, 16, 26]
[6, 9, 0, 16, 26]
[6, 0, 9, 16, 26]
[0, 6, 9, 16, 26]
sorted list is [0, 6, 9, 16, 26]
Output:
enter how many numbers:5 enter values
5
77
4
66
30
unsorted list1 is [5, 77, 4, 66, 30]
[5, 77, 4, 66, 30]
[5, 4, 77, 66, 30]
[5, 4, 66, 77, 30]
[5, 4, 66, 30, 77]
[4, 5, 66, 30, 77]
[4, 5, 66, 30, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
[4, 5, 30, 66, 77]
sorted list is [4, 5, 30, 66, 77]
Output:
[9, 16, 6, 26, 0]
[16, 9, 6, 26, 0]
[16, 9, 6, 26, 0]
[16, 9, 26, 6, 0]
[16, 9, 26, 6, 0]
[16, 9, 26, 6, 0]
[16, 26, 9, 6, 0]
[16, 26, 9, 6, 0]
[16, 26, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
[26, 16, 9, 6, 0]
sorted list is [26, 16, 9, 6, 0]
Selection Sort:
Algorithm:
1. Starting from the first element search for smallest/biggest element in the
list of numbers.
2. Swap min/max number with first element
3. Take the sub-list (ignore sorted part) and repeat step 1 and 2 until all the
elements are sorted.
#Write a python program to arrange the elements in ascending order using selection
sort:
def selection_sort(arr):
n = len(arr)
for i in range(n):
# Find the minimum element in the unsorted part
min_index = i
for j in range(i+1, n):
if arr[j] < arr[min_index]:
min_index = j
# Swap the minimum element with the first element of the unsorted part
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
arr = [5,3,7,1,9,6]
print(selection_sort(arr))
Output:
[5, 3, 7, 1, 9, 6]
[1, 3, 7, 5, 9, 6]
[1, 3, 7, 5, 9, 6]
[1, 3, 5, 7, 9, 6]
[1, 3, 5, 6, 9, 7]
[1, 3, 5, 6, 7, 9]
[1, 3, 5, 6, 7, 9]
#Write a python program to arrange the elements in descending order using selection
sort:
list1=[5,3,7,1,9,6]
print(list1)
for i in range(len(list1)):
min_val=max(list1[i:])
min_ind=list1.index(min_val)
list1[i],list1[min_ind]=list1[min_ind],list1[i]
print(list1)
Output:
[5, 3, 7, 1, 9, 6]
[9, 3, 7, 1, 5, 6]
[9, 7, 3, 1, 5, 6]
[9, 7, 6, 1, 5, 3]
[9, 7, 6, 5, 1, 3]
[9, 7, 6, 5, 3, 1]
[9, 7, 6, 5, 3, 1]
Note: If we want the elements to be sorted in descending order use max ()
method in place of min ().
Merge Sort:
Generally, this merge sort works on the basis of divide and conquer algorithm.
The three steps need to be followed is divide, conquer and combine. We will be
dividing the unsorted list into sub list until the single element in a list is found.
Algorithm:
1. Split the unsorted list.
2. Compare each of the elements and group them
3. Repeat step 2 until whole list is merged and sorted.
def merge_sort(arr):
# Base case: if the input array is empty or has only one element, it is already
sorted
if len(arr) <= 1:
return arr
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(merge_sort(arr))
# Write a python program to arrange the elements in descending order
using Merge sort (with functions)
def merge_sort(arr):
# Base case: if the input array is empty or has only one element, it is already
sorted
if len(arr) <= 1:
return arr
# Divide the array into two halves
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
return result
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(merge_sort(arr))
Quick Sort:
• The quick sort algorithm falls under the divide and conquer class of
algorithms, where we break (divide) a problem into smaller chunks that
are much simpler to solve (conquer).
• In this case, an unsorted array is broken into sub-arrays that are partially
sorted, until all elements in the list are in the right position, by which time
our unsorted list will have become sorted.
• The Quick sorting process is mainly considered in three phases.
• Partitioning the list/array
• Pivot selection
• Implementation
• Before we divide the list into smaller chunks, we have to partition it. This
is the heart of the quick sort algorithm.
• To partition the array, we must first select a pivot. All the elements in the
array will be compared with this pivot. At the end of the partitioning
process, all elements that are less than the pivot will be to the left of the
pivot, while all elements greater than the pivot will lie to the right of the
pivot in the array.
• There are different variations of quicksort where the pivot element is
selected from different positions (start, median, random, end).
• For the sake of simplicity, we’ll take the first element in any array as the
pivot. This kind of pivot selection degrades in performance, especially
when sorting an already sorted list.
• Randomly picking the middle or last element in the array as the pivot does
not improve the situation any further.
• There are two famous partition schemes for Quick sort
• Hoare Partition
• Lomuto Partition
• Hoare Partition : This takes first element as pivot, and places all the
elements smaller than the pivot on the left side and all the elements greater
than the pivot on the right side. It returns the index of the last element on
the smaller side.
• Lomuto Partition : This takes last element as pivot, places the pivot
element at its correct position in sorted array, and places all smaller
(smaller than pivot) to left of pivot and all greater elements to right of pivot.
Hoare Partition:
• The Hoare Partition Scheme is a popular algorithm used for quick sort. It
works by selecting a pivot element, and then partitioning the elements into
two groups such that all elements less than the pivot are in one group and
all elements greater than the pivot are in another.
Algorithm:
1. Select the pivot element
2. Find out the correct position of pivot element in the list by rearranging it.
3. Divide the list based on pivot element
4. Sort the sub list recursively
Note: Pivot element can be first, last, random elements or median of three
values.
In the following program we are going to write 3 functions. The first function is
to find pivot element and its correct position. In second function we divide the
list based on pivot element and sort the sub list and third function (main fun)
is to print input and output.
# Write a python program to arrange the elements in ascending order using Quick
sort (with functions)
elements = [11,9,29,7,2,15,28]
# elements = ["mona", "dhaval", "aamir", "tina", "chang"]
quick_sort(elements, 0, len(elements)-1)
print(elements)
def quick_sort_descending(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[0]
left = []
right = []
for i in range(1, len(arr)):
if arr[i] > pivot:
left.append(arr[i])
else:
right.append(arr[i])
return quick_sort_descending(left) + [pivot] + quick_sort_descending(right)
# Example usage
arr = [3, 5, 2, 8, 1, 4, 7, 6]
sorted_arr = quick_sort_descending(arr)
print(sorted_arr)
• In the Lomuto partition scheme, usually the last element of the list is
chosen as the pivot element.
• Two pointers are also maintained for tracking and comparison purposes.
A pointer i is maintained while another pointer j is scanning through the
array, from its starting all the way to the end (up to the pivot element).
• The scan ensures that any element e₀ that is present from the starting
point of the array to the (i-1)ᵗʰ index is lesser than the pivot element in
terms of value and the elements present at any index ranging from i to j
are equal to or bigger than the pivot element in terms of value.
• Through this technique, the array will be sorted by the time we reach the
end of the array.
if __name__ == "__main__":
A = [4, 2, 7, 3, 1, 9, 6, 0, 8]
low, high = 0, len(A)-1
print("BEFORE SORTING:", A)
quicksort(A, low, high)
print("AFTER SORTING:", A)
UNIT – II
Dynamic Linear Data Structure: Stacks, Stack operations, Characteristics of a Stacks,
Applications of Stacks.
Dynamic Linear Data Structure: Queues, Characteristics of a Queues, Circular Queues,
Applications of Queue.
Stacks:
Stack Operations:
1. push() : Insert the element into linked list nothing but which is the top node
of Stack.
2. pop() : Return top element from the Stack and move the top pointer to the
second node of linked list or Stack.
3. peek(): Return the top element.
4. display(): Print all element of Stack.
Push:
When a new element is added to the stack, it is placed on top of the existing
elements. This is called a push operation.
For instance, consider an empty stack that can store integers. If we push the
following elements in the order given:
push(5)
push(8)
push(2)
Pop:
When an element is removed from the stack, it is removed from the top of the
stack. This is called a pop operation.
Let's continue from the previous example. If we perform the following pop
operations in the order given:
pop()
pop()
Note that if we try to pop an element from an empty stack, we will get an error
as there is no element to remove.
def pop(self):
if self.top == -1:
print("Stack Underflow")
return None
item = self.stack[self.top]
self.top -= 1
return item
def peek(self):
if self.top == -1:
print("Stack is empty")
return None
return self.stack[self.top]
def is_empty(self):
return self.top == -1
def is_full(self):
return self.top == self.capacity - 1
def display(self):
if self.top == -1:
print("Stack is empty")
return
for i in range(self.top, -1, -1):
print(self.stack[i])
# Example usage:
stack = Stack(5)
stack.push(1)
stack.push(2)
stack.push(3)
stack.push(4)
stack.push(5)
stack.push(6) # This will cause a stack overflow
print(stack.pop()) # Outputs: 5
print(stack.pop()) # Outputs: 4
print(stack.peek()) # Outputs: 3
stack.display() # Outputs: 3 2 1
In this implementation, the stack has a fixed capacity specified during
initialization, and it uses a list to store the items. The top variable keeps track of
the index of the top item in the stack. The push() method adds an item to the top
of the stack, the pop() method removes and returns the top item, and the peek()
method returns the top item without removing it. The is_empty() and is_full()
methods check whether the stack is empty or full, respectively. Finally, the
display() method prints all the items in the stack from top to bottom.
def pop(self):
if self.top == -1:
print("Stack Underflow")
return None
item = self.stack.pop()
self.top -= 1
return item
def peek(self):
if self.top == -1:
print("Stack is empty")
return None
return self.stack[self.top]
def is_empty(self):
return self.top == -1
def display(self):
if self.top == -1:
print("Stack is empty")
return
for i in range(self.top, -1, -1):
print(self.stack[i])
# Example usage:
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
stack.push(4)
stack.push(5)
stack.push(6)
print(stack.pop()) # Outputs: 6
print(stack.pop()) # Outputs: 5
print(stack.peek()) # Outputs: 4
stack.display() # Outputs: 4 3 2 1
In this implementation, the stack has no fixed capacity specified during initialization, and it uses a
list to store the items. The top variable keeps track of the index of the top item in the stack. The
push() method adds an item to the top of the stack, and if the list is already full, it automatically
resizes the list by allocating more memory. The pop() method removes and returns the top item,
and the peek() method returns the top item without removing it. The is_empty() method checks
whether the stack is empty. Finally, the display() method prints all the items in the stack from top
to bottom.
stack = []
# Driver code
push(10)
push(20)
push(30)
pop()
pop()
pop()
pop()
OR
stack = []
# Push operation
stack.append(10)
stack.append(20)
stack.append(30)
# Pop operation
stack.pop()
stack.pop()
class Stack:
def __init__(self):
self.top = None
stack = LifoQueue()
# Driver code
push(10)
push(20)
push(30)
pop()
pop()
pop()
pop()
Characteristics of a Stacks:
1. LIFO ordering: The last item pushed onto the stack is the first item popped
off the stack.
2. Push and Pop Operations: The two primary operations that can be
performed on a stack are "push," which adds an item to the top of the
stack, and "pop," which removes the item from the top of the stack.
3. Top Element Access: A stack allows access only to the top element, which
is the most recently added item. Other elements are not directly accessible
and must be removed first.
5. Dynamic Size: Stacks can grow or shrink dynamically as items are added
or removed.
Applications of Stack
There are many applications of a stack. Some of them are:
Stacks are used in backtracking algorithms.
They are also used to implement undo/redo functionality in a software.
Stacks are also used in syntax parsing for many compilers.
Stacks are also used to check proper opening and closing of parenthesis.
queue = []
To implement a queue using a linked list in Python, you can define a Node class
to represent each element in the linked list and a Queue class to manage the
operations of the queue. Here's an example implementation:
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Queue:
def __init__(self):
self.front = None
self.rear = None
def is_empty(self):
return self.front is None
def dequeue(self):
if self.is_empty():
raise Exception("Queue is empty")
else:
dequeued_node = self.front
self.front = dequeued_node.next
if self.front is None:
self.rear = None
return dequeued_node.value
def peek(self):
if self.is_empty():
raise Exception("Queue is empty")
else:
return self.front.value
def __str__(self):
if self.is_empty():
return "[]"
else:
queue_str = "["
node = self.front
while node is not None:
queue_str += str(node.value) + ", "
node = node.next
queue_str = queue_str[:-2] + "]"
return queue_str