Está en la página 1de 254

DATA

STRUCTURES
AND
ALGORITHMS
1

Day One Objectives


Introduction to Data Structures
Introduction to Abstract Data Types

Need for Data Structures, Data Types, Data Abstraction


Array and Structure Overview

Linear Data Structures

Stacks
Queues
Linked lists

Non Linear Data Structure

Trees
Graphs

Sorting and Searching

Introduction to Data
Structures
Data is a set of elementary items.
The data structures deal with the study of how the

data is organized in the memory, how efficiently it


can be retrieved and manipulated and the possible
ways in which different data items are logically
related.
They can be classified in to

Primitive data structures

Non primitive data structure.

Introduction to Data
Structures

Primitive data structure:

These are data structures that can be manipulated directly by


machine instructions.
In C language, the different primitive data structures are int,
float, char, double.

Non primitive data structures:

These are data structures that can not be manipulated directly


by machine instructions. Arrays, linked lists, files etc., are some
of non-primitive data structures and are classified into linear
data structures and non-linear data structures.

Introduction to Abstract Data


Types
An abstract data type is a data structure and a collection of

functions or procedures which operate on the data structure


An Example: Collections

Programs often deal with collections of items. These collections may be


organized in many ways and use many different program structures to
represent them, yet, from an abstract point of view, there will be a few
common operations on any collection. These might include:
create

Create a new collection

add

Add an item to a collection

delete

Delete an item from a collection

find

Find an item matching some criterion in the


collection

destroy Destroy the collection

Lifetime Of A Variable
is a run-time concept
period of time during which a variable has memory space

associated with it

begins when space is allocated


ends when space is de-allocated

three categories of "lifetime"

static - start to end of program execution


