Data Structures – Complete Lecture Notes (129 Pages), Lecture notes of Data Structures and Algorithms

“Comprehensive Data Structures notes covering arrays, stacks, queues, linked lists, trees, graphs, sorting and searching algorithms. Includes examples, diagrams, and explanations to help students understand concepts efficiently. Ideal for exam preparation and self-study for Computer Science & IT courses. 129 pages in total.”

Typology: Lecture notes

2024/2025

Available from 02/18/2026

jayaprakash-lm
jayaprakash-lm 🇮🇳

11 documents

1 / 129

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
1
UNIT I INTRODUCTION TO DATA STRUCTURES,
SEARCHING AND SORTING
Basic Concepts: Introduction to Data Structures:
A data structure is a way of storing data in a computer so that it can be used efficiently and it will allow
the most efficient algorithm to be used. The choice of the data structure begins from the choice of an
abstract data type (ADT). A well-designed data structure allows a variety of critical operations to be
performed, using as few resources, both execution time and memory space, as possible. Data structure
introduction refers to a scheme for organizing data, or in other words it is an arrangement of data in
computer's memory in such a way that it could make the data quickly available to the processor for
required calculations.
A data structure should be seen as a logical concept that must address two fundamental concerns.
1. First, how the data will be stored, and
2. Second, what operations will be performed on it.
As data structure is a scheme for data organization so the functional definition of a data structure should
be independent of its implementation. The functional definition of a data structure is known as ADT
(Abstract Data Type) which is independent of implementation. The way in which the data is organized
affects the performance of a program for different tasks. Computer programmers decide which data
structures to use based on the nature of the data and the processes that need to be performed on that
data. Some of the more commonly used data structures include lists, arrays, stacks, queues, heaps, trees,
and graphs.
Classification of Data Structures:
Data structures can be classified as
Simple data structure
Compound data structure
Linear data structure
Non linear data structure
[Fig 1.1 Classification of Data Structures]
Simple Data Structure:
Simple data structure can be constructed with the help of primitive data structure. A primitive data
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f
pf50
pf51
pf52
pf53
pf54
pf55
pf56
pf57
pf58
pf59
pf5a
pf5b
pf5c
pf5d
pf5e
pf5f
pf60
pf61
pf62
pf63
pf64

Partial preview of the text

Download Data Structures – Complete Lecture Notes (129 Pages) and more Lecture notes Data Structures and Algorithms in PDF only on Docsity!

UNIT – I INTRODUCTION TO DATA STRUCTURES,

SEARCHING AND SORTING

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

  1. First, how the data will be stored, and
  2. Second, what operations will be performed on it. As data structure is a scheme for data organization so the functional definition of a data structure should be independent of its implementation. The functional definition of a data structure is known as ADT (Abstract Data Type) which is independent of implementation. The way in which the data is organized affects the performance of a program for different tasks. Computer programmers decide which data structures to use based on the nature of the data and the processes that need to be performed on that data. Some of the more commonly used data structures include lists, arrays, stacks, queues, heaps, trees, and graphs. Classification of Data Structures: Data structures can be classified as  Simple data structure  Compound data structure  Linear data structure  Non linear data structure [Fig 1.1 Classification of Data Structures] Simple Data Structure: Simple data structure can be constructed with the help of primitive data structure. A primitive data

structure used to represent the standard data types of any one of the computer languages. Variables, arrays, pointers, structures, unions, etc. are examples of primitive data structures. Compound Data structure: Compound data structure can be constructed with the help of any one of the primitive data structure and it is having a specific functionality. It can be designed by user. It can be classified as  Linear data structure  Non-linear data structure Linear Data Structure: Linear data structures can be constructed as a continuous arrangement of data elements in the memory. It can be constructed by using array data type. In the linear Data Structures the relationship of adjacency is maintained between the data elements. Operations applied on linear data structure: The following list of operations applied on linear data structures

  1. Add an element
  2. Delete an element
  3. Traverse
  4. Sort the list of elements
  5. Search for a data element For example Stack, Queue, Tables, List, and Linked Lists. Non-linear Data Structure: Non-linear data structure can be constructed as a collection of randomly distributed set of data item joined together by using a special pointer (tag). In non-linear Data structure the relationship of adjacency is not maintained between the data items. Operations applied on non-linear data structures: The following list of operations applied on non-linear data structures.
  6. Add elements
  7. Delete elements
  8. Display the elements
  9. Sort the list of elements
  10. Search for a data element For example Tree, Decision tree, Graph and Forest Abstract Data Type: An abstract data type, sometimes abbreviated ADT , is a logical description of how we view the data and the operations that are allowed without regard to how they will be implemented. This means that we are concerned only with what data is representing and not with how it will eventually be constructed. By providing this level of abstraction, we are creating an encapsulation around the data. The idea is that by encapsulating the details of the implementation, we are hiding them from the user’s view. This is called information hiding. The implementation of an abstract data type, often referred to as a data structure , will require that we provide a physical view of the data using some collection of programming constructs and primitive data types.

