You are on page 1of 597

By

Shital Dongre
Asst. Prof.
IT & MCA Dept,
VIT, Pune
In Semester Assessment End Semester
Assessment

Assignmen Lab Mid Group Viva/Lab End


t Assessmen Semester Discussion Exam Semester
(%) t Exam / Ppt (%) Exam
(%) (%) (%)

10 30 15 10 20 15

Hands on Theory

Lab GD Mid Assignmen End


Viva/Lab Assessment / Ppt Semester t (%) Semester
Exam (%) (%) Exam Exam
(%) (%) (%)

20 30 10 15 10 15
Program

Data

Algo
 Algo- Ways for Data transformation

 Data structure-
◦ Stores data
◦ makes algorithm simpler
◦ easier to maintain & often faster.
 Sophisticated data str- simpler the algo
 Simple algo- less expensive, less code
 Logic is simple- modifications are less likely
to introduce errors
 Easier to repair defects, make modifications,
or add enhancements
 Ex- 1. array 2. Stack ex- pile of plates, box of
books 3. Non-Linear data str- Tree- used for
indexing, routing table
Section 1: Arrays , Stack , Queue, Linked List

 Single and Multidimensional arrays, Time & Space Complexity


Analysis.
 Sorting Techniques: Insertion, Bucket, Merge, Quick and heap
sort.
 Search techniques Binary search, Fibonacci search.
 Linked Lists: Dynamic memory allocation, Singly Linked Lists,
Doubly linked Lists, Circular liked lists, and Generalized linked
lists, Applications of Linked list.
 Stack: stack representation using array and Linked list.
Applications of stack: Recursion, Validity of parentheses,
Expression conversions and evaluations, mazing problem.
 Queue: representation using array and Linked list, Types of
queue, Applications of Queue: Job Scheduling, Josephus problem
etc.
Section2: Trees, Graphs, Hashing

 Trees:- Basic terminology, representation using array and linked


list, Tree Traversals: Recursive And Non recursive, Operations on
binary tree: Finding Height, Leaf nodes, counting no of Nodes
etc., Construction of binary tree from traversals, Binary Search
trees(BST): Insertion, deletion of a node from BST. Threaded
Binary tree (TBT): Creation and traversals on TBT, AVL tree.
 Graph:-Terminology and representation, Traversals, Connected
components and Spanning trees: Prims and Kruskal‟s Algorithm,
Shortest Paths and Transitive Closures: Single Source All
destinations (Dijkstra‟sAlgorithm), all pair shortest path
algorithm, Topological Sort.
 Hasing:- Hashing techniques: Hash table, Hash functions, and
Collision, Cuckoo Hashing.
 An Array is a collection of variables of the
same type that are referred to through a
common name.
 Declaration
type var_name[size]

e.g

int A[6];
double d[15];

8
After declaration, array contains some garbage
value.

Static initialization
int month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

Run time initialization


int i;
int A[6];
for(i = 0; i < 6; i++)
A[i] = 6 - i;

9
int A[6]; A[0] A[1] A[2] A[3] A[4] A[5]
0x100 0x100 0x100 0x101 0x101 0x102
0 4 8 2 6 0
6 5 4 3 2 1

6 elements of 4 bytes each,


total size = 6 x 4 bytes = 24 bytes
Read an element
int tmp = A[2];

Write to an element

A[3] = 5;

10
 No “Strings” keyword
 A string is an array of characters.
OR
char string*+ = “hello world”;
char *string = “hello world”;

11
char string*+ = “hello world”;
printf(“%s”, string);

• Compiler has to know where the string ends


• „\0‟ denotes the end of string

Some more characters (do $man ascii):


„\n‟ = new line, „\t‟ = horizontal tab, „\v‟ =
vertical tab, „\r‟ = carriage return
„A‟ = 0x41, „a‟ = 0x61, „\0‟ = 0x00

12
• A char pointer points to a single byte.
• An int pointer points to first of the four bytes.
• A pointer itself has an address where it is stored
in the memory. Pointers are usually four bytes.
int *p;  int* p;
 * is called the dereference operator
• *p gives the value pointed by p
4 i
int i = 4;
p = &i; p
• & (ampersand) is called the reference operator
• &i returns the address of variable i

13
 A 32-bit system has 32 bit address space.
 To store any address, 32 bits are required.

 Pointer arithmetic : p+1 gives the next


memory location assuming cells are of the
same type as the base type of p.

14
int *p, x = 20;
p = &x;
printf("p = %p\n", p);
printf("p+1 = %p\n", (int*)p+1);
printf("p+1 = %p\n", (char*)p+1);
printf("p+1 = %p\n", (float*)p+1);
printf("p+1 = %p\n", (double*)p+1);
Sample output:
p = 0022FF70
p+1 = 0022FF74
p+1 = 0022FF71
p+1 = 0022FF74
p+1 = 0022FF78

15
Pointers and arrays are tightly coupled.
char a[] = “Hello World”;
char *p = &a[0];

16
 Idea:
◦ Repeatedly pass through the array
◦ Swaps adjacent elements that are out of order

i
1 2 3 n

8 4 6 9 2 3 1
j

 Easier to implement, but slower than


Insertion sort

17
8 4 6 9 2 3 1 1 8 4 6 9 2 3
i=1 j i=2 j

8 4 6 9 2 1 3 1 2 8 4 6 9 3
i=1 j i=3 j

8 4 6 9 1 2 3 1 2 3 8 4 6 9
i=1 j i=4 j

8 4 6 1 9 2 3 1 2 3 4 8 6 9
i=1 j i=5 j

8 4 1 6 9 2 3 1 2 3 4 6 8 9
i=1 j i=6 j

8 1 4 6 9 2 3 1 2 3 4 6 8 9
i=1 j i=7
j
1 8 4 6 9 2 3
i=1 j 18
Alg.: BUBBLESORT(A)
for i  1 to length[A]
do for j  length[A] downto i + 1
do if A[j] < A[j -1]
then exchange A[j]  A[j-1]
i
8 4 6 9 2 3 1
i=1 j

19
int d[3][2];

Access the point 1, 2 of the array:


d[1][2]

Initialize (without loops):


int d[3][2] = {{1, 2}, {4, 5}, {7, 8}};

20
A Multidimensional array is stored in a row major format.
A two dimensional case:
 next memory element to d[0][3] is d[1][0]

d[0][0] d[0][1] d[0][2] d[0][3]


d[1][0] d[1][1] d[1][2] d[1][3]
d[2][0] d[2][1] d[2][2] d[2][3]

What about memory addresses sequence of a three


dimensional array?
 next memory element to t[0][0][0] is t[0][0][1]
21
 Arrangement of data items in ascending or
descending order.
 For unstructured data or records, keys are
used to distinguish or sort items.
 Ex. Insertion, selection, bubble, merge etc.
 Idea: like sorting a hand of playing cards
◦ Start with an empty left hand and the cards facing
down on the table.
◦ Remove one card at a time from the table, and
insert it into the correct position in the left hand
 compare it with each of the cards already in the hand,
from right to left
◦ The cards held in the left hand are sorted
 these cards were originally the top cards of the pile on
the table

24
To insert 12, we need to
make room for it by
moving first 36 and then
24.

25
26
27
input array

5 2 4 6 1 3
at each iteration, the array is divided in two sub-arrays:

left sub-array right sub-array

sorted unsorted

28
29
Alg.: INSERTION-SORT(A) 1 2 3 4 5 6 7 8

for j ← 2 to n a1 a2 a3 a4 a5 a6 a7 a8
do key ← A[ j ]
key
Insert A[ j ] into the sorted sequence A[1 . . j -1]
i←j-1
while i > 0 and A[i] > key
do A[i + 1] ← A[i]
i←i–1
A[i + 1] ← key
 Insertion sort – sorts the elements in place

30
INSERTION-SORT(A) cost times
for j ← 2 to n c1 n
do key ← A[ j ] c2 n-1
Insert A[ j ] into the sorted sequence A[1 . . j 0
-1] n-1
i←j-1 c4 n-1
while i > 0 and A[i] > key c5 
n
tj
j2

do A[i + 1] ← A[i] c6 
n
( t j  1)
j2
i←i–1 c7 n
 (t  1)
A[i + 1] ← key c8 n-1
j2 j

tj: # of times the while statement is executed at iteration j


n n n

T ( n )  c 1 n  c 2 ( n  1 )  c 4 ( n  1 )  c 5  t j  c 6  t j  1   c 7  t j  1   c 8 ( n  1 )
j2 j2 j2

31
 Idea:
◦ Find the smallest element in the array
◦ Exchange it with the element in the first position
◦ Find the second smallest element and exchange it
with the element in the second position
◦ Continue until the array is sorted
 Disadvantage:
◦ Running time depends only slightly on the amount
of order in the file

32
8 4 6 9 2 3 1 1 2 3 4 9 6 8

1 4 6 9 2 3 8 1 2 3 4 6 9 8

1 2 6 9 4 3 8 1 2 3 4 6 8 9

1 2 3 9 4 6 8 1 2 3 4 6 8 9

33
Alg.: SELECTION-SORT(A)
8 4 6 9 2 3 1
n ← length[A]
for j ← 1 to n - 1
do smallest ← j
for i ← j + 1 to n
do if A[i] < A[smallest]
then smallest ← i
exchange A[j] ↔ A[smallest]

34
cost times
Alg.: SELECTION-SORT(A)
c1 1
n ← length[A]
c2 n
for j ← 1 to n - 1
do smallest ← j c3 n-1

n2/2 for i ← j + 1 to n c4 n 1
 j 1
( n  j  1)

comparisons do if A[i] < A[smallest] c5 n 1


 j 1
(n  j)

n then smallest ← i c6 
n 1

j 1
(n  j)

exchanges exchange A[j] ↔ A[smallest]c7 n-1


n 1 n 1 n 1

T ( n )  c 1  c 2 n  c 3 ( n  1)  c 4  ( n  j  1)  c 5  n  j   n  j  35
 c 7 ( n  1)   ( n )
2
c6
j 1 j 1 j2
 Insertion sort
◦ Design approach: incremental
◦ Sorts in place: Yes
◦ Best case: (n)
◦ Worst case:
(n2)

 Bubble Sort
◦ Design approach:
incremental
◦ Sorts in place:
Yes
◦ Running time:
(n2)

36
 Selection sort
◦ Design approach: incremental
◦ Sorts in place: Yes
◦ Running time:
(n2)

 Merge Sort
◦ Design approach:
divide and conquer
◦ Sorts in place:
No
◦ Running time:

37
 Bucket sort works by partitioning the
elements into buckets and the return the
result
 Buckets are assigned based on each
element‟s search key
 To return the result, concatenate each bucket
and return as a single array
 Some variations
◦ Make enough buckets so that each will only hold
one element, use a count for duplicates
◦ Use fewer buckets and then sort the contents of
each bucket

 The more buckets you use, the faster the


algorithm will run but it uses more memory
 Time complexity is reduced when the number
of items per bucket is evenly distributed and
as close to 1 per bucket as possible

 Buckets require extra space, so we are


trading increased space consumption for a
lower time complexity

 In fact Bucket Sort beats all other sorting


routines in time complexity but can require a
lot of space
 One value per bucket:
Multiple items per bucket:
In array form:
 Divide the problem into a number of sub-problems
◦ Similar sub-problems of smaller size

 Conquer the sub-problems


◦ Solve the sub-problems recursively
◦ Sub-problem size small enough  solve the problems in
straightforward manner

 Combine the solutions of the sub-problems


◦ Obtain the solution for the original problem

44
 To sort an array A[p . . r]:
 Divide
◦ Divide the n-element sequence to be sorted into
two subsequences of n/2 elements each
 Conquer
◦ Sort the subsequences recursively using merge sort
◦ When the size of the sequences is 1 there is
nothing more to do
 Combine
◦ Merge the two sorted subsequences

45
p q r
1 2 3 4 5 6 7 8

Alg.: MERGE-SORT(A, p, r) 5 2 4 7 1 3 2 6

if p < r Check for base case

then q ← (p + r)/2 Divide


MERGE-SORT(A, p, q) Conquer
MERGE-SORT(A, q + 1, r) Conquer

MERGE(A, p, q, r) Combine

 Initial call: MERGE-SORT(A, 1, n)

46
1 2 3 4 5 6 7 8

Divide 5 2 4 7 1 3 2 6 q=4

1 2 3 4 5 6 7 8

5 2 4 7 1 3 2 6

1 2 3 4 5 6 7 8

5 2 4 7 1 3 2 6

1 2 3 4 5 6 7 8

5 2 4 7 1 3 2 6

47
1 2 3 4 5 6 7 8

Conquer 1 2 2 3 4 5 6 7
and
Merge 1 2 3 4 5 6 7 8

2 4 5 7 1 2 3 6

1 2 3 4 5 6 7 8

2 5 4 7 1 3 2 6

1 2 3 4 5 6 7 8

5 2 4 7 1 3 2 6

48
1 2 3 4 5 6 7 8 9 10 11

4 7 2 6 1 4 7 3 5 2 6 q=6
Divide
1 2 3 4 5 6 7 8 9 10 11

q=3 4 7 2 6 1 4 7 3 5 2 6 q=9

1 2 3 4 5 6 7 8 9 10 11

4 7 2 6 1 4 7 3 5 2 6

1 2 3 4 5 6 7 8 9 10 11

4 7 2 6 1 4 7 3 5 2 6

1 2 4 5 7 8

4 7 6 1 7 3

49
1 2 3 4 5 6 7 8 9 10 11

Conquer 1 2 2 3 4 4 5 6 6 7 7
and
Merge 1 1 2

2
3

4
4

4
5

6
6

7
7

2
8

3
9

5
10

6
11

1 2 3 4 5 6 7 8 9 10 11

2 4 7 1 4 6 3 5 7 2 6

1 2 3 4 5 6 7 8 9 10 11

4 7 2 1 6 4 3 7 5 2 6

1 2 4 5 7 8

4 7 6 1 7 3

50
p q r
1 2 3 4 5 6 7 8

2 4 5 7 1 2 3 6

 Input: Array A and indices p, q, r such that


p≤q<r
◦ Subarrays A[p . . q] and A[q + 1 . . r] are sorted
 Output: One single sorted subarray A[p . .
r]

51
p q r
 Idea for merging: 1 2 3 4 5 6 7 8

2 4 5 7 1 2 3 6
◦ Two piles of sorted cards
 Choose the smaller of the two top cards
 Remove it and place it in the output pile
◦ Repeat the process until one pile is empty
◦ Take the remaining input pile and place it face-
down onto the output pile

A1 A[p, q]
A[p, r]

A2 A[q+1, r]

52
p q r
Alg.: MERGE(A, p, q, r) 1 2 3 4 5 6 7 8

2 4 5 7 1 2 3 6
1. Compute n1 and n2
2. Copy the first n1 elements into
n1 n2
L[1 . . n1 + 1] and the next n2 elements into R[1 . . n2
+ 1] p q

3. L[n1 + 1] ← ; R[n2 + 1] ← 
L 2 4 5 7 
4. i ← 1; j←1 q+1 r

5. for k ← p to r R 1 2 3 6 
6. do if L[ i ] ≤ R[ j ]
7. then A[k] ← L[ i ]
8. i ←i + 1
9. else A[k] ← R[ j ]
10. j←j+1
53
void merge(int a[], int low, int
high, int mid)
{
void mergesort(int a[], int low, int high)
int i,j,k,c[max];
{
i=low;
j=mid+1; int mid;
k=0; if(low<high)
while(i<=mid) && (j<=high) {
{ mid=(low+high)/2;
if(a[i]<a[j]) mergesort(a,low,mid);
c[k]=a[i++]; mergesort(a,mid+1,high);
else merge(a,low,high,mid);
c[k]=a[j++]; }
k++; }
}
while(i<=mid)
c[k++]=a[i++];
while(j<=high)
c[k++]=a[j++];
for(i=low,j=0;i<=high;i++,j++)
{
a[i]=c[j];
}
}
Given an array of n elements (e.g., integers):
 If array only contains one element, return
 Else
◦ pick one element to use as pivot.
◦ Partition elements into two sub-arrays:
 Elements less than or equal to pivot
 Elements greater than pivot
◦ Quicksort two sub-arrays
◦ Return results
We are given array of n integers to sort:

40 20 10 80 60 50 7 30 100
There are a number of ways to pick the pivot
element. In this example, we will use the first
element in the array:

40 20 10 80 60 50 7 30 100
Given a pivot, partition the elements of the
array such that the resulting array consists
of:
1. One sub-array that contains elements >= pivot
2. Another sub-array that contains elements <
pivot

The sub-arrays are stored in the original data


array.

Partitioning loops through, swapping elements


below/above pivot.
pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index

pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index

pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index

pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index

pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index

pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]

pivot_index = 040 20 10 80 60 50 7 30 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 60 50 7 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.
5. Swap data[too_small_index] and data[pivot_index]

pivot_index = 040 20 10 30 7 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
1. While data[too_big_index] <= data[pivot]
++too_big_index
2. While data[too_small_index] > data[pivot]
--too_small_index
3. If too_big_index < too_small_index
swap data[too_big_index] and data[too_small_index]
4. While too_small_index > too_big_index, go to 1.
5. Swap data[too_small_index] and data[pivot_index]

pivot_index = 4 7 20 10 30 40 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

too_big_index too_small_index
7 20 10 30 40 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

<= data[pivot] > data[pivot]


7 20 10 30 40 50 60 80 100

[0] [1] [2] [3] [4] [5] [6] [7] [8]

<= data[pivot] > data[pivot]


 Def: Full binary tree = a 4

binary tree in which each


1 3
node is either a leaf or has
2 16 9 10
degree exactly 2.
14 8 12 7
Full binary tree

 Def: Complete binary tree = a 4

binary tree in which all


1 3
leaves are on the same level
2 16 9 10
and all internal nodes have
degree 2. Complete binary tree

86
 Def: A heap is a nearly complete binary tree
with the following two properties:
◦ Structural property: all levels are full, except
possibly the last one, which is filled from left to
right
◦ Order (heap) property: for any node x
Parent(x) ≥ x
8 From the heap
property, it follows
7 4 that:
5 2
“The root is the
maximum
Heap
element of the heap!”
A heap is a binary tree that is filled 87in order
 A heap can be stored as an
array A.
◦ Root of tree is A[1]
◦ Left child of A[i] = A[2i]
◦ Right child of A[i] = A[2i + 1]
◦ Parent of A[i] = A[ i/2 ]
◦ Heapsize[A] ≤ length[A]
 The elements in the
subarray A[(n/2+1) .. n]
are leaves

88
 Max-heaps (largest element at root), have the
max-heap property:
◦ for all nodes i, excluding the root:
A[PARENT(i)] ≥ A[i]

 Min-heaps (smallest element at root), have


the min-heap property:
◦ for all nodes i, excluding the root:
A[PARENT(i)] ≤ A[i]

