Dsu 22317 Notes
Dsu 22317 Notes
PROF. V.D.GHOGARE
DATA STRUCTURE USING ‘C’ (22317)
CO3I
I SCHEME
SYLLABUS
Unit – I Introduction to Data Structure (Marks-06)
1.1 Concept and need of Data Structure, Abstract Data Type
1.2 Types of Data Structure : (i)Linear Data Structure, (ii)Non-Linear Data Structure
Reversing a List
Polish Notations.
3.2 Conversion of infix to postfix expression, Evaluation of postfix expression, Conversion of
infix to prefix expression, Evaluation of prefix expression, Recursion, Tower of Hanoi.
3.3 Introduction to Queue :
Terminologies: Tree, degree of node, degree of a tree, level of a node, leaf node,
depth/height of a tree, in-degree and out-degree, path, ancestor, and descendant
nodes.
5.2 Tree types and Traversal methods :
Data types
A particular kind of data item, as defined by the values it can take, the programming
language used, or the operations that can be performed on it.
Examples of Non-primitive data type are Array, List, and File etc.
A Non-primitive data type is further divided into Linear and Non-Linear data structure
o Array: An array is a fixed-size sequenced collection of elements of the same data
type.
o List: An ordered set containing variable number of elements is called as Lists.
o File: A file is a collection of logically related information. It can be viewed
as a large list of records consisting of various fields.
Directed Graph
Mixed Graph
Multi Graph
Simple Graph
Null Graph
Weighted Graph
Difference between Linear and Non Linear Data Structure
Linear Data Structure Non-Linear Data Structure
Every item is related to its previous and next Every item is attached with many other
Data is arranged in linear sequence. Data is not arranged in sequence.
Data items can be traversed in a single run. Data cannot be traversed in a single run.
Eg. Array, Stacks, linked list, queue. Eg. tree, graph.
Implementation is easy. Implementation is difficult.
Algorithm Complexity
An algorithm is basically a set of instructions that solve a problem. It is not uncommon to have
multiple algorithms to tackle the same problem, but the choice of a particular algorithm must
depend on the time and space complexity of the algorithm
Algorithms are used to manipulate the data contained in data structures. When working with
data structures, algorithms are used to perform operations on the stored data. A complex
algorithm is often divided into smaller units called modules. This process of dividing an
algorithm into modules is called modularization. The key advantages of modularization are as
follows:
Each module can be designed independently. While designing one module, the details
of other modules can be ignored, thereby enhancing clarity in design which in turn
simplifies implementation, debugging, testing, documenting, and maintenance of the
overall algorithm.
There are two main approaches to design an algorithm—top-down approach and bottom-up
approach as shown in figure
Top-down approach A top-down design approach starts by dividing the complex algorithm
into one or more modules. These modules can further be decomposed into one or more sub-
modules, and this process of decomposition is iterated until the desired level of module
complexity is achieved. Top-down design method is a form of stepwise refinement where we
begin with the topmost module and incrementally add modules that it calls.
Therefore, in a top-down approach, we start from an abstract design and then at each step,
this design is refined into more concrete levels until a level is reached that requires no further
refinement.
Bottom-up approach A bottom-up approach is just the reverse of top-down approach. In the
bottom-up design, we start with designing the most basic or concrete modules and then
proceed towards designing higher level modules. The higher level modules are implemented
by using the operations performed by lower level modules. Thus, in this approach sub-
modules are grouped together to form a higher level module. All the higher level modules are
clubbed together to form even higher level modules. This process is repeated until the design
of the complete algorithm is obtained.
Algorithm Complexity
Analyzing an algorithm means determining the amount of resources (such as time and
memory) needed to execute it. Algorithms are generally designed to work with an arbitrary
number of inputs, so the efficiency or complexity of an algorithm is stated in terms of time and
space complexity.
Traversing It means to access each data item exactly once so that it can be processed. For
example, to print the names of all the students in a class.
Searching It is used to find the location of one or more data items that satisfy the given
constraint. Such a data item may or may not be present in the given collection of data items.
For example, to find the names of all the students who secured 100 marks in mathematics.
Inserting It is used to add new data items to the given list of data items. For example, to add
the details of a new student who has recently joined the course?
Deleting It means to remove (delete) a particular data item from the given collection of data
items. For example, to delete the name of a student who has left the course?
Sorting Data items can be arranged in some order like ascending order or descending order
depending on the type of application. For example, arranging the names of students in a class
in an alphabetical order, or calculating the top three winners by arranging the
participants‘scores in descending order and then extracting the top three.
Merging Lists of two sorted data items can be combined to form a single list of sorted data
items.
Searching is used to find the location where an element is available. There are two
types of search techniques. They are:
Linear Search:
This is the simplest of all searching techniques. In this technique, an ordered or
unordered list will be searched one by one from the beginning until the desired element is
found. If the desired element is found in the list then the search is successful otherwise
unsuccessful. Suppose there are n elements organized sequentially on a List. The number of
comparisons required to retrieve an element from the list, purely depends on where the
element is stored in the list. If it is the first element, one comparison will do; if it is second
element two comparisons are necessary and so on. On an average you need [(n+1)/2]
comparisons to search an element. If search is not successful, you would need ‗n’
comparisons.
Algorithm :-
LINEAR_SEARCH(A, N, VAL)
Step 1: [INITIALIZE] SET POS = -1
Step 2: [INITIALIZE] SET I = 1
Step 3: Repeat Step 4 while I<=N
Step 4: IF A[I] = VAL
SET POS = I
PRINT POS
Go to Step 6
[END OF IF]
[END OF LOOP]
Step 6: EXIT
SET I = I + 1
Step 5: IF POS = –1
PRINT VALUE IS NOT PRESENT
IN THE ARRAY
[END OF IF]
Example
Now, let us consider how this mechanism is applied to search for a value in a sorted array.
Consider an array A[] that is declared and initialized as
int A[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; and the value to be searched is VAL = 9. The
algorithm will proceed in the following manner.
Algorithm
Example
Sorting
Sorting means arranging the elements of an array so that they are placed in some relevant
order which may be either ascending or descending.
There are two types of sorting techniques:
1. Internal sorting
2. External sorting
If all the elements to be sorted are present in the main memory then such sorting is called
internal sorting on the other hand, if some of the elements to be sorted are kept on the
secondary storage, it is called external sorting. Here we study only internal sorting
techniques.
Sorting techniques
1. Bubble Sort
2. Selection Sort
3. Insertion Sort
4. Quick Sort
5. Radix Sort.
1. Bubble Sort
Bubble sort is a very simple method that sorts the array elements by repeatedly moving the
largest element to the highest index position of the array segment (in case of arranging
elements in ascending order). In bubble sorting, consecutive adjacent pairs of elements in the
array are compared with each other. If the element at the lower index is greater than the
element at the higher index, the two elements are interchanged so that the element is placed
before the bigger one. This process will continue till the list of unsorted elements exhausts.
This procedure of sorting is called bubble sorting because elements ‗bubble‘ to the top of the
list. Note that at the end of the first pass, the largest element in the list will be placed at its
proper position (i.e., at the end of the list).
[1] In Pass 1, A[0] and A[1] are compared, then A[1] is compared with A[2], A[2] is
compared with A[3], and so on. Finally, A[N–2] is compared with A[N–1]. Pass 1
involves n–1 comparisons and places the biggest element at the highest index of the
array.
[2] In Pass 2, A[0] and A[1] are compared, then A[1] is compared with A[2], A[2] is
compared with A[3], and so on. Finally, A[N–3] is compared with A[N–2]. Pass 2
involves n–2 comparisons and places the second biggest element at the second highest
index of the array.
[3] In Pass 3, A[0] and A[1] are compared, then A[1] is compared with A[2], A[2] is
compared with A[3], and so on. Finally, A[N–4] is compared with A[N–3]. Pass 3
involves n–3 comparisons and places the third biggest element at the third highest
index of the array.
[4] In Pass n–1, A[0] and A[1] are compared so that A[0]<A[1]. After this step, all the
elements of the array are arranged in ascending order.
#include <stdio.h>
#include <conio.h>
int i, j, temp;
temp = x[j];
x[j] = x[j+1];
x[j+1] = temp;
main()
2. Selection Sort
It is a natural sorting algorithm in which we find minimum, second minimum, third minimum
and so on and arrange them in increasing order. Like bubble sort, irrespective of the input,
during ith stage this algorithm incurs (n − i) comparisons.
Working of Selection sort: Selection Sort algorithm is used to arrange a list of elements in a
particular order (Ascending or Descending). In selection sort, the first element in the list is
selected and it is compared repeatedly with remaining all the elements in the list. If any element
is smaller than the selected element (for ascending order), then both are swapped. Then we select
the element at second position in the list and it is compared with remaining all elements in the
list. If any element is smaller than the selected element, then both are swapped. This procedure is
repeated till the entire list is sorted
3. Insertion sort
Insertion sort is a very simple sorting algorithm in which the sorted array (or list) is built one
element at a time. The main idea behind insertion sort is that it inserts each item into its proper
place in the final list. To save memory, most implementations of the insertion sort algorithm
work by moving the current data element past the already sorted values and repeatedly
interchanging it with the preceding value until it is in its correct place. Insertion sort is less
efficient as compared to other more advanced algorithms such as quicksort, heap sort, and merge
sort.Insertion sort provides several advantages:
Simple implementation
Efficient for (quite) small data sets
Stable; i.e., does not change the relative order of elements with equal keys
In-place; i.e., only requires a constant amount O(1) of additional memory space
4. Radix sort
Radix sort is a linear sorting algorithm for integers and uses the concept of sorting names in
alphabetical order. When we have a list of sorted names, the radix is 26 (or 26 buckets) because
there are 26 letters in the English alphabet. So radix sort is also known as bucket sort. Observe
that words are first sorted according to the first letter of the name. That is, 26 classes are used
to arrange the names, where the first class stores the names that begin with A, the second class
contains the names with B, and so on. During the second pass, names are grouped according to
the second letter. After the second pass, names are sorted on the first two letters. This process is
continued till the nth pass, where n is the length of the name with maximum number of letters.
After every pass, all the names are collected in order of buckets. That is, first pick up the names
in the first bucket that contains the names beginning with A. In the second pass, collect the
namesfrom the second bucket, and so on. When radix sort is used on integers, sorting is done on
each of the digits in the number. The sorting procedure proceeds by sorting the least significant
to the most significant digit. While sorting the numbers, we have ten buckets, each for one digit
(0, 1, 2, …, 9) and the number of passes will depend on the length of the number having
maximum number of digts.
5. Quick Sort
Quick sort is a divide and conquer algorithm. Quick sort first divides a large list into two smaller
sublists: the low elements and the high elements. Quick sort can then recursively sort the sub-
lists.
The steps are:
1. Pick an element, called a pivot, from the list.
2. Reorder the list so that all elements with values less than the pivot come before the pivot,
while all elements with values greater than the pivot come after it (equal values can go either
way). After this partitioning, the pivot is in its final position. This is called the partition
operation.
3. Recursively apply the above steps to the sub-list of elements with smaller values and
separately the sub-list of elements with greater values. The base case of the recursion is lists of
size zero or one, which never need to be sorted.
Quick sort, or partition-exchange sort, is a sorting algorithm developed by Tony Hoare that, on
average, makes O(n log n) comparisons to sort n items. In the worst case, it makes O(n 2 )
comparisons, though this behavior is rare. Quick sort is often faster in practice than other O(n log
n) algorithms. It works by first of all by partitioning the array around a pivot value and then
dealing with the 2 smaller partitions separately. Partitioning is the most complex part of quick
sort. The simplest thing is to use the first value in the array, a[l] (or a[0] as l = 0 to begin with) as
the pivot. After the partitioning, all values to the left of the pivot are <= pivot and all values to
the right are > pivot. The same procedure for the two remaining sub lists is repeated and so on
recursively until we have the entire list sorted.
Advantages:
One of the fastest algorithms on average.
Does not need additional memory (the sorting takes place in the array - this is called in-place
processing).
Disadvantages:
Example
10,1,9,4,3
All insertions and deletions take place at the same end, so the last element added to the stack will
be the first element removed from the stack. When a stack is created, the stack base remains
fixed while the stack top changes as elements are added and removed. The most accessible
element is the top and the least accessible element is the bottom of the stack.
Representation of Stack:
Let us consider a stack with 6 elements capacity. This is called as the size of the stack.
The number of elements to be added should not exceed the maximum size of the stack.
If we attempt to add new element beyond the maximum size, we will encounter a stack
overflow condition. Similarly, you cannot remove elements beyond the base of the
stack. If such is the case, we will reach a stack underflow condition.
When an element is added to a stack, the operation is performed by push(). Figure
shows the creation of a stack and addition of elements using push().
When an element is taken off from the stack, the operation is performed by pop().
Figure shows a stack initially with three elements and shows the deletion of elements using
pop().
if(top == 0)
{
printf("\n\nStack empty..");
return;
}
else
{
printf("\n\nElements in stack:");
for(i = 0; i < top; i++)
printf("\t%d", stack[i]);
}
}
void pop()
{
if(top == 0)
{
printf("\n\nStack Underflow..");
return;
}
else
printf("\n\npopped element is: %d ", stack[--top]);
}
void push()
{
int data;
if(top == MAX)
{
printf("\n\nStack Overflow..");
return;
}
else
{
printf("\n\nEnter data: ");
scanf("%d", &data);
stack[top] = data;
top = top + 1;
printf("\n\nData Pushed into the stack");
}
}
void main()
{
int ch;
do
{
ch = menu();
switch(ch)
{
case 1:
push();
break;
case 2:
pop();
break;
case 3:
display();
break;
case 4:
exit(0);
}
getch();
} while(1);
}
Linked List Implementation of Stack:
We can represent a stack as a linked list. In a stack push and pop operations are performed at one
end called top. We can perform similar operations at one end of list using top pointer. The linked
stack looks as shown in figure
{
temp = start;
while( temp -> next != NULL)
temp = temp -> next;
temp -> next = newnode;
top = newnode;
}
printf("\n\n\t Data pushed into stack");
}
void pop()
{
node *temp;
if(top == NULL)
{
printf("\n\n\t Stack
underflow"); return;
}
temp = start;
if( start -> next == NULL)
{
printf("\n\n\t Popped element is %d ", top -> data);
start = NULL;
free(top);
top = NULL;
}
else
{
while(temp -> next != top)
{
temp = temp -> next;
}
temp -> next = NULL;
printf("\n\n\t Popped element is %d ", top -> data);
free(top);
top = temp;
}
}
void display()
{
node *temp;
if(top == NULL)
{
printf("\n\n\t\t Stack is empty ");
}
else
{
temp = start;
printf("\n\n\n\t\t Elements in the stack: \n");
printf("%5d ", temp -> data);
while(temp != top)
{
temp = temp -> next;
printf("%5d ", temp -> data);
}
}
}
char menu()
{
char ch;
clrscr();
printf("\n \tStack operations using pointers.. ");
printf("\n -----------********** ------------ \n");
printf("\n 1. Push ");
printf("\n 2. Pop ");
printf("\n 3. Display");
printf("\n 4. Quit ");
printf("\n Enter your choice:
"); ch = getche();
return ch;
}
void main()
{
char ch;
node *newnode;
do
{
ch = menu();
switch(ch)
{
case '1' :
newnode = getnode();
push(newnode); break;
case '2' :
pop();
break;
case '3' :
display();
break;
case '4':
return;
}
getch();
} while( ch != '4' );
}
Applications Of stacks
1.Reversing a list
2.Polish notation
Polish notation is a legal combination of operators and operands. Operand is the quantity on
which a mathematical operation is performed. Operand may be a variable like x, y, z or a
constant like 5, 4, 6 etc. Operator is a symbol which signifies a mathematical or logical operation
between the operands. Examples of familiar operators include +, -, *, /, ^ etc.
An algebraic expression can be represented using three different notations. They are infix,
postfix and prefix notations:
Infix: It is the form of an arithmetic expression in which we fix (place) the
arithmetic operator in between the two operands.
Example: (A + B) * (C - D)
Prefix: It is the form of an arithmetic notation in which we fix (place) the arithmetic operator
before (pre) its two operands. The prefix notation is called as polish notation (due to the polish
mathematician Jan Lukasiewicz in the year 1920).
Example: * + A B – C D
Postfix: It is the form of an arithmetic expression in which we fix (place) the arithmetic operator
after (post) its two operands. The postfix notation is called as suffix notation and is also referred
to reverse polish notation.
Example: A B + C D - *
The three important features of postfix expression are:
1. The operands maintain the same order as in the equivalent infix expression.
2. The parentheses are not needed to designate the expression unambiguously.
3. While evaluating the postfix expression the priority of the operators is no longer relevant.
1. Infix to postfix
2. Infix to prefix
3. Evaluation Of postfix
4. Evaluation of prefix
Example
Example
3. Evaluation of postfix
4. Evaluation of Prefix
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.
a) Factorial(n)
Input: integer n ≥ 0
Output: n!
1. If n = 0 then return (1)
2. else return prod(n, factorial(n − 1))
b) Towers of Hanoi
Input: The aim of the tower of Hanoi problem is to move the initial n different sized disks
from needle A to needle C using a temporary needle B. The rule is that no larger disk is
to be placed above the smaller disk in any of the needle while moving or at any time, and
only the top of the disk is to be moved at a time from any needle to any needle.
Output:
1. If n=1, move the single disk from A to C and return,
2. If n>1, move the top n-1 disks from A to B using C as temporary.
3. Move the remaining disk from A to C.
4. Move the n-1 disk disks from B to C, using A as temporary
Applications of stacks:
1. Stack is used by compilers to check for balancing of parentheses, brackets and braces.
2. Stack is used to evaluate a postfix expression.
3. Stack is used to convert an infix expression into postfix/prefix form.
4. In recursion, all intermediate arguments and return values are stored on the processor‗s stack.
5. During a function call the return address and arguments are pushed onto a stack and on return
they are popped off.
Queue
A queue is another special kind of list, where items are inserted at one end called the rear and
deleted at the other end called the front. Another name for a queue is a ―FIFO or ―First-in-
first-out‖ list.The operations for a queue are analogues to those for a stack, the difference is that
the insertions go at the end of the list, rather than the beginning. We shall use the following
operations on queues:
• enqueue: which inserts an element at the end of the queue.
• dequeue: which deletes an element at the start of the queue.
Now it is not possible to insert an element 66 even though there are two vacant positions in the
linear queue. To over come this problem the elements of the queue are to be shifted towards the
beginning of the queue so that it creates vacant position at the rear end. Then the FRONT and
REAR are to be adjusted properly. The element 66 can be inserted at the rear end. After this
operation, the queue status is as follows:
This difficulty can overcome if we treat queue position with index 0 as a position that comes
after position with index 4 i.e., we treat the queue as a circular queue.
Types Of queues
1. Linear Queue
2. Circular Queue
3. Priority queue
4. Dequeue
1. Linear Queue
A queue is a FIFO (First-In, First-Out) data structure in which the element that is inserted first is
the first one to be taken out. The elements in a queue are added at one end called the REAR and
removed from the other end called the FRONT. Queues can be implemented by using either
arrays or linked lists.
A queue has two basic operations: insert and delete. The insert operation adds an element to
the end of the queue, and the delete operation removes an element from the front or the start of
the queue.
Algorithm for Insert operation
Step 1: IF REAR = MAX-1
Write OVERFLOW
Goto step 4
[END OF IF]
Step 2: IF FRONT = -1 and REAR = -1
SET FRONT = REAR =
ELSE
SET REAR = REAR + 1
[END OF IF]
Step 3: SET QUEUE[REAR] = NUM
Step 4: EXIT
2. Circular Queue
A more efficient queue representation is obtained by regarding the array Q[MAX] as circular.
Any number of items could be placed on the queue. This implementation of a queue is called a
circular queue because it uses its storage array as if it were a circle instead of a linear list.
There are two problems associated with linear queue. They are:
• Time consuming: linear time to be spent in shifting the elements to the
beginning of the queue.
• Signaling queue full: even if the queue is having vacant position.
Next insert another element, say 66 to the queue. We cannot insert 66 to the queue as the rear
crossed the maximum size of the queue (i.e., 5). There will be queue full signal. The queue status
is as follows:
This difficulty can be overcome if we treat queue position with index zero as a position that
comes after position with index four then we treat the queue as a circular queue.
In circular queue if we reach the end for inserting elements to it, it is possible to insert
new elements if the slots at the beginning of the circular queue are empty.
3. Priority queue
A priority queue is a collection of elements such that each element has been assigned a priority
and such that the order in which elements are deleted and processed comes from the following
rules:
1. An element of higher priority is processed before any element of lower priority.
2. two elements with same priority are processed according to the order in which they were
added to the queue.
A prototype of a priority queue is time sharing system: programs of high priority are
processed first, and programs with the same priority form a standard queue. An
efficient implementation for the Priority Queue is to use heap, which in turn can be
used for sorting purpose called heap sort.
A deque (pronounced as ‗deck‘ or ‗dequeue‘) is a list in which the elements can be inserted or
deleted at either end.
• Input restricted deque (IRD) - An Input restricted deque is a deque, which allows insertions
at one end but allows deletions at both ends of the list.
• Output restricted deque (ORD) - An output restricted deque is a deque, which allows
deletions at one end but allows insertions at both ends of the list.
Applications of queues
Queues are widely used as waiting lists for a single shared resource like printer, disk,
CPU.
Queues are used to transfer data asynchronously (data not necessarily received at same
rate as sent) between two processes (IO buffers), e.g., pipes, file IO, sockets.
Queues are used as buffers on MP3 players and portable CD players, iPod playlist.
Queues are used in Playlist for jukebox to add songs to the end, play from the front of the
list.
Queues are used in operating system for handling interrupts. When programming a real-
time system that can be interrupted, for example, by a mouse click, it is necessary to
process the interrupts immediately, before proceeding with the current job. If the
interrupts have to be handled in the order of arrival, then a FIFO queue is the appropriate
data structure.
Linked list
A linked list allocates space for each element separately in its own block of memory called a
"node". The list gets an overall structure by using pointers to connect all its nodes together like
the links in a chain. Each node contains two fields; a "data" field to store whatever element, and
a "next" field which is a pointer used to link to the next node. Each node is allocated in the heap
using malloc(), so the node memory continues to exist until it is explicitly de-allocated using
free(). The front of the list is a pointer to the ―start‖ node.
The beginning of the linked list is stored in a "start" pointer which points to the first node. The
first node contains a pointer to the second node. The second node contains a pointer to the third
node, ... and so on. The last node in the list has its next field set to NULL to mark the end of the
list. Code can access any node in the list by starting at the start and following the next pointers.
The start pointer is an ordinary local pointer variable, so it is drawn separately on the left top to
show that it is in the stack. The list nodes are drawn on the right to show that they are allocated
in the heap.
Creating a singly linked list starts with creating a node. Sufficient memory has to be allocated for
creating a node. The information is stored in the memory.
Insertion of a Node:
One of the most primitive operations that can be done in a singly linked list is the insertion of a
node. Memory is to be allocated for the new node (in a similar way that is done while creating a
list) before reading the data. The new node will contain empty data field and empty next field.
The data field of the new node is then stored with the information read from the user. The next
field of the new node is assigned to NULL. The new node can then be inserted at three different
places namely:
• Inserting a node at the beginning.
• Inserting a node at the end.
• Inserting a node at intermediate position.
Deletion of a node:
Another primitive operation that can be done in a singly linked list is the deletion of a node.
Memory is to be released for the node to be deleted. A node can be deleted from the list from
three different places namely.
• Deleting a node at the beginning.
• Deleting a node at the end.
• Deleting a node at intermediate position.
To display the information, you have to traverse (move) a linked list, node by node from the first
node, until the end of the list is reached.
2. Double Linked List: A double linked list is a two-way list in which all nodes will have two
links. This helps in accessing both successor node and predecessor node from the given node
position. It provides bidirectional traversing. Each node contains three fields:
Left link.
Data.
Right link.
The left link points to the predecessor node and the right link points to the successor node. The
data field stores the required data. Many applications require searching forward and backward
thru nodes of a list. For example searching for a name in a telephone directory would need
forward and backward scanning thru a region of the whole list.
The basic operations in a double linked list are:
Creation.
Insertion.
Deletion.
Traversing.
The beginning of the double linked list is stored in a "start" pointer which points to the first node.
The first node‟s left link and last node‟s right link is set to NULL.
A tree is a non-empty set one element of which is designated the root of the tree while the
remaining elements are partitioned into non-empty sets each of which is a sub-tree of the root.
A tree T is a set of nodes storing elements such that the nodes have a parent-child relationship
that satisfies the following
• If T is not empty, T has a special tree called the root that has no parent.
• Each node v of T different than the root has a unique parent node w; each node with parent w is
a child of w.
Tree nodes have many useful properties. The depth of a node is the length of the path (or the
number of edges) from the root to that node. The height of a node is the longest path from that
node to its leaves. The height of a tree is the height of the root. A leaf node has no children -- its
only path is up to its parent.
Tree Terminologies
Figure Tree
Root node The root node R is the topmost node in the tree. If R = NULL, then it means the tree
is empty.
Sub-trees If the root node R is not NULL, then the trees T1, T2, and T3 are called the sub-trees
of R.
Leaf node A node that has no children is called the leaf node or the terminal node.
Path A sequence of consecutive edges is called a path. For example, in Fig, the path from
the root node A to node I is given as: A, D, and I.
Ancestor node An ancestor of a node is any predecessor node on the path from root to that
node. The root node does not have any ancestors. In the tree given in Fig. 9.1, nodes A, C, and G
are the ancestors of node K.
Descendant node A descendant node is any successor node on any path from the node to a leaf
node. Leaf nodes do not have any descendants. In the treegiven in Fig., nodes C, G, J, and K are
the descendants of node A.
Level number Every node in the tree is assigned a level number in such a way that the root node
is at level 0, children of the root node are at level number 1. Thus, every node is at one level
higher than its parent. So, all child nodes have a level number given by parent‘s level number +1.
Degree Degree of a node is equal to the number of children that a node has.The degree of a leaf
node is zero.
Types of trees
1. General tree
2. Binary Tree
3. Binary search tree
1. General tree
General trees are data structures that store elements hierarchically. The top node of a tree is the
root node and each node, except the root, has a parent. A node in a general tree (except the leaf
nodes) may have zero or more sub-trees. General trees which have 3 sub-trees per node are
called ternary trees. However, the number of sub-trees for any node may be variable. For
example, a node can have 1 sub-tree, whereas some other node can have 3 sub-trees.
General Tree
2. Binary Tree:
In a binary tree, each node can have at most two children. A binary tree is either empty or
consists of a node called the root together with two binary trees called the left subtree and the
right subtree
Binary tree
A binary search tree, also knownas an ordered binary tree, is a variant of binary trees in which
the nodes are arranged in an order.In a binary search tree, all the nodes in the left sub-tree have a
value less than that of the rootnode. Correspondingly, all the nodes in the right sub-tree have a
value either equal to or greaterthan the root node. The same rule is applicable to every sub-tree in
the tree.
Traversal of a binary tree means to visit each node in the tree exactly once.
The three main methods of traversing a tree are:
Inorder Traversal
Preorder Traversal
Postorder Traversal
Inorder Traversal:
Algorithm In-order(tree)
1. Traverse the left sub-tree, i.e., call In-order(left-sub-tree)
2. Visit the root
3. Traverse the right sub-tree, i.e., call In-order(right-sub-tree)
4.
Preorder Traversal:
Algorithm Pre-order(tree)
1. Visit the root.
2. Traverse the left sub-tree, i.e., call Pre-order(left-sub-tree)
3. Traverse the right sub-tree, i.e., call Pre-order(right-sub-tree)
Post-order Traversal:
Algorithm Post-order(tree)
1. Traverse the left sub-tree, i.e., call Post-order(left-sub-tree)
2. Traverse the right sub-tree, i.e., call Post-order(right-sub-tree)
3. Visit the root
Expression Tree
Binary trees are widely used to store algebraic expressions. For example, consider the algebraic
expression given as:
Exp = (a – b) + (c * d)
Example 1 Given an expression, Exp = ((a + b) – (c * d)) % ((e ^f) / (g – h)), construct
the corresponding binary tree.
Solution
Applications of tree
Trees are used to store simple as well as complex data. Here simple means an integer
value, character value and complex data means a structure or a record.
Trees are often used for implementing other types of data structures like hash tables, sets,
and maps.
A self-balancing tree, Red-black tree is used in kernel scheduling, to preempt massively
multiprocessor computer operating system use
Trees are an important data structure used for compiler construction.
Trees are also used in database design.
Trees are used in file system directories.
Trees are also widely used for information storage and retrieval in symbol tables.
Graph
Graph terminologies
1. A graph G is comprised of two sets V and E, V=Vertices,E= Edges (edges are pairs of
vertices). The graph is denoted G=(V,E), and edge set is E(G), vertex set V(G). Vertices are
also called nodes, and edges are also called arcs.
2. Edges are unordered in an undirected graph: (V1, V2) ≡ (V2, V1) Edges are ordered in a
directed graph: (V1, V2) = (V2, V1). A directed graph is also called a digraph
3. We restrict for now to simple graphs (i.e., no self-loops)V1 = V2 for all edges. Also since E
and V are sets we do not allow multi-edges i.e. (V1, V2) can only appear once. A graph with
multi-edges is called a multi-graph.
4. Two vertices (V1, V2) in an undirected graph are said to be adjacent if edge (V1, V2) is
incident to both V1 and V2. In directed graph, given edge (V1, V2) V1 is adjacent to V2, and
V2 is adjacent from V1. We can also say that V1 is a predecessor of V2 and V2 is a
successor of V1.
5. A Subgraph G‘ of G is such that G‘=(V‘,E‘) where E‘(G‘) is a subset of E(G) and V‘(G‘) is a
subset of V(G). 6. A Path from Vp to Vq in G is a sequence of vertices Vp, Vi1, Vi2,...,ViN ,
Vq, such that (Vp, Vi1),(Vi1, Vi2),...,(ViN , Vq) are edges in E(G). A simple path is one
which all vertices (except first and last) are distinct.
6. Length of the path is the number of edges in it, which is 1 less than the number of nodes on
the path. A trivial path of length 0 (with no edges) exists between a node and itself.
7. A Cycle is a simple path in which the first and last vertices are the same. For an undirected
graph, a simple cycle must have at path of 3 or more that starts and ends at the same node
and doesn‘t visit any node more thatn once. An acyclic graph is a graph that contains no
cycles.
Graph representation
Graphs are used to represent many real life applications: Graphs are used to represent networks.
The networks may include paths in a city or telephone network or circuit network. Graphs are
also used in social networks like linkedIn, facebook. For example, in facebook, each person is
represented with a vertex(or node). Each node is a structure and contains information like person
id, name, gender and locale.
Following is an example undirected graph with 5 vertices.
Adjacency List:
An array of linked lists is used. Size of the array is equal to number of vertices. Let the array be
array[]. An entry array[i] represents the linked list of vertices adjacent to the ith vertex. This
representation can also be used to represent a weighted graph. The weights of edges can be
stored in nodes of linked lists.
Following is adjacency list representation of the above graph.
Applications of graph
In circuit networks where points of connection are drawn as vertices and component
wires
become the edges of the graph.
In transport networks where stations are drawn as vertices and routes become the edges
of
the graph.
In maps that draw cities/states/regions as vertices and adjacency relations as edges.
In program flow analysis where procedures or modules are treated as vertices and calls to
these procedures are drawn as edges of the graph.
Once we have a graph of a particular concept, they can be easily used for finding shortest
paths, project planning, etc.
In flowcharts or control-flow graphs, the statements and conditions in a program are
represented as nodes and the flow of control is represented by the edges.
In state transition diagrams, the nodes are used to represent states and the edges represent
legal moves from one state to the other.
Graphs are also used to draw activity network diagrams. These diagrams are extensively
usedas a project management tool to represent the interdependent relationships between
groups,steps, and tasks that have a significant impact on the project.
Programs
#include<stdio.h>
#include<conio.h>
void insert(int *,int,int);
void del(int *,int);
void display(int *);
void main()
{
int a[5];
clrscr();
insert(a,1,11);
insert(a,2,12);
insert(a,3,13);
insert(a,4,14);
insert(a,5,15);
display(a);
del(a,3);
printf("\n");
display(a);
getch();
}
void insert(int *a,int pos,int num)
{
int i;
for(i=4;i>=pos;i--)
a[i]=a[i-1];
a[i]=num;
}
void del(int *a,int pos)
{
int i;
for(i=pos;i<5;i++)
a[i-1]=a[i];
a[i-1]=0;
}
#include<stdio.h>
#include<conio.h>
void main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int i,x;
clrscr();
printf("enter the value to search = ");
scanf("%d",&x);
for(i=0;i<=9;i++)
{
if(x==a[i])
break;
}
if(i==10)
printf("not found");
else
printf("%d is found at %d location",x,i);
getch();
}
/* OUTPUT
enter the value to search = 4
4 is found at 3 location
*/
#include<stdio.h>
#include<conio.h>
void main()
{
int data[10]={1,2,3,4,5,6,7,8,9,10};
int mid,lower=0,upper=9,num,flag=1;
clrscr();
printf("enter no to search = ");
scanf("%d",&num);
for(mid=(lower+upper)/2;lower<=upper;mid=(lower+upper)/2)
{
if(data[mid]==num)
{
printf("\nThe no is at position %d in array",mid);
flag=0;
break;
}
if(data[mid]>num)
upper=mid-1;
else
lower=mid+1;
}
if(flag)
printf("\nElement is not present in the array.");
getch();
}
/* OUTPUT
enter no to search = 9
arr[k+1]=temp;
}
}
}
printf("\n\narray after sorting:\n");
for(i=0;i<=4;i++)
printf("%d\t",arr[i]);
getch();
}
/* OUTPUT
insertion sort.
array before sorting:
25 17 31 13 2
#include<stdio.h>
#include<conio.h>
#define MAX 5
struct stack
{
int arr[MAX];
int top;
};
void initstack(struct stack*);
void push(struct stack*,int item);
int pop(struct stack*);
void main ()
{
struct stack s;
int i;
clrscr();
initstack(&s);
push(&s,1);
push(&s,2);
push(&s,3);
push(&s,4);
push(&s,5);
push(&s,6);
i=pop(&s);
printf("\n\n item popped is:%d",i);
i=pop(&s);
printf("\n item popped is:%d",i);
i=pop(&s);
printf("\n item popped is:%d",i);
i=pop(&s);
printf("\n item popped is:%d",i);
i=pop(&s);
printf("\n item popped is:%d",i);
i=pop(&s);
getch();
}
void initstack(struct stack *s)
{
s->top=-1;
}
void push(struct stack *s,int item)
{
if(s->top==4)
{
printf(" \n Stack Overflow ");
return;
}
s->top++;
s->arr[s->top]=item;
}
int pop(struct stack*s)
{
int data;
if(s->top==-1)
{
printf("\n Stack underflow ");
return NULL;
}
data=s->arr[s->top];
s->arr[s->top]=0;
s->top--;
return data;
}
/*OUTPUT
Stack Overflow
void main()
{
clrscr();
init(&q);
insert(&q,10);
insert(&q,20);
insert(&q,30);
insert(&q,40);
insert(&q,50);
insert(&q,60);
display(&q);
del(&q);
del(&q);
del(&q);
del(&q);
del(&q);
del(&q);
display(&q);
getch();
}
void init (struct queue *q)
{
q->f=q->r=-1;
}
void insert(struct queue*q,int num)
{
if(q->r==4)
{
printf("queue overflow \n");
return;
}
q->r++;
q->a[q->r]=num;
if (q->f==-1)
q->f=q->r=0;
}
void del(struct queue * q)
{
if (q->r==-1)
{
printf("\nqueue underflow\n");
return;
}
q->a[q->f]=0;
if (q->f==q->r)
q->f=q->r=-1;
else
q->f++;
}
void display (struct queue *q)
{
int i;
if (q->r==-1)
{
printf("\nqueue empty\n");
return;
}
for(i=q->f;i<=q->r;i++)
{
printf("%d \t", q->a[i]);
}
}
/* OUTPUT
queue overflow
10 20 30 40 50
queue underflow
queue empty */
#include <stdio.h>
#include <conio.h>
#define MAX 10
void main( )
{
int arr[MAX] ;
int i, front, rear ;
clrscr( ) ;
getch( ) ;
}
if ( *prear == MAX - 1 )
*prear = 0 ;
else
( *prear )++ ;
arr[*prear] = item ;
if ( *pfront == -1 )
*pfront = 0 ;
}
if ( *pfront == -1 )
{
printf ( "\nQueue is empty." ) ;
return NULL ;
}
data = arr[*pfront] ;
arr[*pfront] = 0 ;
if ( *pfront == *prear )
{
*pfront = -1 ;
*prear = -1 ;
}
else
{
if ( *pfront == MAX - 1 )
*pfront = 0 ;
else
( *pfront )++ ;
}
return data ;
}
Item deleted: 14
Item deleted: 22
Elements in the circular queue after deletion:
0 0 13 -6 25 0 0 0 0 0
#include <stdio.h>
#include <conio.h>
#include <alloc.h>
void main( )
{
struct node *p ;
p = NULL ; /* empty linked list */
display ( p ) ;
display ( p ) ;
addafter ( p, 7, 0 ) ;
addafter ( p, 2, 1 ) ;
addafter ( p, 5, 99 ) ;
display ( p ) ;
printf ( "\nNo. of elements in the Linked List = %d", count ( p ) ) ;
delete ( &p, 99 ) ;
delete ( &p, 1 ) ;
delete ( &p, 10 ) ;
display ( p ) ;
printf ( "\nNo. of elements in the Linked List = %d", count ( p ) ) ;
}
/* go to last node */
while ( temp -> link != NULL )
temp = temp -> link ;
temp = q ;
/* skip to desired portion */
for ( i = 0 ; i < loc ; i++ )
{
temp = temp -> link ;
return c ;
}
temp = *q ;
/* OUTPUT
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
struct node
{
int data;
struct node * link;
};
void cinsert(struct node **,struct node **,int );
int cdel(struct node **,struct node **);
void cdisplay(struct node *);
void cerase(struct node **,struct node **);
void main()
{
item=(*f)->data;
free(*f);
*f=*r=NULL;
}
else
{
q=*f;
item=q->data;
*f=(*f)->link;
(*r)->link=*f;
free(q);
}
return(item);
}
return NULL;
}
void cdisplay(struct node *f)
{
struct node *q=f,*p=NULL;
while(q!=p)
{
printf("%d\t",q->data);
q=q->link;
p=f;
}
}
void cerase(struct node **f,struct node **r)
{
struct node *temp;
while(*f!=*r)
{
temp=*f;
*f=(*f)->link;
(*r)->link=*f;
free(temp);
}
temp=*f;
free(temp);
*f=*r=NULL;
}
/* OUTPUT
before deletion = 10 20 30 40 50
after deletion = 30 40 50
*/
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
struct tnode
{
struct tnode *lc;
struct tnode*rc;
int data;
};
void insert(struct tnode **,int);
void inorder(struct tnode *);
void preorder(struct tnode *);
void postorder(struct tnode *);
void main()
{
struct tnode *bt;
int req,i=1,num;
bt=NULL;
clrscr();
printf("\n enter no of nodes = ");
scanf("%d",&req);
while(i++<=req)
{
printf("enter data=");
scanf("%d",&num);
insert(& bt,num);
}
printf("\nInorder Traversal =");
inorder(bt);
printf("%d\t",sr->data);
preorder(sr->lc);
preorder(sr->rc);
}
else
return;
}
void postorder(struct tnode *sr)
{
if(sr!=NULL)
{
postorder(sr->lc);
postorder(sr->rc);
printf("%d\t",sr->data);
}
else
return;
}
/* OUTPUT
enter no of nodes = 5
enter data=22
enter data=6
enter data=24
enter data=77
enter data=33
Inorder Traversal =6 22 24 33 77
preorder Traversal =22 6 24 77 33
postorder Traversal =6 33 77 24 22
*/