considerable desirable. Efficiency of Algorithms: The performances of algorithms can be measured on the scales of time and space. The performance of a program is the amount of computer memory and time needed to run a program. We use two approaches to determine the performance of a program. One is analytical and the other is experimental. In performance analysis we use analytical methods, while in performance measurement we conduct experiments. Time Complexity: The time complexity of an algorithm or a program is a function of the running time of the algorithm or a program. In other words, it is the amount of computer time it needs to run to completion. Space Complexity: The space complexity of an algorithm or program is a function of the space needed by the algorithm or program to run to completion. The time complexity of an algorithm can be computed either by an empirical or theoretical approach. The empirical or posteriori testing approach calls for implementing the complete algorithms and executing them on a computer for various instances of the problem. The time taken by the execution of the programs for various instances of the problem are noted and compared. The algorithm whose implementation yields the least time is considered as the best among the candidate algorithmic solutions. Analyzing Algorithms Suppose M is an algorithm, and suppose n is the size of the input data. Clearly the complexity f(n) of M increases as n increases. It is usually the rate of increase of f(n) with some standard functions. The most common computing times are O(1), O(log 2 n), O(n), O(n log 2 n), O(n^2 ), O(n^3 ), O(2n) Example:

The total frequency counts of the program segments A, B and C given by 1, (3n+1) and (3n 2 +3n+1) respectively are expressed as O(1), O(n) and O(n^2 ). These are referred to as the time complexities of the program segments since they are indicative of the running times of the program segments. In a similar manner space complexities of a program can also be expressed in terms of mathematical notations,

Little oh(o): Definition: f(n) = O(g(n)) ( read as f of n is little oh of g of n), if f(n) = O(g(n)) and f(n) ≠ Ω(g(n)). Time Complexity: Time Complexities of various Algorithms: Numerical Comparision of Different Algorithms: S.No. log 2 n n nlog 2 n n^2 n^3 2 n

  1. 0 1 1 1 1 2
  2. 1 2 2 4 8 4
  3. 2 4 8 16 64 16
  4. 3 8 24 64 512 256
  5. 4 16 64 256 4096 65536 Reasons for analyzing algorithms:
  6. To predict the resources that the algorithm requires

 Computational Time(CPU consumption).  Memory Space(RAM consumption).  Communication bandwidth consumption.

  1. To predict the running time of an algorithm  Total number of primitive operations executed. Recursive Algorithms: GCD Design: Given two integers a and b, the greatest common divisor is recursively found using the formula gcd(a,b) = a if b= b if a= gcd(b, a mod b) Fibonacci Design: To start a fibonacci series, we need to know the first two numbers. Fibonacci(n) = 0 if n= 1 if n= Fibonacci(n-1) + fibonacci(n-2) Difference between Recursion and Iteration:
  2. A function is said to be recursive if it calls itself again and again within its body whereas iterative functions are loop based imperative functions.
  3. Reursion uses stack whereas iteration does not use stack.
  4. Recursion uses more memory than iteration as its concept is based on stacks.
  5. Recursion is comparatively slower than iteration due to overhead condition of maintaining stacks.
  6. Recursion makes code smaller and iteration makes code longer.
  7. Iteration terminates when the loop-continuation condition fails whereas recursion terminates when a base case is recognized.
  8. While using recursion multiple activation records are created on stack for each call where as in iteration everything is done in one activation record.
  9. Infinite recursion can crash the system whereas infinite looping uses CPU cycles repeatedly.
  10. Recursion uses selection structure whereas iteration uses repetetion structure. Types of Recursion: Recursion is of two types depending on whether a function calls itself from within itself or whether two functions call one another mutually. The former is called direct recursion and the later is called Base case General case Base case General case