89
 New nodes are always inserted at the bottom
level (left to right)
 Nodes are removed from the bottom level
(right to left)

90
 Maintain/Restore the max-heap property
◦ MAX-HEAPIFY
 Create a max-heap from an unordered array
◦ BUILD-MAX-HEAP
 Sort an array in place
◦ HEAPSORT
 Priority queues

91
 Suppose a node is smaller than a
child
◦ Left and Right subtrees of i are max-
heaps
 To eliminate the violation:
◦ Exchange with larger child
◦ Move down the tree
◦ Continue until node is not smaller than
children

92
MAX-HEAPIFY(A, 2, 10)

A[2]  A[4]

A[2] violates the heap property A[4] violates the heap property

A[4]  A[9]

Heap property restored


93
 Assumptions: Alg: MAX-HEAPIFY(A, i, n)
◦ Left and Right 1. l ← LEFT(i)
subtrees of i 2. r ← RIGHT(i)
are max-heaps
3. if l ≤ n and A[l] > A[i]
◦ A[i] may be
smaller than its 4. then largest ←l
children 5. else largest ←i
6. if r ≤ n and A[r] > A[largest]
7. then largest ←r
8. if largest  i
9. then exchange A[i] ↔ A[largest]
10. MAX-HEAPIFY(A, largest,
n)
94
 Goal:
◦ Sort an array using heap representations
 Idea:
◦ Build a max-heap from the array
◦ Swap the root (the maximum element) with the last
element in the array
◦ “Discard” this last node by decreasing the heap size
◦ Call MAX-HEAPIFY on the new root
◦ Repeat this process until only one node remains

95
MAX-HEAPIFY(A, 1, 4) MAX-HEAPIFY(A, 1, 3) MAX-HEAPIFY(A, 1, 2)

MAX-HEAPIFY(A, 1, 1)

96
1. BUILD-MAX-HEAP(A) O(n)
2. for i ← length[A] downto 2
3. do exchange A[1] ↔ A[i] n-1 times
4. MAX-HEAPIFY(A, 1, i - 1)
O(lgn)

 Running time: O(nlgn) --- Can


be shown to be Θ(nlgn)

97
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Algorithm maintains a[lo]  value  a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.

 Invariant. Algorithm maintains a[lo]  value 


a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo mid hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.

6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo mid hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.

6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo mid hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo
hi
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo
hi
mid
 Binary search. Given value and sorted array
a[], find index i

such that a[i] = value, or report that no such


index exists.
 Invariant. Algorithm maintains a[lo]  value 
a[hi].

 Ex. Binary search for 33.


6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

lo
hi
mid
low = 0;
high = length - 1;
while (low <= high) {
mid = (low + high) / 2;
if (a[mid] < target) {
low = mid + 1;
} else if (a[mid] > target) {
high = mid - 1;
} else {
return mid; // target found
}
}
Similarities with Binary Search:
 Works for sorted arrays
 A Divide and Conquer Algorithm.
 Has Log n time complexity.
Differences with Binary Search:
 Fibonacci Search divides given array in unequal parts
 Binary Search uses division operator to divide range.
Fibonacci Search doesn‟t use /, but uses + and -. The
division operator may be costly on some CPUs.
 Fibonacci Search examines relatively closer elements
in subsequent steps. So when input array is big that
cannot fit in CPU cache or even in RAM, Fibonacci
Search can be useful.
 Fibonacci Numbers are recursively defined as
F(n) = F(n-1) + F(n-2), F(0) = 0, F(1) = 1.
First few Fibinacci Numbers are 0, 1, 1, 2, 3,
5, 8, 13, 21, 34, 55, 89, 144, …
 Below observation is used for range
elimination, and hence for the O(log(n))
complexity.
 F(n - 2) &approx; (1/3)*F(n) and F(n - 1)
&approx; (2/3)*F(n).
 Let the searched element be x.
 The idea is to first find the smallest
Fibonacci number that is greater than or
equal to the length of given array. Let the
found Fibonacci number be fib (m‟th
Fibonacci number). We use (m-2)‟th
Fibonacci number as the index (If it is a
valid index). Let (m-2)‟th Fibonacci
Number be i, we compare arr[i] with x, if x
is same, we return i. Else if x is greater, we
recur for subarray after i, else we recur for
subarray before i.
 Below is the complete algorithm
Let arr[0..n-1] be the input array and element to be searched be x.
 Find the smallest Fibonacci Number greater than or equal to n. Let
this number be fibM [m‟th Fibonacci Number]. Let the two Fibonacci
numbers preceding it be fibMm1 [(m-1)‟th Fibonacci Number] and
fibMm2 [(m-2)‟th Fibonacci Number].
 While the array has elements to be inspected:
◦ Compare x with the last element of the range covered by fibMm2
◦ If x matches, return index
◦ Else If x is less than the element, move the three Fibonacci variables two
Fibonacci down, indicating elimination of approximately rear two-third of
the remaining array.
◦ Else x is greater than the element, move the three Fibonacci variables one
Fibonacci down. Reset offset to index. Together these indicate elimination
of approximately front one-third of the remaining array.
 Since there might be a single element remaining for comparison,
check if fibMm1 is 1. If Yes, compare x with that remaining element.
If match, return index.
Ex
A={10,22,35,40,45,50,80,82,85,90,100},
1 2 3 4 5 6 7 8 9 10 11
10 22 35 40 45 50 80 82 85 90 100

X=85
N=11
Fib=0,1,1,2,3,5,8,13,21,34
 Fib(7)=13 >11
 (m-1)= 8, (m-2)=5
 i=min(offset+m2,n)
 Offset-It marks the range that has been
eliminated, starting from the front. We will
update it time to time.

Input Algorithm Output

An algorithm is a step-by-step procedure for


solving a problem in a finite amount of time.
 Given 2 or more algorithms to solve the
same problem, how do we select the best
one?
 Some criteria for selecting an algorithm
1) Is it easy to implement, understand, modify?
2) How long does it take to run it to completion?
3) How much of computer memory does it use?
 Software engineering is primarily
concerned with the first criteria
 In this course we are interested in the
second and third criteria
 Time complexity
◦ The amount of time that an algorithm needs to
run to completion
 Space complexity
◦ The amount of memory an algorithm needs to
run
 We will occasionally look at space
complexity, but we are mostly interested in
time complexity in this course
 Thus in this course the better algorithm is
the one which runs faster (has smaller time
complexity)
1
1
Analysis of Algorithms 6
 Most algorithms transform input objects into
output objects
sorting
5 3 1 2 1 2 3 5
algorithm
input object output object

 The running time of an algorithm typically


grows with the input size
 idea: analyze running time as a function of input size

Analysis of Algorithms 117


 Even on inputs of the same size, running time
can be very different
◦ Example: algorithm that finds the first prime number
in an array by scanning it left to right
 Idea: analyze running time in the
 best case
 worst case
 average case

Analysis of Algorithms 118


 Best case running best case

time is usually useless average case


worst case

Average case time is


120

very useful but often
100

Running Time
80

difficult to determine 60

 We focus on the worst 40

case running time 20

◦ Easier to analyze
0
1000 2000 3000 4000

◦ Crucial to applications
Input Size

such as games, finance


and robotics

Analysis of Algorithms 119


 When we analyze algorithms, we should
employ mathematical techniques that
analyze algorithms independently of
specific implementations, computers, or
data.

 To analyze algorithms:
◦ First, we start to count the number of
significant operations in a particular solution
to assess its efficiency.
◦ Then, we will express the efficiency of
algorithms using growth functions.

12
0
 Each operation in an algorithm (or a program) has a
cost.
 Each operation takes a certain of time.

count = count + 1;  take a certain amount of time, but it is


constant

A sequence of operations:

count = count + 1; Cost: c1


sum = sum + count; Cost: c2

 Total Cost = c1 + c2

12
1
Example: Simple If-Statement
Cost Times
if (n < 0) c1 1
absval = -n c2 1
else
absval = n; c3 1

Total Cost <= c1 + max(c2,c3)

12
2
Example: Simple Loop
Cost Times
i = 1; c1 1
sum = 0; c2 1
while (i <= n) { c3 n+1
i = i + 1; c4 n
sum = sum + i; c5 n
}

Total Cost = c1 + c2 + (n+1)*c3 + n*c4 + n*c5


 The time required for this algorithm is proportional
to n

12
3
Example: Nested Loop
Cost Times
i=1; c1 1
sum = 0; c2 1
while (i <= n) { c3 n+1
j=1; c4 n
while (j <= n) { c5 n*(n+1)
sum = sum + i; c6 n*n
j = j + 1; c7 n*n
}
i = i +1; c8 n
}
Total Cost = c1 + c2 + (n+1)*c3 + n*c4 +
n*(n+1)*c5+n*n*c6+n*n*c7+n*c8
 The time required for this algorithm is proportional to n2

12
4
 Loops: The running time of a loop is at most the
running time of the statements inside of that loop
times the number of iterations.
 Nested Loops: Running time of a nested loop
containing a statement in the inner most loop is the
running time of statement multiplied by the product
of the sized of all loops.
 Consecutive Statements: Just add the running times
of those consecutive statements.
 If/Else: Never more than the running time of the test
plus the larger of running times of S1 and S2.

12
5
 We measure an algorithm‟s time requirement as a function
of the problem size.
◦ Problem size depends on the application: e.g. number of elements
in a list for a sorting algorithm, the number disks for towers of
hanoi.
 So, for instance, we say that (if the problem size is n)
◦ Algorithm A requires 5*n2 time units to solve a problem of size n.
◦ Algorithm B requires 7*n time units to solve a problem of size n.
 The most important thing to learn is how quickly the
algorithm‟s time requirement grows as a function of the
problem size.
◦ Algorithm A requires time proportional to n2.
◦ Algorithm B requires time proportional to n.
 An algorithm‟s proportional time requirement is known as
growth rate.
 We can compare the efficiency of two algorithms by
comparing their growth rates.

12
6
Time requirements as a function of the problem size n

12
7
Function Growth Rate Name
c Constant
log N Logarithmic
log2N Log-squared
N Linear
N log N
N2 Quadratic
N3 Cubic
2N Exponential
128
CENG 213 Data Structures
 The big-Oh notation gives an upper bound on the
growth rate of a function
 The statement “f(n) is O(g(n))” means that the
growth rate of f(n) is no more than the growth
rate of g(n)
 We can use the big-Oh notation to rank functions
according to their growth rate
f(n) is O(g(n)) g(n) is O(f(n))
g(n) grows more Yes No
f(n) grows more No Yes
Same growth Yes Yes
12
Analysis of Algorithms 9
- Shital Dongre
- Assistant Professor
- VIT, Pune.
What is a stack?
 linear data structure
 It is an ordered group of homogeneous items
of elements.
 Elements are added to and removed from the
top of the stack
 Stack principle: LAST IN FIRST OUT(LIFO)
 It means the last element inserted is the first
one to be removed
 Ex- stack of plates
Last In First Out

4 top
3 top 3 3 top
2 top
2 2 2
1 top 1 1 1 1
Applications of stack
 Balancing of symbols
 Infix to Postfix /Prefix conversion
 Redo-undo features at many places like in editors.
 Forward and backward feature in web browsers
 Used in many algorithms like Tower of
Hanoi, tree traversals, topological graph sorting
etc.
 Other applications can be Backtracking, N queen
problem etc.
Operations on stack
 isEmpty
 Push
 Pop
 isFull
 Below is the complete algorithm
Let arr[0..n-1] be the input array and element to be searched be x.
 Find the smallest Fibonacci Number greater than or equal to n. Let this
number be fibM [m’th Fibonacci Number]. Let the two Fibonacci numbers
preceding it be fibMm1 [(m-1)’th Fibonacci Number] and fibMm2 [(m-2)’th
Fibonacci Number].
 While the array has elements to be inspected:
 Compare x with the last element of the range covered by fibMm2
 If x matches, return index
 Else If x is less than the element, move the three Fibonacci variables two Fibonacci
down, indicating elimination of approximately rear two-third of the remaining array.
 Else x is greater than the element, move the three Fibonacci variables one Fibonacci
down. Reset offset to index. Together these indicate elimination of approximately
front one-third of the remaining array.
 Since there might be a single element remaining for comparison, check if
fibMm1 is 1. If Yes, compare x with that remaining element. If match, return
index.
 i=min(offset+m2,n)
 Offset-It marks the range that has been eliminated,
starting from the front. We will update it time to time.

isEmpty - Returns true(1) if stack is empty,
else false(0).

int isEmpty()
{
if (top==-1)
return 1;
else
return 0;
}

#define MAX_STACK_SIZE 100

int top= -1

int stack[MAX_STACK_SIZE]
isFull - Returns true(1) if stack is Full,
else false(0).

int isFull()
{
if (top==(MAX_STACK_SIZE -1))
return 1;
else
return 0;
}
 Push- Add item in stack

void push( int num)


{
if(isFull())
printf(“\n Stack is Full”);

top = top + 1;
stack[top] = num;
}
 Pop- Remove item from stack

int pop()
{
int num;

if(isEmpty())
printf(“\n Stack is empty”);

num=stack[top];
top--;
return num;
}
Stack using Linked list
 Extend stack size dynamically

 isFull() - condition not applicable

 isEmpty()- head node not available


void push(struct Node** head, int data)
{
struct Node* node = (struct
Node*)malloc(sizeof (struct Node));
node->data =data;
node->next = *head;
*head = node; //top
}

Push(..,3)
void pop(struct Node** head)
{
if (isEmpty(*head))
printf(“ Stack is Empty”);

struct Node* temp = *head;


*head = (*head)->next;
int num = temp->data;
free(temp);
printf(“ Popped element: %d”, num);
}
pop
1. Infix to Postfix

2. Postfix to Infix
Input : abc++
Output : (a + (b + c))

Input : ab*c+
Output : ((a*b)+c)
1.While there are input symbol left
…1.1 Read the next symbol from the input.
2.If the symbol is an operand
…2.1 Push it onto the stack.
3.Otherwise,
…3.1 the symbol is an operator.
…3.2 Pop the top 2 values from the stack.
…3.3 Put the operator, with the values as arguments and form a string.
…3.4 Push the resulted string back to stack.
4.If there is only one value in the stack
…4.1 That value in the stack is the desired infix string.
3. Solving postfix expression
The Postfix notation is used to represent algebraic expressions. The expressions
written in postfix form are evaluated faster compared to infix notation as parenthesis
are not required in postfix.
Following is algorithm for evaluation postfix expressions.
1) Create a stack to store operands (or values).
2) Scan the given expression and do following for every scanned element.
…..a) If the element is a number, push it into the stack
…..b) If the element is a operator, pop operands for the operator from stack. Evaluate
the operator and push the result back to the stack
3) When the expression is ended, the number in the stack is the final answer

