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

Linked List

The document provides an overview of linked lists in C, detailing dynamic memory allocation using functions like malloc, calloc, and realloc. It explains the structure and operations of linked lists, including traversing, insertion, and deletion, along with examples of C routines for each operation. Additionally, it discusses the implementation of stacks and queues using linked lists and introduces circular linked lists, highlighting their unique characteristics.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Linked List

The document provides an overview of linked lists in C, detailing dynamic memory allocation using functions like malloc, calloc, and realloc. It explains the structure and operations of linked lists, including traversing, insertion, and deletion, along with examples of C routines for each operation. Additionally, it discusses the implementation of stacks and queues using linked lists and introduces circular linked lists, highlighting their unique characteristics.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

LINKED LIST

INTRODUCTION: Dynamic allocation is a pretty unique feature to C (amongst high level languages). It
enables us to create data types and structures of any size and length to suit our programs need within
the program.

Malloc, Sizeof, and Free


The Function malloc is most commonly used to attempt to ``grab'' a continuous portion of memory. It
is defined by:
void *malloc(size_t number_of_bytes)
That is to say it returns a pointer of type void * that is the start in memory of the reserved portion of
size number_of_bytes. If memory cannot be allocated a NULL pointer is returned.
Since a void * is returned the C standard states that this pointer can be converted to any type. The
size_t argument type is defined in stdlib.h and is an unsigned type.
So:

char *cp;
cp = malloc(100);

attempts to get 100 bytes and assigns the start address to cp.

Also it is usual to use the sizeof() function to specify the number of bytes:

int *ip;
ip = (int *) malloc(100*sizeof(int));

Some C compilers may require to cast the type of conversion. The (int *) means coercion to an integer
pointer. Coercion to the correct pointer type is very important to ensure pointer arithmetic is
performed correctly. I personally use it as a means of ensuring that I am totally correct in my coding
and use cast all the time.

Calloc and Realloc


There are two additional memory allocation functions, Calloc() and Realloc(). Their prototypes are
given below:

void *calloc(size_t num_elements, size_t element_size};

void *realloc( void *ptr, size_t new_size);


Malloc does not initialize memory (to zero) in any way. If you wish to initialize memory then use calloc.
Calloc there is slightly more computationally expensive but, occasionally, more convenient than
malloc. Also note the different syntax between calloc and malloc in that calloc takes the number of
desired elements, num_elements, and element_size, element_size, as two individual arguments.
Thus to assign 100 integer elements that are all initially zero you would do:

int *ip;
ip = (int *) calloc(100, sizeof(int));

Realloc is a function which attempts to change the size of a previous allocated block of memory. The
new size can be larger or smaller. If the block is made larger then the old contents remain unchanged
and memory is added to the end of the block. If the size is made smaller then the remaining contents
are unchanged.
If the original block size cannot be resized then realloc will attempt to assign a new block of memory
and will copy the old block contents. Note a new pointer (of different value) will consequently be
returned. You must use this new value. If new memory cannot be reallocated then realloc returns
NULL

Thus to change the size of memory allocated to the *ip pointer above to an array block of 50 integers
instead of 100, simply do:

ip = (int *) calloc( ip, 50);

Releasing memory: When you have finished with a piece of memory allocated in this way you should
be sure to free it up using the free function:

free(p);

where p must be a pointer to some memory allocated by malloc. If you fail to free up memory after
you have finished with it your program will gradually use more and more memory until the computer
runs out. Horrible things happen if you try to use memory after it has been freed.

LINKED LIST:

In a sequential representation, the items of a stack or a queue are implicitly ordered by sequential
order of storage. Suppose that items of stack or queue were explicitly ordered, that is each item
contained within itself the address of the next item.

Such an explicit ordering gives rise to a new data structure called LINKED LIST.

Definition: A LINKED LIST or ONE WAY LIST is a linear collection of data elements called nodes
where the linear order is given by means of pointers. i.e each node is divided into two parts : the first
part contains information of element, and the second part called link field or next pointer field
contains address of next node in list.

Depicted in below figure:

START

Inf’n next pointer NULL


Part field pointer

NULL pointer -> any invalid address

REPRESENTATION:

Let LIST  linked list

To maintain it in memory requires two linear arrays. We call them as INFO and LIST.
INFO[K]  Information Part
LINK[K]  Next pointer field
START  Beginning of the list
NULL  End of list