automatic (stack) - start to end of declaring function's execution
heap (variable declared dynamic at runtime, and also deallocated dynamically at runtime.

Data Memory Model


static data

space for global variables

automatic data
run-time stack - activation records
added and removed as program runs
(expands and shrinks in an orderly
LIFO manner)

heap data

space for variables allocated at run-time


(allocation and de-allocation requests
occur in unpredictable order)
7

Heap Variables
Space for heap variables is allocated from an area of

runtime memory known as the heap or free store.

Heap variables do not have an explicit name, and are

accessed indirectly via a pointer.

Memory space for heap variables is explicitly allocated at

runtime using malloc( ).

Space occupied by heap variables must be explicitly

returned back to the heap to avoid memory leaks. This is


done using the free( ) function.
8

Array Usage A Perspective


Consider the following example:
#include<stdio.h>
main( )
{
int num_array[50],i;
for( i=0; i < 50; i++ )
{
num_array[i]=0;
scanf( "%d",&num_array[i] );
fflush(stdin);
}
}

Array Usage A Perspective


When this program is compiled, the compiler estimates the

amount of memory required for the variables, and also the


instructions defined by you as part of the program.

The compiler writes this information into the header of the

executable file that it creates. When the executable is loaded


into memory at runtime, the specified amount of memory is
set aside.

A part of the memory allocated to the program is in an area

of memory called a runtime stack or the call stack. Once


the size of the stack is fixed, it cannot be changed
dynamically.

10

Array Usage A Perspective


Therefore, arrays present the classic problem of the

programmer having allocated too few elements in the


array at the time of writing the program, and then finding
at run time that more values are required to be stored in
the array than what had originally been defined.
The other extreme is of the programmer allocating too

many elements in the array and then finding at run time


that not many values need to be stored in the array
thereby resulting in wastage of precious memory.

11

Array Usage A Perspective


Moreover, array manipulation (in terms of insertion and

deletion of elements from the array) is more complex


and tedious, and a better alternative to all this is to go for
dynamic data structures.

Before venturing into dynamic variables, or dynamic data

structures, it would be prudent at this juncture to


differentiate between stack and heap variables, and their
characteristics.

Let us begin by understanding the concept of lifetime of

variables.

12

Dynamic Data Structures


A structure in C is a collection of items, each of which has its

own identifier.
Each item is called a member of the structure.
To access the members of a structure, we need to create

objects or instances of the structure.


A structure is declared as:
struct <structure name>
ex: struct employee {
{
char name[50];
members;
char designation[25];
}object;
float salary;
} emp;

13

Dynamic Data Structures


Rather than pre-define array on the stack at compile

time, the alternative should be to define a structure type,


and dynamically declare at runtime as many instances of
the structure variables as needed by the application.
When the code containing the structure type is compiled,

what the compiler sees is only a structure type


declaration. It therefore, does not allocate memory for
the structure type since no variable has been defined
based on the structure type.

14

Dynamic Data Structures


When the program begins execution, it will need to

create variables of the structure type. Therefore, the


language must support runtime declaration of variables.

The problem with these variables is that they cannot be

accommodated into the stack, as they were not declared


at the time of compilation, and the stack would have
been sized based on the stack variables already
declared at compile-time.

The C language provides the malloc() function which a

program can use to declare variables dynamically.

15

The malloc( ) Function


The parameter to malloc( ) is an unsigned integer which

represents the number of bytes that the programmer has


requested malloc( ) to allocate on the heap.

A more effective way of passing the number of bytes to

malloc( ) would be to pass the structure type along with


the sizeof( ) operator to malloc( ).

The sizeof( ) operator can be used to determine the size

of any data type in C, instead of manually determining the


size and using that value. Therefore, the benefit of using
sizeof( ) operator in any program makes it portable.
16

The malloc( ) Function


Consider the following example:
#include<stdio.h>
main()
{
struct marks_data
{
char name[11];
int marks;
};
struct marks_data *ptr;
/* declaration of a stack variable */
ptr = (struct marks_data *)malloc(sizeof(struct marks_data));
/* declaration of a block of memory on the heap and the block in turn
being referenced by ptr */
}

17

The malloc( ) Function

Stack
ptr

Heap
100

100
Name

Marks

18

The malloc( ) Function


The malloc( ) function also returns the starting

address to the marks_data structure variable on the


heap.

However, malloc( ) returns this not as a pointer to a

structure variable of type marks_data , but as a void


pointer.

Therefore, the cast operator was used on the return

value of malloc( ) to cast it as a pointer to a structure


of type marks_data before being assigned to ptr,
which has accordingly been defined to be a pointer to
a structure of type marks_data.
19

The free() Function


The free() function is used to deallocate memory

allocated to a variable by the malloc() function.

If the call to the function free( ) is not issued before

returning from the function, the pointer pointing to


the heap variable will be destroyed when returning
from the function, and therefore, the address to the
heap variable will be lost. There will then be no way
to de-allocate this block of memory on the heap, and
return it back to the heap. This amount of memory
will not be available for future use by other
applications. This is what is referred to as a memory
leak.
20

Self-Referential Structures
Suppose, you have been given a task to store a list of

marks. The size of the list is not known.


If it were known, then it would have facilitated the

creation of an array of the said number of elements and


have the marks entered into it.
Elements of an array are contiguously located, and

therefore, array manipulation is easy using an integer


variable as a subscript, or using pointer arithmetic.

21

Self-Referential Structures
However, when runtime variables of a particular type

are declared on the heap, let's say a structure type in


which we are going to store the marks, each variable of
the structure type marks will be located at a different
memory location on the heap, and not contiguously
located.
Therefore, these variables cannot be processed the

way arrays are processed, i.e., using a subscript, or


using pointer arithmetic.
An answer to this is a self-referential structure.
22

Self-Referential Structures
A self-referential structure is so defined that one of the

elements of the structure variable is able to reference


another subsequent structure variable of the same type,
wherever it may be located on the heap.
In other words, each variable maintains a link to another

variable of the same type, thus forming a noncontiguous, loosely linked data structure.
This self-referential data structure is also called a linked

list.
23

Linear and non- linear data


structures
The data structures that shows the relationship of

logical adjacency between the elements are called


linear data structures.

Otherwise, they are called non-linear data structures.


Different linear data structures are stacks, queues,

linear linked lists such as singly linked list, doubly


linked linear lists etc.

Trees, graphs and files are non-linear data structures.

24

What is the need for ADT,


Data types and Data
Structures?

25

LINEAR DATA
STRUCTURES

26

STACKS
27

Objectives
Define a stack
Describe the operations on a stack
Implement a stack as a special case of a linked list
Describe applications of stacks

28

What is a Stack?
A stack is a data structure in which insertions and

deletions can only be done at the top.


A common example of a stack, which permits the

selection of only its end elements, is a stack of books.


A person desiring a book can pick up only the book at

the top, and if one wants to place a plate on the pile of


plates, one has to place it on the top.

29

What is a Stack?
You would have noticed that whenever a book is placed

on top of the stack, the top of the stack moves upward to


correspond to the new element (the stack grows).
And, whenever a book is picked, or removed from the

top of the stack, the top of the stack moves downward to


correspond to the new highest element (the stack
shrinks).

30

What is a Stack?
top
top

10

12

top

14

10

12
10
8
6
4
2

(a)

(b)
(c)

31

Characteristics of a Stack
When you add an element to the stack, you say that you

push it on the stack (it is always done at the top), and if


you delete an element from a stack, you say that you
pop it from the stack( again from the top of the stack).
Since the last item pushed into the stack is always the

first item to be popped from the stack, a stack is also


called as a Last In, First Out or a LIFO structure.
Unlike an array that is static in terms of its size, a stack

is a dynamic data structure.


32

Characteristics of a Stack
Since the definition of the stack provides for the

insertion and deletion of nodes into it, a stack can grow


and shrink dynamically at runtime.
An ideal implementation of a stack would be a special

kind of linked list in which insertions and deletions can


only be done at the top of the list.

33

Operations on Stacks
Some of the typical operations performed on stacks are:
create (s)
push (s, i)

to create s as an empty stack

to insert the element i on top of the

stack s
pop (s)

to remove the top element of the stack


and
to eturn the removed element as a function
value.
top (s)

to return the top element of stack(s)


empty(s)
to check whether the stack is empty or
not. It returns true if the stack is empty,
and returns
false otherwise.
34

Implementation of Stacks
An array can be used to implement a stack.
But since array size is defined at compile time, it cannot

grow dynamically at runtime, and therefore, an attempt to


insert an element into a array implementation of a stack
that is already full causes a stack overflow.
A stack, by definition, is a data structure that cannot be full

since it can dynamically grow and shrink at runtime.

35

Implementation of Stacks
An ideal implementation for a stack is a linked list that

can dynamically grow and shrink at runtime.

Since you are going to employ a variation of a linked list

that functions as a stack, you need to employ an


additional pointer (top) that always points to the first
node in the stack, or the top of the stack.

It is using top that a node will either be inserted at the

beginning or top of the stack (push a node into the


stack), or deleted from the top of the stack (popping a
node at the top or beginning of the stack).

36

Code Implementing for a


The push( ) and the pop( ) operations on a stack are analogous
Stack
to insert-first-node and delete-first-node operations on a linked
list that functions as a stack.
struct stack
{ int info;
struct stack *next;
};
/* pointer declaration to point to the top of the stack */
struct stack *top;
main( )
{

37

Code Implementing for a


top
= NULL;
Stack
char menu = 0 ;
while (menu != 3)
{
printf( Add Nodes :\n);
printf( Delete Nodes :\n);
printf( Exit
:\n);
menu = getchar( );
switch (menu)
{
case 1 : push( );
break;
case 2 : pop( )
break;

38

Code Implementing for a


Stack
case 3: exit( );
break;
} /* end of switch */
} /* end of main( ) */

39

Implementing push( )
push( )
{
struct stack * new;
char ch;
ch = y;
while (ch = = y)
{
new = makenode( );
/* checking for an empty stack */
if ( top = = null)
{
top = new;
}

40

Implementing push( )
else
{
new->next = top;
top = new;
}
printf("%s","Want to add more nodes\n");
scanf( "%c", &ch );
fflush( stdin );
} /* end of while */
} /* end of push( )
41

A View of the Stack After


Insertion
new
1

info

top

next

info

next

42

Creating a Node on a Stack


struct stack *makenode()
{
struct stack *new;
new=(struct stack *) malloc(sizeof(struct(stack)));
scanf("%d",&new->info);
new->next = NULL;
return(new);
}

43

Implementing pop( )
pop( )
{
struct stack * temp;
int x;
/* check for an empty stack */
if (top = = null)
{
printf (Cannot remove nodes from an empty stack */
exit( );
}
44

Implementing pop( )
else
{
temp = top;
x = top->info;
top = top->next;
free( temp);
return x;
}

45

A View of the Stack After


Deletion
temp

top

next

46

Applications of Stacks
As a stack is a LIFO structure, it is an appropriate data

structure for applications in which information must be


saved and later retrieved in reverse order.
Consider what happens within a computer when function

main( ) calls another function.


How does a program remember where to resume execution

from after returning from a function call?


From where does it pick up the values of the local variables in

the function main( ) after returning from the subprogram?

47

Applications of Stacks
As an example, let main( ) call a( ). Function a( ), in turn,

calls function b( ), and function b( ) in turn invokes


function c( ).
main( ) is the first one to execute, but it is the last one to

finish, after a( ) has finished and returned.


a( ) cannot finish its work until b( ) has finished and

returned. b( ) cannot finish its work until c( ) has finished


and returned.

48

Applications of Stacks
When a( ) is called, its calling information is pushed on to

the stack (calling information consists of the address of


the return instruction in main( ) after a( ) was called, and
the local variables and parameter declarations in main().
When b( ) is called from a( ), b( )s calling information is

pushed onto the stack (calling information consists of the


address of the return instruction in a( ) after b( ) was
called, and the local variables and parameter
declarations in a( )).

49

Applications of Stacks
Then, when b( ) calls c( ), c( )s calling information is

pushed onto the stack (calling information consists of the


address of the return instruction in b( ) after c( ) was
called, and the local variables and parameter
declarations in b( )).
When c( ) finishes execution, the information needed to

return to b( ) is retrieved by popping the stack.


Then, when b( ) finishes execution, its return address is

popped from the stack to return to a( )


50

Applications of Stacks
Finally, when a( ) completes, the stack is again popped

to get back to main( ).


When main( ) finishes, the stack becomes empty.
Thus, a stack plays an important role in function calls.
The same technique is used in recursion when a function

invokes itself.

51

Queues
52

Objectives
In this session, you will learn to:
Define a queue
Describe the operations on a queue
Implement a queue as a special case of a linked list
Describe applications of queues

53

Defining a Queue
A Queue is a special kind of data structure in which

elements are added at one end (called the rear), and


elements are removed from the other end (called the
front).

You come across a number of examples of a queue in

real life situations.

For example, consider a line of students at a fee counter.

Whenever a student enters the queue, he stands at the


end of the queue (analogous to the addition of nodes to
the queue)

54

Defining a Queue
Every time the student at the front of the queue

deposits the fee, he leaves the queue (analogous to


deleting nodes from a queue).
The student who comes first in the queue is the one

who leaves the queue first.


Since the first item inserted is the first item

removed, a queue is commonly called a first-infirst-out or a FIFO data structure.


55

Queue Insertions and


Deletions
1

front

front

rear
(a)

rear
(b)

rear

front
(c)

56

Queue Operations
To complete this definition of a queue, you must specify

all the operations that it permits.

The first step you must perform in working with any

queue is to initialize the queue for further use. Other


important operations are

Add or insert an element into a queue.


Delete an element from a queue.
Display the contents of a queue.

Adding an element is popularly known as ENQ and

deleting an element is know as DEQ. The following slide


lists operations typically performed on a queue.
57

Queue Operations
create(q) which creates q as an empty queue
enq(i)
adds the element i to the rear of the queue

and returns the new queue


deq(q)
removes the element at the front end of the
queue (q) and returns the resulting queue as
well
as the removed element
empty (q) it checks the queue (q) whether it is empty or
not. It returns true if the queue is empty and
returns false otherwise
front(q)
returns the front element of the queue
without changing the queue
queuesize (q) returns the number of entries in the queue
58

Implementing Queues
Linked lists offer a flexible implementation of a queue

since insertion and deletion of elements into a list are


simple, and a linked list has the advantage of
dynamically growing or shrinking at runtime.
Having a list that has the functionality of a queue implies

that insertions to the list can only be done at the rear of


the list, and deletions to the list can only be done at front
of the list.

59

Implementing Queues
Queue functionality of a linked list can be achieved by

having two pointers front and rear, pointing to the first


element, and the last element of the queue respectively.
The following figure gives a visual depiction of linked list

implementation of a queue.
rear

front

60

Queue Declaration &


struct queue
Operations
{
int info;
struct queue *next;
};
struct queue *front, *rear;
An empty queue is represented by q->front = q->rear = null. Therefore,
clearq( ) can be implemented as follows:
void clearq(struct queue * queue_pointer)
{
queue_pointer->front = queue_pointer ->rear = null;
}

61

Queue Operations
You can determine whether a queue is empty or not by checking

its front pointer. The front pointer of a queue can be passed as


an argument to emptyq( ) to determine whether it is empty or
not.
int emptyq (queue_pointer)
{
if (queue_pointer = = null)
return (1);
else
return(0);
}

62

Insertion into a Queue


struct queue
{ int info;
struct queue *next;
};
/* pointer declarations to point to the front,
and rear of the queue */
struct queue *front, *rear;
main( )
{
front = NULL;
rear = NULL;

63

Insertion into a Queue


char menu = 0 ;
while (menu != 3)
{
printf( Add Nodes :\n);
printf( Delete Nodes :\n);
printf( Exit
:\n);
menu = getchar( );
switch (menu)
{
case 1 : enq( );
break;
case 2 : deq( )
break;

64

Insertion into a Queue


case 3: exit( );
break;
} /* end of switch */
} /* end of main( ) */

65

Insertion into a Queue


void enq( )
{
struct queue *new;
new = getnode( );
if(queue_pointer->front= =queue_pointer->rear = = null)
{
queue_pointer->front = new;
queue_pointer->rear = new;
}
else
{
rear->next = new;
rear = new;
}
}
66

Creating a Node on a Queue


struct queue * makenode()
{
struct queue *new;
new=(struct queue *) malloc(sizeof(struct(queue));
scanf("%d",&new->info);
new->next = NULL;
return(new);
}

67

Insertion into a Queue


New node inserted at
rear of queue
rear

front

4
next

7
next

8
next

New

68

Deletion from a Queue


int deq( )
{
struct queue *temp;
int x;
if(queue_pointer->front= =queue_pointer->rear = = null)
{
printf( Queue Underflow\n);
exit (1);
}
temp = front;
x=temp->info;
front = front->next;
free(temp);
if(front = = null) /* check for queue becoming empty after node deletion */
rear = null;
return(x);
}

69

Deletion of a Node From a


Queue
rear

front

Node being deleted at


the front of the queue

70

Applications of Queues
Queues are also very useful in a time-sharing multi-user

operating system where many users share the CPU


simultaneously.
Whenever a user requests the CPU to run a particular

program, the operating system adds the request ( by first of


all converting the program into a process that is a running
instance of the program, and assigning the process an ID).
This process ID is then added at the end of the queue

of jobs waiting to be executed.

71

Applications of Queues
Whenever the CPU is free, it executes the job that is at

the front of the job queue.

Similarly, there are queues for shared I/O devices. Each

device maintains its own queue of requests.

An example is a print queue on a network printer, which

queues up print jobs issued by various users on the


network.

The first print request is the first one to be processed.

New print requests are added at the end of the queue.


72

Disadvantages of a Queue
The operations such as inserting an element and deleting an

element from queue works perfectly until the rear index


reaches the end of the array.
If some items are deleted from the front, there will be some
empty spaces in the beginning of the queue. Since the rear
index points to the end of the array, the queue is thought to be
full and no more insertions are possible.
This disadvantage can be eliminated by removing the item in
the front end of the queue and then shifting the data from the
second locations onwards to their respective previous
locations.
Another option is to use a circular queue.
73

Double ended Queue


A double ended queue or deque is a special

kind of queue in which insertions and deletions


can be done at both ends of queue.
The operations that can be performed on a
deque are:

Insert an item at the front end


Insert an item at the rear end
Delete an item at the front end
Delete an item at the rear end
Display the contents of the queue
74

Circular Queue
In circular queue, the elements of a given queue can be

stored efficiently in an array so as to wrap around in


such a way that the rear of the queue is followed by the
front of the queue.

Allows the entire array to store the elements without

shifting any data within the queue.

Assume that circular queue contains only one item as

shown in the next slide [fig (a)]. In this case, the rear end
identified by r is 0 and the front end identified by f is also
0. Since only r is incremented upon item insertion, its
initial value has to be -1 and f should be 0.
75

Pictorial Representation of
Circular Queue
2

10

1
10

f
(a)

f, r
3

30

2 r
20

10

20

30

(b)

10

f
40

30
20

50

After inserting 20 & 30

10

(c)

10

20

30

40

4
50

After inserting 40 & 50


76

Pictorial Representation of
Circular Queue (contd)
2

3
40

30

(d)

40

50

30

60

1
60
0

After deleting 10 & 20


2

50

40

2
30

50

30

40

4
50

(e)

r
After inserting 60

77

Pictorial Representation of
Circular Queue (contd)
The configuration shown in fig (b) is obtained on

inserting 20 and 30 into the queue. To insert an item, r


has to be incremented first. The statement used is:
r=r+1;

or r = (r+1)%QUEUE_SIZE;

The configuration shown in fig (c) is obtained on

inserting 40 and 50 into the queue. At this point the


queue is full. r is now pointing to the last element in the
queue.
Suppose we delete 2 items, 10 and 20, one after the
other. The resulting queue is shown in fig (d).
Now, if we insert 60 into the queue, r will again be
incremented. Now r will become 0.
78

Pictorial Representation of
Circular Queue (contd)
In case of items being deleted, as in an ordinary queue,

the front end identifier f is updated accordingly [fig (d)]


and is made to point to the next front item in the queue.

The statement used to increment f is:


f = (f+1)%QUEUE_SIZE;

In case of this kind of queue, to check for overflow or

underflow, a variable is used that contains the number of


items in the queue at any time. When an item is inserted
or deleted, the variable is incremented or decremented
by 1 respectively.
>>Program

79

Priority Queue
A special type of queue in which items can be

inserted or deleted based on the priority.

An element with highest priority is processed

before processing any of the lower priority


elements.

If the elements in the queue are of same priority,

then the element, which is inserted first is


processed.

80

Types of Priority Queues


Classified into two groups:

Ascending priority queue


Descending priority queue

In ascending priority queue, elements can be inserted in

any order. But, while deleting an element from the


queue, only the smallest element is removed first.
In descending priority queue too, elements can be

inserted in any order. But, while deleting an element from


the queue, only the largest element is deleted first.
81

LINKED LISTS
82

Objectives
Describe linked lists
Types of linked lists
Write code to:
Create a sorted linked list,
Insert nodes into a sorted linked list
Traverse a linked list
Delete nodes from a linked list
Doubly linked lists
Circular linked lists
83

Linked Lists An
Introduction
Linked lists were developed in 1955-56 by Allen Newell, Cliff

Shaw and Herbert Simon at RAND Corporation as the primary


data structure for their Information Processing Language. IPL
was used by the authors to develop several early artificial
intelligence programs.
Can be defined as a collection of self referential structures
ordered not by their physical placement in memory but by
logical links that are stored as part of the data in the structure
itself.
Each structure of the list is called a node.
Each node consists of the data and a pointer pointing to the
next node on the list.
Accessed via a pointer to the first node of the list
84

Linked List
Subsequent nodes are accessed via the link-pointer

member of the current node

Link pointer in the last node is set to null to mark the lists

end

Linked lists permit insertion and removal of nodes at any

point in the list in constant time, but do not allow random


access.

Use a linked list instead of an array when

You have an unpredictable number of data elements


Your list needs to be sorted quickly

85

Types of linked lists


Types of linked lists are:

Singly linked list

Circular, singly linked

Pointer in the last node points back to the first node

Doubly linked list

Begins with a pointer to the first node


Terminates with a null pointer
Only traversed in one direction

Two start pointers first element and last element


Each node has a forward pointer and a backward pointer
Allows traversals both forwards and backwards

Circular, doubly linked list

Forward pointer of the last node points to the first node and
backward pointer of the first node points to the last node
86

Elementary linked list


functions
These functions allow the user to add or remove elements
to the linked list.

Depending on the language you wish to use for actual


implementation, you can use virtually any datatype.

The following basic operations can be performed on a linked


list:

Creating a linked list


Traversing the list
Counting the items in the list
Printing the list
Looking up an item for editing or printing
Inserting an item
Deleting an item
Concatenating two lists
87

Declaring a Linked List


Let us define a self-referential structure to store a list of

marks the size of which may not be known.


struct marks_list
{
int marks;
struct marks_list *next;
};

We have defined a structure of type marks_list. It consists

of two elements, one integer element marks and the other


element, a pointer next, which is a pointer to a structure
of the same type, i.e., of type marks_list itself.

88

Declaring a Linked List


Therefore, a part of the structure is referencing a

structure type of itself, and hence the name selfreferential structure.


Such data structures are also popularly known as linked

lists, since each structure variable contains a link to


other structure variables of the same type.
One can visualize a linked list as shown in the following

slide:

89

Visualizing a Linked List


Stack
start

100

Heap
100

75
marks

140

140
next

85
marks

180

180
next

95
marks

230
next

100 x
marks

next

90

Insertion of an element into a


linked
list
An element can be inserted into the list in 3 ways:
1.
2.
3.

Insertion at the front of the list


Insertion in the middle
Insertion in the end of the list

A general algorithm for insertion is :


Begin
if list is empty or the new node comes before the head node
then,
insert the new node as the head node
else
if the new node comes after the last node then,
insert the new node as the end node
else
inset the new node in the body of the list
End
91

Inserting an element at the head


of a linked list
Adding an element to the head of a linked list is quite

simple.
Algorithm for placing the new element at the head of the
list:

Obtain space for the new node


Assign data to the item field of the new node
Set the next pointer of the new node to point to the start of the list
Change the head pointer to point to the new node.

92

Inserting an element in the middle


of a linked list
Adding a new node in the middle of a list is relatively

simple.
Assuming a new node X is being inserted between
nodes N1 and N2
Algorithm for inserting a new node between two nodes in
the list

Set space for new node X


Assign value to the item field of X
Set the next field of X to point to N2
Set the next field of N1 to point to X

93

Inserting an element at the tail of


a linked list
Adding an element to the tail of a list is similar to

inserting an element in the middle except the next filed is


set to NULL.
Algorithm for placing a new node at the end of the list

Set space for new node


Assign value to the item field of the new node.
Set the next field to point to NULL or to dummy or sentinel node)
Set the next pointer of the last node to point to the new node.

94

Deleting a node in a linked

a node from a linked list is a very simple procedure


listDeleting
as only one pointer value needs to be changed.
A node can be deleted from a linked list as follows:

1.
2.
3.

Deleting the first item


Deleting the last item
Deleting an item between 2 nodes in the middle of the list.

A general algorithm for deletion is:


Begin
if list is empty, then,
node cannot be deleted
else
if node to be deleted is the first node, then,
make the head to point to the second node
else
delete the node from the body of the list
End
95

Creating a Sorted Linked List


#include<stdio.h>
#include<malloc.h>
struct marks_list *start, *prev;
/* variables declared outside main() are global in nature and can be
accessed by other functions called from main() */
struct marks_list
{
int marks;
struct marks_list *next;
};
main()
{
struct marks_list * makenode();
/* function prototype declaration */

96

Creating a Sorted Linked List


struct marks_list *new;
start = NULL;
char menu = 0 ;
while (menu != 4)
{
printf( Add Nodes :\n);
printf( Delete Nodes :\n);
printf( Traverse a list :\n);
printf( Exit
:\n);
menu = getchar( );

97

Creating a Sorted Linked List


switch (menu)
{
case 1 : addnode( );
break;
case 2 : deletenode( )
break;
case 3 : traverse( );
break;
case 4: exit( );
break;
} /* end of switch */
} /* end of main( ) */

98

Creating a Sorted Linked List


addnode( )
{
char ch = 'y';
while ( ch = = 'y' )
{
new = makenode();
/* creation of a list is treated as a special case of insertion */
if ( start == NULL)
{
start = new;
}
else
{
insert();
traverse( );
}
99

Creating a Sorted Linked List


printf("%s","Want to add more nodes\n");
scanf( "%c", &ch );
fflush( stdin );
} /* end of while */
} /* end of addnode( )

100

Creating a Sorted Linked List


struct marks_list * makenode()
{
struct marks_list *new;
new=(struct marks_list *)
malloc(sizeof(struct(marks_list));
scanf("%d",&new->marks);
new->next = NULL;
return(new);
}

101

Creating a Sorted Linked List


insert(struct marks_list *start)
{
struct marks_list *ptr, *prev;
for(ptr=start,prev=start;(ptr);prev=ptr,ptr=ptr->next)
{
if (new->marks < start->marks)
{
/* insertion at the beginning of a list */
new->next = start;
start = new;
}
102

Creating a Sorted Linked List


/* insertion in the middle of a list */
if(new->marks > ptr->marks)
{
continue;
}
else
{
prev->next = new;
new->next = ptr;
}
} /* end of for loop */

103

Creating a Sorted Linked List


/* insertion at the end of the list */
if (ptr == null)
{
prev->next = new;
new->next = null;
}
} /* end of insert */

104

Searching a Value in a
Linked List
struct marks_list *search( int val)
{
for( ptr = start; (ptr); ptr = ptr->next)
{
if (val = = ptr-> marks)
return ptr;
}

105

Deleting a Node From a


delete ( )
Linked
List
{
struct marks_list *ptr, *prev, *temp;
int score;
/* search the linked list for the value to be deleted */
scanf(%d, &score);
fflush(stdin);
for (ptr = start, prev = start; (ptr); prev = ptr, ptr = ptr->next)
{
/* deletion of the first node in the list */
if (score = = start-> marks)
{
temp =start;
start = start-> next;
free(temp);
}

106

Deleting a Node From a


Linked List
/* this code would hold true for deletion in the middle
and at the end of a linked list */
if (score = = ptr-> marks)
{
prev-> next = ptr-> next;
free(ptr);
}
}/* end of for loop */
} /* end of delete */
107

Doubly Linked Lists


108

Objectives
In this session, you will learn to:
Describe the need for, and advantages of a

doubly linked list


Write code to:

Create a sorted doubly linked list,


Insert nodes into a sorted doubly linked list
Traverse a doubly linked list
Delete nodes from a doubly linked list

109

Need For a Doubly Linked


List
The disadvantage with a singly linked list is that traversal

is possible in only direction, i.e., from the beginning of


the list till the end.
If the value to be searched in a linked list is toward the

end of the list, the search time would be higher in the


case of a singly linked list.
It would have been efficient had it been possible to

search for a value in a linked list from the end of the list.

110

Properties of a Doubly
Linked
List

This would be possible only if we have a doubly linked list.

In a doubly linked list, each node has two pointers, one

say, next pointing to the next node in the list, and another
say, prior pointing to the previous node in the list.
Therefore, traversing a doubly linked list in either direction

is possible, from the start to the end using next, and


from the end of the list to the beginning of the list
using prior.

111

Properties of a Doubly
Linked
List
In a doubly linked
list, the prior pointer of the first node will be
null, as there is no node before the first node.
In a doubly linked list, the next pointer of the last node will be

null, as there is no node after this list.


Bidirectional traversal of a doubly linked list is useful for

implementing page up, and page down functionality when using


doubly linked lists to create editors.
A doubly linked list would have two pointers, start and last to

facilitate traversal from the beginning and end of the list


respectively.

112

Declaration of a Doubly
Linkedstruct
List
marks_list
{
struct double_list *prior;
int info;
struct marks_list *next;
}

113

Visualizing a Doubly Linked


List
last
start
140

100

100
null

prior

120
1

marks

140

120

100

next

prior

marks

140

120

next

prior

marks

null

next

114

Creating a Sorted Doubly


Linked
#include<stdio.h> List
#include<malloc.h>
struct marks_list *start, *last;
/* variables declared outside main() are global in nature and can be accessed by
other functions called from main() */
struct marks_list
{
struct marks_list *prior;
int marks;
struct marks_list *next;
};
main()
{
struct marks_list * makenode();
/* function prototype declaration */

115

Creating a Sorted Doubly


Linked List
struct marks_list *new;
start = NULL;
char menu = 0 ;
while (menu != 5)
{
printf( Add Nodes :\n);
printf( Delete Nodes :\n);
printf( Forward Traverse a list :\n);
printf( Reverse Traverse a list :\n);
printf( Exit
:\n);
menu = getchar( );

116

Creating a Sorted Doubly


Linked
List
switch
(menu)
{

case 1 : addnode( );
break;
case 2 : deletenode( )
break;
case 3 : forward_traverse( );
break;
case 4 : reverse_traverse( );
break;
case 5: exit( );
break;
} /* end of switch */
} /* end of main( ) */

117

Creating a Sorted Doubly


addnode( )
Linked
List
{
char ch = 'y';
while ( ch = = 'y' )
{
new = makenode();
/* creation of a list is treated as a special case of insertion */
if ( start = = NULL)
{
start = new;
start->prior = null;
}
else
{
insert();
traverse( );
}
118

Creating a Sorted Doubly


Linked List
printf("%s","Want to add more nodes\n");
scanf( "%c", &ch );
fflush( stdin );
} /* end of while */
} /* end of addnode( )

119

Creating a Sorted Doubly


Linked List
struct marks_list * makenode()
{
struct marks_list *new;
new=(struct marks_list *) malloc(sizeof(struct(marks_list));
scanf("%d",&new->marks);
new->prior = NULL;
new->next = NULL;
return(new);
}

120

Creating a Sorted Linked List


insert( )
{
struct marks_list *ptr, *prev;
for(ptr=start,prev=start;(ptr);prev=ptr,ptr=ptr->next)
{
if (new->marks < start->marks)
{
/* insertion at the beginning of a list */
new->next = start;
new->prior = NULL;
start->prior = new;
last = start;
start = new;
}

121

Creating a Sorted Doubly


Linked
List
/* insertion in the middle of a list */
if(new->marks > ptr->marks)
{
continue;
}
else
{
prev->next = new;
new->prior = prev;
new->next = ptr;
ptr->prior = new;
}
} /* end of for loop */
122

Creating a Sorted Linked List


/* insertion at the end of the list */
if (ptr = = null)
{
prev->next = new;
new->prior = prev;
new->next = null;
last = new;
}
} /* end of insert */

123

Searching a Value in a
Doubly Linked List
struct marks_list *search( int val)
{
for( ptr = start; (ptr); ptr = ptr->next)
{
if (val = = ptr-> marks)
return ptr;
}
struct marks_list *search( int val)
{
for( ptr = last; (ptr); ptr = ptr->prior)
{
if (val = = ptr-> marks)
return ptr;
}
124

Deleting a Node From a


Doubly Linked List
delete ( )
{
struct marks_list *ptr, *prev,
*temp;
int score;
/* search the linked list for the
value to be deleted */
scanf(%d, &score);
fflush(stdin);
for (ptr = start, prev = start;
(ptr); prev = ptr, ptr = ptr>next)
{

/* deletion of the first node in


the list */
if (score = = start-> marks)
{
temp =start;
start = start-> next;
start->prior = null;
free(temp);
}

125

Deleting a Node From a


Linked
List
/* deletion in the middle of a
/* deletion at the end of the list */
linked list */
if (score = = ptr-> marks)
{
prev-> next = ptr-> next;
ptr->next->prior = ptr>prior;
free(ptr);
}

if (ptr->next = = null)
{
temp = ptr;
prev->next = null;
last = ptr->prior;
}
}
}

126

Traversal of a Doubly
Linked List
/* forward traversal of a doubly linked list */
for( ptr = start; (ptr); ptr = ptr->next)
{
printf(%d, ptr->marks);
}
/* reverse traversal of a doubly linked list */
for( ptr = last; (ptr); ptr = ptr->prior)
{
printf(%d, ptr->marks);
}
127

Summary
In this session, you learnt to:
Describe the need for, and advantages of a

doubly linked list


Write code to:

Create a sorted doubly linked list,


Insert nodes into a sorted doubly linked list
Traverse a doubly linked list
Delete nodes from a doubly linked list

128

Binary Trees
129

Objectives
At the end of this lesson, you will be able to:
Define a binary tree
Describe the terminologies associated with a binary

tree
Use a dynamically allocated data structure to
represent a binary tree
Traverse, Add and delete nodes from a binary tree
Understand the Huffman Algorithm
Describe the types of Trees

130

Trees
Compared to linked lists that are linear data structures,

trees are non-linear data structures.


In a linked list, each node has a link which points to another

node.
In a tree structure, however, each node may point to several

nodes, which may in turn point to several other nodes.


Thus, a tree is a very flexible and a powerful data structure

that can be used for a wide variety of applications.

131

Trees

Rajeev

Ravi

Ramesh

Vijay

Suresh

Anjali

Sukesh

Arvind

132

Trees
A tree consists of a collection of nodes that are

connected to each other.


A tree contains a unique first element known as the

root, which is shown at the top of the tree structure.


A node which points to other nodes is said to be the

parent of the nodes to which it is pointing, and the nodes


that the parent node points to are called the children, or
child nodes of the parent node.

133

Trees
The root is the only node in the tree that does not have a

parent.
All other nodes in the tree have exactly one parent.
There are nodes in the tree that do not have any

children. Such nodes are called leaf nodes.


Nodes are siblings if they have the same parent.

134

Trees
A node is an ancestor of another node if it is the parent of

that node, or the parent of some other ancestor of that


node.

The root is an ancestor of every other node in the tree.


Similarly, we can define a node to be a descendant of

another node if it is the child of the node, or the child of


some other descendant of that node.

You may note that all the nodes in the tree are

descendants of the root node.

135

Tree
An important feature of a tree is that there is a single

unique path from the root to any particular node.

The length of the longest path from the root to any node

is known as the depth of the tree.

The root is at level 0 and the level of any node in the tree

is one more than the level of its parent.

In a tree, any node can be considered to be a root of the

tree formed by considering only the descendants of that


node. Such a tree is called the subtree that itself is a tree.
136

Binary Tree
If you can introduce a restriction that each node can

have a maximum of two children or two child nodes, then


you can have a binary tree.
You can give a formal definition of a binary tree as a tree

which is either empty or consists of a root node together


with two nodes, each of which in turn forms a subtree.
You therefore have a left subtree and a right subtree

under the root node.

137

Binary Tree
A complete binary tree can be defined as one whose non-leaf

nodes have non-empty left and right subtrees and all leaves
are at the same level.
This is also called as a balanced binary tree.
If a binary tree has the property that all elements in the left

subtree of a node n are less than the contents of n, and all


elements in the right subtree are greater than the contents of
n, such a tree is called a binary search tree.
The following is an example of a balanced binary search tree.

138

Balanced Binary Tree


4

139

Data Structure Representation


of a Binary Trees
A tree node may be implemented through a structure

declaration whose elements consist of a variable for


holding the information and also consist of two pointers,
one pointing to the left subtree and the other pointing to
the right subtree.
A binary tree can also be looked at as a special case of a

doubly linked list that is traversed hierarchically.


The following is the structure declaration for a tree node:

140

Data Structure Representation


of a Binary Trees
struct btreenode
{
int info;
struct btreenode *left;
struct btreenode *right;
};

141

Traversing a Binary Tree


Traversing a binary tree entails visiting each node in the

tree exactly once.


Binary tree traversal is useful in many applications,

especially those involving an indexed search.


Nodes of a binary search tree are traversed hierarchically.
The methods of traversing a binary search tree differ

primarily in the order in which they visit the nodes.

142

Traversing a Binary Tree


At a given node, there are three things to do in some

order. They are:

To visit the node itself


To traverse its left subtree
To traverse its right subtree

We can traverse the node before traversing either subtree.


Or, we can traverse the node between the subtrees.
Or, we can traverse the node after traversing both

subtrees.

143

Traversing a Binary Tree


If we designate the task of visiting the root as R,

traversing the left subtree as L and traversing the


right subtree as R, then the three modes of tree
traversal discussed earlier would be represented as:

RLR Preorder
LRR Postorder
LRR Inorder

144

Traversing a Binary Tree


The functions used to traverse a binary tree using these

methods can be kept quite short if we understand the


recursive nature of the binary tree.

Recall that a binary tree is recursive in that each subtree

is really a binary tree itself.

Thus traversing a binary tree involves visiting the root

node, and traversing its left and right subtrees.

The only difference among the methods is the order in

which these three operations are performed.

145

Traversing a Binary Tree


Depending on the position at which the given node or the

root is visited, the name is given.


If the root is visited before traversing the subtree, it is

called the preorder traversal.


If the root is visited after traversing the subtrees, it is

called postorder traversal.


If the root is visited in between the subtrees, it is called

the inorder traversal.


146

Traversing a Binary Tree


A

147

Preorder Traversal
When we traverse the tree in preorder, the root node is

visited first. So, the node containing A is traversed first.

Next, we traverse the left subtree. This subtree must

again be traversed using the preorder method.

Therefore, we visit the root of the subtree containing B

and then traverse its left subtree.

The left subtree of B is empty, so its traversal does

nothing. Next we traverse the right subtree that has root


labeled C.

148

Preorder Traversal
Then, we traverse the left and right subtrees of C

getting D and E as a result.


Now, we have traversed the left subtree of the root

containing A completely, so we move to traverse the


right subtree of A.
The right subtree of A is empty, so its traversal does

nothing. Thus the preorder traversal of the binary


tree results in the values ABCDE.
149

Inorder Traversal
For inorder traversal, we begin with the left subtree

rooted at B of the root.


Before we visit the root of the left subtree, we must

visit its left subtree, which is empty.


Hence the root of the left subtree rooted at B is

visited first. Next, the right subtree of this node is


traversed inorder.

150

Inorder Traversal
Again, first its left subtree containing only one node D

is visited, then its root C is visited, and finally the right


subtree of C that contains only one node E is visited.
After completing the left subtree of root A, we must

visit the root A, and then traverse its right subtree,


which is empty.
Thus, the complete inorder traversal of the binary tree

results in values BDCEA.


151

Postorder Traversal
For postorder traversal, we must traverse both the left and

the right subtrees of each node before visiting the node


itself.

Hence, we traverse the left subtree in postorder yielding

values D, E, C and B.

Then we traverse the empty right subtree of root A, and

finally we visit the root which is always the last node to be


visited in a postorder traversal.

Thus, the complete postorder traversal of the tree results

in DECBA.

152

Code - Preorder Traversal


void preorder (p)
struct btreenode *p;
{
/* Checking for an empty tree */
if ( p != null)
{
/* print the value of the root node */
printf(%d, p->info);
/* traverse its left subtree */
preorder(p->left);
/* traverse its right subtree */
preorder(p->right);
}
}

153

Code Inorder Traversal


void inorder(p)
struct btreenode *p;
{
/* checking for an empty tree */
if (p != null)
{
/* traverse the left subtree inorder */
inorder(p->left);
/* print the value of the root node */
printf(%d, p->info);
/*traverse the right subtree inorder */
inorder(p->right);
}
}

154

Code Postorder Traversal


void postorder(p)
struct btreenode *p;
{
/* checking for an empty tree */
if (p != null)
{
/* traverse the left subtree */
postorder(p->left);
/* traverse the right subtree */
postorder(p->right);
/* print the value of the root node */
printf(%d, p->info);
}
}

155

Accessing Values From a Binary


Search Tree Using Inorder

You may note that when you traverse a binary search


Traversal

tree inorder, the keys will be in sorted order because all


the keys in the left subtree are less than the key in the
root, and all the keys in the right subtree are greater than
that in the root.

The same rule applies to all the subtrees until they have

only one key.

Therefore, given the entries, we can build them into a

binary search tree and use inorder traversal to get them


in sorted order.
156

Insertion into a Tree


Another important operation is to create and maintain a

binary search tree.

While inserting any node, we have to take care the

resulting tree satisfies the properties of a binary search


tree.

A new node will always be inserted at its proper position

in the binary search tree as a leaf.

Before writing a routine for inserting a node, consider

how a binary tree may be created for the following input:


10, 15, 12, 7, 8, 18, 6, 20.
157

Insertion into a Tree


First of all, you must initialize the tree.
To create an empty tree, you must initialize the root

to null. The first node will be inserted into the tree as


a root node as shown in the following figure.
Root

10

158

Insertion into a Tree


Since 15 is greater than 10, it must be inserted as the

right child of the root as shown in the following figure.

Root

10

15

159

Insertion into a Tree


Now 12 is larger than the root; it must go to the right

subtree of the root.


Further, since it is smaller than 15, it must be inserted as

the left child of the root as shown below.


10

Root

15

12

160

Insertion into a Tree


Next, 7 is smaller than the root. Therefore, it must

be inserted as the left child of the root as shown in


the following figure.
10

15

12

161

Insertion into a Tree


Similarly, 8, 18, 6 and 20 are inserted at the proper

place as shown in the following figures.


10

10

15

12

15

12

18

162

Insertion into a Tree

10

10

15

12

15

18

12

18
20

163

Insertion into a Tree


This example clearly illustrates that given the root of a

binary search tree and a value to be added to the tree,


we must search for the proper place where the new
value can be inserted.

We must also create a node for the new value and

finally, we have to adjust the left and right pointers to


insert the new node.

To find the insertion place for the new value, say 17, we

initialize a temporary pointer p, which points to the root


node.

164

Insertion into a Tree


We can change the contents of p to either

move left or right through the tree depending


on the value to be inserted.
When p becomes null, we know that we have

found the insertion place as in the following


figure.

165

Insertion into a Tree


10

Root

15

12

18

20

166

Insertion into a Tree


But once p becomes null, it is not possible to link the

new node at this position because there is no


access to the node that p was pointing to (node with
value 18) just before it became null.
From the following figure, p becomes null when we

have found that 17 will be inserted at the left of 18.

167

Insertion into a Tree

10

Root

15

12

18

null

20

168

Insertion into a Tree


You therefore need a way to climb back into the

tree so that you can access the node containing


18, in order to make its left pointer point to the new
node with the value 17.
For this, you need a pointer that points to the node

containing 18 when p becomes null.


To achieve this, you need to have another pointer

(trail) that must follow p as p moves through the


tree.
169

Insertion into a Tree


When p becomes null, this pointer will point to the leaf

node (the node with value 18) to which you must link the
new node (node with value 17).

Once you know the insertion place, you must adjust the

pointers of the new node.

At this point, you only have a pointer to the leaf node to

which the new node is to be linked.

You must determine whether the insertion is to be done at

the left subtree or the right subtree of the leaf node.

170

Insertion into a Tree


To do that, you must compare the value to be

inserted with the value in the leaf node.


If the value in the leaf node is greater, we insert the

new node as its left child; otherwise we insert the


new node as its right child.

171

Creating a Tree A Special


Case

case of insertion that you need to watch out


ofA special
Insertion
for arises when the tree in which you are inserting a
node is an empty tree.

You must treat it as a special case because when p

equals null, the second pointer (trail) trailing p will also


be null, and any reference to info of trail like trail->info
will be illegal.

You can check for an empty tree by determining if trail

is equal to null. If that is so, we can initialize root to


point to the new node.

172

Code Implementation For


Insertion

Into
Treefor insertion into a binary tree takes
The Cafunction
two parameters; one is the pointer to the root node
(root), and the other is the value to be inserted (x).

You will implement this algorithm by allocating the

nodes dynamically and by linking them using pointer


variables. The following is the code implementation
of the insert algorithm.

173

Code Implementation For


Insertion
tree insert(s,x)
Into
a
Tree
int x;
tree *s;
{
tree *trail, *p, *q;
q = (struct tree *) malloc (sizeof(tree));
q->info = x;
q->left = null;
q->right = null;
p = s;
trail = null;
174

Code Implementation For


Insertion
while
(p !=a
null)
Into
Tree
{
trail = p;
if (x < p->info)
{
p = p->left;
}
else
{
p = p->right;
}
}

175

Code Implementation For


Insertion
/*insertion
into
an
empty tree; a special case of insertion */
Into
a
Tree
if (trail == null)
{
s = q;
return (s);
}
if(x < trail->info)
{
trail->left = q;
}
else
{
trail->right = q;
}
return (s);
}
176

Code Implementation For


Insertion

You have
seen thatUsing
to insert a node,
you must compare x
Into
a Tree
Recursion
with root->info.

If x is less than root->info, then x must be inserted into

the left subtree.

Otherwise, x must be inserted into the right subtree.


This description suggests a recursive method where you

compare the new value (x) with the one in the root and
you use exactly the same insertion method either on the
left subtree or on the right subtree.

177

Code Implementation For


Insertion
The base case is inserting a node into an empty tree.
Into a Tree Using Recursion
You can write a recursive routine (rinsert) to insert a node

recursively as follows:
tree rinsert (s,x)
tree *s;
int x;
{
/* insertion into an empty tree; a special case of insertion */
if (!s)
{
s=(struct tree*) malloc (sizeof(struct tree));
178

Code Implementation For


Insertion
s->info = x;
Into
a
Tree
Using
Recursion
s->left = null;
s->right = null;
return (s);
}
if (x < s->info)
s->left = rinsert(x, s->left);
else
if (x > s->info)
s->right = rinsert(x, s->right);
return (s);
}
179

Circumstances When a Binary


Tree Degenerates into a
Linked
The shapeList
of a binary tree is determined by the
order in which the nodes are inserted.
Given the following input, their insertion into the tree

in the same order would more or less produce a


balanced binary search tree as shown below:

Input values: 10, 15, 12, 7, 8, 18, 6, 20

180

Circumstances When a Binary


Tree Degenerates into a
Linked List
10

Root

15

12

18

20

181

Circumstances When a Binary


Tree Degenerates into a
Linked
List
If the same
input is given in the sorted order as
6, 7, 8, 10, 12, 15, 18, 20, you will construct a

lopsided tree with only right subtrees starting from


the root.
Such a tree will be conspicuous by the absence of

its left subtree from the top.

182

Circumstances When a Binary


Tree Degenerates into a
Linked List
6

7
8
10
12
15
18

20

A Lopsided Binary Tree With Only Right Subtrees

183

Circumstances When a Binary


Tree Degenerates into a
Linked
HoweverList
if you reverse the input as
20, 18, 15, 12, 10, 8, 7, 6, and insert them

into a tree in the same sequence, you will


construct a lopsided tree with only the left
subtrees starting from the root.
Such a tree will be conspicuous by the

absence of its right subtree from the top.


184

Circumstances When a Binary


Tree Degenerates into a
Linked List
20

18
15
12
10
8
7
6

A Lopsided Binary Tree With Only Left Subtrees

185

Deletion from a Binary


Search Tree
An important function for maintaining a binary search

tree is to delete a specific node from the tree.


The method to delete a node depends on the specific

position of the node in the tree.


The algorithm to delete a node can be subdivided into

different cases.

186

Case I Deletion Of The Leaf


Node
If the node to be deleted is a leaf, you only need to set

appropriate link of its parent to null, and do away with the


node that is to be deleted.
For example, to delete a node containing 1 in the

following figure, we have to set the left pointer of its


parent (pointing to 1) to null.
The following diagram illustrates this.

187

Case I Deletion Of The Leaf


Node
2

p
1

null

188

Case II Deletion Of a Node


With a Single Child
If the node to be deleted has only one child, you cannot

simply make the link of the parent to nil as you did in the
case of a leaf node.
Because if you do so, you will lose all of the descendants

of the node that you are deleting from the tree.


So, you need to adjust the link from the parent of deleted

node to point to the child of the node you intend to


delete. You can subsequently dispose of the deleted
node.
189

Case II Deletion Of a Node


With a Single Child
5

Node to be deleted

Node to be deleted

To delete node containing the value 3, where the right subtree of


3 is empty, we simply make the link of the parent of the node
with the value 3 (node with value 5) point to the child of 3 (node
with the value 2).

190

Case III Deletion Of a Node


With Two Child Nodes
Complications arise when you have to delete a node with

two children.
There is no way you can make the parent of the deleted

node to point to both of the children of the deleted node.


So, you attach one of the subtrees of the node to be

deleted to the parent, and then link the other subtree


onto the appropriate node of the first subtree.

191

Case III Deletion Of a Node


With Two Child Nodes
You can attach the right subtree to the parent node and then

link the left subtree on to the appropriate node of the right


subtree.

Therefore, you must attach the left subtree as far to the left

as possible. This proper place can be found by going left


until an empty left subtree is found.

For example, if you delete the node containing x as shown

in the following figure, you make the parent of x (node with


the value r) point to the right subtree of x (node containing y)
and then go as far left as possible (to the left of the node
containing y) and attach the left subtree there.
192

Case III Deletion Of a Node


With Two Child Nodes
r
r
q

Delete node x

q
t

y
t

z
s

Before Deletion of Node x

u
After Deletion of Node x

193

Code Implementation for Node


Deletion
for Cases I, II & III p = p->right;
void delete (p)
struct tree *p
{
struct tree *temp
if (p == null)
printf(Trying to delete a
non-existent node);
else if (p->left == null)
{
temp=p;

free(temp);
}
else if (p->right
== null)
{
temp = p;
p = p->left;
free (temp);
}

194

Code Implementation for Node


Deletion
else
!= null &&
forif(p->left
Cases
I,p->right!=
II &null)
III
{
temp = p->right;
while (temp->left != null)
{
temp = temp->left;
}
temp->left = p->left;
temp = p;
p = p->right;
free (Temp);
}

}
195

Code Implementation for Node


Deletion

for
I, II
IIIwhen it finds a node with
NoteCases
that the while
loop&
stops
an empty left subtree so that the left subtree of the node
to be deleted can be attached here.
Also, note that you first attach the left subtree at the

proper place and then attach the right subtree to the


parent node of the node to be deleted.

196

Search The Tree


To search a tree, you employ a traversal pointer p, and set

it equal to the root of the tree.


Then you compare the information field of p with the given

value x. If the information is equal to x, you exit the routine


and return the current value of p.
If x is less than p->info, you search in the left subtree of p.
Otherwise, you search in the right subtree of p by making

p equal to p->right.

197

Search the Tree


You continue searching until you have found the desired value or reach the end

of the tree. You can write the code implementation for a tree search as follows:

search (p,x)
int x;
struct tree *p;
{
p = root;
while (p != null && p->info != x)
{
if (p->info > x)
p = p->left;
else
p = p->right;
} return (p);
}

198

GRAPHS
199

SEARCHING

Searching
Sequential Searches

Time is proportional to n

We call this time complexity O(n)

Pronounce this big oh of n


Both arrays (unsorted) and linked lists

Binary search
Sorted array

Time proportional to log2 n

Time complexity O(log n)

Eliminative or a Binary
Search
The mode of accessing data in a linked list is linear.
Therefore, in the worst case scenario of the data in

question being stored at the extremes of the list, it would


involve starting with the first node, and traversing
through all the nodes till one reaches the last node of the
list to access the data.
Therefore, search through a linked list is always

linear.

202

Eliminative or a Binary
Search
A linear search is fine if the nodes to be searched in a
linked list are small in number.
But the linear search becomes ineffective as the

number of nodes in a linked list increase.


The search time increases in direct proportion with the

size of the linked list.


It becomes imperative to have better searching

mechanisms than a linear search.


203

Eliminative or a Binary
Search
You will now be exposed to a game that will highlight
a new mechanism of searching.

Coin Search in a Group


X
YES

NO

204

Eliminative or a Binary
Search
A coin is with one of the members in the audience

divided into the two sections on the left and the right
respectively as shown in the diagram.
The challenge facing X, the protagonist, is to find

the person with the coin in the least number of


searches.

205

Searching - Binary search


Creating the sorted array
AddToCollection

adds each item in correct place

Find position

c1 log2 n

Shuffle down

Overall

c2 n
c1 log2 n + c2 n

or

c2 n

Each add to the sorted array is O(n)

Can we maintain a sorted array

with cheaper insertions?

Dominant
term

Employing the Linear Search


X, familiar with a linear search, starts using it to search

for the coin among the group.


Let us assume the worst-case scenario of the coin being

with R.
If X was to start the search with A and progress linearly

through B, C, .M and finally to R, he would have taken


a minimum of 18 searches (R being the 18th person
searched in sequence to find the coin.

207

Employing the Linear Search


As you can see, this kind of search is not

very efficient especially when the number of


elements to be searched is high.
An eliminative search, also called a binary

search, provides a far better searching


mechanism.

208

Employing the Eliminative


or
Assume that X can pose intelligent questions to the
The
Binary
Search
audience to cut down on the number of searches.
A valid question that he could pose is Which side of the

audience has the coin?


A in the audience to the left of him says that his side of

the audience does not have the coin.


So X can completely do away with searching the

audience to his left. That saves him 9 searches.


209

Employing the Eliminative


or
X has now got to search the audience to the right of
The
Binary Search
him for searching out the coin.

Here too, he can split the audience into half by

standing adjacent to the middle row, and posing the


same question that he asked earlier Which side of
the audience has the coin?
M in the middle row replies that the coin is with the

audience to the left of him.


210

Employing the Eliminative


or
X has in the process now eliminated 6 more searches, that
The
Binary
Search
is, the middle row and the row to the left of the middle as
shown in the diagram below.

6 searches saved
X

NO

YES

211

Employing the Eliminative


or
X is now left with row to the right of the middle row
The
Binary
Search
containing P, Q, and R that has to be searched.
Here too, he can position himself right at the middle of

the row adjacent to Q and pose the same question,


Which side of the audience has the coin? Q replies

that the coin is to his left.


P

Q
NO

R
YES

212

Eliminative or Binary Search


That completes our eliminative or binary search.
It is called a binary search because at each stage of

the search, the search is cut by more than half.


This kind of search forms the basis for the searching

mechanism employed in a data structure wherein the


data is represented in a hierarchal manner unlike the
linear mechanism of storage employed in a linked list.

213

Binary Vs. Linear Search


As the name suggests, balanced binary search trees are

very useful for searching an element just as with a binary


search.
If we use linked lists for searching, we have to move

through the list linearly, one node at a time.


If we search an element in a binary search tree, we

move to the left subtree for smaller values, and to the


right subtree for larger values, every time reducing the
search list by half approximately.
214

Linear Search in a Linked


List

100

1 120

2 140

3 160

4 180

5 200

6 220

7 null

215

Binary Search in a Binary


Search Tree
Search Value

Search Value

Search Value

216

The Essence of a Binary


Search
To summarize, you have completely done away with

searching with the entire left subtree of the root node and
its descendant subtrees, in the process doing away with
searching one-half of the binary search tree.

Even while searching the right subtree of the root node and

its descendant subtrees, we keep searching only one-half


of the right subtree and its descendants.

This is more because of the search value in particular,

which is 7. The left subtree of the right subtree of the root


could have been searched in case the value being
searched for was say 5.
217

The Essence of a Binary


Search
Thus we can conclude that while searching for a

value in a balanced binary search tree, the number


of searches is cut by more than half (3 searches in a
balanced binary search tree) compared to searching
in a linked list (7 searches).
Thus a search that is hierarchical, eliminative

and binary in nature is far efficient when


compared to a linear search.

218

Searching - Re-visited
Binary tree O(log n) if it stays balanced
Simple binary tree good for static collections
Low (preferably zero) frequency of
insertions/deletions

but my collection keeps changing!

Its dynamic
Need to keep the tree balanced

First, examine some basic tree operations


Useful in several ways!

Tree Traversal
Traversal = visiting every node of a tree
Three basic alternatives

Pre-order

Root

Left sub-tree
Right sub-tree

x A +x+BC xDE F
L
L R L

Tree Traversal
Traversal = visiting every node of a tree
Three basic alternatives

In-order

Left sub-tree

Root
11

Right sub-tree

A x B+C xDxE +F
L

L
R

Tree Traversal
Traversal = visiting every node of a tree
Three basic alternatives
Post-order

Left sub-tree
Right sub-tree
Root

11

A
L

B C + D E xx F+x
L
R

Tree Traversal
Post-order

Left sub-tree
Right sub-tree
Root

Reverse-Polish

11

(A (((BC+)(DEx) x) F +)x )

Normal algebraic form


(A x(((B+C)(DxE))+F))

which traversal?

Trees -

Searching

Binary search tree


Produces a sorted list by in-order traversal

In order:

AD E G H K L M N O PT V

Trees -

Searching

Binary search tree


Preserving the order
Observe that this transformation preserves the
search tree

Trees -

Searching
Binary search tree

Preserving the order


Observe that this transformation preserves the
search tree

Weve performed a rotation of the sub-tree

about the T and O nodes

Trees -

Rotations
Binary search tree

Rotations can be either left- or right-rotations

For both trees: the inorder traversal is


AxByC

Trees -

Rotations

Binary search tree


Rotations can be either left- or right-rotations

Note that in this rotation, it was necessary to


move
B from the right child of x to the left child of y

Trees -

Red-Black Trees

A Red-Black Tree
Binary search tree
Each node is coloured red or black

An ordinary binary search tree with node colourings


to make a red-black tree

Trees -

Red-Black Trees

A Red-Black Tree

Every node is RED or BLACK


Every leaf is BLACK

When you examine


rb-tree code, you will
see sentinel nodes (black)
added as the leaves.
They contain no data.
Sentinel nodes (black)

Trees -

Red-Black Trees

A Red-Black Tree

Every node is RED or BLACK


Every leaf is BLACK
If a node is RED,
then both children
are BLACK

This implies that no path


may have two adjacent
RED nodes.
(But any number of BLACK
nodes may be adjacent.)

Trees -

Red-Black Trees

A Red-Black Tree

Every node is RED or BLACK


Every leaf is BLACK
If a node is RED,
then both children
are BLACK
Every path
from a node to a leaf
contains the same number
of BLACK nodes
From the root,
there are 3 BLACK nodes
on every path

Trees -

Red-Black Trees

A Red-Black Tree

Every node is RED or BLACK


Every leaf is BLACK
If a node is RED,
then both children
are BLACK
Every path
from a node to a leaf
contains the same number
of BLACK nodes

The length of this path is the


black height of the tree

Trees -

Red-Black Trees

Lemma
A RB-Tree with n nodes has
height 2 log(n+1)

Proof .. See Cormen

Essentially,

height 2 black height


Search time
O( log n )

Trees -

Red-Black Trees

Data structure

As well see, nodes in red-black trees need to know their


parents,
so we need this data structure

struct t_red_black_node {
enum { red, black } colour;
void *item;
struct t_red_black_node *left,
*right,
Same as a
*parent;
binary tree
}

with these two


attributes
added

Trees -

Insertion

Insertion of a new node


Requires a re-balance of the tree
rb_insert( Tree T, node x ) {
/* Insert in the tree in the usual way */
tree_insert( T, x );
/* Now restore the red-black property */
x->colour = red;

while ( (x != T->root) && (x->parent->colour == red) ) {


Insert
node
if ( x->parent == x->parent->parent->left ) {
4 /* If x's parent is a left, y is x's right 'uncle' */
Mark ity red
= x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour
= black;
Label the current
node
y->colour
= black;
x
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

Trees -

Insertion

rb_insert( Tree T, node x ) {


/* Insert in the tree in the usual way */
tree_insert( T, x );

/* Now restore the red-black property */


x->colour = red;
while ( (x != T->root) && (x->parent->colour == red) )
While

if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
we havent
reached the root
y->colour == red ) {
and if
xs(parent
is red
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent
x->parent->parent->colour
= red;
/* Move x up the tree */
x = x->parent->parent;

Trees -

Insertion

rb_insert( Tree T, node x ) {


/* Insert in the tree in the usual way */
tree_insert( T, x );
/* Now restore the red-black property */
x->colour = red;

while ( (x != T->root) && (x->parent->colour == red) )


if ( x->parent == x->parent->parent->left ) {
If x is to

/* If x's parent is a left, y is x's right 'uncle' */


y =left
x->parent->parent->right;
the
of its granparent
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
x->parent->parent
y->colour = black;
x->parent->parent->colour = red;
x->parent
/* Move x up the tree */
x = x->parent->parent;

Trees -

Insertion

/* Now restore the red-black property */


x->colour = red;

while ( (x != T->root) && (x->parent->colour == red) )


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle'
y = x->parent->parent->right;
if ( y->colour == red ) {
the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
x->parent->parent
/* Move x up the tree */
x = x->parent
x->parent->parent;

y is xs
/* right
case uncle
1 - change

right uncle

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) )


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle'
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
is
change
x red,
= x->parent->parent;

If the uncle
x->parent->parent
the colours of y, the grand-parent
and the parent
x->parent

right uncle

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) )


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle'
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
= red;
xsx->parent->parent->colour
parent is a left again,
/*mark
Move xs
x up
the tree */
uncle
x = x->parent->parent;

but the uncle is black this time

New x

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;

if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

.. but the uncle is black this time


and x is to the right of its parent
else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;

if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

.. So move x up and
rotate about x as root ...

else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;

if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;

if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
.. but
is still red
/* case 2 - move
x xs
up parent
and rotate
*/ ...
x = x->parent;
left_rotate( T, x );

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;

.. The uncle is black ..

else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

.. and x is to the left of its parent

uncle

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;
else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

.. So we have the final case ..

else { /* case 3 */
x->parent->colour = black;
x->parent->parent->colour = red;
right_rotate( T, x->parent->parent );
}

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;
else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

.. Change colours
and rotate ..

else { /* case 3 */
x->parent->colour = black;
x->parent->parent->colour = red;
right_rotate( T, x->parent->parent );
}

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;
else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

else { /* case 3 */
x->parent->colour = black;
x->parent->parent->colour = red;
right_rotate( T, x->parent->parent );
}

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;
else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

This is now a red-black tree ..


So were finished!

else { /* case 3 */
x->parent->colour = black;
x->parent->parent->colour = red;
right_rotate( T, x->parent->parent );
}

Trees -

Insertion

while ( (x != T->root) && (x->parent->colour == red) ) {


if ( x->parent == x->parent->parent->left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->parent->parent->right;
if ( y->colour == red ) {
/* case 1 - change the colours */
x->parent->colour = black;
y->colour = black;
x->parent->parent->colour = red;
/* Move x up the tree */
x = x->parent->parent;
else {
/* y is a black node */
if ( x == x->parent->right ) {
/* and x is to the right */
/* case 2 - move x up and rotate */
x = x->parent;
left_rotate( T, x );

Theres an equivalent set of


cases when the parent is to
the right of the grandparent!

else { /* case 3 */
x->parent->colour = black;
x->parent->parent->colour = red;
right_rotate( T, x->parent->parent );
}
}

else ....

Red-black trees -

Analysis

Addition

Insertion
Fix-up

Comparisons

At every stage,
x moves up the tree
at least one level

Overall

O(log n)

O(log n)
O(log n)

Deletion

Also

O(log n)

More complex
... but gives O(log n) behaviour in dynamic cases

Red Black Trees -

What you need to

know?
Code?

This is not a course for masochists!

You can find it in a text-book

You need to know


The algorithm exists
What its called
When to use it

ie what problem does it solve?

Its complexity
Basically how it works
Where to find an implementation

How to transform it to your application

También podría gustarte