“2 3 1 * + 9 -“
abc-+de-fg-h+/*

Expression Stack

abc-+de-fg-h+/* NuLL

bc-+de-fg-h+/* "a"

c-+de-fg-h+/* "b"
"a"

"c"
-+de-fg-h+/*
"b"
"a"

+de-fg-h+/* "b - c"


"a"
de-fg-h+/*
"a+b-c"

e-fg-h+/* "d"
"a+b-c"

"e"
-fg-h+/*
"d"
"a+b-c"

fg-h+/* "d - e"


"a+b-c"

"f"
g-h+/*
"d - e"
"a+b-c"

"g"
-h+/* "f"
"d - e"
"a+b-c"

"f-g"
h+/*
"d - e"
"a+b-c"

"h"
+/* "f-g"
"d - e"
"a+b-c"
"f-g+h"
/*
"d - e"
"a+b-c"

* "(d-e)/(f-g-h)"
"a+b-c"
Null
"(a+b-c)*(d-e)/(f-g+h)"

Ans = (a+b-c)*(d-e)/(f-g+h)

for (i = 0; exp[i]; ++i)

{
// If the scanned character is an operand (number here),
// push it to the stack.
if (isdigit(exp[i]))
push(stack, exp[i] - '0');

// If the scanned character is an operator, pop two


// elements from stack apply the operator
else
{
int val1 = pop(stack);
int val2 = pop(stack);
switch (exp[i])
{
case '+': push(stack, val2 + val1); break;
case '-': push(stack, val2 - val1); break;
case '*': push(stack, val2 * val1); break;
case '/': push(stack, val2/val1); break;
}
}
}

4. Infix to Prefix
Input : A * B + C / D
Output : + * A B/ C D

Input : (A - B/C) * (A/K-L)


Output : *-A/BC-/AKL
Step 1: Reverse the infix expression i.e A+B*C will become C*B+A. Note while
reversing each ‘(‘ will become ‘)’ and each ‘)’ becomes ‘(‘.
Step 2: Obtain the postfix expression of the modified expression i.e CB*A+.
Step 3: Reverse the postfix expression. Hence in our example prefix is +A*BC.

5. Prefix to Infix
6. Prefix to Postfix
Input : Prefix : *+AB-CD
Output : Postfix : AB+CD-*
Explanation : Prefix to Infix : (A+B) * (C-D)
Infix to Postfix : AB+CD-*

Input : Prefix : *-A/BC-/AKL


Output : Postfix : ABC/-AK/L-*
Explanation : Prefix to Infix : A-(B/C)*(A/K)-L
Infix to Postfix : ABC/-AK/L-*
 Read the Prefix expression in reverse order (from right to left)
 If the symbol is an operand, then push it onto the Stack
 If the symbol is an operator, then pop two operands from the Stack
Create a string by concatenating the two operands and the operator after them.
string = operand1 + operand2 + operator
And push the resultant string back to Stack
 Repeat the above steps until end of Prefix expression.

7. Postfix to Prefix
Input : Postfix : AB+CD-*
Output : Prefix : *+AB-CD
Explanation : Postfix to Infix : (A+B) * (C-D)
Infix to Prefix : *+AB-CD

Input : Postfix : ABC/-AK/L-*


Output : Prefix : *-A/BC-/AKL
Explanation : Postfix to Infix : ((A-(B/C))*((A/K)-L))
Infix to Prefix : *-A/BC-/AKL
 Read the Postfix expression from left to right
 If the symbol is an operand, then push it onto the Stack
 If the symbol is an operator, then pop two operands from the Stack
Create a string by concatenating the two operands and the operator before
them.
string = operator + operand2 + operand1
And push the resultant string back to Stack
 Repeat the above steps until end of Prefix expression.
Infix Expression Prefix Expression Postfix Expression

A+B +AB AB+

A+B*C +A*BC ABC*+

Infix Expression Prefix Expression Postfix Expression

(A + B) * C *+ABC AB+C*

Infix Expression Prefix Expression Postfix Expression

A+B*C+D ++A*BCD ABC*+D+

(A + B) * (C + D) *+AB+CD AB+CD+*

A*B+C*D +*AB*CD AB*CD*+

A+B+C+D +++ABCD AB+C+D+


Infix to Postfix Conversion
This problem requires you to write a program to convert an infix expression to a postfix expression. The
evaluation of an infix expression such as A + B * C requires knowledge of which of the two operations, +
and *, should be performed first. In general, A + B * C is to be interpreted as A + ( B * C ) unless
otherwise specified. We say that multiplication takes precedence over addition. Suppose that we would
now like to convert A + B * C to postfix. Applying the rules of precedence, we first convert the portion of
the expression that is evaluated first, namely the multiplication. Doing this conversion in stages, we obtain

A +B *C Given infix form


A +BC* Convert the multiplication
A B C* + Convert the addition

The major rules to remember during the conversion process are that the operations with highest precedence
are converted first and that after a portion of an expression has been converted to postfix, it is to be treated
as a single operand. Let us now consider the same example with the precedence of operators reversed by
the deliberate insertion of parentheses.

(A+B)*C Given infix form


AB+* C Convert the addition
AB+C * Convert the multiplication

Note that in the conversion from AB + * C to AB + C *, AB+ was treated as a single operand. The rules
for converting from infix to postfix are simple, provided that you know the order of precedence.

We consider five binary operations: addition, subtraction, multiplication, division, and exponentiation.
These operations are denoted by the usual operators, +, –, *, /, and ^, respectively. There are three levels of
operator precedence. Both * and / have higher precedence than + and –. ^ has higher precedence than *
and /. Furthermore, when operators of the same precedence are scanned, +, –, * and / are left associative,
but ^ is right associative. Parentheses may be used in infix expressions to override the default precedence.

The postfix form requires no parentheses. The order of the operators in the postfix expressions determines
the actual order of operations in evaluating the expression, making the use of parentheses unnecessary.

Input
A collection of error-free simple arithmetic expressions. Expressions are presented one per line. The input
has an arbitrary number of blanks between any two symbols. A symbol may be a letter (A – Z), an operator
(+, – , *, or /), a left parenthesis, or a right parenthesis. Each operand is composed of a single letter. The
input expressions are in infix notation.

Example
A + B – C
A + B * C
(A + B) / (C – D)
( ( A + B ) * ( C – D ) + E ) / (F + G)

Output
Your output will consist of the input expression, followed by its corresponding postfix expression. All
output (including the original infix expression) must be clearly formatted (or reformatted) and also clearly
labeled.
Example
(Only the four postfix expressions corresponding to the above sample input are shown here.)
AB+C–
ABC*+
AB+CD-/
AB+CD-*E+FG+/

Discussion
In converting infix expressions to postfix notation, the following fact should be taken into consideration: In
infix form, the order of applying operators is governed by the possible appearance of parentheses and the
operator precedence relations; however, in postfix form, the order is simply the “natural” order – i.e., the
order of appearance from left to right.

Accordingly, subexpressions within innermost parentheses must first be converted to postfix, so that they
can then be treated as single operands. In this fashion, parentheses can be successively eliminated until the
entire expression has been converted. The last pair of parentheses to be opened within a group of nested
parentheses encloses the first subexpression within the group to be transformed. This last-in, first-out
behavior should immediately suggest the use of a stack.

Your program should utilize the basic stack methods. You will need to PUSH certain symbols on the stack,
POP symbols, test to see if the stack is EMPTY, look at the TOP element of the stack, etc.

In addition, you must devise a boolean method that takes two operators and tells you which has higher
precedence. This will be helpful, because in Rule 3 below, you need to compare the next symbol to the one
on the top of the stack. [Question: what precedence do you assign to ‘(‘? You need to answer this question
since ‘(‘ may be on top of the stack.]

You should formulate the conversion algorithm using the following six rules:

1. Scan the input string (infix notation) from left to right. One pass is sufficient.
2. If the next symbol scanned is an operand, it may be immediately appended to the postfix string.
3. If the next symbol is an operator,
i. Pop and append to the postfix string every operator on the stack that
a. is above the most recently scanned left parenthesis, and
b. has precedence higher than or is a right-associative operator of equal precedence to that of the
new operator symbol.
ii. Push the new operator onto the stack.
4. When a left parenthesis is seen, it must be pushed onto the stack.
5. When a right parenthesis is seen, all operators down to the most recently scanned left parenthesis must
be popped and appended to the postfix string. Furthermore, this pair of parentheses must be discarded.
6. When the infix string is completely scanned, the stack may still contain some operators. [Why are there
no parentheses on the stack at this point?] All the remaining operators should be popped and appended
to the postfix string.

Examples
Here are two examples to help you understand how the algorithm works. Each line below demonstrates the
state of the postfix string and the stack when the corresponding next infix symbol is scanned. The
rightmost symbol of the stack is the top symbol. The rule number corresponding to each line demonstrates
which of the six rules was used to reach the current state from that of the previous line.

Example 1
Input expression: A + B * C / D - E
Next Symbol Postfix String Stack Rule
A A 2
+ A + 3
B AB + 2
* AB +* 3
C ABC +* 2
/ ABC* +/ 3
D ABC*D +/ 2
- ABC*D/+ - 3
E ABC*D/+E - 2
ABC*D/+E- 6

Example 2
Input expression: ( A + B * ( C - D ) ) / E.

Next Symbol Postfix String Stack Rule


( ( 4
A A ( 2
+ A (+ 3
B AB (+ 2
* AB (+* 3
( AB (+*( 4
C ABC (+*( 2
- ABC (+*(- 3
D ABCD (+*(- 2
) ABCD- (+* 5
) ABCD-*+ 5
/ ABCD-*+ / 3
E ABCD-*+E / 2
ABCD-*+E/ 6
Data Structures and Algorithms
V22.0102
Otávio Braga
Infix to Postfix Conversion
• We use a stack
• When an operand is read, output it
• When an operator is read
– Pop until the top of the stack has an element of lower
precedence
– Then push it
• When ) is found, pop until we find the matching (
• ( has the lowest precedence when in the stack
• but has the highest precedence when in the input
• When we reach the end of input, pop until the stack is
empty
Infix to Postfix Conversion
Example 1
• 3+4*5/6
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack:
• Output:
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack:
• Output: 3
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: +
• Output: 3
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: +
• Output: 3 4
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: + *
• Output: 3 4
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: + *
• Output: 3 4 5
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: +
• Output: 3 4 5 *
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: + /
• Output: 3 4 5 *
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: + /
• Output: 3 4 5 * 6
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack: +
• Output: 3 4 5 * 6 /
Infix to Postfix Conversion
Example 1
• 3+4*5/6
• Stack:
• Output: 3 4 5 * 6 / +

• Done!
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack:
• Output:
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: (
• Output:
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: (
• Output: 300
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: ( +
• Output: 300
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: ( +
• Output: 300 23
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: (
• Output: 300 23 +
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack:
• Output: 300 23 +
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: *
• Output: 300 23 +
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: * (
• Output: 300 23 +
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: * (
• Output: 300 23 + 43
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: * ( -
• Output: 300 23 + 43
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: * ( -
• Output: 300 23 + 43 21
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: * (
• Output: 300 23 + 43 21 -
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: *
• Output: 300 23 + 43 21 -
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack:
• Output: 300 23 + 43 21 - *
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: /
• Output: 300 23 + 43 21 - *
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: / (
• Output: 300 23 + 43 21 - *
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: / (
• Output: 300 23 + 43 21 - * 84
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: / ( +
• Output: 300 23 + 43 21 - * 84
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: / ( +
• Output: 300 23 + 43 21 - * 84 7
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: / (
• Output: 300 23 + 43 21 - * 84 7 +
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack: /
• Output: 300 23 + 43 21 - * 84 7 +
Infix to Postfix Conversion
Example 2
• (300+23)*(43-21)/(84+7)
• Stack:
• Output: 300 23 + 43 21 - * 84 7 + /

• Done!
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack:
• Output:
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: (
• Output:
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: (
• Output: 4
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: ( +
• Output: 4
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: ( +
• Output: 4 8
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: (
• Output: 4 8 +
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack:
• Output: 4 8 +
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: *
• Output: 4 8 +
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: * (
• Output: 4 8 +
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: * (
• Output: 4 8 + 6
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: * ( -
• Output: 4 8 + 6
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: * ( -
• Output: 4 8 + 6 5
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: * (
• Output: 4 8 + 6 5 -
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: *
• Output: 4 8 + 6 5 -
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack:
• Output: 4 8 + 6 5 - *
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: /
• Output: 4 8 + 6 5 - *
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / (
• Output: 4 8 + 6 5 - *
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( (
• Output: 4 8 + 6 5 - *
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( (
• Output: 4 8 + 6 5 - * 3
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( ( -
• Output: 4 8 + 6 5 - * 3
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( ( -
• Output: 4 8 + 6 5 - * 3 2
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( (
• Output: 4 8 + 6 5 - * 3 2 -
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / (
• Output: 4 8 + 6 5 - * 3 2 -
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( *
• Output: 4 8 + 6 5 - * 3 2 -
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( * (
• Output: 4 8 + 6 5 - * 3 2 -
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( * (
• Output: 4 8 + 6 5 - * 3 2 - 2
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( * ( +
• Output: 4 8 + 6 5 - * 3 2 - 2
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( * ( +
• Output: 4 8 + 6 5 - * 3 2 – 2 2
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( * (
• Output: 4 8 + 6 5 - * 3 2 – 2 2 +
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / ( *
• Output: 4 8 + 6 5 - * 3 2 – 2 2 +
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: / (
• Output: 4 8 + 6 5 - * 3 2 – 2 2 + *
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack: /
• Output: 4 8 + 6 5 - * 3 2 – 2 2 + *
Infix to Postfix Conversion
Example 3
• (4+8)*(6-5)/((3-2)*(2+2))
• Stack:
• Output: 4 8 + 6 5 - * 3 2 – 2 2 + * /

• Done!
Balanced Parentheses
(5+6)∗(7+8)/(4+3) balanced
2 * ( ( 4/2 ) + 5 not balanced
((()[]()())

{((()))}

(()((())()))

Algorithm:
 Declare a character stack S.
 Now traverse the expression string exp.
1. If the current character is a starting bracket (‘(‘ or ‘{‘ or ‘[‘) then push it
to stack.
2. If the current character is a closing bracket (‘)’ or ‘}’ or ‘]’) then pop
from stack and if the popped character is the matching starting bracket
then fine else brackets are not balanced.
 After complete traversal, if there is some starting bracket left in stack then
“not balanced”
Parenthesis checking

bool areParanthesisBalanced(string expr)


{
stack<char> s;
char x;

// Traversing the Expression


for (int i=0; i<expr.length(); i++)
{
if (expr[i]=='('||expr[i]=='['||expr[i]=='{')
{
// Push the element in the stack
s.push(expr[i]);
continue;
}

// IF current current character is not opening


// bracket, then it must be closing. So stack
// cannot be empty at this point.
if (s.empty())
return false;

switch (expr[i])
{
case ')':

// Store the top element in a


x = s.top();
s.pop();
if (x=='{' || x=='[')
return false;
break;

case '}':

// Store the top element in b


x = s.top();
s.pop();
if (x=='(' || x=='[')
return false;
break;

case ']':

// Store the top element in c


x = s.top();
s.pop();
if (x =='(' || x == '{')
return false;
break;
}
}

// Check Empty Stack


return (s.empty());
}

// Driver program to test above function


int main()
{
string expr = "{()}[]";

if (areParanthesisBalanced(expr))
cout << "Balanced";
else
cout << "Not Balanced";
return 0;
}

Infix to post fix

#include<stdio.h>
char stack[20];
int top = -1;
void push(char x)
{
stack[++top] = x;
}

char pop()
{
if(top == -1)
return -1;
else
return stack[top--];
}

int priority(char x)
{
if(x == '(')
return 0;
if(x == '+' || x == '-')
return 1;
if(x == '*' || x == '/')
return 2;
}

main()
{
char exp[20];
char *e, x;
printf("Enter the expression :: ");
scanf("%s",exp);
e = exp;
while(*e != '\0')
{
if(isalnum(*e))
printf("%c",*e);
else if(*e == '(')
push(*e);
else if(*e == ')')
{
while((x = pop()) != '(')
printf("%c", x);
}
else
{
while(priority(stack[top]) >= priority(*e))
printf("%c",pop());
push(*e);
}
e++;
}
while(top != -1)
{

printf("%c",pop());
}
}
Enter the expression :: a+b*c
abc*+
Enter the expression :: (a+b)*c+(d-a)
ab+c*da-+

// CPP Program to convert prefix to Infix


#include <iostream>
#include <stack>
using namespace std;

// function to check if character is operator or not


bool isOperator(char x) {
switch (x) {
case '+':
case '-':
case '/':
case '*':
return true;
}
return false;
}

// Convert prefix to Infix expression


string preToInfix(string pre_exp) {
stack<string> s;

// length of expression
int length = pre_exp.size();

// reading from right to left


for (int i = length - 1; i >= 0; i--) {

// check if symbol is operator


if (isOperator(pre_exp[i])) {
// pop two operands from stack
string op1 = s.top(); s.pop();
string op2 = s.top(); s.pop();

// concat the operands and operator


string temp = "(" + op1 + pre_exp[i] + op2 + ")";

// Push string temp back to stack


s.push(temp);
}

// if symbol is an operand
else {

// push the operand to the stack


s.push(string(1, pre_exp[i]));
}
}

// Stack now contains the Infix expression


return s.top();
}

// Driver Code
int main() {
string pre_exp = "*-A/BC-/AKL";
cout << "Infix : " << preToInfix(pre_exp);
return 0;
}
Run on IDE
Output:
Infix : ((A-(B/C))*((A/K)-L))
Input : Prefix : *+AB-CD
Output : Infix : ((A+B)*(C-D))

Input : Prefix : *-A/BC-/AKL


Output : Infix : ((A-(B/C))*((A/K)-L))
Why postfix representation of the expression?
The compiler scans the expression either from left to right or from right to left.
Consider the below expression: a op1 b op2 c op3 d
If op1 = +, op2 = *, op3 = +
The compiler first scans the expression to evaluate the expression b * c, then again
scan the expression to add a to it. The result is then added to d after another scan.
The repeated scanning makes it very in-efficient. It is better to convert the expression to
postfix(or prefix) form before evaluation.
The corresponding expression in postfix form is: abc*+d+. The postfix expressions can
be evaluated easily using a stack. We will cover postfix expression evaluation in a
separate post.
Infix to postfix

Algorithm
1. Scan the infix expression from left to right.
2. If the scanned character is an operand, output it.
3. Else,
…..3.1 If the precedence of the scanned operator is greater than the precedence of the operator in
the stack(or the stack is empty), push it.
…..3.2 Else, Pop the operator from the stack until the precedence of the scanned operator is less-
equal to the precedence of the operator residing on the top of the stack. Push the scanned operator
to the stack.
4. If the scanned character is an ‘(‘, push it to the stack.
5. If the scanned character is an ‘)’, pop and output from the stack until an ‘(‘ is encountered.
6. Repeat steps 2-6 until infix expression is scanned.
7. Pop and output from the stack until it is not empty.

Prefix : An expression is called the prefix expression if the operator appears in the expression
before the operands. Simply of the form (operator operand1 operand2).
Example : *+AB-CD (Infix : (A+B) * (C-D) )

Algorithm for Prefix to Infix:


 Read the Prefix expression in reverse order (from right to left)
 If the symbol is an operand, then push it onto the Stack
 If the symbol is an operator, then pop two operands from the Stack
Create a string by concatenating the two operands and the operator between them.
string = (operand1 + operator + operand2)
And push the resultant string back to Stack
 Repeat the above steps until end of Prefix expression.

 Input : Prefix : *+AB-CD

 Output : Infix : ((A+B)*(C-D))


 Input : Prefix : *-A/BC-/AKL
 Output : Infix : ((A-(B/C))*((A/K)-L))

Postfix to Infix
Algorithm
1.While there are input symbol left
…1.1 Read the next symbol from the input.
2.If the symbol is an operand
…2.1 Push it onto the stack.
3.Otherwise,
…3.1 the symbol is an operator.
…3.2 Pop the top 2 values from the stack.
…3.3 Put the operator, with the values as arguments and form a string.
…3.4 Push the resulted string back to stack.
4.If there is only one value in the stack
…4.1 That value in the stack is the desired infix string.
- Shital Dongre
- Assistant Professor
- VIT, Pune.
What is a Queue?
 Linear data structure
 Queue Principle: FIRST IN FIRST OUT
 Access set of elements in FIFO order
 It means the first element inserted is the
first one to be removed
 Ex- waiting queue for bus-first one in
line is the first one to get entered in bus
 FIRST IN FIRST OUT
Applications of Queue
 Job scheduling

 Resource scheduling

 Graph traversing algorithm like for


Breadth first search
Operations on Queue
 isEmpty
 EnQueue
 DeQueue
 isFull
Queue using array
 #define MAX_QUEUE_SIZE 100

 int front, rear = -1

 int queue[MAX_QUEUE_SIZE]
void isEmpty()
{
if(front==rear)
{
printf("\n Queue is empty");
}
}
int isFull()
{
if(rear== (MAX_QUEUE_SIZE-1))
printf("\n Queue is Full");
}
void EnQueue( int num)
{
if(rear== (MAX_QUEUE_SIZE-1))
printf("\n Queue is Full");
else
{
rear = rear + 1;
Queue[rear] = num;
}
}
void DeQueue()
{
if(front==rear)
{
printf("\n Queue is empty");
}
else
{
front ++;
printf("\n Deleted Element is %d", queue[front]);
}
}
void Q_Display
{
printf("\n Queue Elements are:\n ");
if(front==rear)
printf("\n Queue is Empty");
else
{
for(i=(front+1); i<=rear; i++)
{
printf(“\n%d",queue[i]);
}
}
First In First Out

5 rear
4 rear 4
3 rear 3 3
2 rear
2 2
1 2
1 rear 1 1 front
front =-1 front =-1 front =-1 front =-1
Queue using Linked list
struct Node
{
int data;
struct Node *next;
};
struct Queue
{
struct Node *front, *rear;
};
struct Queue *createQ()
{
struct Queue *Q = (struct
Queue*)malloc(sizeof(struct Queue));

Q->front = Q->rear = NULL;


return Q;
}
void enQueue(struct Queue *Q, int data)
{
struct Node *temp = (struct Node*)
malloc(sizeof(struct Node));

if (Q->rear == NULL)
{
Q->front = Q->rear = temp;
return;
}

Q->rear->next = temp;
Q->rear = temp;
}
struct QNode *deQueue(struct Queue *Q)
{

if (Q ->front == NULL)
return NULL;

struct Node *temp = Q ->front;


Q ->front = Q ->front->next;

if (Q ->front == NULL)
Q ->rear = NULL;
return temp;
}
void display(struct Queue *Q)
{
struct Node *temp = Q->front;
while(temp!=NULL)
{
printf(“Queue element= %d”, temp->data);
temp=temp->next;
}
Queue

9/14/2020
09/10/08 S P Dongre, VIT, Pune 1
Queue (Linear Queue)
• It is a linear data structure consisting of list of items.
• In queue, data elements are added at one end, called the rear and removed from another
end, called the front of the list.
• Two basic operations are associated with queue:
7
1. “Insert” operation is used to insert an element into a queue.
2. “Delete” operation is used to delete an element from a queue. 6
● FIFO list
• Example: EEE 5
Queue: AAA, BBB, CCC, DDD, EEE
1 2 3 4 5 6 7 DDD 4

AAA BBB CCC DDD EEE CCC 3

BBB 2
Front Rear
AAA 1
Rear Front

9/14/2020
09/10/08 S P Dongre, VIT, Pune 2
Algorithms for Insert and Delete Operations in Linear Queue
For Insert Operation
Insert-Queue(Queue, Rear, Front, N, Item)
Here, Queue is the place where to store data. Rear represents the location in which the
data element is to be inserted and Front represents the location from which the data
element is to be removed. Here N is the maximum size of the Queue and finally, Item is
the new item to be added.

1. If Rear = N then Print: Overflow and Return. /*…Queue already filled..*/

