Python Data Structure - Quick Guide
Python Data Structure - Quick Guide
Python - DS Introduction
Here, we will understand what is data structure with regards to Python programming language.
In this chapter we are going to study a short overview of some frequently used data structures in general and how they are related to
some specific python data types. There are also some data structures specific to python which is listed as another category.
These are the data structures which store the data elements in a sequential manner.
Array − It is a sequential arrangement of data elements paired with the index of the data element.
Linked List − Each data element contains a link to another element along with the data present in it.
Stack − It is a data structure which follows only to specific order of operation. LIFO(last in First Out) or FILO(First in Last Out).
Queue − It is similar to Stack but the order of operation is only FIFO(First In First Out).
Matrix − It is two dimensional data structure in which the data element is referred by a pair of indices.
Binary Tree − It is a data structure where each data element can be connected to maximum two other data elements and it
starts with a root node.
Heap − It is a special case of Tree data structure where the data in the parent node is either strictly greater than/ equal to the
child nodes or strictly less than its child nodes.
Hash Table − It is a data structure which is made of arrays associated with each other using a hash function. It retrieves
values using keys rather than index from a data element.
Graph − It is an arrangement of vertices and nodes where some of the nodes are connected to each other through links.
List − It is similar to array with the exception that the data elements can be of different data types. You can have both numeric
and string data in a python list.
Page 2 of 67
Tuple − Tuples are similar to lists but they are immutable which means the values in a tuple cannot be modified they can only
be read.
Dictionary − The dictionary contains Key-value pairs as its data elements.
In the next chapters we are going to learn the details of how each of these data structures can be implemented using Python.
Python - DS Environment
Python is available on a wide variety of platforms including Linux and Mac OS X. Let's understand how to set up our Python
environment.
Win 9x/NT/2000
Macintosh (Intel, PPC, 68K)
OS/2
DOS (multiple versions)
PalmOS
Nokia mobile phones
Windows CE
Acorn/RISC OS
BeOS
Amiga
VMS/OpenVMS
QNX
VxWorks
Psion
Python has also been ported to the Java and .NET virtual machines
Getting Python
The most up-to-date and current source code, binaries, documentation, news, etc., is available on the official website of Python
[Link]
You can download Python documentation from this website given herewith,[Link]/doc. The documentation is available in
HTML, PDF, and PostScript formats.
Installing Python
Python distribution is available for a wide variety of platforms. You need to download only the binary code applicable for your platform
and install Python.
If the binary code for your platform is not available, you need a C compiler to compile the source code manually. Compiling the source
code offers more flexibility in terms of choice of features that you require in your installation.
Follow the link to download zipped source code available for Unix/Linux.
Download and extract files.
make
make install
This installs Python at standard location /usr/local/bin and its libraries at /usr/local/lib/pythonXX where XX is the version of
Python.
Windows Installation
Here are the steps to install Python on Windows machine.
Follow the link for the Windows installer [Link] file where XYZ is the version you need to install.
To use this installer [Link], the Windows system must support Microsoft Installer 2.0. Save the installer file to your
local machine and then run it to find out if your machine supports MSI.
Run the downloaded file. This brings up the Python install wizard, which is really easy to use. Just accept the default settings,
wait until the install is finished, and you are done.
Macintosh Installation
Recent Macs come with Python installed, but it may be several years out of date. See [Link]/download/mac/ for instructions
on getting the current version along with extra tools to support development on the Mac. For older Mac OS's before Mac OS X 10.3
(released in 2003), MacPython is available.
Jack Jansen maintains it and you can have full access to the entire documentation at his website −
[Link] You can find complete installation details for Mac OS installation.
Setting up PATH
Programs and other executable files can be in many directories, so operating systems provide a search path that lists the directories
that the OS searches for executables.
The path is stored in an environment variable, which is a named string maintained by the operating system. This variable contains
information available to the command shell and other programs.
The path variable is named as PATH in Unix or Path in Windows (Unix is case sensitive; Windows is not).
In Mac OS, the installer handles the path details. To invoke the Python interpreter from any particular directory, you must add the
Python directory to your path.
In the csh shell − type setenv PATH "$PATH:/usr/local/bin/python" and press Enter.
In the bash shell (Linux) − type export ATH="$PATH:/usr/local/bin/python" and press Enter.
To add the Python directory to the path for a particular session in Windows −
PYTHONPATH
It has a role similar to PATH. This variable tells the Python interpreter where to locate the module files imported into a
1
program. It should include the Python source library directory and the directories containing Python source code.
PYTHONPATH is sometimes preset by the Python installer.
PYTHONSTARTUP
2 It contains the path of an initialization file containing Python source code. It is executed every time you start the interpreter.
It is named as .[Link] in Unix and it contains commands that load utilities or modify PYTHONPATH.
PYTHONCASEOK
3 It is used in Windows to instruct Python to find the first case-insensitive match in an import statement. Set this variable to
any value to activate it.
PYTHONHOME
4 It is an alternative module search path. It is usually embedded in the PYTHONSTARTUP or PYTHONPATH directories to make
switching module libraries easy.
Running Python
There are three different ways to start Python, which are as follows −
Interactive Interpreter
You can start Python from Unix, DOS, or any other system that provides you a command-line interpreter or shell window.
$python # Unix/Linux
or
python% # Unix/Linux
or
C:> python # Windows/DOS
Here is the list of all the available command line options, which is as mentioned below −
-d
1
It provides debug output.
-O
2
It generates optimized bytecode (resulting in .pyo files).
-S
3
Do not run import site to look for Python paths on startup.
-v
4
verbose output (detailed trace on import statements).
-X
5
disable class-based built-in exceptions (just use strings); obsolete starting with version 1.6.
-c cmd
6
run Python script sent in as cmd string
file
7
run Python script from given file
A Python script can be executed at command line by invoking the interpreter on your application, as in the following −
or
Page 5 of 67
or
You can run Python from a Graphical User Interface (GUI) environment as well, if you have a GUI application on your system that
supports Python.
Windows − PythonWin is the first Windows interface for Python and is an IDE with a GUI.
Macintosh − The Macintosh version of Python along with the IDLE IDE is available from the main website, downloadable as
either MacBinary or BinHex'd files.
If you are not able to set up the environment properly, then you can take help from your system admin. Make sure the Python
environment is properly set up and working perfectly fine.
Note − All the examples given in subsequent chapters are executed with Python 2.4.3 version available on CentOS flavor of
Linux.
We already have set up Python Programming environment online, so that you can execute all the available examples online at the same
time when you are learning theory. Feel free to modify any example and execute it online.
Python - Arrays
Array is a container which can hold a fix number of items and these items should be of the same type. Most of the data structures make
use of arrays to implement their algorithms. Following are the important terms to understand the concept of Array are as follows −
Index − Each location of an element in an array has a numerical index, which is used to identify the element.
Array Representation
Arrays can be declared in various ways in different languages. Below is an illustration.
As per the above illustration, following are the important points to be considered −
Basic Operations
Page 6 of 67
Array is created in Python by importing array module to the python program. Then, the array is declared as shown below −
Typecode are the codes that are used to define the type of value the array will hold. Some common typecodes used are as follows −
Typecode Value
Before looking at various array operations lets create and print an array using python.
Example
for x in array1:
print(x)
Output
When we compile and execute the above program, it produces the following result −
10
20
30
40
50
Example
Page 7 of 67
print (array1[0])
print (array1[2])
Output
When we compile and execute the above program, it produces the following result, which shows the element is inserted at index
position 1.
10
30
Insertion Operation
Insert operation is to insert one or more data elements into an array. Based on the requirement, a new element can be added at the
beginning, end, or any given index of array.
Example
Here, we add a data element at the middle of the array using the python in-built insert() method.
[Link](1,60)
for x in array1:
print(x)
When we compile and execute the above program, it produces the following result which shows the element is inserted at index position
1.
Output
10
60
20
30
40
50
Deletion Operation
Deletion refers to removing an existing element from the array and re-organizing all elements of an array.
Example
Here, we remove a data element at the middle of the array using the python in-built remove() method.
[Link](40)
Page 8 of 67
for x in array1:
print(x)
Output
When we compile and execute the above program, it produces the following result which shows the element is removed form the array.
10
20
30
50
Search Operation
You can perform a search for an array element based on its value or its index.
Example
Here, we search a data element using the python in-built index() method.
print ([Link](40))
Output
When we compile and execute the above program, it produces the following result which shows the index of the element. If the value is
not present in the array then th eprogram returns an error.
Update Operation
Update operation refers to updating an existing element from the array at a given index.
Example
Here, we simply reassign a new value to the desired index we want to update.
array1[2] = 80
for x in array1:
print(x)
Output
When we compile and execute the above program, it produces the following result which shows the new value at the index position 2.
10
20
80
40
50
Page 9 of 67
Python - Lists
The list is a most versatile datatype available in Python which can be written as a list of comma-separated values (items) between
square brackets. Important thing about a list is that items in a list need not be of the same type.
Creating a list is as simple as putting different comma-separated values between square brackets.
For example
Similar to string indices, list indices start at 0, and lists can be sliced, concatenated and so on.
Accessing Values
To access values in lists, use the square brackets for slicing along with the index or indices to obtain value available at that index.
For example
#!/usr/bin/python
list1[0]: physics
list2[1:5]: [2, 3, 4, 5]
Updating Lists
You can update single or multiple elements of lists by giving the slice on the left-hand side of the assignment operator, and you can add
to elements in a list with the append() method.
For example
#!/usr/bin/python
For example
#!/usr/bin/python
In fact, lists respond to all of the general sequence operations we used on strings in the prior chapter.
Python - Tuples
A tuple is a sequence of immutable Python objects. Tuples are sequences, just like lists. The differences between tuples and lists are,
the tuples cannot be changed unlike lists and tuples use parentheses, whereas lists use square brackets.
Creating a tuple is as simple as putting different comma-separated values. Optionally you can put these comma-separated values
between parentheses also.
For example
tup1 = ();
To write a tuple containing a single value you have to include a comma, even though there is only one value −
tup1 = (50,);
Like string indices, tuple indices start at 0, and they can be sliced, concatenated, and so on.
For example
#!/usr/bin/python
tup1[0]: physics
tup2[1:5]: [2, 3, 4, 5]
Updating Tuples
Tuples are immutable which means you cannot update or change the values of tuple elements. You are able to take portions of existing
tuples to create new tuples as the following example demonstrates −
#!/usr/bin/python
Removing individual tuple elements is not possible. There is, of course, nothing wrong with putting together another tuple with the
undesired elements discarded.
For example
#!/usr/bin/python
Note − an exception raised, this is because after del tup tuple does not exist anymore.
In fact, tuples respond to all of the general sequence operations we used on strings in the prior chapter.
Python - Dictionary
In Dictionary each key is separated from its value by a colon (:), the items are separated by commas, and the whole thing is enclosed
in curly braces. An empty dictionary without any items is written with just two curly braces, like this − {}.
Keys are unique within a dictionary while values may not be. The values of a dictionary can be of any type, but the keys must be of an
immutable data type such as strings, numbers, or tuples.
Example
A simple example is as follows −
Page 13 of 67
#!/usr/bin/python
Output
When the above code is executed, it produces the following result −
dict['Name']: Zara
dict['Age']: 7
If we attempt to access a data item with a key, which is not part of the dictionary, we get an error as follows −
Example
#!/usr/bin/python
Output
When the above code is executed, it produces the following result −
dict['Alice']:
Traceback (most recent call last):
File "[Link]", line 4, in <module>
print "dict['Alice']: ", dict['Alice'];
KeyError: 'Alice'
Updating Dictionary
You can update a dictionary by adding a new entry or a key-value pair, modifying an existing entry, or deleting an existing entry as
shown below in the simple example −
Example
#!/usr/bin/python
Output
dict['Age']: 8
dict['School']: DPS School
Example
To explicitly remove an entire dictionary, just use the del statement. A simple example is as mentioned below −
#!/usr/bin/python
Note −that an exception is raised because after del dict dictionary does not exist any more −
Output
This produces the following result −
dict['Age']: dict['Age']
dict['School']: dict['School']
More than one entry per key not allowed. Which means no duplicate key is allowed. When duplicate keys encountered during
assignment, the last assignment wins.
For example
#!/usr/bin/python
Output
Page 15 of 67
dict['Name']: Manni
Keys must be immutable. Which means you can use strings, numbers or tuples as dictionary keys but something like ['key'] is not
allowed.
Example
An example is as follows −
#!/usr/bin/python
Output
When the above code is executed, it produces the following result −
In the below example of a two dimensional array, observer that each array element itself is also an array.
Consider the example of recording temperatures 4 times a day, every day. Some times the recording instrument may be faulty and we
fail to record data. Such data for 4 days can be presented as a two dimensional array as below.
Day 1 - 11 12 5 2
Day 2 - 15 6 10
Day 3 - 10 8 12 5
Day 4 - 12 15 8 6
Accessing Values
The data elements in two dimesnional arrays can be accessed using two indices. One index referring to the main or parent array and
another index referring to the position of the data element in the inner [Link] we mention only one index then the entire inner array is
printed for that index position.
Example
The example below illustrates how it works.
Page 16 of 67
print(T[0])
print(T[1][2])
Output
[11, 12, 5, 2]
10
To print out the entire two dimensional array we can use python for loop as shown below. We use end of line to print out the values in
different rows.
Example
Output
When the above code is executed, it produces the following result −
11 12 5 2
15 6 10
10 8 12 5
12 15 8 6
Inserting Values
We can insert new data elements at specific position by using the insert() method and specifying the index.
Example
[Link](2, [0,5,11,13,6])
for r in T:
for c in r:
print(c,end = " ")
print()
Page 17 of 67
Output
11 12 5 2
15 6 10
0 5 11 13 6
10 8 12 5
12 15 8 6
Updating Values
We can update the entire inner array or some specific data elements of the inner array by reassigning the values using the array index.
Example
T[2] = [11,9]
T[0][3] = 7
for r in T:
for c in r:
print(c,end = " ")
print()
Output
When the above code is executed, it produces the following result −
11 12 5 7
15 6 10
11 9
12 15 8 6
Example
del T[3]
for r in T:
for c in r:
print(c,end = " ")
print()
Page 18 of 67
Output
11 12 5 2
15 6 10
10 8 12 5
Python - Matrix
Matrix is a special case of two dimensional array where each data element is of strictly same size. So every matrix is also a two
dimensional array but not vice versa.
Matrices are very important data structures for many mathematical and scientific calculations. As we have already discussed two
dimnsional array data structure in the previous chapter we will be focusing on data structure operations specific to matrices in this
chapter.
Matrix Example
Consider the case of recording temprature for 1 week measured in the morning, mid-day, evening and mid-night. It can be presented
as a 7X5 matrix using an array and the reshape method available in numpy.
Output
The above data can be represented as a two dimensional array as below −
[
['Mon' '18' '20' '22' '17']
['Tue' '11' '18' '21' '18']
['Wed' '15' '21' '20' '19']
['Thu' '11' '20' '22' '21']
['Fri' '18' '17' '23' '22']
['Sat' '12' '22' '20' '18']
['Sun' '13' '15' '19' '16']
]
Accessing Values
The data elements in a matrix can be accessed by using the indexes. The access method is same as the way data is accessed in Two
dimensional array.
Example
['Fri',18,17,23,22],['Sat',12,22,20,18],
['Sun',13,15,19,16]])
Output
When the above code is executed, it produces the following result −
Adding a row
Use the below mentioned code to add a row in a matrix.
Example
print(m_r)
Output
When the above code is executed, it produces the following result −
[
['Mon' '18' '20' '22' '17']
['Tue' '11' '18' '21' '18']
['Wed' '15' '21' '20' '19']
['Thu' '11' '20' '22' '21']
['Fri' '18' '17' '23' '22']
['Sat' '12' '22' '20' '18']
['Sun' '13' '15' '19' '16']
['Avg' '12' '15' '13' '11']
]
Adding a column
We can add column to a matrix using the insert() method. here we have to mention the index where we want to add the column and a
array containing the new values of the columns [Link] the below example we add t a new column at the fifth position from the
beginning.
Example
Page 20 of 67
print(m_c)
Output
[
['Mon' '18' '20' '22' '17' '1']
['Tue' '11' '18' '21' '18' '2']
['Wed' '15' '21' '20' '19' '3']
['Thu' '11' '20' '22' '21' '4']
['Fri' '18' '17' '23' '22' '5']
['Sat' '12' '22' '20' '18' '6']
['Sun' '13' '15' '19' '16' '7']
]
Delete a row
We can delete a row from a matrix using the delete() method. We have to specify the index of the row and also the axis value which is
0 for a row and 1 for a column.
Example
print(m)
Output
When the above code is executed, it produces the following result −
[
['Mon' '18' '20' '22' '17']
['Tue' '11' '18' '21' '18']
['Thu' '11' '20' '22' '21']
['Fri' '18' '17' '23' '22']
['Sat' '12' '22' '20' '18']
['Sun' '13' '15' '19' '16']
]
Delete a column
We can delete a column from a matrix using the delete() method. We have to specify the index of the column and also the axis value
which is 0 for a row and 1 for a column.
Page 21 of 67
Example
print(m)
Output
When the above code is executed, it produces the following result −
[
['Mon' '18' '22' '17']
['Tue' '11' '21' '18']
['Wed' '15' '20' '19']
['Thu' '11' '22' '21']
['Fri' '18' '23' '22']
['Sat' '12' '20' '18']
['Sun' '13' '19' '16']
]
Update a row
To update the values in the row of a matrix we simply re-assign the values at the index of the row. In the below example all the values
for thrusday's data is marked as zero. The index for this row is 3.
Example
print(m)
Output
When the above code is executed, it produces the following result −
[
['Mon' '18' '20' '22' '17']
['Tue' '11' '18' '21' '18']
['Wed' '15' '21' '20' '19']
['Thu' '0' '0' '0' '0']
['Fri' '18' '17' '23' '22']
['Sat' '12' '22' '20' '18']
['Sun' '13' '15' '19' '16']
]
Page 22 of 67
Python - Sets
Mathematically a set is a collection of items not in any particular order. A Python set is similar to this mathematical definition with below
additional conditions.
There is no index attached to any element in a python set. So they do not support any indexing or slicing operation.
Set Operations
The sets in python are typically used for mathematical operations like union, intersection, difference and complement etc. We can
create a set, access its elements and carry out these mathematical operations as shown below.
Creating a set
A set is created by using the set() function or placing all the elements within a pair of curly braces.
Example
Days=set(["Mon","Tue","Wed","Thu","Fri","Sat","Sun"])
Months={"Jan","Feb","Mar"}
Dates={21,22,17}
print(Days)
print(Months)
print(Dates)
Output
When the above code is executed, it produces the following result. Please note how the order of the elements has changed in the result.
Example
Days=set(["Mon","Tue","Wed","Thu","Fri","Sat","Sun"])
for d in Days:
print(d)
Output
Wed
Sun
Page 23 of 67
Fri
Tue
Mon
Thu
Sat
Example
Days=set(["Mon","Tue","Wed","Thu","Fri","Sat"])
[Link]("Sun")
print(Days)
Output
When the above code is executed, it produces the following result −
Example
Days=set(["Mon","Tue","Wed","Thu","Fri","Sat"])
[Link]("Sun")
print(Days)
Output
Union of Sets
The union operation on two sets produces a new set containing all the distinct elements from both the sets. In the below example the
element Wed is present in both the sets.
Example
DaysA = set(["Mon","Tue","Wed"])
DaysB = set(["Wed","Thu","Fri","Sat","Sun"])
Page 24 of 67
AllDays = DaysA|DaysB
print(AllDays)
Output
When the above code is executed, it produces the following result. Please note the result has only one wed.
Intersection of Sets
The intersection operation on two sets produces a new set containing only the common elements from both the sets. In the below
example the element Wed is present in both the sets.
Example
DaysA = set(["Mon","Tue","Wed"])
DaysB = set(["Wed","Thu","Fri","Sat","Sun"])
AllDays = DaysA & DaysB
print(AllDays)
Output
When the above code is executed, it produces the following result. Please note the result has only one wed.
set(['Wed'])
Difference of Sets
The difference operation on two sets produces a new set containing only the elements from the first set and none from the second set.
In the below example the element Wed is present in both the sets so it will not be found in the result set.
Example
DaysA = set(["Mon","Tue","Wed"])
DaysB = set(["Wed","Thu","Fri","Sat","Sun"])
AllDays = DaysA - DaysB
print(AllDays)
Output
When the above code is executed, it produces the following result. Please note the result has only one wed.
set(['Mon', 'Tue'])
Compare Sets
We can check if a given set is a subset or superset of another set. The result is True or False depending on the elements present in the
sets.
Example
Page 25 of 67
DaysA = set(["Mon","Tue","Wed"])
DaysB = set(["Mon","Tue","Wed","Thu","Fri","Sat","Sun"])
SubsetRes = DaysA <= DaysB
SupersetRes = DaysB >= DaysA
print(SubsetRes)
print(SupersetRes)
Output
True
True
Python - Maps
Python Maps also called ChainMap is a type of data structure to manage multiple dictionaries together as one unit. The combined
dictionary contains the key and value pairs in a specific sequence eliminating any duplicate keys. The best use of ChainMap is to search
through multiple dictionaries at a time and get the proper key-value pair mapping. We also see that these ChainMaps behave as stack
data structure.
Creating a ChainMap
We create two dictionaries and club them using the ChainMap method from the collections library. Then we print the keys and values of
the result of the combination of the dictionaries. If there are duplicate keys, then only the value from the first key is preserved.
Example
import collections
print('Keys = {}'.format(list([Link]())))
print('Values = {}'.format(list([Link]())))
print()
Output
When the above code is executed, it produces the following result −
Page 26 of 67
elements:
day1 = Mon
day3 = Wed
day2 = Tue
Map Reordering
If we change the order the dictionaries while clubbing them in the above example we see that the position of the elements get
interchanged as if they are in a continuous chain. This again shows the behavior of Maps as stacks.
Example
import collections
Output
Updating Map
When the element of the dictionary is updated, the result is instantly updated in the result of the ChainMap. In the below example we
see that the new updated value reflects in the result without explicitly applying the ChainMap method again.
Example
import collections
dict2['day4'] = 'Fri'
print([Link],'\n')
Output
We have already seen how we create a node class and how to traverse the elements of a [Link] this chapter we are going to study the
types of linked lists known as singly linked lists. In this type of data structure there is only one link between any two data elements. We
create such a list and create additional methods to insert, update and remove elements from the list.
class Node:
def __init__(self, dataval=None):
[Link] = dataval
[Link] = None
class SLinkedList:
def __init__(self):
[Link] = None
list1 = SLinkedList()
[Link] = Node("Mon")
e2 = Node("Tue")
e3 = Node("Wed")
# Link first Node to second node
[Link] = e2
Example
class Node:
def __init__(self, dataval=None):
[Link] = dataval
[Link] = None
Page 28 of 67
class SLinkedList:
def __init__(self):
[Link] = None
def listprint(self):
printval = [Link]
while printval is not None:
print ([Link])
printval = [Link]
list = SLinkedList()
[Link] = Node("Mon")
e2 = Node("Tue")
e3 = Node("Wed")
[Link]()
Output
Mon
Tue
Wed
This involves pointing the next pointer of the new data node to the current head of the linked list. So the current head of the linked list
becomes the second data element and the new node becomes the head of the linked list.
Example
class Node:
def __init__(self, dataval=None):
[Link] = dataval
[Link] = None
class SLinkedList:
def __init__(self):
[Link] = None
# Print the linked list
def listprint(self):
printval = [Link]
while printval is not None:
print ([Link])
printval = [Link]
def AtBegining(self,newdata):
NewNode = Node(newdata)
[Link] = [Link]
[Link] = NewNode
list = SLinkedList()
[Link] = Node("Mon")
e2 = Node("Tue")
e3 = Node("Wed")
[Link] = e2
[Link] = e3
[Link]("Sun")
[Link]()
Output
Sun
Mon
Tue
Wed
Example
class Node:
def __init__(self, dataval=None):
[Link] = dataval
[Link] = None
class SLinkedList:
def __init__(self):
[Link] = None
# Function to add newnode
def AtEnd(self, newdata):
NewNode = Node(newdata)
if [Link] is None:
[Link] = NewNode
return
laste = [Link]
while([Link]):
laste = [Link]
[Link]=NewNode
# Print the linked list
def listprint(self):
printval = [Link]
while printval is not None:
print ([Link])
printval = [Link]
list = SLinkedList()
[Link] = Node("Mon")
e2 = Node("Tue")
e3 = Node("Wed")
[Link] = e2
Page 30 of 67
[Link] = e3
[Link]("Thu")
[Link]()
Output
When the above code is executed, it produces the following result −
Mon
Tue
Wed
Thu
This involves changing the pointer of a specific node to point to the new node. That is possible by passing in both the new node and the
existing node after which the new node will be inserted. So we define an additional class which will change the next pointer of the new
node to the next pointer of middle node. Then assign the new node to next pointer of the middle node.
class Node:
def __init__(self, dataval=None):
[Link] = dataval
[Link] = None
class SLinkedList:
def __init__(self):
[Link] = None
NewNode = Node(newdata)
[Link] = middle_node.nextval
middle_node.nextval = NewNode
list = SLinkedList()
[Link] = Node("Mon")
e2 = Node("Tue")
e3 = Node("Thu")
[Link] = e2
[Link] = e3
[Link]([Link],"Fri")
[Link]()
Page 31 of 67
Output
Mon
Tue
Fri
Thu
Removing an Item
We can remove an existing node using the key for that node. In the below program we locate the previous node of the node which is to
be [Link], point the next pointer of this node to the next node of the node to be deleted.
Example
class Node:
def __init__(self, data=None):
[Link] = data
[Link] = None
class SLinkedList:
def __init__(self):
[Link] = None
if (HeadVal == None):
return
[Link] = [Link]
HeadVal = None
def LListprint(self):
printval = [Link]
while (printval):
print([Link]),
printval = [Link]
llist = SLinkedList()
[Link]("Mon")
[Link]("Tue")
[Link]("Wed")
Page 32 of 67
[Link]("Thu")
[Link]("Tue")
[Link]()
Output
Thu
Wed
Mon
Python - Stack
In the english dictionary the word stack means arranging objects on over another. It is the same way memory is allocated in this data
structure. It stores the data elements in a similar fashion as a bunch of plates are stored one above another in the kitchen. So stack
data strcuture allows operations at one end wich can be called top of the [Link] can add elements or remove elements only form this
en dof the stack.
In a stack the element insreted last in sequence will come out first as we can remove only from the top of the stack. Such feature is
known as Last in First Out(LIFO) feature. The operations of adding and removing the elements is known as PUSH and POP. In the
following program we implement it as add and and remove functions. We declare an empty list and use the append() and pop()
methods to add and remove the data elements.
Example
class Stack:
def __init__(self):
[Link] = []
AStack = Stack()
[Link]("Mon")
[Link]("Tue")
[Link]()
print([Link]())
[Link]("Wed")
[Link]("Thu")
print([Link]())
Output
Tue
Thu
class Stack:
def __init__(self):
[Link] = []
AStack = Stack()
[Link]("Mon")
[Link]("Tue")
[Link]("Wed")
[Link]("Thu")
print([Link]())
print([Link]())
Output
When the above code is executed, it produces the following result −
Thu
Wed
Python - Queue
We are familiar with queue in our day to day life as we wait for a service. The queue data structure aslo means the same where the
data elements are arranged in a queue. The uniqueness of queue lies in the way items are added and removed. The items are allowed
at on end but removed form the other end. So it is a First-in-First out method.
A queue can be implemented using python list where we can use the insert() and pop() methods to add and remove elements. Their is
no insertion as data elements are always added at the end of the queue.
Adding Elements
In the below example we create a queue class where we implement the First-in-First-Out method. We use the in-built insert method for
adding data elements.
Example
Page 34 of 67
class Queue:
def __init__(self):
[Link] = list()
def addtoq(self,dataval):
# Insert method to add element
if dataval not in [Link]:
[Link](0,dataval)
return True
return False
def size(self):
return len([Link])
TheQueue = Queue()
[Link]("Mon")
[Link]("Tue")
[Link]("Wed")
print([Link]())
Output
Removing Element
In the below example we create a queue class where we insert the data and then remove the data using the in-built pop method.
Example
class Queue:
def __init__(self):
[Link] = list()
def addtoq(self,dataval):
# Insert method to add element
if dataval not in [Link]:
[Link](0,dataval)
return True
return False
# Pop method to remove element
def removefromq(self):
if len([Link])>0:
return [Link]()
return ("No elements in Queue!")
TheQueue = Queue()
[Link]("Mon")
[Link]("Tue")
[Link]("Wed")
print([Link]())
print([Link]())
Output
Mon
Tue
Python - Dequeue
A double-ended queue, or deque, supports adding and removing elements from either end. The more commonly used stacks and
queues are degenerate forms of deques, where the inputs and outputs are restricted to a single end.
Example
import collections
DoubleEnded = [Link](["Mon","Tue","Wed"])
[Link]("Thu")
[Link]("Sun")
print ("Appended at right at left is - ")
print (DoubleEnded)
[Link]()
print ("Deleting from right - ")
print (DoubleEnded)
[Link]()
print ("Deleting from left - ")
print (DoubleEnded)
Output
When the above code is executed, it produces the following result −
Appended at right -
deque(['Mon', 'Tue', 'Wed', 'Thu'])
Appended at right at left is -
deque(['Sun', 'Mon', 'Tue', 'Wed', 'Thu'])
Deleting from right -
deque(['Sun', 'Mon', 'Tue', 'Wed'])
Deleting from left -
deque(['Mon', 'Tue', 'Wed'])
Doubly Linked List contains a link element called first and last.
Each link carries a data field(s) and two link fields called next and prev.
Each link is linked with its next link using its next link.
Each link is linked with its previous link using its previous link.
The last link carries a link as null to mark the end of the list.
Page 36 of 67
Example
class Node:
def __init__(self, data):
[Link] = data
[Link] = None
[Link] = None
class doubly_linked_list:
def __init__(self):
[Link] = None
dllist = doubly_linked_list()
[Link](12)
[Link](8)
[Link](62)
[Link]([Link])
Output
62 8 12
Example
[Link] = None
dllist = doubly_linked_list()
[Link](12)
[Link](8)
[Link](62)
[Link]([Link], 13)
[Link]([Link])
Output
62 8 13 12
Example
class doubly_linked_list:
def __init__(self):
[Link] = None
dllist = doubly_linked_list()
[Link](12)
[Link](9)
[Link](8)
[Link](62)
[Link](45)
[Link]([Link])
Output
62 8 12 9 45
Please note the position of the elements 9 and 45 for the append operation.
So the search and insertion function of a data element becomes much faster as the key values themselves become the index of the
array which stores the data.
In Python, the Dictionary data types represent the implementation of hash tables. The Keys in the dictionary satisfy the following
requirements.
Page 39 of 67
The keys of the dictionary are hashable i.e. the are generated by hashing function which generates unique result for each unique
value supplied to the hash function.
So we see the implementation of hash table by using the dictionary data types as below.
Example
# Declare a dictionary
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
Output
dict['Name']: Zara
dict['Age']: 7
Updating Dictionary
You can update a dictionary by adding a new entry or a key-value pair, modifying an existing entry, or deleting an existing entry as
shown below in the simple example −
Example
# Declare a dictionary
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
dict['Age'] = 8; # update existing entry
dict['School'] = "DPS School"; # Add new entry
print ("dict['Age']: ", dict['Age'])
print ("dict['School']: ", dict['School'])
Output
dict['Age']: 8
dict['School']: DPS School
Example
Page 40 of 67
Output
This produces the following result. Note that an exception is raised because after del dict dictionary does not exist anymore.
dict['Age']: dict['Age']
dict['School']: dict['School']
Every node other than the root is associated with one parent node.
We create a tree data structure in python by using the concept os node discussed earlier. We designate one node as root node and then
add more nodes as child nodes. Below is program to create the root node.
Create Root
We just create a Node class and add assign a value to the node. This becomes tree with only a root node.
Example
class Node:
def __init__(self, data):
[Link] = None
[Link] = None
[Link] = data
def PrintTree(self):
print([Link])
root = Node(10)
[Link]()
Output
10
Example
class Node:
def __init__(self, data):
[Link] = None
[Link] = None
[Link] = data
Output
When the above code is executed, it produces the following result −
3 6 12 14
Traversing a Tree
The tree can be traversed by deciding on a sequence to visit each node. As we can clearly see we can start at a node then visit the left
sub-tree first and right sub-tree next. Or we can also visit the right sub-tree first and left sub-tree next. Accordingly there are different
names for these tree traversal methods.
In-order Traversal
Pre-order Traversal
Post-order Traversal
In-order Traversal
Page 42 of 67
In this traversal method, the left subtree is visited first, then the root and later the right sub-tree. We should always remember that
every node may represent a subtree itself.
In the below python program, we use the Node class to create place holders for the root node as well as the left and right nodes. Then,
we create an insert function to add data to the tree. Finally, the In-order traversal logic is implemented by creating an empty list and
adding the left node first followed by the root or parent node.
At last the left node is added to complete the In-order traversal. Please note that this process is repeated for each sub-tree until all the
nodes are traversed.
Example
class Node:
def __init__(self, data):
[Link] = None
[Link] = None
[Link] = data
# Insert Node
def insert(self, data):
if [Link]:
if data < [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else data > [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else:
[Link] = data
# Print the Tree
def PrintTree(self):
if [Link]:
[Link]()
print( [Link]),
if [Link]:
[Link]()
# Inorder traversal
# Left -> Root -> Right
def inorderTraversal(self, root):
res = []
if root:
res = [Link]([Link])
[Link]([Link])
res = res + [Link]([Link])
return res
root = Node(27)
[Link](14)
[Link](35)
[Link](10)
[Link](19)
[Link](31)
[Link](42)
print([Link](root))
Output
Pre-order Traversal
In this traversal method, the root node is visited first, then the left subtree and finally the right subtree.
In the below python program, we use the Node class to create place holders for the root node as well as the left and right nodes. Then,
we create an insert function to add data to the tree. Finally, the Pre-order traversal logic is implemented by creating an empty list and
adding the root node first followed by the left node.
At last, the right node is added to complete the Pre-order traversal. Please note that, this process is repeated for each sub-tree until all
the nodes are traversed.
Example
class Node:
def __init__(self, data):
[Link] = None
[Link] = None
[Link] = data
# Insert Node
def insert(self, data):
if [Link]:
if data < [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
elif data > [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else:
[Link] = data
# Print the Tree
def PrintTree(self):
if [Link]:
[Link]()
print( [Link]),
if [Link]:
[Link]()
# Preorder traversal
# Root -> Left ->Right
def PreorderTraversal(self, root):
res = []
if root:
[Link]([Link])
res = res + [Link]([Link])
res = res + [Link]([Link])
return res
root = Node(27)
[Link](14)
[Link](35)
[Link](10)
[Link](19)
[Link](31)
[Link](42)
print([Link](root))
Output
When the above code is executed, it produces the following result −
Page 44 of 67
Post-order Traversal
In this traversal method, the root node is visited last, hence the name. First, we traverse the left subtree, then the right subtree and
finally the root node.
In the below python program, we use the Node class to create place holders for the root node as well as the left and right nodes. Then,
we create an insert function to add data to the tree. Finally, the Post-order traversal logic is implemented by creating an empty list and
adding the left node first followed by the right node.
At last the root or parent node is added to complete the Post-order traversal. Please note that, this process is repeated for each sub-
tree until all the nodes are traversed.
Example
class Node:
def __init__(self, data):
[Link] = None
[Link] = None
[Link] = data
# Insert Node
def insert(self, data):
if [Link]:
if data < [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else if data > [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else:
[Link] = data
# Print the Tree
def PrintTree(self):
if [Link]:
[Link]()
print( [Link]),
if [Link]:
[Link]()
# Postorder traversal
# Left ->Right -> Root
def PostorderTraversal(self, root):
res = []
if root:
res = [Link]([Link])
res = res + [Link]([Link])
[Link]([Link])
return res
root = Node(27)
[Link](14)
[Link](35)
[Link](10)
[Link](19)
[Link](31)
[Link](42)
print([Link](root))
Page 45 of 67
Output
Searching for a value in a tree involves comparing the incoming value with the value exiting nodes. Here also we traverse the nodes
from left to right and then finally with the parent. If the searched for value does not match any of the exiting value, then we return not
found message, or else the found message is returned.
Example
class Node:
def __init__(self, data):
[Link] = None
[Link] = None
[Link] = data
# Insert method to create nodes
def insert(self, data):
if [Link]:
if data < [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else data > [Link]:
if [Link] is None:
[Link] = Node(data)
else:
[Link](data)
else:
[Link] = data
# findval method to compare the value with nodes
def findval(self, lkpval):
if lkpval < [Link]:
if [Link] is None:
return str(lkpval)+" Not Found"
return [Link](lkpval)
else if lkpval > [Link]:
if [Link] is None:
return str(lkpval)+" Not Found"
return [Link](lkpval)
else:
print(str([Link]) + ' is found')
# Print the tree
def PrintTree(self):
if [Link]:
[Link]()
print( [Link]),
if [Link]:
[Link]()
Page 46 of 67
root = Node(12)
[Link](6)
[Link](14)
[Link](3)
print([Link](7))
print([Link](14))
Output
7 Not Found
14 is found
Python - Heaps
Heap is a special tree structure in which each parent node is less than or equal to its child node. Then it is called a Min Heap. If each
parent node is greater than or equal to its child node then it is called a max heap. It is very useful is implementing priority queues
where the queue item with higher weightage is given more priority in processing.
A detailed discussion on heaps is available in our website here. Please study it first if you are new to heap data structure. In this
chapter we will see the implementation of heap data structure using python.
Create a Heap
A heap is created by using pythons inbuilt library named heapq. This library has the relevant functions to carry out various operations
on heap data structure. Below is a list of these functions.
heapify − This function converts a regular list to a heap. In the resulting heap the smallest element gets pushed to the index
position 0. But rest of the data elements are not necessarily sorted.
heappush − This function adds an element to the heap without altering the current heap.
heappop − This function returns the smallest data element from the heap.
heapreplace − This function replaces the smallest data element with a new value supplied in the function.
Creating a Heap
A heap is created by simply using a list of elements with the heapify function. In the below example we supply a list of elements and
the heapify function rearranges the elements bringing the smallest element to the first position.
Example
import heapq
H = [21,1,45,78,3,5]
# Use heapify to rearrange the elements
[Link](H)
print(H)
Output
Inserting a data element to a heap always adds the element at the last index. But you can apply heapify function again to bring the
newly added element to the first index only if it smallest in value. In the below example we insert the number 8.
Example
import heapq
H = [21,1,45,78,3,5]
# Covert to a heap
[Link](H)
print(H)
# Add element
[Link](H,8)
print(H)
Output
Example
import heapq
H = [21,1,45,78,3,5]
# Create the heap
[Link](H)
print(H)
print(H)
Output
Replacing in a Heap
The heap replace function always removes the smallest element of the heap and inserts the new incoming element at some place not
fixed by any order.
Page 48 of 67
Example
import heapq
H = [21,1,45,78,3,5]
# Create the heap
[Link](H)
print(H)
# Replace an element
[Link](H,6)
print(H)
Output
Python - Graphs
A graph is a pictorial representation of a set of objects where some pairs of objects are connected by links. The interconnected objects
are represented by points termed as vertices, and the links that connect the vertices are called edges. The various terms and
functionalities associated with a graph is described in great detail in our tutorial here.
In this chapter we are going to see how to create a graph and add various data elements to it using a python program. Following are
the basic operations we perform on graphs.
Add a vertex
Add an edge
Creating a graph
A graph can be easily presented using the python dictionary data types. We represent the vertices as the keys of the dictionary and the
connection between the vertices also called edges as the values in the dictionary.
V = {a, b, c, d, e}
E = {ab, ac, bd, cd, de}
Example
Output
{'c': ['a', 'd'], 'a': ['b', 'c'], 'e': ['d'], 'd': ['e'], 'b': ['a', 'd']}
class graph:
def __init__(self,gdict=None):
if gdict is None:
gdict = []
[Link] = gdict
# Get the keys of the dictionary
def getVertices(self):
return list([Link]())
# Create the dictionary with graph elements
graph_elements = {
"a" : ["b","c"],
"b" : ["a", "d"],
"c" : ["a", "d"],
"d" : ["e"],
"e" : ["d"]
}
g = graph(graph_elements)
print([Link]())
Output
class graph:
def __init__(self,gdict=None):
if gdict is None:
gdict = {}
[Link] = gdict
def edges(self):
return [Link]()
# Find the distinct list of edges
def findedges(self):
edgename = []
for vrtx in [Link]:
for nxtvrtx in [Link][vrtx]:
if {nxtvrtx, vrtx} not in edgename:
[Link]({vrtx, nxtvrtx})
return edgename
# Create the dictionary with graph elements
graph_elements = {
"a" : ["b","c"],
"b" : ["a", "d"],
"c" : ["a", "d"],
"d" : ["e"],
"e" : ["d"]
}
g = graph(graph_elements)
print([Link]())
Output
[{'b', 'a'}, {'b', 'd'}, {'e', 'd'}, {'a', 'c'}, {'c', 'd'}]
Adding a vertex
Adding a vertex is straight forward where we add another additional key to the graph dictionary.
Example
class graph:
def __init__(self,gdict=None):
if gdict is None:
gdict = {}
[Link] = gdict
def getVertices(self):
return list([Link]())
# Add the vertex as a key
def addVertex(self, vrtx):
if vrtx not in [Link]:
[Link][vrtx] = []
# Create the dictionary with graph elements
graph_elements = {
"a" : ["b","c"],
"b" : ["a", "d"],
"c" : ["a", "d"],
"d" : ["e"],
"e" : ["d"]
}
g = graph(graph_elements)
Page 51 of 67
[Link]("f")
print([Link]())
Output
Adding an edge
Adding an edge to an existing graph involves treating the new vertex as a tuple and validating if the edge is already present. If not then
the edge is added.
class graph:
def __init__(self,gdict=None):
if gdict is None:
gdict = {}
[Link] = gdict
def edges(self):
return [Link]()
# Add the new edge
def AddEdge(self, edge):
edge = set(edge)
(vrtx1, vrtx2) = tuple(edge)
if vrtx1 in [Link]:
[Link][vrtx1].append(vrtx2)
else:
[Link][vrtx1] = [vrtx2]
# List the edge names
def findedges(self):
edgename = []
for vrtx in [Link]:
for nxtvrtx in [Link][vrtx]:
if {nxtvrtx, vrtx} not in edgename:
[Link]({vrtx, nxtvrtx})
return edgename
# Create the dictionary with graph elements
graph_elements = {
"a" : ["b","c"],
"b" : ["a", "d"],
"c" : ["a", "d"],
"d" : ["e"],
"e" : ["d"]
}
g = graph(graph_elements)
[Link]({'a','e'})
[Link]({'a','c'})
print([Link]())
Output
[{'e', 'd'}, {'b', 'a'}, {'b', 'd'}, {'a', 'c'}, {'a', 'e'}, {'c', 'd'}]
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output.
Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one
programming language.
From the data structure point of view, following are some important categories of algorithms −
Characteristics of an Algorithm
Not all procedures can be called an algorithm. An algorithm should have the following characteristics −
Unambiguous − Algorithm should be clear and unambiguous. Each of its steps (or phases), and their inputs/outputs should be
clear and must lead to only one meaning.
Output − An algorithm should have 1 or more well-defined outputs, and should match the desired output.
Independent − An algorithm should have step-by-step directions, which should be independent of any programming code.
As we know that all programming languages share basic code constructs like loops (do, for, while), flow-control (if-else), etc. These
common constructs can be used to write an algorithm.
We write algorithms in a step-by-step manner, but it is not always the case. Algorithm writing is a process and is executed after the
problem domain is well-defined. That is, we should know the problem domain, for which we are designing a solution.
Example
Problem − Design an algorithm to add two numbers and display the result.
step 1 − START
step 6 − print c
step 7 − STOP
Algorithms tell the programmers how to code the program. Alternatively, the algorithm can be written as −
step 3 − c ← a + b
step 4 − display c
step 5 − STOP
Page 53 of 67
In design and analysis of algorithms, usually the second method is used to describe an algorithm. It makes it easy for the analyst to
analyze the algorithm ignoring all unwanted definitions. He can observe what operations are being used and how the process is flowing.
We design an algorithm to get a solution of a given problem. A problem can be solved in more than one ways.
Hence, many solution algorithms can be derived for a given problem. The next step is to analyze those proposed solution algorithms
and implement the best suitable solution.
Divide/Break
This step involves breaking the problem into smaller sub-problems. Sub-problems should represent a part of the original problem. This
step generally takes a recursive approach to divide the problem until no sub-problem is further divisible. At this stage, sub-problems
become atomic in nature but still represent some part of the actual problem.
Conquer/Solve
This step receives a lot of smaller sub-problems to be solved. Generally, at this level, the problems are considered 'solved' on their own.
Merge/Combine
When the smaller sub-problems are solved, this stage recursively combines them until they formulate a solution of the original problem.
This algorithmic approach works recursively and conquer &s; merge steps works so close that they appear as one.
Examples
The following program is an example of divide-and-conquer programming approach where the binary search is implemented using
python.
Page 54 of 67
This is possible as the list is sorted and it is much quicker than linear [Link] we divide the given list and conquer by choosing the
proper half of the list. We repeat this approcah till we find the element or conclude about it's absence in the list.
Example
Output
5
None
Python - Recursion
Recursion allows a function to call itself. Fixed steps of code get executed again and again for new values. We also have to set criteria
for deciding when the recursive call ends. In the below example we see a recursive approach to the binary search. We take a sorted list
and give its index range as input to the recursive function.
Example
Output
2
None
Python - Backtracking
Backtracking is a form of recursion. But it involves choosing only option out of any possibilities. We begin by choosing an option and
backtrack from it, if we reach a state where we conclude that this specific option does not give the required solution. We repeat these
steps by going across each available option until we get the desired solution.
Below is an example of finding all possible order of arrangements of a given set of letters. When we choose a pair we apply
backtracking to verify if that exact pair has already been created or not. If not already created, the pair is added to the answer list else
it is ignored.
Example
Output
When the above code is executed, it produces the following result −
The importance of sorting lies in the fact that data searching can be optimized to a very high level, if data is stored in a sorted manner.
Sorting is also used to represent data in more readable formats. Below we see five such implementations of sorting in python.
Bubble Sort
Page 56 of 67
Merge Sort
Insertion Sort
Shell Sort
Selection Sort
Bubble Sort
It is a comparison-based algorithm in which each pair of adjacent elements is compared and the elements are swapped if they are not
in order.
Example
def bubblesort(list):
Output
Merge Sort
Merge sort first divides the array into equal halves and then combines them in a sorted manner.
Example
def merge_sort(unsorted_list):
if len(unsorted_list) <= 1:
return unsorted_list
# Find the middle point and devide it
middle = len(unsorted_list) // 2
left_list = unsorted_list[:middle]
right_list = unsorted_list[middle:]
left_list = merge_sort(left_list)
right_list = merge_sort(right_list)
return list(merge(left_list, right_list))
left_half.remove(left_half[0])
else:
[Link](right_half[0])
right_half.remove(right_half[0])
if len(left_half) == 0:
res = res + right_half
else:
res = res + left_half
return res
unsorted_list = [64, 34, 25, 12, 22, 11, 90]
print(merge_sort(unsorted_list))
Output
Insertion Sort
Insertion sort involves finding the right place for a given element in a sorted list. So in beginning we compare the first two elements
and sort them by comparing them. Then we pick the third element and find its proper position among the previous two sorted
elements. This way we gradually go on adding more elements to the already sorted list by putting them in their proper position.
Example
def insertion_sort(InputList):
for i in range(1, len(InputList)):
j = i-1
nxt_element = InputList[i]
# Compare the current element with next one
while (InputList[j] > nxt_element) and (j >= 0):
InputList[j+1] = InputList[j]
j=j-1
InputList[j+1] = nxt_element
list = [19,2,31,45,30,11,121,27]
insertion_sort(list)
print(list)
Output
Shell Sort
Shell Sort involves sorting elements which are away from each other. We sort a large sublist of a given list and go on reducing the size
of the list until all elements are sorted. The below program finds the gap by equating it to half of the length of the list size and then
starts sorting all elements in it. Then we keep resetting the gap until the entire list is sorted.
Example
def shellSort(input_list):
gap = len(input_list) // 2
Page 58 of 67
Output
Selection Sort
In selection sort we start by finding the minimum value in a given list and move it to a sorted list. Then we repeat the process for each
of the remaining elements in the unsorted list. The next element entering the sorted list is compared with the existing elements and
placed at its correct [Link], at the end all the elements from the unsorted list are sorted.
Example
def selection_sort(input_list):
for idx in range(len(input_list)):
min_idx = idx
for j in range( idx +1, len(input_list)):
if input_list[min_idx] > input_list[j]:
min_idx = j
# Swap the minimum value with the compared value
input_list[idx], input_list[min_idx] = input_list[min_idx], input_list[idx]
l = [19,2,31,45,30,11,121,27]
selection_sort(l)
print(l)
Output
Linear Search
In this type of search, a sequential search is made over all items one by one. Every item is checked and if a match is found then that
particular item is returned, otherwise the search continues till the end of the data structure.
Page 59 of 67
Example
Output
True
False
Interpolation Search
This search algorithm works on the probing position of the required value. For this algorithm to work properly, the data collection should
be in a sorted form and equally [Link], the probe position is the position of the middle most item of the [Link] a
match occurs, then the index of the item is [Link] the middle item is greater than the item, then the probe position is again
calculated in the sub-array to the right of the middle item. Otherwise, the item is searched in the subarray to the left of the middle
item. This process continues on the sub-array as well until the size of subarray reduces to zero.
Example
There is a specific formula to calculate the middle position which is indicated in the program below −
def intpolsearch(values,x ):
idx0 = 0
idxn = (len(values) - 1)
while idx0 <= idxn and x >= values[idx0] and x <= values[idxn]:
# Find the mid point
mid = idx0 +\
int(((float(idxn - idx0)/( values[idxn] - values[idx0]))
* ( x - values[idx0])))
# Compare the value at mid point with search value
if values[mid] == x:
return "Found "+str(x)+" at index "+str(mid)
if values[mid] < x:
idx0 = mid + 1
return "Searched element not in the list"
Output
Found 2 at index 0
Page 60 of 67
Example
class graph:
def __init__(self,gdict=None):
if gdict is None:
gdict = {}
[Link] = gdict
# Check for the visisted and unvisited nodes
def dfs(graph, start, visited = None):
if visited is None:
visited = set()
[Link](start)
print(start)
for next in graph[start] - visited:
dfs(graph, next, visited)
return visited
gdict = {
"a" : set(["b","c"]),
"b" : set(["a", "d"]),
"c" : set(["a", "d"]),
"d" : set(["e"]),
"e" : set(["a"])
}
dfs(gdict, 'a')
Output
a
b
d
e
c
We implement BFS for a graph in python using queue data structure discussed earlier. When we keep visiting the adjacent unvisited
nodes and keep adding it to the queue. Then we start dequeue only the node which is left with no unvisited nodes. We stop the
program when there is no next adjacent node to be visited.
Page 61 of 67
Example
import collections
class graph:
def __init__(self,gdict=None):
if gdict is None:
gdict = {}
[Link] = gdict
def bfs(graph, startnode):
# Track the visited and unvisited nodes using queue
seen, queue = set([startnode]), [Link]([startnode])
while queue:
vertex = [Link]()
marked(vertex)
for node in graph[vertex]:
if node not in seen:
[Link](node)
[Link](node)
def marked(n):
print(n)
Output
a
c
b
d
e
A Priori Analysis − This is a theoretical analysis of an algorithm. Efficiency of an algorithm is measured by assuming that all
other factors, for example, processor speed, are constant and have no effect on the implementation.
A Posterior Analysis − This is an empirical analysis of an algorithm. The selected algorithm is implemented using
programming language. This is then executed on target computer machine. In this analysis, actual statistics like running time
and space required, are collected.
Algorithm Complexity
Suppose X is an algorithm and n is the size of input data, the time and space used by the algorithm X are the two main factors, which
decide the efficiency of X.
Page 62 of 67
Time Factor − Time is measured by counting the number of key operations such as comparisons in the sorting algorithm.
Space Factor − Space is measured by counting the maximum memory space required by the algorithm.
The complexity of an algorithm f(n) gives the running time and/or the storage space required by the algorithm in terms of n as the size
of input data.
Space Complexity
Space complexity of an algorithm represents the amount of memory space required by the algorithm in its life cycle. The space required
by an algorithm is equal to the sum of the following two components −
A fixed part that is a space required to store certain data and variables, that are independent of the size of the problem. For
example, simple variables and constants used, program size, etc.
A variable part is a space required by variables, whose size depends on the size of the problem. For example, dynamic memory
allocation, recursion stack space, etc.
Space complexity S(P) of any algorithm P is S(P) = C + SP(I), where C is the fixed part and S(I) is the variable part of the
algorithm, which depends on instance characteristic I. Following is a simple example that tries to explain the concept −
Algorithm: SUM(A, B)
Step 1 − START
Step 3 − Stop
Here we have three variables A, B, and C and one constant. Hence S(P) = 1 + 3. Now, space depends on data types of given
variables and constant types and it will be multiplied accordingly.
Time Complexity
Time complexity of an algorithm represents the amount of time required by the algorithm to run to completion. Time requirements can
be defined as a numerical function T(n), where T(n) can be measured as the number of steps, provided each step consumes constant
time.
For example, addition of two n-bit integers takes n steps. Consequently, the total computational time is T(n) = c ∗ n, where c is the
time taken for the addition of two bits. Here, we observe that T(n) grows linearly as the input size increases.
For example, the running time of one operation is computed as f(n) and may be for another operation it is computed as g(n2). This
means the first operation running time will increase linearly with the increase in n and the running time of the second operation will
increase exponentially when n increases. Similarly, the running time of both operations will be nearly the same if n is significantly small.
Asymptotic Notations
The commonly used asymptotic notations to calculate the running time complexity of an algorithm.
Ο Notation
Ω Notation
θ Notation
Page 63 of 67
Big Oh Notation, Ο
The notation Ο(n) is the formal way to express the upper bound of an algorithm's running time. It measures the worst case time
complexity or the longest amount of time an algorithm can possibly take to complete.
Ο(f(n)) = { g(n) : there exists c > 0 and n0 such that f(n) ≤ c.g(n) for all n > n0. }
Omega Notation, Ω
The notation Ω(n) is the formal way to express the lower bound of an algorithm's running time. It measures the best case time
complexity or the best amount of time an algorithm can possibly take to complete.
Ω(f(n)) ≥ { g(n) : there exists c > 0 and n0 such that g(n) ≤ c.f(n) for all n > n0. }
Theta Notation, θ
The notation θ(n) is the formal way to express both the lower bound and the upper bound of an algorithm's running time. It is
represented as follows −
θ(f(n)) = { g(n) if and only if g(n) = Ο(f(n)) and g(n) = Ω(f(n)) for all n > n0. }
constant − Ο(1)
logarithmic − Ο(log n)
Page 64 of 67
linear − Ο(n)
quadratic − Ο(n2)
cubic − Ο(n3)
polynomial − nΟ(1)
exponential − 2Ο(n)
Greedy Algorithms
Greedy algorithms try to find a localized optimum solution, which may eventually lead to globally optimized solutions. However,
generally greedy algorithms do not provide globally optimized solutions.
So greedy algorithms look for a easy solution at that point in time without considering how it impacts the future steps. It is similar to
how humans solve problems without going through the complete details of the inputs provided.
Most networking algorithms use the greedy approach. Here is a list of few of them −
Merge Sort
Quick Sort
Binary Search
Dynamic Programming
Dynamic programming involves dividing the bigger problem into smaller ones but unlike divide and conquer it does not involve solving
each sub-problem independently. Rather the results of smaller sub-problems are remembered and used for similar or overlapping sub-
problems.
Mostly, these algorithms are used for optimization. Before solving the in-hand sub-problem, dynamic algorithm will try to examine the
results of the previously solved [Link] algorithms are motivated for an overall optimization of the problem and not the
local optimization.
Tower of Hanoi
Page 65 of 67
If the list is already sorted, it does not matter how distributed the data is. But of course the length of the list has an impact as it
decides the number of steps the algorithm has to go through to get the final result.
So we see that if the initial cost of a single step of obtaining a sorted list is high, then the cost of subsequent steps of finding an
element becomes considerably low. So Amortized analysis helps us find a bound on the worst-case running time for a sequence of
operations. There are three approaches to amortized analysis.
Accounting Method − This involves assigning a cost to each operation performed. If the actual operation finishes quicker than
the assigned time then some positive credit is accumulated in the analysis.
In the reverse scenario it will be negative credit. To keep track of these accumulated credits, we use a stack or tree data structure.
The operations which are carried out early ( like sorting the list) have high amortized cost but the operations that are late in
sequence have lower amortized cost as the accumulated credit is utilized. So the amortized cost is an upper bound of actual cost.
Potential Method − In this method the saved credit is utilized for future operations as mathematical function of the state of
the data structure. The evaluation of the mathematical function and the amortized cost should be equal. So when the actual cost
is greater than amortized cost there is a decrease in potential and it is used utilized for future operations which are expensive.
Aggregate analysis − In this method we estimate the upper bound on the total cost of n steps. The amortized cost is a simple
division of total cost and the number of steps (n)..
Direct Proof − It is direct verification of the statement by using the direct calculations. For example sum of two even numbers
is always an even number. In this case just add the two numbers you are investigating and verify the result as even.
Proof by induction − Here we start with a specific instance of a truth and then generalize it to all possible values which are
part of the truth. The approach is to take a case of verified truth, then prove it is also true for the next case for the same given
condition. For example all positive numbers of the form 2n-1 are odd. We prove it for a certain value of n, then prove it for the
next value of n. This establishes the statement as generally true by proof of induction.
Proof by contraposition − This proof is based on the condition If Not A implies Not B then A implies B. A simple example is if
square of n is even then n must be even. Because if square on n is not even then n is not even.
Proof by exhaustion − This is similar to direct proof but it is established by visiting each case separately and proving each of
them. An example of such proof is the four color theorem.
TOP TUTORIALS
Python Tutorial
Java Tutorial
C++ Tutorial
C Programming Tutorial
C# Tutorial
PHP Tutorial
R Tutorial
HTML Tutorial
CSS Tutorial
JavaScript Tutorial
SQL Tutorial
TRENDING TECHNOLOGIES
Git Tutorial
Kubernetes Tutorial
DSA Tutorial
SDLC Tutorial
Unix Tutorial
CERTIFICATIONS
DevOps Certification
Online Go Compiler
Online C Compiler
Online C# Compiler
ABOUT US | OUR TEAM | CAREERS | JOBS | CONTACT US | TERMS OF USE | PRIVACY POLICY | REFUND POLICY |
Tutorials Point is a leading Ed Tech company striving to provide the best learning material on technical and non-technical subjects.
Page 67 of 67
Tree data structures in dynamic programming algorithms often serve to break down problems into hierarchical sub-problems that are recursively solved and combined. They provide a systematic way to explore state spaces or decision trees without redundancies, by caching previously computed sub-problem results for reuse. This approach effectively manages complex problems like shortest path computations or genetic algorithms by optimizing recursive calculations and solution path evaluations, leading to reduced computational overhead and increased efficiency .
Divide and conquer algorithms solve problems by breaking them down into smaller, more manageable sub-problems, solving these sub-problems independently, and then combining the results to form a solution to the original problem. Merge Sort exemplifies this approach. It divides an unsorted array into two halves, recursively sorts each half, and finally merges the sorted halves. This method effectively reduces the complexity by simplifying problem steps and is efficient for scenarios well-suited to recursive breakdown .
Dynamic programming and divide and conquer both involve breaking problems into sub-problems but differ in handling overlaps among them. Divide and conquer solves each sub-problem independently, optimal for contexts without overlapping solutions. Dynamic programming, however, retains and reuses previous solutions, suitable for optimization problems with overlapping sub-solutions. This reuse minimizes redundant calculations, beneficial for problems like the Fibonacci sequence and the Knapsack problem, where dynamic programming optimizes both time and space complexity .
Understanding the order of operations in tree traversal algorithms such as in-order, pre-order, and post-order is crucial because it affects the sequence and efficiency of data retrieval from hierarchical structures like binary trees. Each traversal method serves different applications; for example, in-order traversal retrieves elements in a sorted manner, useful for binary search trees. Pre-order can be essential for tasks like creating a textual representation of a tree, and post-order is beneficial for evaluating expressions or deleting a tree safely. The choice of traversal matters based on the specific data usage or processing requirements .
Different mathematical proof techniques provide the theoretical foundation and accuracy for validating algorithm performance. Techniques like direct proof, proof by induction, and proof by contraposition offer structured frameworks for asserting the correctness and efficiency of algorithms. Direct proof may apply straightforward deduction, induction steps through specific instances to general cases, whereas contraposition can prove relationships by demonstrating inverse phenomena. These methods collectively assess correctness, justify assumptions, and demonstrate the logical rigor needed in developing robust algorithms, ensuring dependable operations across varied inputs and conditions .
Amortized analysis provides a means to determine the average time complexity over a sequence of operations, rather than analyzing a single costly operation in isolation. By evaluating the total cost over a series of steps and dividing by the number of steps, it yields insights into the overall efficiency. It helps in contexts like maintaining a dynamic array where occasional expensive operations (e.g., resizing) are amortized over many inexpensive operations (e.g., adding elements). This portrays a realistic performance metric for operations like insertions in data structures .
In a binary search tree (BST), insertion involves adding a new node in the correct position to maintain the ordered structure. For each new data, the current node is compared with the data to be inserted. If the data is smaller, the process continues with the left child; if larger, with the right child. If the relevant child is null, the new node is inserted there. For example, in Python: ```python class Node: def __init__(self, data): self.left = None self.right = None self.data = data def insert(self, data): if self.data: if data < self.data: if self.left is None: self.left = Node(data) else: self.left.insert(data) elif data > self.data: if self.right is None: self.right = Node(data) else: self.right.insert(data) else: self.data = data ``` Using this, inserting 6, 14, and 3 into a tree with root 12, the tree will correctly order nodes as 3, 6, 12, and 14 to maintain BST properties .
Amortized analysis differs from traditional worst-case analysis by averaging the cost over a sequence of operations rather than focusing on the single most costly operation. While worst-case analysis considers the maximum time or space required at any time, amortized analysis assumes that the expensive operations are offset by many more inexpensive operations, providing a more realistic and improved estimate of overall efficiency. This distinction is significant for operations like dynamic array resizing where the amortized cost remains low despite individual instances of high-cost events .
The potential method in amortized analysis evaluates the algorithm's performance by using a potential function to account for changes in the state of the data structure over operations. The function estimates the accumulated 'credit' or additional cost saved by previous operations as a measure against future costs. Thus, each operation might reduce or increase the potential. This method provides a rigorous technique for proving performance guarantees, particularly useful when operations have variable costs that depend on the data structure's state, such as resizing an array .
In Python, a list is mutable, meaning that its contents can be changed after it has been created. This allows for modifying elements, adding new elements, or removing existing elements. In contrast, a tuple is immutable; once it is created, its elements cannot be modified. This immutability distinguishes tuples from lists in Python .