You are on page 1of 10

Operations on Data Structures:

The basic operations that are performed on data structures are as follows:
Insertion: Insertion means addition of a new data element in a data structure.
Deletion: Deletion means removal of a data element from a data structure if it is found.
Searching: Searching involves searching for the specified data element in a data structure.
Traversal: Traversal of a data structure means processing all the data elements present in it.
Sorting: Arranging data elements of a data structure in a specified order is called sorting.
Merging: Combining elements of two similar data structures to form a new data structure of
the same type, is called merging.

Recursion:

Recursion is the process of repeating items in a self-similar way. In programming languages, if a


program allows you to call a function inside the same function, then it is called a recursive call of
the function.

void recursion() {
recursion(); /* function calls itself */
}

int main() {
recursion();
}

The C programming language supports recursion, i.e., a function to call itself. But while using
recursion, programmers need to be careful to define an exit condition from the function,
otherwise it will go into an infinite loop.

Recursive functions are very useful to solve many mathematical problems, such as calculating
the factorial of a number, generating Fibonacci series, etc.

Pointers

Pointers in C are easy and fun to learn. Some C programming tasks are performed more easily
with pointers, and other tasks, such as dynamic memory allocation, cannot be performed without
using pointers. So it becomes necessary to learn pointers to become a perfect C programmer.
Let's start learning them in simple and easy steps.

As you know, every variable is a memory location and every memory location has its address
defined which can be accessed using ampersand (&) operator, which denotes an address in
memory. Consider the following example, which prints the address of the variables defined

#include <stdio.h>

int main () {

int var1;
1
char var2[10];

printf("Address of var1 variable: %x\n", &var1 );


printf("Address of var2 variable: %x\n", &var2 );

return 0;
}

When the above code is compiled and executed, it produces the following result

Address of var1 variable: bff5a400


Address of var2 variable: bff5a3f6

What are Pointers?

A pointer is a variable whose value is the address of another variable, i.e., direct address of the
memory location. Like any variable or constant, you must declare a pointer before using it to
store any variable address. The general form of a pointer variable declaration is

type *var-name;

Here, type is the pointer's base type; it must be a valid C data type and var-name is the name of
the pointer variable. The asterisk * used to declare a pointer is the same asterisk used for
multiplication. However, in this statement the asterisk is being used to designate a variable as a
pointer. Take a look at some of the valid pointer declarations

int *ip; /* pointer to an integer */


double *dp; /* pointer to a double */
float *fp; /* pointer to a float */
char *ch /* pointer to a character */

The actual data type of the value of all pointers, whether integer, float, character, or otherwise, is
the same, a long hexadecimal number that represents a memory address. The only difference
between pointers of different data types is the data type of the variable or constant that the
pointer points to.

How to Use Pointers?

There are a few important operations, which we will do with the help of pointers very frequently.
(a) We define a pointer variable, (b) assign the address of a variable to a pointer and (c) finally
access the value at the address available in the pointer variable. This is done by using unary
operator * that returns the value of the variable located at the address specified by its operand.
The following example makes use of these operations

#include <stdio.h>

int main () {

int var = 20; /* actual variable declaration */


int *ip; /* pointer variable declaration */

2
ip = &var; /* store address of var in pointer variable*/

printf("Address of var variable: %x\n", &var );

/* address stored in pointer variable */


printf("Address stored in ip variable: %x\n", ip );

/* access the value using the pointer */


printf("Value of *ip variable: %d\n", *ip );

return 0;
}

When the above code is compiled and executed, it produces the following result

Address of var variable: bffd8b3c


Address stored in ip variable: bffd8b3c
Value of *ip variable: 20

NULL Pointers

It is always a good practice to assign a NULL value to a pointer variable in case you do not have
an exact address to be assigned. This is done at the time of variable declaration. A pointer that is
assigned NULL is called a null pointer.

The NULL pointer is a constant with a value of zero defined in several standard libraries.
Consider the following program

#include <stdio.h>

int main () {

int *ptr = NULL;

printf("The value of ptr is : %x\n", ptr );

return 0;
}

When the above code is compiled and executed, it produces the following result

The value of ptr is 0

In most of the operating systems, programs are not permitted to access memory at address 0
because that memory is reserved by the operating system. However, the memory address 0 has
special significance; it signals that the pointer is not intended to point to an accessible memory
location. But by convention, if a pointer contains the null (zero) value, it is assumed to point to
nothing.

To check for a null pointer, you can use an 'if' statement as follows

3
if(ptr) /* succeeds if p is not null */
if(!ptr) /* succeeds if p is null */

Pointers in Detail

Pointers have many but easy concepts and they are very important to C programming. The
following important pointer concepts should be clear to any C programmer

S.N. Concept & Description