2. Set Rear := Rear +1

3. Set Queue[Rear] := Item

4. Return.

9/14/2020
09/10/08 S P Dongre, VIT, Pune 3
For Delete Operation
Delete-Queue(Queue, Front, Rear, Item)
Here, Queue is the place where data are stored. Rear represents the location in which the
data element is to be inserted and Front represents the location from which the data
element is to be removed. Front element is assigned to Item.

1. If Front = N+1 then Print: Underflow and Return. /*…Queue Empty

2. Set Item := Queue[Front]

3. Set Front := Front + 1

4. Return.

9/14/2020
09/10/08 S P Dongre, VIT, Pune 4
Example: Consider the following queue (linear queue).
Rear = 4 and Front = 1 and N = 7
10 50 30 40
1 2 3 4 5 6 7
(1) Insert 20. Now Rear = 5 and Front = 1

10 50 30 40 20
1 2 3 4 5 6 7
(2) Delete Front Element. Now Rear = 5 and Front = 2
50 30 40 20
1 2 3 4 5 6 7
(3) Delete Front Element. Now Rear = 5 and Front = 3
30 40 20
1 2 3 4 5 6 7
(4) Insert 60. Now Rear = 6 and Front = 3

30 40 20 60
1 2 3 4 5 6 7
9/14/2020
09/10/08 S P Dongre, VIT, Pune 5
Drawback of Linear Queue
• Once the queue is full, even though few elements from the front are deleted and
some occupied space is relieved, it is not possible to add anymore new elements,
as the rear has already reached the Queue’s rear most position.

Circular Queue
• This queue is not linear but circular.

• Its structure can be like the following figure:


• In circular queue, once the Queue is full the

"First" element of the Queue becomes the

"Rear" most element, if and only if the "Front" Figure: Circular Queue having
Rear = 5 and Front = 0
has moved forward. otherwise it will again be

a "Queue overflow" state.

9/14/2020
09/10/08 S P Dongre, VIT, Pune 6
Algorithms for Insert and Delete Operations in Circular Queue
For Insert Operation
Insert-Circular-Q(CQueue, Rear, Front, N, Item)
Here, CQueue is a circular queue where to store data. Rear represents the
location in which the data element is to be inserted and Front represents the
location from which the data element is to be removed. Here N is the maximum
size of CQueue and finally, Item is the new item to be added. Initailly Rear = 0 and
Front = 0.
1. If Front = 0 and Rear = 0 then Set Front := 1 and go to step 4.
2. If Front =1 and Rear = N or Front = Rear + 1
then Print: “Circular Queue Overflow” and Return.
3. If Rear = N then Set Rear := 1 and go to step 5.
4. Set Rear := Rear + 1
5. Set CQueue [Rear] := Item.
6. Return
9/14/2020
09/10/08 S P Dongre, VIT, Pune 7
For Delete Operation
Delete-Circular-Q(CQueue, Front, Rear, Item)
Here, CQueue is the place where data are stored. Rear represents the location in
which the data element is to be inserted and Front represents the location from
which the data element is to be removed. Front element is assigned to Item.
Initially, Front = 1.

1. If Front = 0 then
Print: “Circular Queue Underflow” and Return. /*..Delete without Insertion

2. Set Item := CQueue [Front]


3. If Front = N then Set Front = 1 and Return.
4. If Front = Rear then Set Front = 0 and Rear = 0 and Return.
5. Set Front := Front + 1
6. Return.

9/14/2020
09/10/08 S P Dongre, VIT, Pune 8
Example: Consider the following circular queue with N = 5.
1. Initially, Rear = 0, Front = 0. 4. Insert 20, Rear = 3, Front = 0.
Front

Rear

2. Insert 10, Rear = 1, Front = 1. 5. Insert 70, Rear = 4, Front = 1.


Rear Front
Front

Rear
3. Insert 50, Rear = 2, Front = 1. 6. Delete front, Rear = 4, Front = 2.
Front Rear Front

Rear

9/14/2020
09/10/08 S P Dongre, VIT, Pune 9
7. Insert 100, Rear = 5, Front = 2. 10. Delete front, Rear = 1, Front = 3.
Rear
Front

Front

Rear

8. Insert 40, Rear = 1, Front = 2. 11. Delete front, Rear = 1, Front = 4.


Rear
Rear Front

Front

9. Insert 140, Rear = 1, Front = 2. 12. Delete front, Rear = 1, Front = 5.


As Front = Rear + 1, so Queue overflow. Rear

Rear Front

Front

9/14/2020
09/10/08 S P Dongre, VIT, Pune 10
Types of Queues in Data Structure

Queue in data structure is of the following types


• Simple Queue
• Circular Queue
• Priority Queue
• Dequeue (Double Ended Queue)

9/14/2020 S P Dongre, VIT, Pune


Simple Queue

The simple queue is a normal queue where insertion takes place at


the FRONT of the queue and deletion takes place at the END of the
queue.

Circular Queue
In a circular queue, the last node is connected to the first node.
Circular queue is also called as Ring Buffer.
Insertion in a circular queue happens at the FRONT and deletion at
the END of the queue.

9/14/2020 S P Dongre, VIT, Pune


Priority Queue
In a priority queue, the nodes will have some predefined priority.
Insertion in a priority queue is performed in the order of arrival of the nodes.
The node having the least priority will be the first to be removed from the
priority queue.

Dequeue (Doubly Ended Queue)

In a Double Ended Queue, insertion and deletion operations can be done at


both FRONT and END of the queue.

Deque or Double Ended Queue is a generalized version of Queue data


structure that allows insert and delete at both ends.

9/14/2020 S P Dongre, VIT, Pune


insertFront(): Adds an item at the front of Deque.
insertLast(): Adds an item at the rear of Deque.
deleteFront(): Deletes an item from front of Deque.
deleteLast(): Deletes an item from rear of Deque.

Circular array implementation deque

9/14/2020 S P Dongre, VIT, Pune


Insert Elements at Rear end
a). First we check deque if Full or Not
b). IF Rear == Size-1
then reinitialize Rear = 0 ;
Else increment Rear by '1'
and push current key into Arr[ rear ] = key
Front remain same.

Insert Elements at Front end

a). First we check deque if Full or Not


b). IF Front == 0 || initial position, move Front to
points last index of array front = size - 1
Else decremented front by '1' and push current key
into Arr[ Front] = key Rear remain same.

9/14/2020 S P Dongre, VIT, Pune


Delete Element From Rear end
a). first Check deque is Empty or Not
b). If deque has only one element
front = -1 ; rear =-1 ;
Else IF Rear points to the first index of array it's means we have to move rear to points last index
[ now first inserted element at front end become rear end ]
rear = size-1 ;
Else || decrease rear by '1'
rear = rear-1;

Delete Element From Front end


a). first Check deque is Empty or Not
b). If deque has only one element front = -1 ; rear =-1 ;
Else IF front points to the last index of the array it's means we have no more elements in array
so we move front to points first index of array
front = 0 ;
Else || increment Front by '1'
front = front+1;

9/14/2020 S P Dongre, VIT, Pune


END!!!

9/14/2020
09/10/08 S P Dongre, VIT, Pune 17
By
Shital Dongre
Asst. Prof.
IT & MCA Dept,
VIT, Pune
 linked list is a linear data structure
 elements are not stored at contiguous memory
locations.
 elements in a linked list are linked using pointers
 linked list consists of nodes where each node
contains a data field and a reference(link) to the
next node in the list.
Arrays can be used to store linear data of similar
types, but arrays have following limitations.
1) The size of the arrays is fixed: So we must know
the upper limit on the number of elements in
advance. Also, generally, the allocated memory is
equal to the upper limit irrespective of the usage.
2) Inserting a new element in an array of elements
is expensive, because room has to be created for
the new elements and to create room existing
elements have to shifted.
 Advantages over arrays
1) Dynamic size
2) Ease of insertion/deletion
 Drawbacks:
1) Random access is not allowed. We have to
access elements sequentially starting from the
first node. So we cannot do binary search with
linked lists.
2) Extra memory space for a pointer is required
with each element of the list.
3) Not cache friendly. Since array elements are
contiguous locations, there is locality of
reference which is not there in case of linked
lists.
 A linked list is represented by a pointer to the
first node of the linked list. The first node is
called head. If the linked list is empty, then
value of head is NULL.
Each node in a list consists of at least two
parts:
1) data
2) Pointer (Or Reference) to the next node
// A linked list node
Node
struct Node
{
int data; A
struct Node *next;
data pointer
};
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node* head = NULL;
struct Node* second = NULL;
struct Node* third = NULL;

// allocate 3 nodes in the heap


head = (struct Node*)malloc(sizeof(struct Node));
second = (struct Node*)malloc(sizeof(struct Node));
third = (struct Node*)malloc(sizeof(struct Node));
head->data = 1;
head->next = second;

second->data = 2;
second->next = third;

third->data = 3;
third->next = NULL;
return 0;
}
Linked list traversal

void printList(struct Node *n)


{
while (n != NULL)
{
printf(" %d ", n->data);
n = n->next;
}
}
.
.
.
printList(head);
A node can be added in three ways
1) At the front of the linked list
2) After a given node.
3) At the end of the linked list.
void push(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

/* 2. put in the data */


new_node->data = new_data;

/* 3. Make next of new node as head */


new_node->next = (*head_ref);

/* 4. move the head to point to the new node */


(*head_ref) = new_node;
}
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL)
{
printf("the given previous node cannot be NULL");
return;
}

/* 2. allocate new node */


struct Node* new_node =(struct Node*) malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make next of new node as next of prev_node */


new_node->next = prev_node->next;

/* 5. move the next of prev_node as new_node */


prev_node->next = new_node;
}
void append(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

struct Node *last = *head_ref; /* used in step 5*/

/* 2. put in the data */


new_node->data = new_data;

/* 3. This new node is going to be the last node, so make next


of it as NULL*/
new_node->next = NULL;

/* 4. If the Linked List is empty, then make the new node as head */
if (*head_ref == NULL)
{
*head_ref = new_node;
return;
}

/* 5. Else traverse till the last node */


while (last->next != NULL)
last = last->next;

/* 6. Change the next of last node */


last->next = new_node;
return;
}
void search(struct Node* head, int key)
{
Int i=1;
struct Node* tmp;
tmp =head;
while (tmp->next != NULL)
{
if(tmp->data==key)
{
printf(“node location=%d”, i);
break;
}
tmp = tmp->next;
i++;

}
/* Counts no. of nodes in linked list */
int getCount(struct Node* head)
{
int count = 0; // Initialize count
struct Node* current = head; // Initialize current
while (current != NULL)
{
count++;
current = current->next;
}
return count;
}
 From front
 From matching key
 Last
void deleteNode(struct Node *head, int key)
{
struct Node* temp = *head, *prev;
if (temp != NULL && temp->data == key)
{ *head = temp->next;
free(temp);
return;
}
while (temp != NULL && temp->data != key)
{
prev = temp;
temp = temp->next;
}
if (temp == NULL)
return;
prev->next = temp->next;
free(temp);
}
 Delete entire linked list
void deleteList(struct Node* head)
{
/* deref head_ref to get the real head */
struct Node* current = *head;
struct Node* next_node;

while (current != NULL)


{
next_node = current->next;
free(current);
current = next_node;
}

/* deref head_ref to affect the real head back


in the caller. */
*head = NULL;
}
void reverse(struct Node** head)
{
struct Node* prev = NULL;
struct Node* current = *head;
struct Node* next_node;
while (current != NULL)
{
next_node = current->next;
current->next = prev;
prev = current;
current = next_node;
}
*head = prev;
}
 Example Program
1. Polynomial addition, subtraction
2. RN, Name list
3. Function to check if a singly linked list is
palindrome
coef expo
A-> 3x7+2x5+9
B-> 5x2+ 7

3 7 2 5 9 0 NU
C=A+B LL

struct Node
{
int coef;
int expo;
struct Node *next;
};
A Doubly Linked List (DLL) contains an extra
pointer, typically called previous pointer,
together with next pointer and data which are
there in singly linked list.

/* Node of a doubly linked list */


struct Node {
int data;
struct Node* next; // Pointer to next node in DLL
struct Node* prev; // Pointer to previous node in DLL
};
 Advantages over singly linked list
1) A DLL can be traversed in both forward and backward
direction.
2) The delete operation in DLL is more efficient if pointer
to the node to be deleted is given.
3) We can quickly insert a new node before a given node.
In singly linked list, to delete a node, pointer to the
previous node is needed. To get this previous node,
sometimes the list is traversed. In DLL, we can get the
previous node using previous pointer.
 Disadvantages over singly linked list
1) Every node of DLL Require extra space for an previous
pointer. It is possible to implement DLL with single pointer
though.
2) All operations require an extra pointer previous to be
maintained. For example, in insertion, we need to modify
previous pointers together with next pointers. For example
in following functions for insertions at different positions,
we need 1 or 2 extra steps to set previous pointer.
void printList(struct Node* head)
{
struct Node* node;
node= head;
printf("\nTraversal in forward direction \n");
while (node != NULL) {
printf(" %d ", node->data);
last = node;
node = node->next;
}
node=last;
printf("\nTraversal in reverse direction \n");
while (node != NULL) {
printf(" %d ", last->data);
last = last->prev;
}
}
1) At the front of the DLL
2) After a given node.
3) At the end of the DLL
4) Before a given node.
 at the front
void push(struct Node* head, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

/* 2. put in the data */


new_node->data = new_data;

/* 3. Make next of new node as head and previous as NULL */


new_node->next = (head);
new_node->prev = NULL;

/* 4. change prev of head node to new node */


if ((head) != NULL)
(head)->prev = new_node;

/* 5. move the head to point to the new node */


head= new_node;
}
 after a given node
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}

/* 2. allocate new node */


struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make next of new node as next of prev_node */


new_node->next = prev_node->next;

/* 5. Make the next of prev_node as new_node */


prev_node->next = new_node;

/* 6. Make prev_node as previous of new_node */


new_node->prev = prev_node;

/* 7. Change previous of new_node's next node */


if (new_node->next != NULL)
new_node->next->prev = new_node;
}
void append(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

struct Node* last = *head_ref; /* used in step 5*/

/* 2. put in the data */


new_node->data = new_data;

/* 3. This new node is going to be the last node, so


make next of it as NULL*/
new_node->next = NULL;

/* 4. If the Linked List is empty, then make the new


node as head */
if (*head_ref == NULL) {
new_node->prev = NULL;
*head_ref = new_node;
return;
}

/* 5. Else traverse till the last node */


while (last->next != NULL)
last = last->next;

/* 6. Change the next of last node */


last->next = new_node;

/* 7. Make last node as previous of new node */


new_node->prev = last;

return;
}
void insertBefore(struct Node* next_node, int new_data)
{
/*1. check if the given new_node is NULL */
if (next_node == NULL) {
printf("the given next node cannot be NULL");
return;
}

/* 2. allocate new node */


struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make prev of new node as prev of next_node */


new_node->prev = next_node->prev;

/* 5. Make the prev of next_node as new_node */


next_node->prev = new_node;

/* 6. Make next_node as next of new_node */


new_node->next = next_node;

/* 7. Change next of new_node's previous node */


if (new_node->prev != NULL)
new_node->prev->next = new_node;
}
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}

/* 2. allocate new node */


struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make next of new node as next of prev_node */


new_node->next = prev_node->next;

/* 5. Make the next of prev_node as new_node */


prev_node->next = new_node;

/* 6. Make prev_node as previous of new_node */


new_node->prev = prev_node;

/* 7. Change previous of new_node's next node */


if (new_node->next != NULL)
new_node->next->prev = new_node;
}
1. Delete Node

struct Node {
int data;
struct Node* next;
struct Node* prev;
};
void push(struct Node** head_ref, int new_data)
{
/* allocate node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

/* put in the data */


new_node->data = new_data;

/* since we are adding at the beginning,


prev is always NULL */
new_node->prev = NULL;

/* link the old list off the new node */


new_node->next = (*head_ref);

/* change prev of head node to new node */


if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;

/* move the head to point to the new node */


(*head_ref) = new_node;
}

void deleteNode(struct Node** head_ref, struct Node* del)


{
/* base case */
if (*head_ref == NULL || del == NULL)
return;

/* If node to be deleted is head node */


if (*head_ref == del)
*head_ref = del->next;
/* Change next only if node to be deleted is NOT the last node */
if (del->next != NULL)
del->next->prev = del->prev;

/* Change prev only if node to be deleted is NOT the first node */


if (del->prev != NULL)
del->prev->next = del->next;

/* Finally, free the memory occupied by del*/


free(del);
return;
}
Complexity Analysis:
 Time Complexity: O(1).
Since traversal of linked list is not required so the time complexity is constant.
 Space Complexity: O(1).
As no extra space is required, so the space complexity is constant.

2. Delete a Doubly Linked List node at a given


position
void deleteNodeAtGivenPos(struct Node** head_ref, int n)
{
/* if list in NULL or invalid position is given */
if (*head_ref == NULL || n <= 0)
return;

struct Node* current = *head_ref;


int i;

/* traverse up to the node at position 'n' from


the beginning */
for (int i = 1; current != NULL && i < n; i++)
current = current->next;

/* if 'n' is greater than the number of nodes


in the doubly linked list */
if (current == NULL)
return;

/* delete the node pointed to by 'current' */


deleteNode(head_ref, current);
}
void deleteNode(struct Node** head_ref, struct Node* del)
{
/* base case */
if (*head_ref == NULL || del == NULL)
return;

/* If node to be deleted is head node */


if (*head_ref == del)
*head_ref = del->next;

/* Change next only if node to be deleted is NOT


the last node */
if (del->next != NULL)
del->next->prev = del->prev;
/* Change prev only if node to be deleted is NOT
the first node */
if (del->prev != NULL)
del->prev->next = del->next;

/* Finally, free the memory occupied by del*/


free(del);
}