Fib(2) = Fib(1) + Fib(0) = 1 Fib(3) = Fib(2) + Fib(1) = 2 Fib(4) = Fib(3) + Fib(2) = 3 Fib(5) = Fib(4) + Fib(3) = 5 Fib(6) = Fib(5) + Fib(4) = 8

Program to display the Fibonacci sequence up to n-th term where n is provided by the user

change this value for a different result

nterms = 10

uncomment to take input from the user

#nterms = int(input("How many terms? "))

first two terms

n1 = 0 n2 = 1 count = 0

check if the number of terms is valid

if nterms <= 0: print("Please enter a positive integer") elif nterms == 1: print("Fibonacci sequence upto",nterms,":") print(n1) else:

print("Fibonacci sequence upto",nterms,":") while count < nterms: print(n1,end=' , ') nth = n1 + n

update values

n1 = n n2 = nth count += 1 Tail Recursion: Tail recursion is a form of linear recursion. In tail recursion, the recursive call is the last thing the function does. Often, the value of the recursive call is returned. As such, tail recursive functions can often be easily implemented in an iterative manner; by taking out the recursive call and replacing it with a loop, the same effect can generally be achieved. In fact, a good compiler can recognize tail recursion and convert it to iteration in order to optimize the performance of the code. A good example of a tail recursive function is a function to compute the GCD, or Greatest Common Denominator, of two numbers:

def factorial(n):

if n == 0 : return 1

else: return factorial(n- 1 ) * n

def tail_factorial(n, accumulator=1):

if n == 0 : return 1

else: return tail_factorial(n-1, accumulator * n)

Recursive algorithms for Factorial, GCD, Fibonacci Series and Towers of Hanoi: Factorial(n) Input: integer n ≥ 0 Output: n!

  1. If n = 0 then return (1)
  2. else return prod(n, factorial(n − 1)) GCD(m, n) Input: integers m > 0 , n ≥ 0 Output: gcd (m, n)

if n == 1: print "Move disk 1 from rod",from_rod,"to rod",to_rod return TowerOfHanoi(n-1, from_rod, aux_rod, to_rod) print "Move disk",n,"from rod",from_rod,"to rod",to_rod TowerOfHanoi(n-1, aux_rod, to_rod, from_rod) n = 4 TowerOfHanoi(n, 'A', 'C', 'B') Searching Techniques: Linear Search: Searching is a process of finding a particular data item from a collection of data items based on specific criteria. Every day we perform web searches to locate data items containing in various pages. A search typically performed using a search key and it answers either True or False based on the item is present or not in the list. Linear search algorithm is the most simplest algorithm to do sequential search and this technique iterates over the sequence and checks one item at a time, until the desired item is found or all items have been examined. In Python the in operator is used to find the desired item in a sequence of items. The in operator makes searching task simpler and hides the inner working details. Consider an unsorted single dimensional array of integers and we need to check whether 31 is present in the array or not, then search begins with the first element. As the first element doesn't contain the desired value, then the next element is compared to value 31 and this process continues until the desired element is found in the sixth position. Similarly, if we want to search for 8 in the same array, then the search begins in the same manner, starting with the first element until the desired element is found. In linear search, we cannot determine that a given search value is present in the sequence or not until the entire array is traversed.

Source Code : def linear_search(obj, item, start=0): for i in range(start, len(obj)): if obj[i] == item: return i return - 1 arr=[1,2,3,4,5,6,7,8] x= result=linear_search(arr,x) if result==-1: print ("element does not exist") else: print ("element exist in position %d" %result) Time Complexity of Linear Search: Any algorithm is analyzed based on the unit of computation it performs. For linear search, we need to count the number of comparisons performed, but each comparison may or may not search the desired item. Case Best Case Worst Case Average Case If item is present 1 n n / 2 If item is not present n n n Binary Search: In Binary search algorithm, the target key is examined in a sorted sequence and this algorithm starts searching with the middle item of the sorted sequence. a. If the middle item is the target value, then the search item is found and it returns True. b. If the target item < middle item, then search for the target value in the first half of the list. c. If the target item > middle item, then search for the target value in the second half of the list. In binary search as the list is ordered, so we can eliminate half of the values in the list in each iteration. Consider an example, suppose we want to search 10 in a sorted array of elements, then we first determine

