Data - Structure - Using ' C'
Data - Structure - Using ' C'
In computer terms, a data structure is a Specific way to store and organize data in a
computer's memory so that these data can be used efficiently later. Data may be arranged
in many different ways such as the logical or mathematical model for a particular
organization of data is termed as a data structure. The variety of a particular data model
depends on the two factors -
Firstly, it must be loaded enough in structure to reflect the actual relationships of the data
with the real world object.
Secondly, the formation should be simple enough so that anyone can efficiently process
the data each time it is necessary.
Categories of Data Structure:
The data structure can be sub divided into major types:
Linear Data Structure
Non-linear Data Structure
Linear Data Structure:
A data structure is said to be linear if its elements combine to form any specific order.
There are basically two techniques of representing such linear structure within memory.
First way is to provide the linear relationships among all the elements represented
by means of linear memory location. These linear structures are termed as arrays.
The second technique is to provide the linear relationship among all the elements
represented by using the concept of pointers or links. These linear structures are
termed as linked lists.
The common examples of linear data structure are:
Arrays
Queues
Stacks
Linked lists
Nonlinear Data Structure:
This structure is mostly used for representing data that contains a hierarchical
relationship among various elements.
Tree: In this case, data often contain a hierarchical relationship among various elements.
The data structure that reflects this relationship is termed as rooted tree graph or a
tree.
Graph: In this case, data sometimes hold a relationship between the pairs of elements
which is not necessarily following the hierarchical structure. Such data structure is termed
as a Graph.
Array is a container which can hold a fix number of items and these items should be of the
same type. Most of the data structures make use of arrays to implement their algorithms.
Following are the important terms to understand the concept of Array.
Element − Each item stored in an array is called an element.
Index − Each location of an element in an array has a numerical index, which is
used to identify the element.
Array Representation: (Storage structure)
Arrays can be declared in various ways in different languages. For illustration, let's take C
array declaration.
Arrays can be declared in various ways in different languages. For illustration, let's take C
array declaration.
#include <stdio.h>
main() {
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5; int i = 0, j = n;
printf("The original array elements are :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
n = n + 1; while( j >= k) {
LA[j+1] = LA[j];
j = j - 1;
}
LA[k] = item;
printf("The array elements after insertion :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
}
When we compile and execute the above program, it produces the following result −
Output
Deletion Operation
Deletion refers to removing an existing element from the array and re-organizing all
elements of an array.
Algorithm
Consider LA is a linear array with N elements and K is a positive integer such that
K<=N. Following is the algorithm to delete an element available at the Kth position of LA.
1 . Start
2 . Set J = K
3 . Repeat steps 4 and 5 while J < N
4 . Set LA[J] = LA[J + 1]
5 . Set J = J+1
6 . Set N = N-1
7 . Stop
Example
#include <stdio.h>
void main() {
int LA[] = {1,3,5,7,8};
int k = 3, n = 5; int i, j;
printf("The original array elements are :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
j = k;
while( j < n) { LA[j-1] = LA[j];
j = j + 1;
}
n = n -1;
printf("The array elements after deletion :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]); }}
When we compile and execute the above program, it produces the following result −
Output
The original array elements are : LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
The array elements after deletion : LA[0] = 1
LA[1] = 3
LA[2] = 7
LA[3] = 8
Search Operation
You can perform a search for an array element based on its value or its index.
1 . Start
2 . Set J = 0
3 . Repeat steps 4 and 5 while J < N
4 . IF LA[J] is equal ITEM THEN GOTO STEP 6
5 . Set J = J +1
6 . PRINT J, ITEM
7 . Stop
Example
Following is the implementation of the above algorithm −
#include <stdio.h>
void main() {
int LA[] = {1,3,5,7,8};
int item = 5, n = 5; int i = 0, j = 0;
printf("The original array elements are :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
while( j < n){
if( LA[j] == item ) { break;
}
j = j + 1;
}
printf("Found element %d at position %d\n", item, j+1);
}
1 . Start
2 . Set LA[K-1] = ITEM
3 . Stop
Example
Following is the implementation of the above algorithm −
#include <stdio.h>
void main() {
int LA[] = {1,3,5,7,8};
int k = 3, n = 5, item = 10; int i, j;
printf("The original array elements are :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
LA[k-1] = item;
printf("The array elements after updation :\n"); for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
}
Unit - II
Bubble Sort
Data Structure Using ‘C’ - 22317 Page 10
We take an unsorted array for our example. Bubble sort takes Ο(n2) time so we're
keeping it short and precise.
Bubble sort starts with very first two elements, comparing them to check which one is
greater.
In this case, value 33 is greater than 14, so it is already in sorted locations. Next, we
compare 33 with 27.
We find that 27 is smaller than 33 and these two values must be swapped.
Next we compare 33 and 35. We find that both are in already sorted positions.
We know then that 10 is smaller 35. Hence they are not sorted.
To be precise, we are now showing how an array should look like after each iteration.
After the second iteration, it should look like this −
Notice that after each iteration, at least one value moves at the end.
And when there's no swap required, bubble sorts learns that an array is completely
sorted.
begin BubbleSort(list)
for all elements of list if list[i] > list[i+1]
swap(list[i], list[i+1]) end if
end for return list
end BubbleSort
It finds that both 14 and 33 are already in ascending order. For now, 14 is in sorted sub-list.
It swaps 33 with 27. It also checks with all the elements of sorted sub-list. Here we see that
the sorted sub-list has only one element 14, and 27 is greater than 14. Hence, the sorted
sub-list remains sorted after swapping.
By now we have 14 and 27 in the sorted sub-list. Next, it compares 33 with 10.
We swap them again. By the end of third iteration, we have a sorted sub-list of 4 items.
This process goes on until all the unsorted values are covered in a sorted sub-list. Now we
shall see some programming aspects of insertion sort.
Algorithm
Now we have a bigger picture of how this sorting technique works, so we can derive
simple steps by which we can achieve insertion sort.
Selection Sort
Consider the following depicted array as an example.
For the first position in the sorted list, the whole list is scanned sequentially. The first
position where 14 is stored presently, we search the whole list and find that 10 is the
lowest value.
So we replace 14 with 10. After one iteration 10, which happens to be the minimum value
in the list, appears in the first position of the sorted list.
For the second position, where 33 is residing, we start scanning the rest of the list in a
linear manner.
After two iterations, two least values are positioned at the beginning in a sorted manner.
The same process is applied to the rest of the items in the array. Following is a pictorial
depiction of the entire sorting process –
Pseudocode
We know that merge sort first divides the whole array iteratively into equal halves unless
the atomic values are achieved. We see here that an array of 8 items is divided into two
arrays of size 4.
This does not change the sequence of appearance of items in the original. Now we divide
these two arrays into halves.
We further divide these arrays and we achieve atomic value which can no more be divided.
Now, we combine them in exactly the same manner as they were broken down. Please note
the color codes given to these lists.
We first compare the element for each list and then combine them into another list in a
sorted manner. We see that 14 and 33 are in sorted positions. We compare 27 and 10 and in
the target list of 2 values we put 10 first, followed by 27. We change the order of 19 and 35
whereas 42 and 44 are placed sequentially.
In the next iteration of the combining phase, we compare lists of two data values, and
merge them into a list of found data values placing all in a sorted order.
The pivot value divides the list into two parts. And recursively, we find
the pivot for each sub-lists until all lists contains only one element.
Quick Sort Pivot Algorithm
Based on our understanding of partitioning in quick sort, we will now try to write an
algorithm for it, which is as follows.
Since a Binary Heap is a Complete Binary Tree, it can be easily represented as array and
array based representation is space efficient. If the parent node is stored at index I, the left
child can be calculated by 2 * I + 1 and right child by 2 * I + 2 (assuming the indexing
starts at 0).
Heap Sort Algorithm for sorting in increasing order:
1. Build a max heap from the input data.
2. At this point, the largest item is stored at the root of the heap. Replace it with the
last item of the heap followed by reducing the size of heap by 1. Finally, heapify
the root of tree.
3. Repeat above steps while size of heap is greater than 1.
How to build the heap?
Heapify procedure can be applied to a node only if its children nodes are heapified. So the
heapification must be performed in the bottom up order.
Lets understand with the help of an example:
Applying heapify
procedure to
index 1: 4(0)
/ \
10(1) 3(2)
/\
5(3) 1(4)
Applying heapify
procedure to
index 0: 10(0)
/\
5(1) 3(2)
/\
4(3) 1(4)
The heapify procedure calls itself recursively to build heap in top down
manner.
Do following for each digit i where i varies from least significant digit to the most
significant digit.
………….a) Sort input array using counting sort (or any stable sort) according to the i’th
digit.
Example:
Original, unsorted list:
170, 45, 75, 90, 802, 24, 2, 66
Sorting by least significant digit (1s place) gives: [*Notice that we keep 802 before 2,
because 802 occurred before 2 in the original list, and similarly for pairs 170 & 90 and 45
& 75.]
170, 90, 802, 2, 24, 45, 75, 66
Sorting by next digit (10s place) gives: [*Notice that 802 again comes before 2 as 802
comes before 2 in the previous list.]
802, 2, 24, 45, 66, 170, 75, 90
Sorting by most significant digit (100s place) gives: 2, 24, 45, 66, 75, 90, 170, 802
What is the running time of Radix Sort?
Let there be d digits in input integers. Radix Sort takes O(d*(n+b)) time where b is the
base for representing numbers, for example, for decimal system, b is 10. What is the value
of d? If k is the maximum possible value, then d would be O(log b(k)). So overall time
complexity is O((n+b) * logb(k)). Which looks more than the time complexity of
comparison based sorting algorithms for a large k. Let us first limit k. Let k <= nc where
c is a constant. In that case, the complexity becomes O(nLog b(n)). But it still doesn’t beat
comparison based sorting algorithms.
Linear search is to check each element one by one in sequence. The following method
linearSearch() searches a target in an array and returns the index of the target; if not found,
it returns -1, which indicates an invalid index.
1 int linearSearch(int arr[], int target)
2 {
3 for (int i = 0; i < arr.length; i++)
4 {
5 if (arr[i] == target) return i;
6 }
7 return -1;
8 }
9
Linear search loops through each element in the array; each loop body takes constant
time. Therefore, it runs in linear time O(n).
Binary Search
For sorted arrays, binary search is more efficient than linear search. The process starts
from the middle of the input array:
If the target equals the element in the middle, return its index.
If the target is larger than the element in the middle, search the right half.
If the target is smaller, search the left half.
In the following binarySearch() method, the two index variables first and last indicates
the searching boundary at each round.
int binarySearch(int arr[], int target)
{
int first = 0, last = arr.length - 1;
while (first <= last)
{
int mid = (first + last) / 2; if (target == arr[mid])
target: 10
first: 0, last: 6, mid: 3, arr[mid]: 27 -- go left
first: 0, last: 2, mid: 1, arr[mid]: 9 -- go right
first: 2, last: 2, mid: 2, arr[mid]: 10 -- found
target: 40
first: 0, last: 6, mid: 3, arr[mid]: 27 -- go right
first: 4, last: 6, mid: 5, arr[mid]: 43 -- go left
first: 4, last: 4, mid: 4, arr[mid]: 38 -- go right first: 5,
last: 4 -- not found
A real-world stack allows operations at one end only. For example, we can place or
remove a card or plate from the top of the stack only. Likewise, Stack ADT allows all data
operations at one end only. At any given time, we can only access the top element of a
stack.
This feature makes it LIFO data structure. LIFO stands for Last-in-first-out. Here, the
element which is placed (inserted or added) last is accessed first. In stack terminology,
insertion operation is called PUSH operation and removal operation is called POP
operation.
Stack Representation
The following diagram depicts a stack and its operations −
A stack can be implemented by means of Array, Structure, Pointer, and Linked List. Stack
can either be a fixed size one or it may have a sense of dynamic resizing. Here, we are
isempty()
Algorithm of isempty() function −
begin procedure isempty
if top less than 1 return true
else
return false endif
end procedure
If the linked list is used to implement the stack, then in step 3, we need to allocate
space dynamically.
Algorithm for PUSH Operation
A simple algorithm for Push operation can be derived as follows –
begin procedure push: stack, data
if stack is full
return null endif
top ← top + 1 stack[top] ← data
end procedure
Pop Operation
Accessing the content while removing it from the stack, is known as a Pop Operation. In
an array implementation of pop() operation, the data element is not actually
removed, instead top is decremented to a lower position in the stack to point to the next
value. But in linked-list implementation, pop() actually removes data element and
deallocates memory space. A Pop operation may involve the following steps −
Step 1 − Checks if the stack is empty.
Step 2 − If the stack is empty, produces an error and exit.
Step 3 − If the stack is not empty, accesses the data element at which top is
pointing.
Step 4 − Decreases the value of top by 1.
Step 5 − Returns success.
A real-world example of queue can be a single-lane one-way road, where the vehicle
enters first, exits first. More real-world examples can be seen as queues at the ticket
windows and bus- stops.
Queue Representation
As we now understand that in queue, we access both ends for different reasons. The
following diagram given below tries to explain queue representation as data structure −
As in stacks, a queue can also be implemented using Arrays, Linked-lists, Pointers and
Structures. For the sake of simplicity, we shall implement queues using one-dimensional
array.
Basic Operations
Queue operations may involve initializing or defining the queue, utilizing it, and then
completely erasing it from the memory. Here we shall try to understand the basic
operations associated with queues −
enqueue() − add (store) an item to the queue.
dequeue() − remove (access) an item from the queue.
isfull()
As we are using single dimension array to implement queue, we just check for the rear
pointer to reach at MAXSIZE to determine that the queue is full. In case we maintain the
queue in a circular linked-list, the algorithm will differ. Algorithm of isfull() function −
Algorithm
begin procedure isfull
if rear equals to MAXSIZE return true
else
return false endif
end procedure
isempty()
Algorithm of isempty() function −
Algorithm
begin procedure isempty
if front is less than MIN OR front is greater than rear return true
else
return false endif
end procedure
If the value of front is less than MIN or 0, it tells that the queue is not yet
initialized, hence empty.
Here's the C programming code −
Example
bool isempty() {
if(front < 0 || front > rear) return true;
else
return false;
}
Enqueue Operation
Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks.
The following steps should be taken to enqueue (insert) data into a queue −
Step 1 − Check if the queue is full.
Step 2 − If the queue is full, produce overflow error and exit.
Step 3 − If the queue is not full, increment rear pointer to point the next empty
space.
Step 4 − Add data element to the queue location, where the rear is pointing.
Step 5 − return success.
Dequeue Operation
Accessing data from the queue is a process of two tasks − access the data where front is
pointing and remove the data after access. The following steps are taken to
perform dequeue operation −
Step 1 − Check if the queue is empty.
Step 2 − If the queue is empty, produce underflow error and exit.
Step 3 − If the queue is not empty, access the data where front is pointing.
Step 4 − Increment front pointer to point to the next available data element.
Step 5 − Return success.
procedure dequeue
if queue is empty return underflow
end if
data = queue[front] front ← front + 1 return true
end procedure
LINKED LIST
A linked list is a sequence of data structures, which are connected together via links.
Linked List is a sequence of links which contains items. Each link contains a connection to
another link. Linked list is the second most-used data structure after array. Following are
the important terms to understand the concept of Linked List.
Link − Each link of a linked list can store a data called an element.
Next − Each link of a linked list contains a link to the next link called Next.
LinkedList − A Linked List contains the connection link to the
first link called First.
Linked List Representation
Linked list can be visualized as a chain of nodes, where every node points to the next
node.
As per the above illustration, following are the important points to be considered.
Linked List contains a link element called first.
Each link carries a data field(s) and a link field called next.
Each link is linked with its next link using its next link.
Last link carries a link as null to mark the end of the list.
Types of Linked List
Following are the various types of linked list.
Simple Linked List − Item navigation is forward only.
Doubly Linked List − Items can be navigated forward and backward.
Circular Linked List − Last item contains link of the first element as
next and the first element has a link to the last element as previous.
Basic Operations
Following are the basic operations supported by a list.
Insertion − Adds an element at the beginning of the list.
Deletion − Deletes an element at the beginning of the list.
Display − Displays the complete list.
Search − Searches an element using the given key.
Delete − Deletes an element using the given key.
Insertion Operation
Now, the next node at the left should point to the new node.
LeftNode.next −> NewNode;
Similar steps should be taken if the node is being inserted at the beginning of the list.
While inserting it at the end, the second last node of the list should point to the new
node and the new node will point to NULL.
Deletion Operation
Deletion is also a more than one step process. We shall learn with pictorial representation.
First, locate the target node to be removed, by using searching algorithms.
The left (previous) node of the target node now should point to the next node of the target
node −
This will remove the link that was pointing to the target node. Now, using the following
code, we will remove what the target node is pointing at.
Reverse Operation
This operation is a thorough one. We need to make the last node to be pointed by the
head node and reverse the whole linked list.
First, we traverse to the end of the list. It should be pointing to NULL. Now, we shall
make it point to its previous node −
We have to make sure that the last node is not the lost node. So we'll have some temp
node, which looks like the head node pointing to the last node. Now, we shall make all left
side nodes point to their previous nodes one by one.
We'll make the head node point to the new first node by using the temp node.
Initially when the list is empty, last pointer will be NULL. After inserting a node T,
After insertion, T is the last node so pointer last points to node T. And Node T is first and
last node, so T is pointing to itself.
Function to insert node in an empty List,
struct Node *addToEmpty(struct Node *last, int data)
{
// This function is
only for empty list if
(last != NULL)
return last;
// Creating a
node
dynamically.
struct Node
*last =
(struct Node*)malloc(sizeof(struct Node));
//
Assig
ning
the
data.
last -
>
data
=
data;
// Note : list was empty. We link single node
// to itself.
last -> next = last;
return last;
}
Run on IDE
OUTPUT:
A binary tree consists of a finite set of nodes that is either empty, or consists of one
specially designated node called the root of the binary tree, and the elements of two
disjoint binary trees called the left subtree and right subtree of the root.
Note that the definition above is recursive: we have defined a binary tree in terms of binary
trees. This is appropriate since recursion is an innate characteristic of tree structures.
Diagram 1: A binary tree
Tree terminology is generally derived from the terminology of family trees (specifically,
the type of family tree called a lineal chart).
Each root is said to be the parent of the roots of its subtrees.
Two nodes with the same parent are said to be siblings; they are the children of
their parent.
The root node has no parent.
A great deal of tree processing takes advantage of the relationship between a parent
and its children, and we commonly say a directed edge (or simply an edge)
extends from a parent to its children. Thus edges connect a root with the roots of
each subtree. An undirected edge extends in both directions between a parent and a
child.
An almost complete strictly binary tree with N leaves has 2N – 1 nodes (as does any other
strictly binary tree). An almost complete binary tree with N leaves that is not strictly
binary has 2N nodes. There are two distinct almost complete binary trees with N
leaves, one of which is strictly binary and one of which is not.
There is only a single almost complete binary tree with N nodes. This tree is strictly binary
if and only if N is odd.
Representing Binary Trees in Memory
Array Representation
For a complete or almost complete binary tree, storing the binary tree as an array may be a
good choice.One way to do this is to store the root of the tree in the first element of the
array. Then, for each node in the tree that is stored at subscript k, the node's left child can
be stored at subscript 2k+1 and the right child can be stored at subscript 2k+2. For
example, the almost complete binary tree shown in Diagram 2 can be stored in an array
like so:
However, if this scheme is used to store a binary tree that is not complete or almost
complete, we can end up with a great deal of wasted space in the array.
Linked Representation
If a binary tree is not complete or almost complete, a better choice for storing it is to use a
linked representation similar to the linked list structures covered earlier in the semester:
Pre-order Traversal
In this traversal method, the root node is visited first, then the left subtree and finally the
right subtree.
We start from A, and following pre-order traversal, we first visit A itself and then move
to its left subtree B. B is also traversed pre-order. The process goes on until all the nodes
are visited. The output of pre-order traversal of this tree will be −
A→B→D→E→C→F→G
Post-order Traversal
In this traversal method, the root node is visited last, hence the name. First we traverse the
left subtree, then the right subtree and finally the root node.
We start from A, and following Post-order traversal, we first visit the left subtree B. B is
also traversed post-order. The process goes on until all the nodes are visited. The output of
post-order traversal of this tree will be −
D→E→B→F→G→C→A
Algorithm
Representation
BST is a collection of nodes arranged in a way where they maintain BST properties. Each
node has a key and an associated value. While searching, the desired key is compared to
the keys in BST and if found, the associated value is retrieved.
Following is a pictorial representation of BST −
We observe that the root node key (27) has all less-valued keys on the left sub-tree
and the higher valued keys on the right sub-tree.
Basic Operations
Search Operation
Whenever an element is to be searched, start searching from the root node. Then if the data
is less than the key value, search for the element in the left subtree. Otherwise, search for
the element in the right subtree. Follow the same algorithm for each node.
Algorithm
if
(
c
u
r
r
4:1010
Adjacency list
The other way to represent a graph is by using an adjacency list. An adjacency list is an
array A of separate lists. Each element of the array Ai is a list, which contains all the
vertices that are adjacent to vertex i.
For a weighted graph, the weight or cost of the edge is stored along with the vertex in the
list using pairs. In an undirected graph, if vertex j is in list Ai then vertex i will be in list
Aj.
The space complexity of adjacency list is O(V + E) because in an adjacency list
information is stored only for those edges that actually exist in the graph. In a lot of cases,
where a matrix is sparse using an adjacency matrix may not be very useful. This is because
using an adjacency matrix will take up a lot of space where most of the elements will be 0,
anyway. In such cases, using an adjacency list is better.
Note: A sparse matrix is a matrix in which most of the elements are zero, whereas a dense
matrix is a matrix in which most of the elements are non-zero.
A4 → 1 → 3
Consider the same directed graph from an adjacency matrix. The adjacency list
of the graph is as follows:
A1 → 2
A2 → 4
A3 → 1 → 4
A4 → 2