3.Swap Kth node from beginning with Kth


node from end in a Doubly Linked List
Input: DLL = 1 <-> 2 <-> 3 <-> 4 <-> 5, K = 1
Output: 5 2 3 4 1

Let K1 be the Kth node from beginning and K2 be Kth node from ending. Then:
 The previous node to K2 has to be changed to the previous node of K1.
 The next node to K2 has to be changed to the next node of K1.
 The previous node to K1 has to be changed to the previous node of K2.
 The next node to K1 has to be changed to the next node of K2.

4.Delete all occurrences of a given key in


a doubly linked list

delAllOccurOfGivenKey(head_ref, x)
if head_ref == NULL
return
Initialize current = head_ref
Declare next
while current != NULL
if current->data == x
next = current->next
deleteNode(head_ref, current)
current = next
else
current = current->next
5.Delete all the nodes from the doubly
linked list that are greater than a given
value
6.Delete all the even nodes from a Doubly
Linked List
void deleteEvenNodes(Node** head_ref)
{
Node* ptr = *head_ref;
Node* next;
while (ptr != NULL) {
next = ptr->next;
// if true, delete node 'ptr'
if (ptr->data % 2 == 0)
deleteNode(head_ref, ptr);
ptr = next;
}
}
void deleteNode(Node** head_ref, Node* del)
{
// base case
if (*head_ref == NULL || del == NULL)
return;

// If node to be deleted is head node


if (*head_ref == del)
*head_ref = del->next;

// Change next only if node to be


// deleted is NOT the last node
if (del->next != NULL)
del->next->prev = del->prev;

// Change prev only if node to be


// deleted is NOT the first node
if (del->prev != NULL)
del->prev->next = del->next;

// Finally, free the memory occupied by del


free(del);

return;
}

7.
8.
9.
10.
11.
12.
13.
14.
15.
Reverse a Doubly Linked List
void reverse(struct Node **head_ref)
{
struct Node *temp = NULL;
struct Node *current = *head_ref;

/* swap next and prev for all nodes of


doubly linked list */
while (current != NULL)
{
temp = current->prev;
current->prev = current->next;
current->next = temp;
current = current->prev;
}

/* Before changing head, check for the cases like empty


list and list with only one node */
if(temp != NULL )
*head_ref = temp->prev;
}

Current- Current Current- temp head


>prev >next
null 1 2
2 Null Null
3 2 1 1
Null 3 2 2
Null

123
Circular Linked List
Circular Linked Lists
⚫ In linear linked lists if a list is traversed (all
the elements visited) an external pointer to
the list must be preserved in order to be able
to reference the list again.
⚫ Circular linked lists can be used to help the
traverse the same list again and again if
needed. A circular list is very similar to the
linear list where in the circular list the pointer
of the last node points not NULL but the first
node.
Circular Linked Lists

A Linear Linked List


Circular Linked Lists
Circular Linked Lists
Circular Linked Lists
⚫ In a circular linked list there are two methods to
know if a node is the first node or not.
⚫ Either a external pointer, list, points the first
node or
⚫ A header node is placed as the first node of
the circular list.
⚫ The header node can be separated from the
others by either heaving a sentinel value as the
info part or having a dedicated flag variable to
specify if the node is a header node or not.
PRIMITIVE FUNCTIONS IN
CIRCULAR LISTS
⚫ The structure definition of the circular linked
lists and the linear linked list is the same:
struct node{
int info;
struct node *next;
};
typedef struct node *NODEPTR;
PRIMITIVE FUNCTIONS IN
CIRCULAR LISTS
⚫The delete after and insert after functions of the linear lists and the circular lists
are almost the same.
The delete after function: delafter( )
void delafter(NODEPTR p, int *px)
{
NODEPTR q;
if((p == NULL) || (p == p->next)){ /*the empty list
contains a single node and may be pointing itself*/
printf(“void deletion\n”);
exit(1);
}
q = p->next;
*px = q->info; /*the data of the deleted node*/
p->next = q->next;
freenode(q);
}
PRIMITIVE FUNCTIONS IN
CIRCULAR LISTS
⚫The insertafter function: insafter( )
void insafter(NODEPTR p, int x)
{
NODEPTR q;
if(p == NULL){
printf(“void insertion\n”);
exit(1);
}
q = getnode();
q->info = x; /*the data of the inserted node*/
q->next = p->next;
p->next = q;
}
CIRCULAR LIST with header
node
⚫The header node in a circular list can be specified
by a sentinel value or a dedicated flag:
⚫Header Node with Sentinel: Assume that info part
contains positive integers. Therefore the info part of
a header node can be -1. The following circular list
is an example for a sentinel used to represent the
header node:
struct node{
int info;
struct node *next;
};
typedef struct node *NODEPTR;
CIRCULAR LIST with header
node
CIRCULAR LIST with header
node
⚫Header Node with Flag: In this case a extra
variable called flag can be used to represent the
header node. For example flag in the header node
can be 1, where the flag is 0 for the other nodes.
struct node{
int flag;
int info;
struct node *next;
};
typedef struct node *NODEPTR;
CIRCULAR LIST with header
node
Example
⚫ Consider a circular linked list with a header
node, where each node contains the name,
account number and the balance of a bank
customer. The header node contains a
sentinel account number to be -99.
⚫ (a) Write an appropriate node structure
definition for the circular linked list.
⚫ (b) Write a function to display the full records
of the customers with negative balance.
a) struct node{
char Name[15]; int AccNo;
float Balance;
struct node *next;
};
typedef struct node *NODEPTR;

b) Assume that the list pointer points the header with the sentinel account number -99.

void DispNegBalanca(NODEPTR *plist)


{
NODEPTR p;
p=*plist;
if(p == NULL){
printf(“There is no list!\n”);
exit(1);
}
p=p->next;
while(p->AccNo!=-99){
if(p->Balance < 0.0)
printf(“The Customer Name:%s\nThe Account No:%d\nThe
Balance:%.2f\n”, p->Name, p->AccNo, p->Balance);
p=p->next;
}
}
Example
⚫Write a function that returns the average of the
numbers in a circular list. Assume that the following
node structure is used, where the flag variable is 1
for the header node and 0 for all the other nodes.

struct node{
int flag;
float info;
struct node *next;
};
typedef struct node *NODEPTR;
float avList(NODEPTR *plist)/*assume that plist points the
header node*/
{
int count=0;
float sum =0.0;
NODEPTR p;
p=*plist;
if((p == NULL)){
printf(“Empty list\n”);
exit(1);
}
do{
sum=sum + p->info;
p =p->next;
count++;
}while(p->flag !=1);
return sum/count;
}
Circular
Linked List
COMP104 Circular Linked List / Slide 2

Circular Linked Lists


 A Circular Linked List is a special type of Linked List
 It supports traversing from the end of the list to the
beginning by making the last node point back to the
head of the list
 A Rear pointer is often used instead of a Head pointer

10 20 40 55 70

Rear
COMP104 Circular Linked List / Slide 3

Motivation

Circular linked lists are usually sorted


Circular linked lists are useful for playing
video and sound files in “looping” mode
They are also a stepping stone to
implementing graphs, an important topic
in comp171
Circular Linked List Definition
#include <iostream>
using namespace std;

struct Node{
int data;
Node* next;
};
typedef Node* NodePtr;
COMP104 Circular Linked List / Slide 5

Circular Linked List Operations

 insertNode(NodePtr& Rear, int item)


//add new node to ordered circular linked list

 deleteNode(NodePtr& Rear, int item)


//remove a node from circular linked list

 print(NodePtr Rear)
//print the Circular Linked List once
COMP104 Circular Linked List / Slide 6

Traverse the list


void print(NodePtr Rear){
NodePtr Cur;
if(Rear != NULL){
Cur = Rear->next;
do{
cout << Cur->data << " ";
Cur = Cur->next;
}while(Cur != Rear->next);
cout << endl;
}
}
10 20 40 55 70

Rear
COMP104 Circular Linked List / Slide 7

Insert Node
 Insert into an empty list

NotePtr New = new Node;


New->data = 10;

Rear = New;
Rear->next = Rear;
10

New Rear
COMP104 Circular Linked List / Slide 8

 Insert to head of a Circular Linked List


New->next = Cur; // same as: New->next = Rear->next;
Prev->next = New; // same as: Rear->next = New;

10 20 40 55 70

New Cur Prev


Rear
COMP104 Circular Linked List / Slide 9

 Insert to middle of a Circular Linked List


between Pre and Cur
New->next = Cur;
Prev->next = New;

10 20 55 70

40
Prev Cur Rear

New
COMP104 Circular Linked List / Slide 10

 Insert to end of a Circular Linked List


New->next = Cur; // same as: New->next = Rear->next;
Prev->next = New; // same as: Rear->next = New;
Rear = New;

10 20 40 55 70

Cur Prev New


Rear
void insertNode(NodePtr& Rear, int item){
NodePtr New, Cur, Prev;
New = new Node;
New->data = item;
if(Rear == NULL){ // insert into empty list
Rear = New;
Rear->next = Rear;
return;
}
Prev = Rear;
Cur = Rear->next;
do{ // find Prev and Cur
if(item <= Cur->data)
break;
Prev = Cur;
Cur = Cur->next;
}while(Cur != Rear->next);
New->next = Cur; // revise pointers
Prev->next = New;
if(item > Rear->data) //revise Rear pointer if adding to end
Rear = New;
}
COMP104 Circular Linked List / Slide 12

 Delete a node from a single-node Circular


Linked List
Rear = NULL;
delete Cur;

10

Rear = Cur = Prev


COMP104 Circular Linked List / Slide 13

Delete Node
 Delete the head node from a Circular Linked List
Prev->next = Cur->next; // same as: Rear->next = Cur->next
delete Cur;

10 20 40 55 70

Rear Prev
Cur
COMP104 Circular Linked List / Slide 14

 Delete a middle node Cur from a Circular


Linked List
Prev->next = Cur->next;
delete Cur;

10 20 40 55 70

Prev Cur Rear


COMP104 Circular Linked List / Slide 15

 Delete the end node from a Circular Linked List


Prev->next = Cur->next; // same as: Rear->next;

delete Cur;
Rear = Prev;

10 20 40 55 70

Prev Cur
Rear
void deleteNode(NodePtr& Rear, int item){
NodePtr Cur, Prev;
if(Rear == NULL){
cout << "Trying to delete empty list" << endl;
return;
}
Prev = Rear;
Cur = Rear->next;
do{ // find Prev and Cur
if(item <= Cur->data) break;
Prev = Cur;
Cur = Cur->next;
}while(Cur != Rear->next);
if(Cur->data != item){ // data does not exist
cout << "Data Not Found" << endl;
return;
}
if(Cur == Prev){ // delete single-node list
Rear = NULL;
delete Cur;
return;
}
if(Cur == Rear) // revise Rear pointer if deleting end
Rear = Prev;
Prev->next = Cur->next; // revise pointers
delete Cur;
}
void main(){
NodePtr Rear = NULL;

insertNode(Rear, 3);
insertNode(Rear, 1);
insertNode(Rear, 7);
insertNode(Rear, 5);
insertNode(Rear, 8); Result is:
print(Rear); 13578
deleteNode(Rear, 1); 57
deleteNode(Rear, 3);
1578
deleteNode(Rear, 8);
print(Rear);
insertNode(Rear, 1);
insertNode(Rear, 8);
print(Rear);
}
Generalized Linked list
• A Generalized Linked List L, is defined as a
finite sequence of n>=0 elements, l1, l2, l3, l4,
…, ln, such that li are either atom or the list of
atoms. Thus
L = (l1, l2, l3, l4, …, ln)
where n is total number of nodes in the list.
To represent a list of items there are certain
assumptions about the node structure.
• Flag = 1 implies that down pointer exists
• Data means the atom
• Down pointer is the address of node which is
down of the current node
• Next pointer is the address of node which is
attached as the next node
• Why Generalized Linked List?
Generalized linked lists are used because
although the efficiency of polynomial
operations using linked list is good but still,
the disadvantage is that the linked list is
unable to use multiple variable polynomial
equation efficiently. It helps us to represent
multi-variable polynomial along with the list
of elements.
typedef struct node {
char c; //Data
int index; //Flag
struct node *next, *down; //Next & Down
pointer
}GLL;
• ( a, (b, c), d)
• Polynomial Representation using Generalized
Linked List
The typical node structure will be:
9x5 + 7x4y + 10xz

Here Flag = 0 means variable is present


Flag = 1 means down pointer is present
Flag = 2 means coefficient and exponent is
present
Structure
Structure is a group of variables of different data types represented by a
single name.
A structure is a user defined data type in C/C++.
A structure creates a data type that can be used to group items of possibly
different types into a single type.
Syntax of struct
struct structureName
{
dataType member1;
dataType member2;
...
};

‘struct’ keyword is used to create a structure. Following is an example.


struct address
{
char name[50];
char street[100];
char city[50];
char state[20];
int pin;
};

struct Person
{
char name[50];
int citNo;
float salary;
};

There are two types of operators used for accessing members of


a structure.
1. . - Member operator
2. -> - Structure pointer operator

A structure variable can either be declared with structure declaration or as a


separate declaration like basic types.
// A variable declaration with structure declaration.
struct Point
{
int x, y;
} p1; // The variable p1 is declared with 'Point'

// A variable declaration like basic data types


struct Point
{
int x, y;
};

int main()
{
struct Point p1;
printf(“%d”,sizeof(p1));
}
#include <stdio.h>
/* Created a structure here. The name of the structure is
* StudentData.
*/
struct StudentData{
char *stu_name;
int stu_id;
int stu_age;
};
int main()
{
/* student is the variable of structure StudentData*/
struct StudentData student;

/*Assigning the values of each struct member here*/


student.stu_name = "Steve";
student.stu_id = 1234;
student.stu_age = 30;

/* Displaying the values of struct members */


printf("Student Name is: %s", student.stu_name);
printf("\nStudent Id is: %d", student.stu_id);
printf("\nStudent Age is: %d", student.stu_age);
return 0;
}
Student Name is: Steve
Student Id is: 1234
Student Age is: 30

#include <stdio.h>
struct Distance
{
int feet;
float inch;
} dist1, dist2, sum;

int main()
{
printf("1st distance\n");
printf("Enter feet: ");
scanf("%d", &dist1.feet);

printf("Enter inch: ");


scanf("%f", &dist1.inch);
printf("2nd distance\n");

printf("Enter feet: ");


scanf("%d", &dist2.feet);

printf("Enter inch: ");


scanf("%f", &dist2.inch);

// adding feet
sum.feet = dist1.feet + dist2.feet;
// adding inches
sum.inch = dist1.inch + dist2.inch;

// changing to feet if inch is greater than 12


while (sum.inch >= 12)
{
++sum.feet;
sum.inch = sum.inch - 12;
}

printf("Sum of distances = %d\'-%.1f\"", sum.feet, sum.inch);


return 0;
}
1st distance
Enter feet: 12
Enter inch: 7.9
2nd distance
Enter feet: 2
Enter inch: 9.8
Sum of distances = 15'-5.7"
#include <stdio.h>

struct Distance {
int feet;
float inch;
} d1, d2, result;

int main() {
// take first distance input
printf("Enter 1st distance\n");
printf("Enter feet: ");
scanf("%d", &d1.feet);
printf("Enter inch: ");
scanf("%f", &d1.inch);

// take second distance input


printf("\nEnter 2nd distance\n");
printf("Enter feet: ");
scanf("%d", &d2.feet);
printf("Enter inch: ");
scanf("%f", &d2.inch);

// adding distances
result.feet = d1.feet + d2.feet;
result.inch = d1.inch + d2.inch;

// convert inches to feet if greater than 12


while (result.inch >= 12.0) {
result.inch = result.inch - 12.0;
++result.feet;
}
printf("\nSum of distances = %d\'-%.1f\"", result.feet, result.inch);
return 0;
}

Output

Enter 1st distance


Enter feet: 23
Enter inch: 8.6

Enter 2nd distance


Enter feet: 34
Enter inch: 2.4
Sum of distances = 57'-11.0"

Add Two Complex Numbers

#include <stdio.h>
typedef struct complex {
float real;
float imag;
} complex;

/*struct complex {
float real;
float imag;
};*/
complex add(complex n1, complex n2);

int main() {
complex n1, n2, result;

printf("For 1st complex number \n");


printf("Enter the real and imaginary parts: ");
scanf("%f %f", &n1.real, &n1.imag);
printf("\nFor 2nd complex number \n");
printf("Enter the real and imaginary parts: ");
scanf("%f %f", &n2.real, &n2.imag);

result = add(n1, n2);

printf("Sum = %.1f + %.1fi", result.real, result.imag);


return 0;
}

complex add(complex n1, complex n2) {


complex temp, temp1;
temp1.real = n1.real + n2.real;
temp1.imag = n1.imag + n2.imag;
return (temp1);
}

Output

For 1st complex number


Enter the real and imaginary parts: 2.1
-2.3

For 2nd complex number


Enter the real and imaginary parts: 5.6
23.2
Sum = 7.7 + 20.9i

Keyword typedef
We use the typedef keyword to create an alias name for data types. It is
commonly used with structures to simplify the syntax of declaring variables.
This code
struct Distance{
int feet;
float inch;
};

int main() {
struct Distance d1, d2;
}

is equivalent to
typedef struct Distance{
int feet;
float inch;
} distances;

int main() {
distances d1, d2;
}

Structure members cannot be initialized with declaration. For example the


following C program fails in compilation.
struct Point
{
int x = 0; // COMPILER ERROR: cannot initialize members here
int y = 0; // COMPILER ERROR: cannot initialize members here
};

The reason for above error is simple, when a datatype is declared, no


memory is allocated for it. Memory is allocated only when variables are
created.
struct Point
{
int x, y;
};

int main()
{
// A valid initialization. member x gets value 0 and y
// gets value 1. The order of declaration is followed.
struct Point p1 = {0, 1};
}

Structure members are accessed using dot (.) operator.


#include<stdio.h>

struct Point
{
int x, y;
};

int main()
{
struct Point p1 = {0, 1};

// Accessing members of point p1


p1.x = 20;
printf ("x = %d, y = %d", p1.x, p1.y);
return 0;
}

Limitations of C Structures
In C language, Structures provide a method for packing together data of
different types. A Structure is a helpful tool to handle a group of logically
related data items. However, C structures have some limitations.
 The C structure does not allow the struct data type to be treated like built-in data types:
 We cannot use operators like +,- etc. on Structure variables.
 No Data Hiding: C Structures do not permit data hiding. Structure members can be