of the list will be eliminated. If this process is repeated for several times, then there will be just one item left in the list. The number of comparisons required to reach to this point is n/2i^ = 1. If we solve for i , then it gives us i = log n. The maximum number is comparison is logarithmic in nature, hence the time complexity of binary search is O(log n). Case Best Case Worst Case Average Case If item is present 1 O(log n) O(log n) If item is not present O(log n) O(log n) O(log n) Fibonacci Search: It is a comparison based technique that uses Fibonacci numbers to search an element in a sorted array. It follows divide and conquer approach and it has a O(log n) time complexity. Let the element to be searched is x, then the idea is to first find the smallest Fibonacci number that is greater than or equal to length of given array. Let the Fibonacci number be fib(nth^ Fibonacci number). Use (n-2)th Fibonacci number as index and say it is i, then compare a[i] with x, if x is same then return i. Else if x is greater, then search the sub array after i, else search the sub array before i. Source Code :

Python3 program for Fibonacci search.

from bisect import bisect_left

Returns index of x if present, else

returns - 1

def fibMonaccianSearch(arr, x, n):

Initialize fibonacci numbers

fibMMm2 = 0 # (m-2)'th Fibonacci No. fibMMm1 = 1 # (m-1)'th Fibonacci No. fibM = fibMMm2 + fibMMm1 # m'th Fibonacci

fibM is going to store the smallest

Fibonacci Number greater than or equal to n

while (fibM < n): fibMMm2 = fibMMm fibMMm1 = fibM fibM = fibMMm2 + fibMMm

Marks the eliminated range from front

offset = - 1;

while there are elements to be inspected.

Note that we compare arr[fibMm2] with x.

When fibM becomes 1, fibMm2 becomes 0

while (fibM > 1):

Check if fibMm2 is a valid location

i = min(offset+fibMMm2, n-1)

If x is greater than the value at

index fibMm2, cut the subarray array

from offset to i

if (arr[i] < x): fibM = fibMMm fibMMm1 = fibMMm fibMMm2 = fibM - fibMMm offset = i

If x is greater than the value at

index fibMm2, cut the subarray

after i+

elif (arr[i] > x): fibM = fibMMm fibMMm1 = fibMMm1 - fibMMm fibMMm2 = fibM - fibMMm

element found. return index

else : return i

comparing the last element with x */

if(fibMMm1 and arr[offset+1] == x): return offset+1;

element not found. return - 1

return - 1

Driver Code

arr = [10, 22, 35, 40, 45, 50, 80, 82, 85, 90, 100] n = len(arr) x = 80 print("Found at index:", fibMonaccianSearch(arr, x, n)) Time Complexity of Fibonacci Search: Time complexity for Fibonacci search is O(log 2 n) Sorting Techniques: Sorting in general refers to various methods of arranging or ordering things based on criteria's (numerical, chronological, alphabetical, hierarchical etc.). There are many approaches to sorting data and each has its own merits and demerits.

Various Passes of Bubble Sort Source Code:

Python program for implementation of Bubble Sort

def bubbleSort(arr): n = len(arr)

Traverse through all array elements

for i in range(n):

Last i elements are already in place

for j in range(0, n-i-1):

traverse the array from 0 to n-i- 1

Swap if the element found is greater

than the next element

if arr[j] > arr[j+1] :

arr[j], arr[j+1] = arr[j+1], arr[j]

Driver code to test above

arr = [64, 34, 25, 12, 22, 11, 90] bubbleSort(arr) print ("Sorted array is:") for i in range(len(arr)): print ("%d" %arr[i]) Step-by-step example: Let us take the array of numbers "5 1 4 2 8", and sort the array from lowest number to greatest number using bubble sort. In each step, elements written in bold are being compared. Three passes will be required. First Pass: ( 5 1 4 2 8 ) ( 1 5 4 2 8 ), Here, algorithm compares the first two elements, and swaps since 5 > 1. ( 1 5 4 2 8 ) ( 1 4 5 2 8 ), Swap since 5 > 4 ( 1 4 5 2 8 ) ( 1 4 2 5 8 ), Swap since 5 > 2 ( 1 4 2 5 8 ) ( 1 4 2 5 8 ), Now, since these elements are already in order (8 > 5), algorithm does not swap them. Second Pass: ( 1 4 2 5 8 ) ( 1 4 2 5 8 ) ( 1 4 2 5 8 ) ( 1 2 4 5 8 ), Swap since 4 > 2 ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) Now, the array is already sorted, but our algorithm does not know if it is completed. The algorithm needs one whole pass without any swap to know it is sorted. Third Pass: ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) ( 1 2 4 5 8 )