PRIMITIVE OPERATIONS: The following primitive operations can be performed on linked list.

Traversing
Insertion
Deletion

TRAVERSING A LINKED LIST:

We traverse the LIST in order to process each node exactly once. Our traversing algorithm uses a
pointer variable p which points to node that is currently being processed. Accordingly pnext points
to next node to be processed.

Thus the statement pnext moves the pointer to next node.

C routine for traversing a linked list:

void traverse(NODE *start) {


NODE *p;
p=start;
while(p!=NULL) {
printf("\n %u %d ",p,p->info);
p=p->next;
}
}

INSERTION INTO A LINKED LIST: Insertion operation on linked list basically comes up in various
situations

1. Inserting at the beginning of the list


2. Inserting at the middle of the list
3. Inserting at the end of the list

C routine for inserting a node at the beginning of the list:

void insert_at_begin(int item) {


NODE *p;
p=(NODE *)malloc(sizeof(NODE));
p->info=item;
if(start==NULL)
p->next=NULL;
else
p->next=start;
start=p;
}
Example: Consider the following linked list

START

500

500

10 X

Insert element 20 at the beginning of the list.


After tracing the above routine we get the following data structure.

START

600

600 500

20 10 X

C routine for inserting a node at the end of the list:

void insert_at_end(int item) {


NODE *p,*loc;
p=(NODE *)malloc(sizeof(NODE));
p->info=item;
p->next=NULL;
if(start==NULL)
start=p;
else {
loc=start;
while(loc->next != NULL)
loc=loc->next;
loc->next=p;
}
}
Insert element 30 at the end of the list.
After tracing the above routine we get the following data structure.

START

600

600 500 700

20 10 30 X

C routine for inserting a node at a particular position in the list:

void insert_at_pos(int item, int loc) {


NODE *p,*temp;
int k;
temp=start;
for(k=1;k<loc;k++) {
temp=temp->next;
if(temp==NULL) {
printf("\n nodes not present ");
return;
}
}
p=(NODE *)malloc(sizeof(NODE));
p->info=item;
p->next=temp->next;
temp->next=p;
}

Insert element 40 after element 10 in the list.


After tracing the above routine we get the following data structure.

START

600

600 500 800 700

20 10 40 30 X
DELETION FROM A LINKED LIST: Deletion operation on linked list basically comes up in various
situations:

1. Deletion at the beginning of the list


2. Deletion at the middle of the list
3. Deletion at the end of the list

C routine for deleting a node at the beginning of the list:

void delete_at_begin(void) {
NODE *p;
if(start==NULL)
return;
else {
p=start;
start=start->next;
printf("\n deleted item = %d ",p->info);
free(p);
}
}

Example: Consider the following linked list

START

600

600 500 800 700

20 10 40 30 X

Delete the first element 20.


After tracing the above routine we get the following data structure.

START

600

500 800 700

10 40 30 X
C routine for deleting a node at the end of the list:

void delete_at_end(void) {
NODE *p,*loc;
if(start==NULL)
return;
else if(start->next==NULL) {
p=start;
start=NULL;
printf("\n deleted item = %d ",p->info);
free(p);
}
else {
loc=start;
p=start->next;
while(p->next!=NULL) {
loc=p;
p=p->next;
}
loc->next=NULL;
fflush(stdout);
printf("\n deleted item = %d ",p->info);
free(p);
}
}

Example: Consider the following linked list

START

600

600 500 800 700

20 10 40 30 X

Delete the last 30.


After tracing the above routine we get the following data structure.

START

600

600 500 800

20 10 40 X
C routine for deleting a node at a particular position in the list:

void delete_at_pos() {
NODE *p,*temp;
int i,loc;
printf("\n enter the location (position) at which to delete : ");
scanf("%d",&loc);
if(start==NULL)
printf("\n EMPTY LIST ");
else {
p=start;
for(i=1;i<loc;i++) {
temp=p;
p=p->next;
}
printf("\n deleted item = %d ",p->info);
temp->next=p->next;
free(p);
}
}

Example: Consider the following linked list

START

600

600 500 800 700

20 10 40 30 X

Delete the element 10 (at second position).


After tracing the above routine we get the following data structure.

START

600

600 800 700

20 40 30 X
Linked implementation of a stack:

