unit 1 cad
unit 1 cad
Data Structure
Data Structure can be defined as the group of data elements which provides an efficient way of storing
and organizing data in the computer so that it can be used efficiently. Some examples of Data
Structures are arrays, Linked List, Stack, Queue, etc. Data Structures are widely used in almost every
aspect of Computer Science i.e. Operating System, Compiler Design, Artifical intelligence, Graphics
and many more.
Data Structures are the main part of many computer science algorithms as they enable the
programmers to handle the data in an efficient way. It plays a vitle role in enhancing the performance
of a software or a program as the main function of the software is to store and retrieve the user's data
as fast as possible
• Interface − Each data structure has an interface. Interface represents the set of operations
that a data structure supports. An interface only provides the list of supported operations, type
of parameters they can accept and return type of these operations.
• Implementation − Implementation provides the internal representation of a data structure.
Implementation also provides the definition of the algorithms used in the operations of the
data structure.
As applications are getting complexed and amount of data is increasing day by day, there may arrise
the following problems:
Processor speed: To handle very large amout of data, high speed processing is required, but as the
data is growing day by day to the billions of files per entity, processor may fail to deal with that much
amount of data.
Data Search: Consider an inventory size of 106 items in a store, If our application needs to search
for a particular item, it needs to traverse 106 items every time, results in slowing down the search
process.
Multiple requests: If thousands of users are searching the data simultaneously on a web server, then
there are the chances that a very large server can be failed during that process
in order to solve the above problems, data structures are used. Data is organized to form a data
structure in such a way that all items are not required to be searched and required data can be searched
instantly.
Efficiency: Efficiency of a program depends upon the choice of data structures. For example: suppose,
we have some data and we need to perform the search for a perticular record. In that case, if we
Linear Data Structures: A data structure is called linear if all of its elements are arranged in the
linear order. In linear data structures, the elements are stored in non-hierarchical way where each
element has the successors and predecessors except the first and last element.
Types of Linear Data Structures are given below:
Arrays: An array is a collection of similar type of data items and each data item is called an element
of the array. The data type of the element may be any valid data type like char, int, float or double.
The elements of array share the same variable name but each one carries a different index number
known as subscript. The array can be one dimensional, two dimensional or multidimensional.
The individual elements of the array age are:
age[0], age[1], age[2], age[3], ......... age[98], age[99].
Algorithm
An algorithm is a procedure having well defined steps for solving a particular problem. Algorithm is
finite set of logic or instructions, written in order for accomplish the certain predefined task. It is not
the complete program or code, it is just a solution (logic) of a problem, which can be represented
either as an informal description using a Flowchart or Pseudo code.
The major categories of algorithms are given below:
o Sort: Algorithm developed for sorting the items in certain order.
o Search: Algorithm developed for searching the items inside a data structure.
o Delete: Algorithm developed for deleting the existing element from the data structure.
o Insert: Algorithm developed for inserting an item inside a data structure.
o Update: Algorithm developed for updating the existing element inside a data structure.
Each algorithm must have:
o Specification: Description of the computational procedure.
o Pre-conditions: The condition(s) on input.
o Body of the Algorithm: A sequence of clear and unambiguous instructions.
o Post-conditions: The condition(s) on output.
Characteristics of an Algorithm
An algorithm must follow the mentioned below characteristics:
o Input: An algorithm must have 0 or well defined inputs.
o Output: An algorithm must have 1 or well defined outputs, and should match with the
desired output.
o Feasibility: An algorithm must be terminated after the finite number of steps.
o Independent: An algorithm must have step-by-step directions which is independent of any
programming code.
o Unambiguous: An algorithm must be unambiguous and clear. Each of their steps and
input/outputs must be clear and lead to only one meaning.
Algorithm Analysis
Efficiency of an algorithm can be analyzed at two different stages, before implementation and after
implementation. They are the following −
• 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.
We shall learn about a priori algorithm analysis. Algorithm analysis deals with the execution or running
time of various operations involved. The running time of an operation can be defined as the number
of computer instructions executed per operation.
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.
• 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 2 - C ← A + B + 10
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.
Asymptotic Analysis
In mathematical analysis, asymptotic analysis of algorithm is a method of defining the mathematical
boundation of its run-time performance. Using the asymptotic analysis, we can easily conclude about
the average case, best case and worst case scenario of an algorithm.
It is used to mathematically calculate the running time of any operation inside an algorithm.
Example: Running time of one operation is x(n) and for another operation it is calculated as f(n2). It
refers to running time will increase linearly with increase in 'n' for first operation and running time will
increase exponentially for second operation. Similarly the running time of both operations will be same
if n is significantly small.
Worst case: It defines the input for which the algorithm takes the huge time.
Best case: It defines the input for which the algorithm takes the lowest time.
Asymptotic Notations
The commonly used asymptotic notations used for calculating the running time complexity
of an algorithm is given below:
o Big oh Notation (Ο)
o Omega Notation (Ω)
o Theta Notation (θ)
For example: If f(n) and g(n) are the two functions defined for positive integers, then f(n) is
O(g(n)) as f(n) is big oh of g(n)or f(n) is on the order of g(n)) if there exists constants c and no
such that:
F(n)≤cg(n) for all n≥no
This implies that f(n) does not grow faster than g(n), or g(n) is an upper bound on the function f(n).
constant − Ο(1)
logarithmic − Ο(log n)
linear − Ο(n)
quadratic − Ο(n2)
cubic − Ο(n3)
polynomial − nΟ(1)
exponential − 2Ο(n)
Recursion
What is Recursion?
The process in which a function calls itself directly or indirectly is called recursion and the corresponding
function is called as recursive function. Using recursive algorithm, certain problems can be solved quite
easily. Examples of such problems are Towers of Hanoi (TOH), Inorder/Preorder/Postorder Tree
Traversals, DFS of Graph, etc.
In the recursive program, the solution to the base case is provided and the solution of the bigger
problem is expressed in terms of smaller problems.
int fact(int n)
{
if (n < = 1) // base case
return 1;
else
return n*fact(n-1);
}
In the above example, base case for n < = 1 is defined and larger value of number can be solved by
converting to smaller one till base case is reached.
How a particular problem is solved using recursion?
The idea is to represent a problem in terms of one or more smaller problems, and add one or more
base conditions that stop the recursion. For example, we compute factorial n if we know factorial of
(n-1). The base case for factorial would be n = 0. We return 1 when n = 0.
If the base case is not reached or not defined, then the stack overflow problem may arise. Let us take
an example to understand this.
int fact(int n)
{
// wrong base case (it may cause
// stack overflow).
if (n == 100)
else
return n*fact(n-1);
}
If fact(10) is called, it will call fact(9), fact(8), fact(7) and so on but the number will never reach 100.
So, the base case is not reached. If the memory is exhausted by these functions on the stack, it will
cause a stack overflow error.
What is the difference between direct and indirect recursion?
A function fun is called direct recursive if it calls the same function fun. A function fun is called indirect
recursive if it calls another function say fun_new and fun_new calls fun directly or indirectly. Difference
between direct and indirect recursion has been illustrated in Table 1.
directRecFun();
// Some code...
}
indirectRecFun2();
// Some code...
}
void indirectRecFun2()
{
// Some code...
indirectRecFun1();
// Some code...
}
Example
void printFun(int test)
{
if (test < 1)
return;
else
{
printf(“%d ”, test);
printFun(test-1);
printf(“%d ”, test);
return;
}
}
int main()
{
int test = 3;
printFun(test);
}
Output:
321123
When printFun(3) is called from main(), memory is allocated to printFun(3) and a local variable test
is initialized to 3 and statement 1 to 4 are pushed on the stack as shown in below diagram. It first prints
‘3’. In statement 2, printFun(2) is called and memory is allocated to printFun(2) and a local variable
test is initialized to 2 and statement 1 to 4 are pushed in the stack. Similarly,
printFun(2) calls printFun(1)and printFun(1) calls printFun(0). printFun(0) goes to if statement
and it return to printFun(1). Remaining statements of printFun(1) are executed and it returns to
printFun(2) and so on. In the output, value from 3 to 1 are printed and then 1 to 3 are printed. The
memory stack has been shown in below diagram.
Types of Recursion
A function is recursive if it makes a call to itself directly or indirectly. If a function f() calls itself within
from its own body, it is called recursive. Secondly, if a function f() calls another function g() that
ultimately calls back to f(), then it can also be considered a recursive function. Following variants of
recursion tell us making recursive calls in different ways depending upon the problem.
Linear Recursion
In linear recursion a function calls exactly once to itself each time the function is invoked, and grows
linearly in proportion to the size of the problem. Finding maximum among an array of integers could
be a good example of linear recursion. The same is demonstrated below:
/*
File: rec_max_array.c
Program: Find maximum among array elements
recursively.
*/
#include <stdio.h>
#define SIZE 10
}
int recursiveMax (int *arr, int n)
{
if (n == 1)
return arr[0];
OUTPUT
======
Maximum element among array items is: 19
/*
File: rec_array_reverse.c
Program: Reverse array elements recursively.
*/
#include <stdio.h>
#define SIZE 10
int main ()
{
int arr[SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9 , 10};
int i;
recursiveRev (arr, 0, SIZE-1);
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
recursiveRev (arr, i+1, j-1);
}
}
OUTPUT
======
Printing array elements after reversing them...
10
9
8
7
6
5
4
3
2
1
Binary Recursion
As name suggests, in binary recursion a function makes two recursive calls to itself when invoked, it
uses binary recursion. Fibonacci series is a very nice example to demonstrate binary recursion. See
the example below:
fib (1) = fib (2) = 1
fib (n) = fib (n-1) + fib (n-2), if n > 2
By definition, the first two numbers in the Fibonacci sequence are 0 and 1, and each subsequent
number is the sum of the previous two [Definition Source wikipedia].
/*
File: bin_rec_fib.c
Program: Computes n-th fibonacci number using
binary recursion.*/
#include <stdio.h>
int recursiveFib (int);
int main ()
{
int n = 6;
printf ("%dth fibonacci number is %d\n", n, recursiveFib(n));
}
int recursiveFib (int n)
// base case
if (n <= 1)
return n; // binary recursive call
return recursiveFib(n-1) + recursiveFib (n - 2);
OUTPUT
======
6th fibonacci number is 8
Above piece of code exercises binary recursion in order to compute 6th fibonacci number. In this piece
of code recursiveFib returns n in case n is less than or equal to 1. This is the base condition in
recursiveFib from where recursive call returns to previous call. Following figure shows how
recursiveFib is executed.
Multiple Recursion
Multiple recursion can be treated a generalized form of binary recursion. When a function makes
multiple recursive calls possibly more than two, it is called multiple recursion.
Towers of Hanoi:
It is a mathematical puzzle, is also called tower of Brahma or Lucas tower. It consists of three pegs or rods and
number of disks of different sizes which can slide onto any peg.
At the beginning of the game, all the plates are arranged in ascending order on peg A that is smallest on top and
biggest on bottom.
The objective of this game is to move all disks from peg A to peg C using auxiliary peg B using some rules.
1. Only one disk must be moved at a time
2. Only top disk must be moved and placed only on top
3. A disk can’t be placed on small disk
4. Game must be completed within 2n-1 steps
Now let us play the game with two plates
It is difficult to play the game as the number of plates grows. The recursive solution to towers of
Hanoi is
tower(beg,end,aux,n-1)
beg->end
tower(aux,beg,end,n-1)
if(n==0)
return;
tower(beg,end,aux,n-1);
tower(aux,beg,end,n-1);
#include<stdio.h>
void tower(char,char,char,int);
int main()
{
int n;
printf("nEnter number of plates:");
scanf("%d",&n);
tower('A','B','C',n);
return 0;
}
void tower(char beg,char aux,char end,int n)
{
if(n==0)
return;
tower(beg,end,aux,n-1);
printf("\n%c -> %c",beg,end);
tower(aux,beg,end,n-1);
}
Execution:
Enter number of plates:3
A -> C
A -> B
C -> B
A -> C
B -> A
B -> C
A -> C
Later a French mathematician “Edouard Lucas” in 1883 published the game in west. According to Lucas, it takes
264-1 seconds to complete the game if it takes 1 sec to move a plate, which is equal to 585 billion years.
Searching
Searching is the process of finding some particular element in the list. If the element is present in
the list, then the process is called successful and the process returns the location of that element,
otherwise the search is called unsuccessful.
There are two popular search methods that are widely used in order to search some item into the
list. However, choice of the algorithm depends upon the arrangement of the list.
o Linear Search
o Binary Search
o Fibonacci Search
Linear Search
Linear search is the simplest search algorithm and often called sequential search. In this type of
searching, we simply traverse the list completely and match each element of the list with the item
whose location is to be found. If the match found then location of the item is returned otherwise the
algorithm return NULL.
Linear search is mostly used to search an unordered list in which the items are not sorted. The
algorithm of linear search is given as follows.
Algorithm
o LINEAR_SEARCH(A, N, VAL)
o Step 1: [INITIALIZE] SET POS = -1
o Step 2: [INITIALIZE] SET I = 1
o Step 3: Repeat Step 4 while I<=N
o Step 4: IF A[I] = VAL
SET POS = I
PRINT POS
Go to Step 6
[END OF IF]
SET I = I + 1
[END OF LOOP]
o Step 5: IF POS = -1
PRINT " VALUE IS NOT PRESENTIN THE ARRAY "
[END OF IF]
o Step 6: EXIT
Complexity of algorithm
Space O(1)
Program
#include<stdio.h>
int linearSearch(int arr[], int n, int key)
{
if(n < 0)
return -1;
else if(arr[n] == key)
return n;
else
return linearSearch(arr, n-1, key);
}
int main()
{
int arr[50], n, i, key, pos;
if(pos == -1)
printf("\n Key %d not found.", key);
else
printf("\n Key %d found at position: %d", key, pos+1);
return 0;
}
Binary Search
Binary search is the search technique which works efficiently on the sorted lists. Hence, in order to
search an element into some list by using binary search technique, we must ensure that the list is
sorted.
Binary search follows divide and conquer approach in which, the list is divided into two halves and the
item is compared with the middle element of the list. If the match is found then, the location of middle
element is returned otherwise, we search into either of the halves depending upon the result produced
through the match.
Binary search algorithm is given below.
Complexity
SN Performance Complexity
Let us consider an array arr = {1, 5, 7, 8, 13, 19, 20, 23, 29}. Find the location of the item
23 in the array.
In 1st step :
1. BEG = 0
2. END = 8ron
3. MID = 4
4. a[mid] = a[4] = 13 < 23, therefore
in Second step:
1. Beg = mid +1 = 5
2. End = 8
3. mid = 13/2 = 6
4. a[mid] = a[6] = 20 < 23, therefore;
in third step:
1. beg = mid + 1 = 7
2. End = 8
3. mid = 15/2 = 7
4. a[mid] = a[7]
5. a[7] = 23 = item;
6. therefore, set location = mid;
7. The location of the item will be 7.
#include<stdio.h>
int binarySearch(int arr[], int first, int last, int key)
{
if(first <= last)
{
int mid;
mid = (first + last) / 2;
if(arr[mid] == key)
return mid;
else if(key < arr[mid])
return binarySearch(arr, first, mid-1, key);
else
return binarySearch(arr, mid+1, last, key);
}
return -1;
}
int main()
{
int arr[50], n, i, key, pos;
return 0;
}
Fibonacci Search
Fibonacci Search is a comparison-based technique that uses Fibonacci numbers to search an element
in a sorted array.
Similarities with Binary Search:
1. Works for sorted arrays
2. A Divide and Conquer Algorithm.
3. Has Log n time complexity.
Differences with Binary Search:
1. Fibonacci Search divides given array in unequal parts
2. Binary Search uses division operator to divide range. Fibonacci Search doesn’t use /, but uses +
and -. The division operator may be costly on some CPUs.
3. Fibonacci Search examines relatively closer elements in subsequent steps. So when input array
is big that cannot fit in CPU cache or even in RAM, Fibonacci Search can be useful.
Background:
Fibonacci Numbers are recursively defined as F(n) = F(n-1) + F(n-2), F(0) = 0, F(1) = 1. First few
Fibinacci Numbers are 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
Observations:
Below observation is used for range elimination, and hence for the O(log(n)) complexity .
Algorithm:
Let the searched element be x.
The idea is to first find the smallest Fibonacci number that is greater than or equal to the length of
given array. Let the found Fibonacci number be fib (m’th Fibonacci number). We use (m-2)’th Fibonacci
number as the index (If it is a valid index). Let (m-2)’th Fibonacci Number be i, we compare arr[i] with
x, if x is same, we return i. Else if x is greater, we recur for subarray after i, else we recur for subarray
before i.
Program
#include<stdio.h>
if(arr[0] == key)
{
/* When array is having only
one element */ return 0;
}
return -1;
}
int main(void)
{
int arr[50], n, i, key, pos;
printf("\n Enter the number of
elements: "); scanf("%d", &n);
printf("\n Enter %d
elements: ", n); for(i=0;
i<n; i++)
{
scanf("%d", &arr[i]);
}
printf("\n Enter element to be
searched: "); scanf("%d",
&key);
pos =
fibonacciSearch(a
rr, n, key); if(pos
== -1)
printf("\n ** Key not found ** \n");
else
printf("\n Key Found at index: %d", pos);
return 0;
}