0% found this document useful (0 votes)
0 views

Module 1

Uploaded by

pratapshivam007
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views

Module 1

Uploaded by

pratapshivam007
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 30

Problem Solving using Data

Structure
Module 1
Introduction to Data Structures:
• A representation of logical relationship existing between individual elements of data.
• The term data structure is used to describe the way data is stored.
• To develop a program of an algorithm we need to select an appropriate data structure
for that algorithm.
• The choice of a particular data structure depends on-
• Actual relationships of data in real world
• How effectively the data is processed when necessary
• A data structure is said to be linear if its elements form a sequence or a linear list
[array, stacks, queues and linked lists organize data in linear order].
• A data structure is said to be non-linear if its elements form a hierarchical
classification where data items appear at various levels [Trees and Graphs are widely
used non-linear data structures].
• Data structures are divided into two types:
✓Primitive data structures.
✓Non-primitive data structures.
• Primitive Data Structures are the basic data structures that directly operate upon
the machine instructions. Integers, floating point numbers, character constants
come under this category.
• Non-primitive data structures are more complicated data structures and are
derived from primitive data structures. They emphasize on grouping same or
different data items with relationship between each data item. Arrays, lists, trees
come under this category.
Data structures: Organization of data
• The collection of data in a program have some kind of structure or organization.
• There are two fundamental types:
• Contiguous
• Non-Contiguous.
• In contiguous structures, data are kept together in memory (either RAM or in a
file). An array is an example of a contiguous structure, since each element in the
array is located next to one or two other elements.
• In contrast, items in a non-contiguous structure are scattered in memory, but we
link them to each other in some way. A linked list is an example of a non-
contiguous data structure. Here, the nodes of the list are linked together using
pointers stored in each node.
Contiguous structures:
Contiguous structures can be of two types:
1. Array contains data items of all the same size. In an array, each element is of the same
type and thus has the same size.
int arr[3] = {1, 2, 3};
2. Structure contains data items where the size may differ as elements may be of different
data types.
struct cust_data
{
int age;
char name[20];
};
Non-contiguous structures:
• Non-contiguous structures are implemented as a collection of data-items, called nodes where,
each node can point to one or more nodes in the collection.
• The simplest kind of non-contiguous structure is linked list which represents a linear, one-
dimension type of non-contiguous structure, where there is only the notation of backwards and
forwards.
• A tree is an example of a two-dimensional non-contiguous structure. Here, there is the notion of
up and down and left and right.
• In a tree each node has only one link that leads into the node and links can only go down the tree.
• The most general type of non-contiguous structure, called a graph has no such restrictions.
Data Structure Operations
Algorithm
An algorithm is a finite sequence of instructions, each of which has a clear meaning
and can be performed with a finite amount of effort in a finite length of time. No
matter what the input values may be, an algorithm terminates after executing a
finite number of instructions. Every algorithm must satisfy the following criteria:
✓Input: There are zero or more quantities, which are externally supplied;
✓Output: At least one quantity is produced;
✓Definiteness: Each instruction must be clear and unambiguous;
✓Finiteness: If we trace out the instructions of an algorithm, then for all cases the
algorithm will terminate after a finite number of steps;
✓Effectiveness: Every instruction must be sufficiently basic that it can in principle
be carried out by a person using only pencil and paper. It is not enough that each
operation be definite, but it must also be feasible.
An algorithm is represented using pseudo language that is a combination of the
constructs of a programming language and informal English statements.
The time and space are two important measures of the efficiency of an algorithm.
Performance of a program:
The performance of a program is the amount of computer memory and time needed to run a
program.
Time Complexity: The time needed by an algorithm expressed as a function of the size of a problem
is called the Time complexity of the algorithm. The time complexity of a program is the amount of
computer time it needs to run to completion.
Space Complexity: The space complexity of a program is the amount of memory it needs to run to
completion. The space need by a program has the following components:
1. Instruction space: The space needed to store the compiled version of the program instructions.
2. Data space: The space needed to store all constant and variable values having two components:
• Space needed by constants and simple variables in program.
• Space needed by dynamically allocated objects such as arrays and class instances.
3. Environment stack space: The environment stack is used to save information needed to
resume execution of partially completed functions.
Algorithmic Notation
Abstract Data Type (ADT):