accessed by any function, anywhere in the scope of the Structure.
 Functions inside Structure: C structures do not permit functions inside Structure
 Static Members: C Structures cannot have static members inside their body
 Access Modifiers: C Programming language do not support access modifiers. So they
cannot be used in C Structures.
 Construction creation in Structure: Structures in C cannot have constructor inside
Structures.

What is designated Initialization?


Designated Initialization allows structure members to be initialized in any
order. This feature has been added in C99 standard.
#include<stdio.h>

struct Point
{
int x, y, z;
};

int main()
{
// Examples of initialization using designated initialization
struct Point p1 = {.y = 0, .z = 1, .x = 2};
struct Point p2 = {.x = 20};

printf ("x = %d, y = %d, z = %d\n", p1.x, p1.y, p1.z);


printf ("x = %d", p2.x);
return 0;
}
x = 2, y = 0, z = 1
x = 20
#include <stdio.h>
struct numbers
{
int num1, num2;
};
int main()
{
// Assignment using using designated initialization
struct numbers s1 = {.num2 = 22, .num1 = 11};
struct numbers s2 = {.num2 = 30};

printf ("num1: %d, num2: %d\n", s1.num1, s1.num2);


printf ("num1: %d", s2.num2);
return 0;
}
Output:

num1: 11, num2: 22


num1: 30
Array of structures
#include<stdio.h>

struct Point
{
int x, y;
};

int main()
{
// Create an array of structures
struct Point arr[10];

// Access array members


arr[0].x = 10;
arr[0].y = 20;

printf("%d %d", arr[0].x, arr[0].y);


return 0;
}
Output:
10 20

Store Information in Structure and Display it

#include <stdio.h>
struct student {
char firstName[50];
int roll;
float marks;
} s[10];

int main() {
int i;
printf("Enter information of students:\n");

// storing information
for (i = 0; i < 5; ++i) {
s[i].roll = i + 1;
printf("\nFor roll number%d,\n", s[i].roll);
printf("Enter first name: ");
scanf("%s", s[i].firstName);
printf("Enter marks: ");
scanf("%f", &s[i].marks);
}
printf("Displaying Information:\n\n");

// displaying information
for (i = 0; i < 5; ++i) {
printf("\nRoll number: %d\n", i + 1);
printf("First name: ");
puts(s[i].firstName);
printf("Marks: %.1f", s[i].marks);
printf("\n");
}
return 0;
}
Output

Enter information of students:

For roll number1,


Enter name: Tom
Enter marks: 98

For roll number2,


Enter name: Jerry
Enter marks: 89
.
.
.
Displaying Information:

Roll number: 1
Name: Tom
Marks: 98
.
.
.

What is a structure pointer?


Like primitive types, we can have pointer to a structure. If we have a pointer
to structure, members are accessed using arrow ( -> ) operator.
#include<stdio.h>

struct Point
{
int x, y;
};

int main()
{
struct Point p1 = {1, 2};

// p2 is a pointer to structure p1
struct Point *p2 ;
p2= &p1;

// Accessing structure members using structure pointer


printf("%d %d %d %d", p1.x, p1.y, p2->x, p2->y);
return 0;
}

Output:
1 2

#include <stdio.h>
struct person
{
int age;
float weight;
};
int main()
{
struct person *personPtr, person1;
personPtr = &person1;

printf("Enter age: ");


scanf("%d", &personPtr->age);

printf("Enter weight: ");


scanf("%f", &personPtr->weight);

printf("Displaying:\n");
printf("Age: %d\n", personPtr->age);
printf("weight: %f", personPtr->weight);

return 0;
}

Dynamic memory allocation of structs

#include <stdio.h>
#include <stdlib.h>
struct person {
int age;
float weight;
char name[30];
};

int main()
{
struct person *ptr;
int i, n;

printf("Enter the number of persons: ");


scanf("%d", &n);

// allocating memory for n numbers of struct person


ptr = (struct person*) malloc(n * sizeof(struct person));

for(i = 0; i < n; ++i)


{
printf("Enter first name and age respectively: ");

// To access members of 1st struct person,


// ptr->name and ptr->age is used

// To access members of 2nd struct person,


// (ptr+1)->name and (ptr+1)->age is used
scanf("%s %d", (ptr+i)->name, &(ptr+i)->age);
}

printf("Displaying Information:\n");
for(i = 0; i < n; ++i)
printf("Name: %s\tAge: %d\n", (ptr+i)->name, (ptr+i)->age);

return 0;
}

When you run the program, the output will be:

Enter the number of persons: 2


Enter first name and age respectively: Harry 24
Enter first name and age respectively: Gary 32
Displaying Information:
Name: Harry Age: 24
Name: Gary Age: 32
#include <stdio.h>
#include <stdlib.h>
struct course {
int marks;
char subject[30];
};

int main() {
struct course *ptr;
int noOfRecords;
printf("Enter the number of records: ");
scanf("%d", &noOfRecords);

// Memory allocation for noOfRecords structures


ptr = (struct course *)malloc(noOfRecords * sizeof(struct course));
for (int i = 0; i < noOfRecords; ++i) {
printf("Enter subject and marks:\n");
scanf("%s %d", (ptr + i)->subject, &(ptr + i)->marks);
}

printf("Displaying Information:\n");
for (int i = 0; i < noOfRecords; ++i) {
printf("%s\t%d\n", (ptr + i)->subject, (ptr + i)->marks);
}

free(ptr);

return 0;
}

Output

Enter the number of records: 2


Enter subject and marks:
Science 82
Enter subject and marks:
DSA 73
Displaying Information:
Science 82
DSA 73

Nested Structures

struct complex

int imag;

float real;

};

struct number

struct complex comp;

int integers;

} num1, num2;

Suppose, you want to set imag of num2 variable to 11. Here's how you can
do it:

num2.comp.imag = 11;

Calculate Difference Between Two Time Periods

#include <stdio.h>
struct TIME {
int seconds;
int minutes;
int hours;
};

void differenceBetweenTimePeriod(struct TIME t1,


struct TIME t2,
struct TIME *diff);

int main() {
struct TIME startTime, stopTime, diff;
printf("Enter the start time. \n");
printf("Enter hours, minutes and seconds: ");
scanf("%d %d %d", &startTime.hours,
&startTime.minutes,
&startTime.seconds);

printf("Enter the stop time. \n");


printf("Enter hours, minutes and seconds: ");
scanf("%d %d %d", &stopTime.hours,
&stopTime.minutes,
&stopTime.seconds);

// Difference between start and stop time


differenceBetweenTimePeriod(startTime, stopTime, &diff);
printf("\nTime Difference: %d:%d:%d - ", startTime.hours,
startTime.minutes,
startTime.seconds);
printf("%d:%d:%d ", stopTime.hours,
stopTime.minutes,
stopTime.seconds);
printf("= %d:%d:%d\n", diff.hours,
diff.minutes,
diff.seconds);
return 0;
}

// Computes difference between time periods


void differenceBetweenTimePeriod(struct TIME start,
struct TIME stop,
struct TIME *diff) {
while (stop.seconds > start.seconds) {
--start.minutes;
start.seconds += 60;
}
diff->seconds = start.seconds - stop.seconds;
while (stop.minutes > start.minutes) {
--start.hours;
start.minutes += 60;
}
diff->minutes = start.minutes - stop.minutes;
diff->hours = start.hours - stop.hours;
}

Output

Enter the start time.


Enter hours, minutes and seconds: 13
34
55
Enter the stop time.
Enter hours, minutes and seconds: 8
12
15

Time Difference: 13:34:55 - 8:12:15 = 5:22:40


-Shital Dongre
Introduction to Tree
 Fundamental data storage structures used in
programming.
 Combines advantages of an ordered array and a linked
list.
 Searching as fast as in ordered array.
 Insertion and deletion as fast as in linked list.
Tree characteristics
 Consists of nodes connected by edges.
 Nodes often represent entities (complex objects) such
as people, car parts etc.
 Edges between the nodes represent the way the nodes
are related.
 Its easy for a program to get from one node to another
if there is a line connecting them.
 The only way to get from node to node is to follow a
path along the edges.
Tree Terminology
 Path: Traversal from node to node along the edges results
in a sequence called path.
 Root: Node at the top of the tree.
 Parent: Any node, except root has exactly one edge running
upward to another node. The node above it is called parent.
 Child: Any node may have one or more lines running
downward to other nodes. Nodes below are children.
 Leaf: A node that has no children.
 Subtree: Any node can be considered to be the root of a
subtree, which consists of its children and its children’s
children and so on.
Tree Terminology
 Visiting: A node is visited when program control
arrives at the node, usually for processing.
 Traversing: To traverse a tree means to visit all the
nodes in some specified order.
 Levels: The level of a particular node refers to how
many generations the node is from the root. Root
is assumed to be level 0.
 Keys: Key value is used to search for the item or
perform other operations on it.
Tree Terminology
 A tree is a collection of elements (nodes)
 Each node may have 0 or more successors
 (Unlike a list, which has 0 or 1 successor)
 Each node has exactly one predecessor
 Except the starting / top node, called the root
 Links from node to its successors are called branches
 Successors of a node are called its children
 Predecessor of a node is called its parent
 Nodes with same parent are siblings
 Nodes with no children are called leaves

8
More terminology
 Node A is the parent of node B if node B is a child of A
 Node A is an ancestor of node B if A is a parent of B, or
if some child of A is an ancestor of B
 In less formal terms, A is an ancestor of B if B is a child
of A, or a child of a child of A, or a child of a child of a
child of A, etc.
 Node B is a descendant of A if A is an ancestor of B
 Nodes A and B are siblings if they have the same
parent

9
More Properties of Trees

 depth: the path length from the root of the tree to this node
 height of a node: The maximum distance (path length) of
any leaf from this node
 a leaf has a height of 0
 the height of a tree is the height of the root of that tree
 descendants: any nodes that can be reached via 1 or more
edges from this node
 ancestors: any nodes for which this node is a descendant

10
Tree
 Length of a path =
number of edges
A
 Depth of a node N
= length of path
from root to N
B C D
 Height of node N =
length of longest
path from N to a
E F
leaf
 Level- level of root
is 0

11
 The maximum number of nodes at level ‘l’ of a
binary tree is 2l.
For root, l = 0, number of nodes = 20 = 1
Assume that maximum number of nodes on level ‘l’ is 2l
Height of Trees
 Trees come in many shapes

• Height of any tree: number of nodes on


the longest path from the root to a leaf
Height
 height of a tree is maximum number of nodes on root to
leaf path. Height of a tree with single node is considered
as 1.
 Maximum number of nodes in a binary tree of height
‘h’ is 2h – 1.
 Maximum number of nodes in a binary tree of height
h is 1 + 2 + 4 + .. + 2h-1
(height of the root is considered as 0. In this convention, the
above formula becomes 2h+1 – 1)
 In a Binary Tree with N nodes, minimum possible
height or minimum number of levels is
Log2(N+1)
Binary Trees
 Binary tree: a node has at most 2 non-empty subtrees
 Set of nodes T is a binary tree if either of these is true:
 T is empty
 Root of T has two subtrees, both binary trees
 (Notice that this is a recursive definition)

15
Examples of Binary Trees
 Expression tree
 Non-leaf (internal) nodes contain operators
 Leaf nodes contain operands
 Huffman tree
 Represents Huffman codes for characters appearing in a file or
stream
 Huffman code may use different numbers of bits to encode
different characters
 ASCII or Unicode uses a fixed number of bits for each character

Chapter 8: Trees 16
Examples of Binary Trees (2)

Chapter 8: Trees 17
Examples of Binary Trees (3)

Code for b = 100000


Code for w = 110001
Code for s = 0011
Code for e = 010

Chapter 8: Trees 18
Fullness and Completeness
 (In computer science) trees grow from the top down
 New values inserted in new leaf nodes
 A binary tree is full if all leaves are at the same level
 In a full tree, every node has 0 or 2 non-null children

Chapter 8: Trees 19
Full Binary Tree
 If the height is h>0
 The number of leaves is 2^(h-1)
 The number of nodes is 2^h – 1
 If the number of nodes is N>0
 The height is log2(N+1)
 The number of leaves is (N+1)/2
Fullness and Completeness (2)
 A binary tree is complete if:
 All leaves are at level h or level h-1 (for some h)
 All level h-1 leaves are to the right

Chapter 8: Trees 21
Balanced Binary Trees
 Balanced binary tree
 The height of any node’s right subtree differs from the
height of the node’s left subtree by no more than 1
 A complete binary tree is balanced
Unbalanced Trees
 Some trees can be unbalanced.
 They have most of their nodes on one side of the
root or the other. Individual subtrees may also be
unbalanced.
 Trees become unbalanced because of the order in
which the data items are inserted.
 If the key values are inserted in ascending or
descending order the tree will be unbalanced.
 For search-centric application (Binary tree), an
unbalanced tree must be re-balanced.
Array-Based Implementation of a
Complete Binary Tree
• If nodes numbered
according to a level-by-
level scheme
– Root index: 0
– Given any node tree[i]
• Left child index: 2*i+1
• Right chile index: 2*i+2
• Parent index: (i-1)/2
Pointer-Based Implementation
Traversals of Binary Trees
 Often want iterate over and process nodes of a tree
 Can walk the tree and visit the nodes in order
 This process is called tree traversal
 Three kinds of binary tree traversal:
 Preorder(Root, Left, Right)
 Inorder(Left, Root, Right)
 Postorder(Left, Right, Root)
 According to order of subtree root w.r.t. its children

Chapter 8: Trees 27
(a) Inorder (Left, Root, Right) : 4 2 5 1 3
(b) Preorder (Root, Left, Right) : 1 2 4 5 3
(c) Postorder (Left, Right, Root) : 4 5 2 3 1
struct node
{
int data;
struct node* left;
struct node* right;
};
struct node* newNode(int data)
{
struct node* node = (struct node*)
malloc(sizeof(struct node));
node->data = data;
node->left = NULL;
node->right = NULL;

return(node);
}
void printInorder(struct node* node)
{
if (node == NULL)
return;

printInorder(node->left);

printf("%d ", node->data);

printInorder(node->right);
}
void printPreorder(struct node* node)
{
if (node == NULL)
return;

printf("%d ", node->data);

printPreorder(node->left);

printPreorder(node->right);
}
void printPostorder(struct node* node)
{
if (node == NULL)
return;

printPostorder(node->left);

printPostorder(node->right);

printf("%d ", node->data);


}
Inorder Tree Traversal without
Recursion

1) Create an empty stack S.


2) Initialize current node as root
3) Push the current node to S and set current = current->left
until current is NULL
4) If current is NULL and stack is not empty then
a) Pop the top item from stack.
b) b) Print the popped item, set current = popped_item-
>right
c) c) Go to step 3.
5) If current is NULL and stack is empty then we are done.
void inOrder(struct tNode *root)
{
struct tNode *current = root;
struct sNode *s = NULL; /* Initialize stack s */
bool done = 0;

while (!done)
{
if(current != NULL)
{
push(&s, current);
current = current->left;
}

else
{
if (!isEmpty(s))
{
current = pop(&s);
printf("%d ", current->data);

current = current->right;
}
else
done = 1;
}
} /* end of while */
}
Construct tree
Construct Tree from given Inorder and
Preorder traversals
Inorder sequence: D B E A F C
Preorder sequence: A B D E C F
• Example
buildTree()
1) Pick an element from Preorder. Increment a Preorder Index
Variable (preIndex in below code) to pick next element in next
recursive call.
2) Create a new tree node tNode with the data as picked
element.
3) Find the picked element’s index in Inorder. Let the index be
inIndex.
4) Call buildTree for elements before inIndex and make the built
tree as left subtree of tNode.
5) Call buildTree for elements after inIndex and make the built
tree as right subtree of tNode.
6) return tNode.
Construct a Binary Tree from Postorder
and Inorder
Process of constructing tree from in[] = {4, 8, 2, 5, 1, 6, 3,
7} and post[] = {8, 4, 5, 2, 6, 7, 3, 1}

1) We first find the last node in post[]. The last node is


“1”, we know this value is root as root always appear in
the end of postorder traversal.
2) We search “1” in in[] to find left and right subtrees of
root. Everything on left of “1” in in[] is in left subtree and
everything on right is in right subtree.
3) We recur the above process for following two.
….b) Recur for in[] = {6, 3, 7} and post[] = {6, 7, 3}
…….Make the created tree as right child of root.
….a) Recur for in[] = {4, 8, 2, 5} and post[] = {8, 4, 5, 2}.
…….Make the created tree as left child of root.
Full Binary Tree
• It is not possible to construct a general Binary
Tree from preorder and postorder traversals.
But if know that the Binary Tree is Full, we can
construct the tree without ambiguity.
Construct Full Binary Tree from given preorder and
postorder traversals

pre[] = {1, 2, 4, 8, 9, 5, 3, 6, 7}
post[] = {8, 9, 4, 5, 2, 6, 7, 3, 1}

• 1 is root and 2 is left child


• So 2 is root of all nodes in left subtree
• Ultimately all nodes before 2 in post[] must be in left subtree
• {8, 9, 4, 5, 2} are in left subtree, and the elements {6, 7, 3} are in right subtree
Binary Search Tree is a node-based binary tree data
structure which has the following properties:
 The left subtree of a node contains only nodes with keys
lesser than the node’s key.
 The right subtree of a node contains only nodes with keys
greater than the node’s key.
 The left and right subtree each must also be a binary search
tree.
Insertion of a key

50, 30, 20, 40, 70, 60,80


50, 30, 20, 40, 70, 60,80
50
30 70
20 40 60 80
struct node
{
int key;
struct node *left, *right;
};

struct node *newNode(int item)


{
struct node *temp = (struct node *)malloc(sizeof(struct node));
temp->key = item;
temp->left = temp->right = NULL;
return temp;
}
struct node* insert(struct node* node, int key)
{
/* If the tree is empty, return a new node */
if (node == NULL) return newNode(key);

/* Otherwise, recur down the tree */


if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);

/* return the (unchanged) node pointer */


return node;
}
Search
struct node* search(struct node* root, int key)
{
// Base Cases: root is null or key is present at
root
if (root == NULL || root->key == key)
return root;

// Key is greater than root's key


if (root->key < key)
return search(root->right, key);

// Key is smaller than root's key


return search(root->left, key);
}
 Time Complexity: The worst case time complexity of
search and insert operations is O(h) where h is height
of Binary Search Tree. In worst case, we may have to
travel from root to the deepest leaf node. The height of
a skewed tree may become n and the time complexity
of search and insert operation may become O(n).
Traversal
void inorder(struct node *root)
{
if (root != NULL)
inorder(root->left);
printf("%d \n", root->key);
inorder(root->right);
} }

1 3 4 6 7 8 10 13 14
Traversal
void preorder(struct node *root)
{
if (root != NULL)
printf("%d \n", root->key);
preorder(root->left);
preorder(root->right);
}