The operation of adding an element to the front of a linked list is quite similar to that of pushing an
element onto a stack. In both cases, a new item is added as the only immediately accessible item in a
collection. A stack can be accessed only from the pointer to its first element. Similarly, the operation of
removing the first element from a linked list is analogous to popping a stack. In both cases the only
immediately accessible item of a collection is removed from that collection, and the next item becomes
immediately accessible.

Thus we have discovered another way of implementing a stack. A stack may be represented by a linear
linked list. The first node of the list is the top of the stack. If an external pointer s points to such a
linked list, the operation push(s, x) may be implemented by

p=getnode ();
info (p) =x;
next (p) =s;
s=p;

The operation empty (s) is merely a test of whether s equals null. The operation x=pop(s) removes the
first node from a nonempty list and signals underflow if the list is empty;

if (empty(s)) {
printf (‘stack underflow’);
exit (1);
}
else {
p = s;
s = next (p);
x = info (p);
freenode (p);
} /* end if */

Example: Consider the following linked list

STACK

500

500

10 X
Pushing element 20 at the top of the stack

START

600

600 500

20 10 X

The advantage of the implementation of stacks is that all stacks being used by a program can share the
same available list. When any stack needs a node, it can obtain it from the single available list. When
any stack no longer needs a node, it returns the node to that same available list. As long as the total
amount of space needed by all the stacks at any one time is less than the amount of space initially
available to them all, each stack is able to grow and shrink to any size.

Linked implementation of a queue:

Let us now examine how to represent a queue as a linked list. Recall that items are deleted from the
front of a queue and inserted at the rear. Let a pointer to the first element of a list represent the rear of
the queue, as depicted below

Let us consider the following QUEUE

QUEUE

600

600 500

20 10 X

Inserting element 30 at the REAR end of the QUEUE

QUEUE

600

600 500 700

20 10 30 X
Under the list representation, a queue q consists of a list and two pointers, q.front and q. rear. The
operations empty (q) and x = remove (q) are completely analogous to empty(s) and x = pop(s), with
the pointer q.front replacing s. However, special attention is required when the last element is
removed from a queue. In that case, q.rear must be null. The algorithm for x = remove (q) is therefore
as follows:

if (empty (q)){
printf (“queue underflow”);
exit (1);
}

p=q.front;
x=info(p);
q.front = next(p);
if (q.front == null)
q. rear = null;
freenode (p);
return (x);

The operation insert (q, x) is implemented by

p= getnode();
info (p) = x;
next (p) = null;
if (q.rear == null)
q.front = p;
else
next (q. rear) = p;
q. rear =p;

What are the disadvantages of representing a stack or queue by a linked list? Clearly, a node in a linked
list occupies more storage than a corresponding element in an array, since two pieces of information
per element are necessary in a list node (info and next), where as only one piece of information is
needed in the array implementation. However the space used for a list node is usually not twice the
space used by an array element, since the element in such a list usually consists of structures with
many sub fields.

Another disadvantage is the additional time spent in managing the available list. Each addition and
deletion of an element from a stack or a queue involves a corresponding deletion or addition to the
available list.

The advantage of using linked lists is that all the stacks and queues of a program have access to the
same free list of nodes. Nodes not used by one stack may be used by another, as long as the total
number of nodes in use at any one time is not greater than the total number of nodes available.

CIRCULAR LINKED LIST:

Given a pointer p to a node in a linear list, we cannot reach any of the nodes that precede node(p).
Suppose that a small change is made to the structure of the linear list, so that the next field in the last
node contains a pointer back to the first node rather than a NULL pointer. Such a list is called circular
list. Illustrated below:
500 600 700

10 20 30

Notice that the only difference between this list and the basic list is the fact that the last node does not
point to the null address. It now points to the "first" node in the list. Instead of using the null address
to test for the "end" of the list we now need to use the beginning address!

Notice also that the only thing that identifies the "start" of the list is where the entry pointer happens
to be pointing. It would make no difference if we were to shift the entry pointer to any of the nodes in
the list. However we must have at least one entry pointer into the list otherwise we end up with an
isolated list which cannot be accessed at all.

STRUCTURE DECLARATION OF CIRCULAR LIST

#include<stdio.h>
#include<conio.h>
#include<malloc.h>
#include<process.h>

struct node {
int info;
struct node *next;
}*start=NULL,*last=NULL;

typedef struct node NODE;