• A conceptual model that defines a set of operations and behaviors for a data
structure without specifying how these operations are implemented or how data
is organized in memory.
• Mentions what operations are to be performed but not how to be implemented.
• It is called “abstract” as it provides an implementation-independent view.
• An abstract data type in a theoretical construct that consists of data as well as the
operations to be performed on the data while hiding implementation.
• For example, a stack is a typical abstract data type. Items stored in a stack can only
be added and removed in certain order(LIFO) – the last item added is the first item
removed. We call these operations, pushing and popping. In this definition, we
haven’t specified how items are stored on the stack or how the items are pushed
and popped. We have only specified the valid operations that can be performed.
Dynamic Memory Allocation
• Memory size initialization and allocation are done by the programmer.
• It is managed and served with pointers that point to the newly allocated
memory space in heap memory.
• Everything is done during runtime.
• Advantages:
• Dynamically allocated at runtime
• Reallocate memory size if needed
• Dynamic allocation leads to no memory wastage.
• Few functions available in stdlib.h header file which will help to allocate
memory dynamically
Dynamic Memory Allocation
• malloc()
✓Whenever a new memory area is needed, malloc is called and requested the
amount in need.
✓If the memory is available, a pointer to the start of an area of memory of the
required size is returned.
✓If the required memory is not available, the pointer NULL is returned.
✓It returns a pointer of type void which can be cast into a pointer of any form.
✓It does not initialize memory at execution time.
✓It takes a single argument, which is the number of bytes to allocate.
Dynamic Memory Allocation
• calloc()
✓It is used to dynamically allocate the specified number of blocks of memory of
the specified type.
✓It initializes each block with a default value 0.
✓It takes two arguments:
1. Number of blocks to be allocated.
2. Size of each block in bytes.
• free()
✓It is used to dynamically deallocate the allocated memory.
✓It helps to reduce wastage of memory by freeing it.
Dynamic Memory Allocation
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr1 = (int *)malloc(5 * sizeof(int));
int* ptr2= (int *)calloc(5, sizeof(int));
for (int i = 0; i < 5; i++) {
ptr1[i] = i+1;
ptr2[i] = 5-i; }
for (int i = 0; i < 5; i++)
printf(“%d ”,ptr1[i]);
printf(“\n”);
for (int i = 0; i < 5; i++)
printf(“%d ”,ptr2[i]);
free(ptr1);
free(ptr2);
return 0;
}
Dynamic Memory Allocation
• Realloc()
✓Enables the programmer to reuse or extend the memory that was previously
allocated using malloc() or calloc().
✓Deallocates the old object pointed to by the pointer and returns a pointer to a
new object that has the size as specified.
✓realloc() should only be used for dynamically allocated memory. If
the memory is not dynamically allocated, then behavior is undefined.
✓Syntax:
int *ptr= (int *) realloc(previous pointer type, new no of elements *
sizeof(int));
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr = (int *)malloc(sizeof(int)*2);
int i;
int *ptr_new;
*ptr = 10;
*(ptr + 1) = 20;
ptr_new = (int *)realloc(ptr, sizeof(int)*3);
*(ptr_new + 2) = 30;
for(i = 0; i < 3; i++)
printf("%d ", *(ptr_new + i));
return 0;
}
Recursion
• Recursion is used to solve complex problems by breaking them down into
simpler sub-problems.
• A function that calls itself is called Recursive function.
• Syntax:
return_type function_name(args) {
// base condition
// recursion case(self-call) }
• Requirement→
1. Recursion case → f(n)= n+ f(n-1)
2. Base Condition → when the recursion is going to terminate(exit point)
• Define base condition before the recursive case, otherwise the base
condition may never encountered and recursion may continue infinitely.
#include <stdio.h>
int rSum(int n)
{
if (n == 0) { //base condition
return 0; }
int res = n + rSum(n - 1); // recursive case
return res;
}
int main()
{
int n = 5;
int sum = rSum(n);
printf("Sum of First %d Natural Numbers: %d", n, sum);
return 0;
} OUTPUT: 15
Application
• Mathematical Problem
• Divide & Conquer
• Dynamic programming
• Postfix to Infix conversion
• Searching and sorting algorithm
• Tree and graph algorithm
• Advantage:
1. Effectively reduce the length of the code
2. Tower of Hanoi and Tree traversal
3. Linked-list
• Disadvantage:
1. A bit slower due to function call overload
2. Takes extra space in the function-call stack due to separate stack frames.
3. Space complexity higher , time complexity relatively lower.
4. Stack overflow
Tower of Hanoi
Tower of Hanoi is a mathematical puzzle where we have three rods and n disks.
The objective of the puzzle is to move the entire stack to another rod, obeying the following
simple rules:
1) Only one disk can be moved at a time.
2) Each move consists of taking the upper disk from one of the stacks and placing it on top
of another stack i.e. a disk can only be moved if it is the uppermost disk on a stack.
3) No disk may be placed on top of a smaller disk.
// C recursive function to solve tower of hanoi puzzle
#include <stdio.h>
void towerOfHanoi(int n, char from_rod, char to_rod, char aux_rod)
{ if (n == 1)
{ printf("\n Move disk 1 from rod %c to rod %c", from_rod, to_rod);
return;
}
towerOfHanoi(n-1, from_rod, aux_rod, to_rod);
printf("\n Move disk %d from rod %c to rod %c", n, from_rod, to_rod);
towerOfHanoi(n-1, aux_rod, to_rod, from_rod);
}
int main()
{ int n = 4; // Number of disks
towerOfHanoi(n, 'A', 'C', 'B'); // A, B and C are names of rods
return 0;
}
Recursive Linear Search Algorithm
Pseudocode for Recursive Linear Search:
LinearSearch (array, index, key):
if index < 0:
return -1;
if item = key:
return index
return LinearSearch (array, index-1, key)
#include <stdio.h>
int linearSearch(int arr[], int size, int key)
{ if (size == 0) {
return -1; }
if (arr[size - 1] == key) {
return size - 1; }
return linearSearch(arr, size - 1, key);
}
int main()
{
int arr[] = { 5, 15, 6, 9, 4 };
int key = 4;
int index = linearSearch(arr, sizeof(arr) / sizeof(int), key);
if (index == -1) { printf("Key not found in the array.\n"); }
else {
printf("The element %d is found at %d index of the given array \n", key, index);
}
return 0;
}
Iterative Binary Search Algorithm:
#include <stdio.h>
int binarySearch(int arr[], int low, int high, int x)
{ while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == x)
return mid;
if (arr[mid] < x)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
int main(void)
{
int arr[] = { 2, 3, 4, 10, 40 };
int n = sizeof(arr) / sizeof(arr[0]);
int x = 10;
int result = binarySearch(arr, 0, n - 1, x);
if(result == -1) printf("Element is not present in array");
else
printf("Element is present at index %d",result);
}
Recursive Binary Search Algorithm:
#include <stdio.h>
int binarySearch(int arr[], int low, int high, int x)
{ if (high >= low) {
int mid = low + (high - low) / 2;
if (arr[mid] == x)
return mid;
if (arr[mid] > x)
return binarySearch(arr, low, mid - 1, x);
return binarySearch(arr, mid + 1, high, x);
}
return -1;
}
int main()
{
int arr[] = { 2, 3, 4, 10, 40 };
int n = sizeof(arr) / sizeof(arr[0]);
int x = 10;
int result = binarySearch(arr, 0, n - 1, x);
if (result == -1) printf("Element is not present in array");
else printf("Element is present at index %d", result);
return 0;
}

You might also like