8 3 1 6 4 7 10 14 13
Traversal
void postorder(struct node *root)
{
if (root != NULL)
postorder(root->left);
postorder(root->right);
printf("%d \n", root->key);
}

1 4 7 6 3 13 14 10 8
Delete
1. Node to be deleted at leaf
2. Node to be deleted has only one child
3. Node to be deleted has two children
 Node to be deleted at leaf
Node to be deleted has only one child
 Node to be deleted has two children
struct node* deleteNode(struct node* root, int key)
{
// base case
if (root == NULL) return root;

// If the key to be deleted is smaller than the root's key,


// then it lies in left subtree
if (key < root->key)
root->left = deleteNode(root->left, key);

// If the key to be deleted is greater than the root's key,


// then it lies in right subtree
else if (key > root->key)
root->right = deleteNode(root->right, key);
else
{
// node with only one child or no child
if (root->left == NULL)
{
struct node *temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL)
{
struct node *temp = root->left;
free(root);
return temp;
}

// node with two children: Get the inorder successor (smallest


// in the right subtree)
struct node* temp = minValueNode(root->right);

// Copy the inorder successor's content to this node


root->key = temp->key;

// Delete the inorder successor


root->right = deleteNode(root->right, temp->key);
}
return root;
}
struct node * minValueNode(struct node* node)
{
struct node* current = node;

/* loop down to find the leftmost leaf */


while (current && current->left != NULL)
current = current->left;

return current;
}
Construct tree
 Preorder-{10, 5, 1, 7, 40, 50}
 [8,5,1,7,10,12]
 50, 70, 60, 20, 90, 10, 40, 100
 The first element of preorder traversal is always root.
We first construct the root.
 Then we find the index of first element which is
greater than root. Let the index be ‘i’. The values
between root and ‘i’ will be part of left subtree, and the
values between ‘i+1’ and ‘n-1’ will be part of right
subtree.
 Divide given pre[] at index “i” and recur for left and
right sub-trees.
 ( O(n2) time complexity )
From postorder traversal
 {1, 7, 5, 50, 40, 10}

{2, 6, 4, 9, 13, 11, 7},


Algorithm:
 Push root of the BST to the stack i.e, last element of the array.
 Start traversing the array in reverse, if next element is > the
element at the top of the stack then,
set this element as the right child of the element at the top of the
stack and also push it to the stack.
 Else if, next element is < the element at the top of the stack then,
start popping all the elements from the stack until either the
stack is empty or the current element becomes > the element at
the top of the stack.
 Make this element left child of the last popped node and repeat
the above steps until the array is traversed completely.
from its given level order traversal

 {7, 4, 12, 3, 6, 8, 1, 5, 10}


Count nodes
Iterative
1) Create an empty Queue Node and push root node to
Queue.
2) Do following while nodeQueue is not empty.
a) Pop an item from Queue and process it.
a. 1) If it is full node then increment count++.
b) Push left child of popped item to Queue, if
available.
c) Push right child of popped item to Queue, if
available.
// If tree is empty
if (!node)
return 0;
queue<Node *> q;

// Do level order traversal starting from root


int count = 0; // Initialize count of full nodes
q.push(node);
while (!q.empty())
{
struct Node *temp = q.front();
q.pop(); Time Complexity: O(n)

if (temp->left && temp->right)


count++;

if (temp->left != NULL)
q.push(temp->left);
if (temp->right != NULL)
q.push(temp->right);
}
return count;
 Recursive
unsigned int getCount(struct Node* root)
{
if (root == NULL)
return 0;

int res = 0;
if (root->left && root->right)
res++;

res += (getCount(root->left) + getCount(root->right));


return res;
}
Height
maxDepth()
1. If tree is empty then return 0
2. Else
(a) Get the max depth of left subtree recursively i.e., call
maxDepth( tree->left-subtree)
(b) Get the max depth of right subtree recursively i.e., call
maxDepth( tree->right-subtree)
(c) Get the max of max depths of left and right subtrees and
add 1 to it for the current node. max_depth = max(max dept
of left subtree, max depth of right subtree) + 1
(d) Return max_depth
int maxDepth(node* node)
{
if (node == NULL)
return 0;
else
{
/* compute the depth of each subtree */
int lDepth = maxDepth(node->left);
int rDepth = maxDepth(node->right);

/* use the larger one */


if (lDepth > rDepth)
return(lDepth + 1);
else return(rDepth + 1);
}
}
Mirror image
(1) Call Mirror for left-subtree i.e., Mirror(left-subtree)
(2) Call Mirror for right-subtree i.e., Mirror(right-
subtree)
(3) Swap left and right subtrees. temp = left-subtree left-
subtree = right-subtree right-subtree = temp
void mirror(struct Node* node)
{
if (node==NULL)
return;
else
{
struct Node* temp;

/* do the subtrees */
mirror(node->left);
mirror(node->right);

/* swap the pointers in this node */


temp = node->left;
node->left = node->right;
node->right = temp;
}
}
Construct all possible BSTs for keys 1 to N
 All node in left subtree are smaller than root and in
right subtree are larger than root.
 So if we have ith number as root, all numbers from 1 to
i-1 will be in left subtree and i+1 to N will be in right
subtree.
 If 1 to i-1 can form x different trees and i+1 to N can
from y different trees then we will have x*y total trees
when ith number is root.
 N choices for root, so iterate from 1 to N for root and
another loop for left and right subtree.
Threaded BT/BST

1
Threaded Trees
• Binary trees have a lot of wasted space: the
leaf nodes each have 2 null pointers
• We can use these pointers to help us in
inorder traversals
• We have the pointers reference the next node
in an inorder traversal; called threads
• We need to know if a pointer is an actual link
or a thread, so we keep a boolean for each
pointer
2
• Inorder traversal of a Binary tree can either be
done using recursion or with the use of a
auxiliary stack. The idea of threaded binary
trees is to make inorder traversal faster and do
it without stack and without recursion. A
binary tree is made threaded by making all
right child pointers that would normally be
NULL point to the inorder successor of the
node (if it exists).
• The threads are also useful for fast accessing
ancestors of a node.

3
There are two types of threaded binary trees.
• Single Threaded: Where a NULL right pointers is
made to point to the inorder successor (if
successor exists)
• Double Threaded: Where both left and right
NULL pointers are made to point to inorder
predecessor and inorder successor respectively.
The predecessor threads are useful for reverse
inorder traversal and postorder traversal.
4
Threaded Tree

5
Threaded Tree Traversal
• We start at the leftmost node in the tree, print
it, and follow its right thread
• If we follow a thread to the right, we output
the node and continue to its right
• If we follow a link to the right, we go to the
leftmost node, print it, and continue

6
Threaded Tree Traversal
Output
6 1

3 8
1 5 7 11
9 13
Start at leftmost node, print it

7
Threaded Tree Traversal
Output
6 1
3

3 8
1 5 7 11
9 13
Follow thread to right, print node

8
Threaded Tree Traversal
Output
6 1
3
5
3 8
1 5 7 11
9 13
Follow link to right, go to leftmost
node and print
9
Threaded Tree Traversal
Output
6 1
3
5
3 8 6

1 5 7 11
9 13
Follow thread to right, print node

10
Threaded Tree Traversal
Output
6 1
3
5
3 8 6
7
1 5 7 11
9 13
Follow link to right, go to
leftmost node and print
11
Threaded Tree Traversal
Output
6 1
3
5
3 8 6
7
1 5 7 11 8

9 13
Follow thread to right, print node

12
Threaded Tree Traversal
Output
6 1
3
5
3 8 6
7
1 5 7 11 8
9

9 13
Follow link to right, go to
leftmost node and print
13
Threaded Tree Traversal
Output
6 1
3
5
3 8 6
7
1 5 7 11 8
9
11
9 13
Follow thread to right, print node

14
Threaded Tree Traversal
Output
6 1
3
5
3 8 6
7
1 5 7 11 8
9
11
9 13 13

Follow link to right, go to


leftmost node and print
15
struct Node
{
int data;
Node *left, *right;
bool rightThread;
}

16
Threaded Tree Traversal Code
// C code to do inorder traversal in a threaded binary tree
void inOrder(struct Node *root)
{
struct Node *cur = leftmost(root);
while (cur != NULL)
{
printf("%d ", cur->data);

// If this node is a thread node, then go to


// inorder successor
if (cur->rightThread)
cur = cur->right;
else // Else go to the leftmost child in right subtree
cur = leftmost(cur->right);
} 17
struct Node* leftMost(struct Node *n)
{
if (n == NULL)
return NULL;

while (n->left != NULL)


n = n->left;

return n;
}

18
Threaded Tree Modification
• We’re still wasting pointers, since half of our
leafs’ pointers are still null
• We can add threads to the previous node in
an inorder traversal as well, which we can use
to traverse the tree backwards or even to do
postorder traversals

19
Threaded Tree Modification
6
3 8
1 5 7 11
9 13

20
Insertion
• First element
• Left Child
• Right Child

21
• First element
root = temp;
temp -> left = NULL;
temp -> right = NULL;

22
• Left Child
temp -> left = parent ->left;
temp -> right = parent;
parent -> leftThreaded = false;
parent -> left = temp;

23
• Right Child
temp -> left = parent;
temp -> right = parent -> right;
parent -> rightThreaded = false;
parent -> right = temp;

24
struct Node insert(struct Node root, int val)
{
// Search for the correct position
struct Node *ptr = root;
struct Node *parent = NULL; // parentent of key to be inserted
while (ptr != NULL)
{

// Moving on left subtree.


if (val < ptr->data)
{
if (ptr -> leftThreaded == false)
ptr = ptr -> left;
else
break;
}
// Moving on right subtree.
else
{
if (ptr->rightThreaded == false)
ptr = ptr -> right;
else
break;
}
}

25
// Create a new node
struct Node *temp = (struct Node* )malloc(sizeof(struct Node));
temp -> data = val;
temp -> leftThreaded = true;
temp -> rightThreaded = true;
if (parent == NULL)
{
root = temp;
temp -> left = NULL;
temp -> right = NULL;
}
else if (val < (parent -> data))
{
temp -> left = parent -> left;
temp -> right = parent;
parent -> leftThreaded = false;
parent -> left = temp;
}
else
{
temp -> left = parent;
temp -> right = parent -> right;
parent -> rightThreaded = false;
parent -> right = temp;
}
return root;
}
26
AVL Tree

- Shital Dongre, VIT, Pune


Background
So far …
– Binary search trees store linearly ordered
data
– Best case height: Q(ln(n))
– Worst case height: O(n)

Requirement:
– Define and maintain a balance to ensure
Q(ln(n)) height
Prototypical Examples
These two examples demonstrate how we can correct
for imbalances: starting with this tree, add 1:
Prototypical Examples
This is more like a linked list; however, we can fix this…
Prototypical Examples
Promote 2 to the root, demote 3 to be 2’s right child, and
1 remains the left child of 2
Prototypical Examples
Again, the product is a linked list; however, we can fix
this, too
Why AVL Trees?
• Most of the BST operations (e.g., search, max, min,
insert, delete.. etc) take O(h) time where h is the height
of the BST.
• The cost of these operations may become O(n) for a
skewed Binary tree.
• If we make sure that height of the tree remains
O(Logn) after every insertion and deletion, then we can
guarantee an upper bound of O(Logn) for all these
operations.
• The height of an AVL tree is always O(Logn) where n is
the number of nodes in the tree
AVL Trees
We will focus on the first strategy: AVL trees
– Named after Adelson-Velskii and Landis

Balance is defined by comparing the height of


the two sub-trees

Recall:
– An empty tree has height –1
– A tree with a single node has height 0
AVL Trees
• AVL tree is a self-balancing Binary Search Tree
(BST) where the difference between heights of
left and right subtrees cannot be more than
one for all nodes.
• A binary search tree is said to be AVL
balanced if:
– The difference in the heights between the left
and right sub-trees is at most 1, and
– Both sub-trees are themselves AVL trees
AVL Trees
AVL trees with 1, 2, 3, and 4 nodes:
AVL Trees
Here is a larger AVL tree (42 nodes):
AVL Trees
All other nodes are AVL balanced
– The sub-trees differ in height by at most one
Height of an AVL Tree
By the definition of complete trees, any
complete binary search tree is an AVL tree

Thus an upper bound on the number of


nodes in an AVL tree of height h a perfect
binary tree with 2h + 1 – 1 nodes
Insertion
Steps to follow for insertion
Let the newly inserted node be w
1) Perform standard BST insert for w.
2) Starting from w, travel up and find the first unbalanced node. Let z
be the first unbalanced node, y be the child of z that comes on the
path from w to z and x be the grandchild of z that comes on the path
from w to z.
3) Re-balance the tree by performing appropriate rotations on the
subtree rooted with z. There can be 4 possible cases that needs to be
handled as x, y and z can be arranged in 4 ways. Following are the
possible 4 arrangements:
a) y is left child of z and x is left child of y (Left Left Case)
b) y is left child of z and x is right child of y (Left Right Case)
c) y is right child of z and x is right child of y (Right Right Case)
d) y is right child of z and x is left child of y (Right Left Case)
Examples
• Insert 14, 17, 11, 7, 53, 4, 13 into an empty
AVL tree. Remove 53, 11, 8
• Build an AVL tree with the following values:
15, 20, 24, 10, 13, 7, 30, 36, 25
AVL Tree Example:
• Insert 14, 17, 11, 7, 53, 4, 13 into an empty AVL tree

14

11 17

7 53

4
AVL Tree Example:
• Insert 14, 17, 11, 7, 53, 4, 13 into an empty AVL tree

14

7 17

4 11 53

13
AVL Tree Example:
• Now insert 12

14

7 17

4 11 53

13

12
AVL Tree Example:
• Now insert 12

14

7 17

4 11 53

12

13
AVL Tree Example:
• Now the AVL tree is balanced.

14

7 17

4 12 53

11 13
AVL Tree Example:
• Now insert 8

14

7 17

4 12 53

11 13

8
AVL Tree Example:
• Now insert 8

14

7 17

4 11 53

8 12

13
AVL Tree Example:
• Now the AVL tree is balanced.

14

11 17

7 12 53

4 8 13
AVL Tree Example:
• Now remove 53

14

11 17

7 12 53

4 8 13
AVL Tree Example:
• Now remove 53, unbalanced

14

11 17

7 12

4 8 13
AVL Tree Example:
• Balanced! Remove 11

11

7 14

4 8 12 17

13
AVL Tree Example:
• Remove 11, replace it with the largest in its left branch

7 14

4 12 17

13
AVL Tree Example:
• Remove 8, unbalanced

4 14

12 17

13
AVL Tree Example:
• Remove 8, unbalanced

4 12

14

13 17
AVL Tree Example:
• Balanced!!

12

7 14

4 13 17
• Build an AVL tree with the following values:
15, 20, 24, 10, 13, 7, 30, 36, 25
15, 20, 24, 10, 13, 7, 30, 36, 25
20

15
15 24
20
10
24

13

20 20

13 24 15 24

10 15 13

10
15, 20, 24, 10, 13, 7, 30, 36, 25

20
13

13 24 10 20

10 15 7 15 24

7 30

13 36

10 20

7 15 30

24 36
15, 20, 24, 10, 13, 7, 30, 36, 25

13 13

10 20 10 20

7 15 30 7 15 24

24 36 30

25 13 25 36

10 24

7 20 30

15 25 36
Remove 24 and 20 from the AVL tree.

13 13

10 24 10 20

7 20 30 7 15 30

15 25 36 25 36

13 13

10 30 10 15

7 15 36 7 30

25 25 36
struct Node *rightRotate(struct Node *y)
{
struct Node *x = y->left;
struct Node *T2 = x->right;

// Perform rotation
x->right = y;
y->left = T2;

// Update heights
y->height = max(height(y->left), height(y->right))+1;
x->height = max(height(x->left), height(x->right))+1;

// Return new root


return x;
}
struct Node *leftRotate(struct Node *x)
{
struct Node *y = x->right;
struct Node *T2 = y->left;

// Perform rotation
y->left = x;
x->right = T2;

// Update heights
x->height = max(height(x->left), height(x->right))+1;
y->height = max(height(y->left), height(y->right))+1;

// Return new root


return y;
}
int getBalance(struct Node *N)
{
if (N == NULL)
return 0;
return height(N->left) - height(N->right);
}
struct Node* insert(struct Node* node, int key)
{
// If this node becomes unbalanced, then
/* 1. Perform the normal BST insertion */ // there are 4 cases
if (node == NULL)
return(newNode(key)); // Left Left Case
if (balance > 1 && key < node->left->key)
if (key < node->key) return rightRotate(node);
node->left = insert(node->left, key);
else if (key > node->key) // Right Right Case
node->right = insert(node->right, key); if (balance < -1 && key > node->right->key)
else // Equal keys are not allowed in BST return leftRotate(node);
return node;
// Left Right Case
/* 2. Update height of this ancestor node */ if (balance > 1 && key > node->left->key)
node->height = 1 + max(height(node->left),
{
height(node->right));
node->left = leftRotate(node->left);
/* 3. Get the balance factor of this ancestor
return rightRotate(node);
node to check whether this node became }
unbalanced */
int balance = getBalance(node); // Right Left Case
if (balance < -1 && key < node->right->key)
{
node->right = rightRotate(node->right);
return leftRotate(node);
}
/* return the (unchanged) node pointer */
return node;
}
Red-Black Tree
Red-Black Tree is a self-balancing Binary Search Tree
(BST) where every node follows following rules.

1) Every node has a color either


red or black.
2) Root of tree is always black.
3) There are no two adjacent red
nodes (A red node cannot have a
red parent or red child).
4) Every path from a node
(including root) to any of its
descendant NULL node has the
same number of black nodes.
Why Red-Black Trees?
• Most of the BST operations (e.g., search, max,
min, insert, delete.. etc) take O(h) time where
h is the height of the BST. The cost of these
operations may become O(n) for a skewed
Binary tree. If we make sure that height of the
tree remains O(Logn) after every insertion and
deletion, then we can guarantee an upper
bound of O(Logn) for all these operations. The
height of a Red-Black tree is always O(Logn)
where n is the number of nodes in the tree.
Comparison with AVL Tree
The AVL trees are more balanced compared to
Red-Black Trees, but they may cause more
rotations during insertion and deletion. So if
your application involves many frequent
insertions and deletions, then Red Black trees
should be preferred. And if the insertions and
deletions are less frequent and search is a more
frequent operation, then AVL tree should be
preferred over Red-Black Tree.
How does a Red-Black Tree ensure
balance?
A chain of 3 nodes is nodes is not possible in Red-Black Trees.
Following are NOT Red-Black Trees
30 30 30
/\ / \ / \
20 NIL 20 NIL 20 NIL
/\ /\ / \
10 NIL 10 NIL 10 NIL
Violates Violates Violates
Property 4. Property 4 Property 3

Following are different possible Red-Black Trees with above 3 keys