// routine for insertion @ beginning of circular list

NODE * insert_beg(NODE *start) {


NODE *p;
p=(NODE *)malloc(sizeof(node));
printf("\n enter the number : ");
scanf("%d",&p->info);
if(start==NULL) {
p->next=p;
start=p;
last=p;
}
else {
p->next=start;
start=p;
last->next=p;
}
return start;
}
Example: Consider the following circular linked list

500

10

Insert 20 at the beginning of the list.

After tracing the above routine we get the following data structure.

600 500

20 10

// routine for insertion @ end of circular list

NODE * insert_end(NODE *start) {


NODE *p;
p=(NODE *)malloc(sizeof(node));
printf("\n enter the number : ");
scanf("%d",&p->info);
if(start==NULL) {
p->next=p;
start=last=p;
}
else {
last->next=p;
last=p;
last->next=start;
}

return start;
}

Example: Consider the following circular linked list

600 500

20 10
Insert 30 at the end of the list.

After tracing the above routine we get the following data structure.

600 500 700

20 10 30

// routine for deletion @ beginning of circular list

NODE * delete_beg(NODE *start) {


NODE *p;
p=start;
if(start==NULL)
printf("\n EMPTY LIST " );
else {
p=start;
start=start->next;
printf("\n Element deleted is %d ",p->info);
last->next=start;
}

return start;
}

Example: Consider the following circular linked list

600 500 700

20 10 30

Performing deletion at the beginning (Node 20).

After tracing the above routine we get the following data structure.

500 700

10 30
// routine for deletion @ end of circular list

NODE * delete_end(NODE *start) {


NODE *p,*q;
p=start;
if(start==NULL)
printf("\n EMPTY LIST " );
else {
while(p->next != start ) {
q=p;
p=p->next;
}
printf("\n Element deleted is %d ",p->info);
q->next=p->next;
last=q;
}

return start;
}

Example: Consider the following circular linked list

500 700

10 30

Performing deletion at the end (Node 30)

After tracing the above routine we get the following data structure.

500

10

DOUBLY LINKED LIST:

Thus so far we have been restricted to traversing linear linked list in only one direction. In certain
applications, it is very desirable and sometimes indispensable that a list may be traversed in either the
forward or reverse manner. This property of a linked list gave birth to another variation called as
DOUBLY LINKED LIST or TWO WAY LIST.

Double linked list is a linear collection of elements called as NODES, where each node is divided into
three parts.

1. An information field INFO- which contains data of N.


2. A pointer field FORW- which contains the location of next node in the list.
3. A pointer field BACK- which contains location of preceding node in the list.

FIRST LAST

600 700

600 500 700

X 20 10 30 X

The list also requires two pointer variables:

FIRST  points to first node in list


LAST  points to last node in list

NOTE: The FORW pointer of the last node and BACK pointer of the first node in case of double linked
list is always a NULL POINTER.

// DLL structure declaration

#include<stdio.h>
#include<conio.h>
#include<malloc.h>
#include<process.h>

struct node {
int info;
struct node *next;
struct node *prev;
};

typedef struct node NODE;

// routine for insertion @ beginning of doubly list

void insert_beg(int item) {


NODE *p;
p=(NODE *)malloc(sizeof(node));
p->info=item;
if(head==NULL) {
p->prev=p->next=NULL;
head=tail=p;
}
else {
p->prev=NULL;
p->next=head;
head->prev=p;
head=p;
}
}

// routine for insertion @ end of doubly list

void insert_end(int item) {


NODE *p;
p=(NODE *)malloc(sizeof(node));
p->info=item;
if(tail==NULL) {
p->prev=p->next=NULL;
head=tail=p;
}
else {
p->next=NULL;
p->prev=tail;
tail->next=p;
tail=p;
}
}

// routine for deletion @ beginning of doubly list

void del_beg(int item) {


NODE *p;
if(head==NULL) { }
else if(head->next ==NULL {
p=head;
head=tail=NULL;
}
else {
p=head;
head=head->next;
head->prev=NULL;
}
free(p);
}

// routine for deletion @ end of doubly list

void del_beg(int item) {


NODE *p;
if(tail==NULL) { }
else if(tail->prev ==NULL {
p=tail;
head=tail=NULL;
}
else {
p=tail;
tail=tail->prev;
tail->next=NULL;
}
free(p);
}

You might also like