Pointer arithmetic
1
There are four arithmetic operators that can be used in pointers: ++, --, +, -
Array of pointers
2
You can define arrays to hold a number of pointers.
Pointer to pointer
3
C allows you to have pointer on a pointer and so on.
Passing pointers to functions in C
4
Passing an argument by reference or by address enable the passed argument to be changed in
the calling function by the called function.
Return pointer from functions in C
5
C allows a function to return a pointer to the local variable, static variable, and dynamically
allocated memory as well.

Dynamic Memory Allocation

C dynamic memory allocation refers to performing manual memory management for dynamic memory
allocation in the C programming language via a group of functions in the C standard library, namely
malloc, realloc, calloc and free.

There are three types of allocation static, automatic, and dynamic.

Static Allocation means, that the memory for your variables is allocated when the program
starts. The size is fixed when the program is created. It applies to global variables, file scope
variables, and variables qualified with static defined inside functions.

Automatic memory allocation occurs for (non-static) variables defined inside functions, and is
usually stored on the stack (though the C standard doesn't mandate that a stack is used). You do
not have to reserve extra memory using them, but on the other hand, have also limited control

4
over the lifetime of this memory. E.g: automatic variables in a function are only there until the
function finishes.

void func() {
int i; /* `i` only exists during `func` */
}

Dynamic memory allocation is a bit different. You now control the exact size and the lifetime
of these memory locations. If you don't free it, you'll run into memory leaks, which may cause
your application to crash, since it, at some point cannot allocation more memory.

int* func() {
int* mem = malloc(1024);
return mem;
}

int* mem = func(); /* still accessible */

In the upper example, the allocated memory is still valid and accessible, even though the
function terminated. When you are done with the memory, you have to free it:

free(mem);

Self Referential Structures:


A self referential data structure is essentially a structure definition which includes at least one member
that is a pointer to the structure of its own kind. A chain of such structures can thus be expressed as
follows.

struct name {
member 1;
member 2;
...
struct name *pointer;
};
The above illustrated structure prototype describes one node that comprises of two logical segments.
One of them stores data/information and the other one is a pointer indicating where the next
component can be found. .Several such inter-connected nodes create a chain of structures.

The following figure depicts the composition of such a node. The figure is a simplified illustration of
nodes that collectively form a chain of structures or linked list.

Such self-referential structures are very useful in applications that involve linked data structures, such as
lists and trees. Unlike a static data structure such as array where the number of elements that can be
inserted in the array is limited by the size of the array, a self-referential structure can dynamically be
expanded or contracted. Operations like insertion or deletion of nodes in a self- referential structure
involve simple and straight forward alteration of pointers.

5
Dynamically Allocated Data Structures

Dynamicly created data structures like trees, linked lists and hash tables (which can be
implemented as arrays of linked lists) are key to the construction of many large software
systems. For example, a compiler for a programming language will maintain symbol tables and
type information which is dynamically constructed by reading the source program. Many modern
compilers also read the source program (e.g., parse it) and translate it into an internal tree form
(commonly called an abstract syntax tree) that is also dynamically created. Graphics programs,
like 3D rendering packages, also make extensive use of dynamic data structures. In fact it is rare
to find any program that is larger than a couple of thousand lines that does not make use of
dynamically allocated data structures.

Problems with Dynamic Allocation

To avoid consuming vast amounts of virtual memory and destroying performance through page
swapping, many programs that use dynamic allocation also dynamically deallocate memory
when the programmer believes it is no longer needed. This leads to a number of problems
including:

Poor performance. The openGL graphics library supports "display lists" which describe
the 3D polygons that make up a shape. A complex scene may have a large number of
display lists. Deallocation of a display list, one element at a time, can be very time
consuming and can have a large impact on program performance. This can be avoided
using a block based memory allocator, where the display list elements are allocated from
large memory blocks. When a display list is no longer needed, a pool of blocks can be
deallocated. While this technique is effective, designing, implementing and debugging a
good block allocator is time consuming.
Memory leaks. A memory leak takes place when memory is allocated but never
deallocated. A major computer manufacturer had a subtle memory leak in one of the
window types for their window system. One group of users would run large test programs
over night and then expect to scroll back through the windows buffer. The buffer was not
infinite and only buffered a certain number of lines. The software displaying this
scrolling buffer had a memory leak. It would recover most, but not all of the memory
allocated as the window scrolled. As the test ran over night, the window software used up
more and more memory, until the entire virtual memory was consumed. The window
would then crash, destroying the test run information the user wanted. Tools like purify
where not available at the time and this bug tool a huge amount of effort to track down
and fix.
Pointers to deallocated memory. When memory is deallocated there may still be pointers
in use to the deallocated memory. The deallocated memory will be recovered by the
memory allocation system and reused. Since it may now have two pointers to it (e.g., the
live pointer to the old deallocated data structure and the pointer to the newly allocated
data structure) the program behavior may be bizzare, running correctly sometimes and
getting the wrong result in others.

(a) Memory Allocation-

6
Whenever a new node is created, memory is allocated by the system. This memory is taken from
list of those memory locations which are free i.e. not allocated. This list is called AVAIL List.
Similarly, whenever a node is deleted, the deleted space becomes reusable and is added to the list
of unused space i.e. to AVAIL List. This unused space can be used in future for memory
allocation.

Memory allocation is of two types-

1. Static Memory Allocation


2. Dynamic Memory Allocation

1. Static Memory Allocation:

When memory is allocated during compilation time, it is called Static Memory Allocation. This
memory is fixed and cannot be increased or decreased after allocation. If more memory is
allocated than requirement, then memory is wasted. If less memory is allocated than requirement,
then program will not run successfully. So exact memory requirements must be known in
advance.

2. Dynamic Memory Allocation:

When memory is allocated during run/execution time, it is called Dynamic Memory Allocation.
This memory is not fixed and is allocated according to our requirements. Thus in it there is no
wastage of memory. So there is no need to know exact memory requirements in advance.

(b) Garbage Collection-

Whenever a node is deleted, some memory space becomes reusable. This memory space should
be available for future use. One way to do this is to immediately insert the free space into
availability list. But this method may be time consuming for the operating system. So another
method is used which is called Garbage Collection. This method is described below: In this
method the OS collects the deleted space time to time onto the availability list. This process
happens in two steps. In first step, the OS goes through all the lists and tags all those cells which
are currently being used. In the second step, the OS goes through all the lists again and collects
untagged space and adds this collected space to availability list. The garbage collection may
occur when small amount of free space is left in the system or no free space is left in the system
or when CPU is idle and has time to do the garbage collection.

(c) Overflow & Underflow-

Overflow happens at the time of insertion. If we have to insert new space into the data structure,
but there is no free space i.e. availability list is empty, then this situation is called Overflow.
The programmer can handle this situation by printing the message of OVERFLOW.

Underflow happens at the time of deletion. If we have to delete data from the data structure, but
there is no data in the data structure i.e. data structure is empty, then this situation is called

7
Underflow. The programmer can handle this situation by printing the message of
UNDERFLOW.

Explain different types of linked lists.


1. Linear Linked List or One Way List or Singly Linked List:-

It is linear collection of data elements which are called Nodes. The elements may or may not be
stored in consecutive memory locations. So pointers are used maintain linear order. Each node is
divided into two parts. The first part contains the information of the element and is called INFO
Field. The second part contains the address of the next node and is called LINK Field or
NEXT Pointer Field. The START contains the starting address of the linked list i.e. it contains
the address of the first node of the linked list. The LINK Field of last node contains NULL Value
which indicates that it is the end of linked list. It is shown below:

2. Doubly Linked List or Two-Way Linked List or Two-Way Chain:-

In it each node is divided into three parts:

1. The first part is PREV part. It is previous pointer field. It contains the address of the node
which is before the current node.

2. The second part is the INFO part. It contains the information of the element.

3. The third part is NEXT part. It is next pointer field. It contains the address of the node which
is after the current node.

There are two pointers FIRST and LAST. FIRST points to the first node in the list. LAST points
to the last node in the list. The PREV field of first node and the NEXT field of last node contain
NULL value. This shows the end of list on both sides. This list can be traversed in both
directions that is forward and backward.

8
It is shown below:

3. Circular Linked List:-

In it the last node does not contain NULL pointer. Instead the last node contains a pointer that
has the address of first node and thus points back to the first node.

It is shown below:

Binary Search Tree (Search and Insertion)

The following is definition of Binary Search Tree(BST)

Binary Search Tree, is a node-based binary tree data structure which has the following
properties:

The left subtree of a node contains only nodes with keys less than the nodes key.
The right subtree of a node contains only nodes with keys greater than the nodes key.
The left and right subtree each must also be a binary search tree.
There must be no duplicate nodes.

9
The above properties of Binary Search Tree provide an ordering among keys so that the
operations like search, minimum and maximum can be done fast. If there is no ordering, then we
may have to compare every key to search a given key.

Searching a key

To search a given key in Bianry Search Tree, we first compare it with root, if the key is present
at root, we return root. If key is greater than roots key, we recur for right subtree of root node.
Otherwise we recur for left subtree.

Insertion of a key

A new key is always inserted at leaf. We start searching a key from root till we hit a leaf node.
Once a leaf node is found, the new node is added as a child of the leaf node.

100 100
/ \ Insert 40 / \
20 500 ---------> 20 500
/ \ / \
10 30 10 30
\
40

10