20 20
/ \ / \
10 30 10 30
/ \ / \ / \ / \
NIL NIL NIL NIL NIL NIL NIL NIL
In Red-Black tree, we use two tools to do
balancing.
1) Recoloring
2) Rotation
• Color of a NULL node is considered as BLACK.
• Let x be the newly inserted node.
1) Perform standard BST insertion and make the color of newly
inserted nodes as RED.
2) If x is root, change color of x as BLACK (Black height of complete tree
increases by 1).
3) Do following if color of x’s parent is not BLACK or x is not root.
….a) If x’s uncle is RED (Grand parent must have been black
from property 4)
……..(i) Change color of parent and uncle as BLACK.
……..(ii) color of grand parent as RED.
……..(iii) Change x = x’s grandparent, repeat steps 2 and 3 for new x.
….b) If x’s uncle is BLACK, then there can be four
configurations for x, x’s parent (p) and x’s
grandparent (g) (This is similar to AVL Tree)
……..i) Left Left Case (p is left child of g and x is left
child of p)
……..ii) Left Right Case (p is left child of g and x is
right child of p)
……..iii) Right Right Case (Mirror of case i)
……..iv) Right Left Case (Mirror of case ii)
All four cases when Uncle is BLACK
• Left Left Case
• Left Right Case
• Right Right Case
• Right Left Case
Insert logic
• enum Color {RED, BLACK};
• bool color;
• int color;

struct Node
{
int data;
bool color;
Node *left, *right, *parent;
};
void inorder (Node *root)
{
if (root == NULL)
return;

inorder (root->left);
cout << root->data << " ";
inorder (root->right);
}
Node* BSTInsert(Node* root, Node *pt)
{
/* If the tree is empty, return a new node */
if (root == NULL)
return pt;

/* Otherwise, recur down the tree */


if (pt->data < root->data)
{
root->left = BSTInsert(root->left, pt);
root->left->parent = root;
}
else if (pt->data > root->data)
{
root->right = BSTInsert(root->right, pt);
root->right->parent = root;
}

/* return the (unchanged) node pointer */


return root;
}
RB_rules(Node *&root, Node *&pt)
{
Node *parent_pt = NULL;
Node *grand_parent_pt = NULL;

while ((pt != root) && (pt->color != BLACK) && (pt->parent->color == RED))


{
parent_pt = pt->parent;
grand_parent_pt = pt->parent->parent;

/* Case : A Parent of pt is left child of Grand-parent of pt */


if (parent_pt == grand_parent_pt->left)
{

Node *uncle_pt = grand_parent_pt->right;

/* Case : 1 The uncle of pt is also red Only Recoloring required */


if (uncle_pt != NULL && uncle_pt->color == RED)
{
grand_parent_pt->color = RED;
parent_pt->color = BLACK;
uncle_pt->color = BLACK;
pt = grand_parent_pt;
}
else
{
/* Case : 2 pt is right child of its parent Left-rotation required */
if (pt == parent_pt->right)
{
rotateLeft(root, parent_pt);
pt = parent_pt;
parent_pt = pt->parent;
}

/* Case : 3 pt is left child of its parent Right-rotation required */


rotateRight(root, grand_parent_pt);
swap(parent_pt->color, grand_parent_pt->color);
pt = parent_pt;
}
}
/* Case : B
Parent of pt is right child of Grand-parent of pt */
else
{
Node *uncle_pt = grand_parent_pt->left;

/* Case : 1 The uncle of pt is also red Only Recoloring required */

if ((uncle_pt != NULL) && (uncle_pt->color == RED))


{
grand_parent_pt->color = RED;
parent_pt->color = BLACK;
uncle_pt->color = BLACK;
pt = grand_parent_pt;

else
{
}
/* Case : 2 pt is left child of its parent Right-rotation required */

if (pt == parent_pt->left)
{
rotateRight(root, parent_pt);
pt = parent_pt;
parent_pt = pt->parent;
}

/* Case : 3 pt is right child of its parent, Left-rotation required */

rotateLeft(root, grand_parent_pt);
swap(parent_pt->color, grand_parent_pt->color);
pt = parent_pt;
}
root->color = BLACK;

RBTree tree;

tree.insert(7);
void rotateLeft(Node *&root, Node *&pt)
{
Node *pt_right = pt->right;

pt->right = pt_right->left;

if (pt->right != NULL)
pt->right->parent = pt;

pt_right->parent = pt->parent;

if (pt->parent == NULL)
root = pt_right;

else if (pt == pt->parent->left)


pt->parent->left = pt_right;

else
pt->parent->right = pt_right;

pt_right->left = pt;
pt->parent = pt_right;
}
void rotateRight(Node *&root, Node *&pt)
{
Node *pt_left = pt->left;

pt->left = pt_left->right;

if (pt->left != NULL)
pt->left->parent = pt;

pt_left->parent = pt->parent;

if (pt->parent == NULL)
root = pt_left;

else if (pt == pt->parent->left)


pt->parent->left = pt_left;

else
pt->parent->right = pt_left;

pt_left->right = pt;
pt->parent = pt_left;
}
Insertion Vs Delete
Insert Delete
Recoloring and rotations are used Recoloring and rotations are used
We check color of uncle to decide the We check color of sibling to decide the
appropriate case appropriate case.
Main property that violates after insertion Main violated property is, change of black
is two consecutive red. height in subtrees as deletion of a black
node may cause reduced black height in
one root to leaf path.

When a black node is deleted and replaced by a black child, the child is
marked as double black. The main task now becomes to convert this double
black to single black.
Deletion Steps
1) Perform standard BST delete.
(Delete node which is either leaf or has only one
child. Successor will be used to replace. )
2) Case 1. If either u or v is red-
we mark the replaced child as black (No change
in black height). Note that both u and v cannot
be red as v is parent of u and two consecutive
reds are not allowed in red-black tree.
• If Both u and v are Black
Color u as double black. Now our task reduces to convert this
double black to single black. Note that If v is leaf, then u is
NULL and color of NULL is considered as black. So the deletion
of a black leaf also causes a double black.

while the current node u is double black and it is not root. Let sibling of
node be s.
Case1:If sibling s is black and at least one of sibling’s children is red
Case2: If sibling is black and its both children are black
Case3:If sibling is red
.(a): If sibling s is black and at least one of sibling’s
children is red, perform rotation(s). Let the red child of s
be r. This case can be divided in four subcases depending
upon positions of s and r.
…………..(i) Left Left Case (s is left child of its parent and r
is left child of s or both children of s are red). This is
mirror of right right case.

…………..(ii) Left Right Case (s is left child of its parent and


r is right child). This is mirror of right left.
…………..(iii) Right Right Case (s is right child of its parent
and r is right child of s or both children of s are red)

…………..(iv) Right Left Case (s is right child of its parent


and r is left child of s)
(iii) Right Right Case (s is right child of its parent
and r is right child of s or both children of s are
red)
(iv) Right Left Case (s is right child of its parent
and r is left child of s)
.(b): If sibling is black and its both children are
black, perform recoloring, and recur for the
parent if parent is black.

In this case, if parent was red, then we didn’t


need to recur for prent, we can simply make it
black (red + double black = single black)
…..(c): If sibling is red,
perform a rotation to move old sibling up,
recolor the old sibling and parent. The new
sibling is always black
• If u is root, make it single black and return
(Black height of complete tree reduces by 1).
Node * finduncle() {
// If no parent or grandparent, then no uncle
if (parent == NULL or parent->parent == NULL)
return NULL;

if (parent->isOnLeft())
// uncle on right
return parent->parent->right;
else
// uncle on left
return parent->parent->left;
}
Node * findsibling() {
// sibling null if no parent
if (parent == NULL)
return NULL;

if (isOnLeft())
return parent->right;

return parent->left;
}
bool hasRedChild() {
return (left != NULL and left->color == RED) or
(right != NULL and right->color == RED);
}
• bool isOnLeft() { return this == parent->left; }

• int isOnLeft() { return current == parent->left; }


void moveDown(Node *nParent) {
if (parent != NULL) {
if (isOnLeft()) {
parent->left = nParent;
} else {
parent->right = nParent;
}
}
nParent->parent = parent;
parent = nParent;
}
void leftRotate(Node *x) {
// new parent will be node's right child
Node *nParent = x->right;

// update root if current node is root


if (x == root)
root = nParent;

x->moveDown(nParent);

// connect x with new parent's left element


x->right = nParent->left;
// connect new parent's left element with node
// if it is not null
if (nParent->left != NULL)
nParent->left->parent = x;

// connect new parent with x


nParent->left = x;
}
void rightRotate(Node *x) {
// new parent will be node's left child
Node *nParent = x->left;

// update root if current node is root


if (x == root)
root = nParent;

x->moveDown(nParent);

// connect x with new parent's right element


x->left = nParent->right;
// connect new parent's right element with node
// if it is not null
if (nParent->right != NULL)
nParent->right->parent = x;

// connect new parent with x


nParent->right = x;
}
void swapColors(Node *x1, Node *x2) {
COLOR temp;
temp = x1->color;
x1->color = x2->color;
x2->color = temp;
}
void swapValues(Node *u, Node *v) {
int temp;
temp = u->val;
u->val = v->val;
v->val = temp;
}
B-Trees

B-Trees
1
Motivation for B-Trees

• Index structures for large datasets cannot be stored in main


memory
• Storing it on disk requires different approach to efficiency

• Assuming that a disk spins at 3600 RPM, one revolution


occurs in 1/60 of a second, or 16.7ms
• Crudely speaking, one disk access takes about the same time
as 200,000 instructions

B-Trees 2
Motivation (cont.)

• Assume that we use an AVL tree to store about 20 million


records
• We end up with a very deep binary tree with lots of different
disk accesses; log2 20,000,000 is about 24, so this takes about
0.2 seconds
• We know we can’t improve on the log n lower bound on
search for a binary tree
• But, the solution is to use more branches and thus reduce the
height of the tree!
– As branching increases, depth decreases

B-Trees 3
Definition of a B-tree

• A B-tree of order m is an m-way tree (i.e., a tree where each


node may have up to m children) in which:
1. the number of keys in each non-leaf node is one less than the number
of its children and these keys partition the keys in the children in the
fashion of a search tree
2. all leaves are on the same level
3. all non-leaf nodes except the root have at least m / 2 children
4. the root is either a leaf node, or it has from two to m children
5. a leaf node contains no more than m – 1 keys
• The number m should always be odd

B-Trees 4
An example B-Tree

26 A B-tree of order 5
containing 26 items
6 12

42 51 62
1 2 4 7 8 13 15 18 25

27 29 45 46 48 53 55 60 64 70 90

Note that all the leaves are at the same level

B-Trees 5
Constructing a B-tree

• Suppose we start with an empty B-tree and keys arrive in the


following order: 1 12 8 2 25 6 14 28 17 7 52 16 48 68
3 26 29 53 55 45
• We want to construct a B-tree of order 5
• The first four items go into the root:

1 2 8 12

• To put the fifth item in the root would violate condition 5


• Therefore, when 25 arrives, pick the middle key to make a
new root

B-Trees 6
1 12 8 2 25 6 14 28 17 7 52 16 48 68 3 26 29 53 55 45

1 2 12 25

6, 14, 28 get added to the leaf nodes:


8

1 2 6 12 14 25 28

B-Trees 7
1 12 8 2 25 6 14 28 17 7 52 16 48 68 3 26 29 53 55 45

Adding 17 to the right leaf node would over-fill it, so we take the
middle key, promote it (to the root) and split the leaf
8 17

1 2 6 12 14 25 28

7, 52, 16, 48 get added to the leaf nodes


8 17

1 2 6 7 12 14 16 25 28 48 52

B-Trees 8
1 12 8 2 25 6 14 28 17 7 52 16 48 68 3 26 29 53 55 45

Adding 68 causes us to split the right most leaf, promoting 48 to the


root, and adding 3 causes us to split the left most leaf, promoting 3
to the root; 26, 29, 53, 55 then go into the leaves
3 8 17 48

1 2 6 7 12 14 16 25 26 28 29 52 53 55 68

Adding 45 causes a split of 25 26 28 29

and promoting 28 to the root then causes the root to split

B-Trees 9
Constructing a B-tree (contd.)

17

3 8 28 48

1 2 6 7 12 14 16 25 26 29 45 52 53 55 68

B-Trees 10
Inserting into a B-Tree

• Attempt to insert the new key into a leaf


• If this would result in that leaf becoming too big, split the leaf
into two, promoting the middle key to the leaf’s parent
• If this would result in the parent becoming too big, split the
parent into two, promoting the middle key
• This strategy might have to be repeated all the way to the top
• If necessary, the root is split in two and the middle key is
promoted to a new root, making the tree one level higher

B-Trees 11
Exercise in Inserting a B-Tree

• Insert the following keys to a 5-way B-tree:


• 3, 7, 9, 23, 45, 1, 5, 14, 25, 24, 13, 11, 8, 19, 4, 31, 35, 56

B-Trees 12
Removal from a B-tree

• During insertion, the key always goes into a leaf. For deletion
we wish to remove from a leaf. There are three possible ways
we can do this:
• 1 - If the key is already in a leaf node, and removing it doesn’t
cause that leaf node to have too few keys, then simply remove
the key to be deleted.
• 2 - If the key is not in a leaf then it is guaranteed (by the
nature of a B-tree) that its predecessor or successor will be in
a leaf -- in this case we can delete the key and promote the
predecessor or successor key to the non-leaf deleted key’s
position.

B-Trees 13
Removal from a B-tree (2)

• If (1) or (2) lead to a leaf node containing less than the


minimum number of keys then we have to look at the siblings
immediately adjacent to the leaf in question:
– 3: if one of them has more than the min. number of keys then we can
promote one of its keys to the parent and take the parent key into our
lacking leaf
– 4: if neither of them has more than the min. number of keys then the
lacking leaf and one of its neighbours can be combined with their
shared parent (the opposite of promoting a key) and the new leaf will
have the correct number of keys; if this step leave the parent with too
few keys then we repeat the process up to the root itself, if required

B-Trees 14
Type #1: Simple leaf deletion

Assuming a 5-way
B-Tree, as before... 12 29 52

2 7 9 15 22 31 43 56 69 72

Delete 2: Since there are enough


keys in the node, just delete it
Note when printed: this slide is animated

B-Trees 15
Type #2: Simple non-leaf deletion

12 29 56
52 Delete 52

7 9 15 22 31 43 56 69 72

Borrow the predecessor


or (in this case) successor

Note when printed: this slide is animated

B-Trees 16
Type #4: Too few keys in node and
its siblings

12 29 56

Join back together

7 9 15 22 31 43 69 72
Too few keys!
Delete 72

Note when printed: this slide is animated

B-Trees 17
Type #4: Too few keys in node and
its siblings

12 29

7 9 15 22 31 43 56 69

Note when printed: this slide is animated

B-Trees 18
Type #3: Enough siblings

12 29
Demote root key and
promote leaf key

7 9 15 22 31 43 56 69

Delete 22

Note when printed: this slide is animated

B-Trees 19
Type #3: Enough siblings

12 31

7 9 15 29 43 56 69

Note when printed: this slide is animated

B-Trees 20
Exercise in Removal from a B-Tree

• Given 5-way B-tree created by these data (last exercise):


• 3, 7, 9, 23, 45, 1, 5, 14, 25, 24, 13, 11, 8, 19, 4, 31, 35, 56

• Add these further keys: 2, 6,12

• Delete these keys: 4, 5, 7, 3, 14

B-Trees 21
Analysis of B-Trees

• The maximum number of items in a B-tree of order m and height h:


root m–1
level 1 m(m – 1)
level 2 m2(m – 1)
. . .
level h mh(m – 1)
• So, the total number of items is
(1 + m + m2 + m3 + … + mh)(m – 1) =
[(mh+1 – 1)/ (m – 1)] (m – 1) = mh+1 – 1
• When m = 5 and h = 2 this gives 53 – 1 = 124

B-Trees 22
Reasons for using B-Trees

• When searching tables held on disc, the cost of each disc


transfer is high but doesn't depend much on the amount of
data transferred, especially if consecutive items are transferred
– If we use a B-tree of order 101, say, we can transfer each node in one
disc read operation
– A B-tree of order 101 and height 3 can hold 1014 – 1 items
(approximately 100 million) and any item can be accessed with 3 disc
reads (assuming we hold the root in memory)
• If we take m = 3, we get a 2-3 tree, in which non-leaf nodes
have two or three children (i.e., one or two keys)
– B-Trees are always balanced (since the leaves are all at the same
level), so 2-3 trees make a good type of balanced tree

B-Trees 23
Comparing Trees

• Binary trees
– Can become unbalanced and lose their good time complexity (big O)
– AVL trees are strict binary trees that overcome the balance problem
– Heaps remain balanced but only prioritise (not order) the keys

• Multi-way trees
– B-Trees can be m-way, they can have any (odd) number of children
– One B-Tree, the 2-3 (or 3-way) B-Tree, approximates a permanently
balanced binary tree, exchanging the AVL tree’s balancing operations
for insertion and (more complex) deletion operations

B-Trees 24
B-Trees 25
B+ Tree
• B+ tree eliminates the drawback B-tree used for indexing
• It store data pointers only at the leaf nodes of the tree.
• Data pointers are present only at the leaf nodes, the leaf nodes
must necessarily store all the key values along with their
corresponding data pointers to the disk file block, in order to
access them.
• Leaf nodes are linked to providing ordered access to the
records.
• The leaf nodes, therefore form the first level of the index, with
the internal nodes forming the other levels of a multilevel
index.
• Some of the key values of the leaf nodes also appear in the
internal nodes, to simply act as a medium to control the
B-Trees 26
searching of a record.
B-Trees 27
Insertion
• Each node except root can have a maximum of M children and at
least ceil(M/2) children.
• Each node can contain a maximum of M – 1 keys and a minimum of ceil(M/2) –
1 keys.
• The root has at least two children and atleast one search key.
• While insertion overflow of the node occurs when it contains more than M – 1 search
key values.
Here M is the order of B+ tree.
Steps for insertion in B+ Tree
• Every element is inserted into a leaf node. So, go to the appropriate leaf node.
• Insert the key into the leaf node in increasing order only if there is no overflow. If there
is an overflow go ahead with the following steps mentioned below to deal with
overflow while maintaining the B+ Tree properties.

B-Trees 28
Case 1: Overflow in leaf node
• Split the leaf node into two nodes.
• First node contains ceil((m-1)/2) values.
• Second node contains the remaining values.
• Copy the smallest search key value from second node to the
parent node.(Right biased)

B-Trees 29
Case 2: Overflow in non-leaf node
• Split the non leaf node into two nodes.
• First node contains ceil(m/2)-1 values.
• Move the smallest among remaining to the parent.
• Second node contains the remaining keys.

B-Trees 30
Insert the following key values 6, 16, 26, 36, 46 on a B+ tree
with order = 3.

B-Trees 31

You might also like