0% found this document useful (0 votes)
28 views276 pages

Data Structures

The document provides an overview of data structures, including definitions, types (linear and non-linear), and operations such as traversing, searching, and sorting. It also discusses pointers in C, their advantages and disadvantages, and various types of pointers, including void and null pointers. Additionally, the document covers hashing techniques, collision handling methods, and the applications of linear probing in areas like symbol tables and caching.

Uploaded by

akshathashetty
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views276 pages

Data Structures

The document provides an overview of data structures, including definitions, types (linear and non-linear), and operations such as traversing, searching, and sorting. It also discusses pointers in C, their advantages and disadvantages, and various types of pointers, including void and null pointers. Additionally, the document covers hashing techniques, collision handling methods, and the applications of linear probing in areas like symbol tables and caching.

Uploaded by

akshathashetty
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 276

“Data Structures”

Dr. Roshan Fernandes


Professor
Dept. of CS&E
NMAMIT, Nitte
• A data structure is a storage that is used to
store and organize data. It is a way of
arranging data on a computer so that it can be
accessed and updated efficiently.
• A data structure is not only used for organizing the data

• It is also used for processing, retrieving, and storing data

• There are different basic and advanced types of data


structures that are used in almost every program or
software system that has been developed

• So we must have good knowledge about data structures


• Linear data structure: Data structure in which data elements are
arranged sequentially or linearly, where each element is attached
to its previous and next adjacent elements, is called a linear data
structure

Examples of linear data structures are array, stack, queue, linked list,
etc.

– Static data structure: Static data structure has a fixed memory size. It is
easier to access the elements in a static data structure.
An example of this data structure is an array

– Dynamic data structure: In dynamic data structure, the size is not fixed. It
can be randomly updated during the runtime which may be considered
efficient concerning the memory (space) complexity of the code.
Examples of this data structure are queue, stack, etc.
• Non-linear data structure: Data structures where
data elements are not placed sequentially or linearly
are called non-linear data structures

• In a non-linear data structure, we can’t traverse all


the elements in a single run only

• Examples of non-linear data structures are trees and


graphs
Operations on different Data
Structure:

• Traversing: Traversing a Data Structure means to


visit the element stored in it. It visits data in a
systematic manner

• Searching: Searching means to find a particular


element in the given data-structure

• Insertion: It is the operation which we apply on all


the data-structures. Insertion means to add an
element in the given data structure
• Deletion: It is the operation which we apply on all
the data-structures. Deletion means to delete an
element in the given data structure.

• Create: – It reserves memory for program


elements by declaring them

• The creation of data structure Can be done


during Compile-time Run-time.

Selection:-
It selects specific data from present data. You can select any specific data by
giving condition in loop .
Update
It updates the data in the data structure. You can also update any specific data
by giving some condition in loop like select approach.
Sort
Sorting data in a particular order (ascending or descending).
We can take the help of many sorting algorithms to sort data in less time.
Example: bubble sort which takes O(n^2)time to sort data. There are many
algorithms present like merge sort, insertion sort, selection sort, quick sort, etc.
Merge
Merging data of two different orders in a specific order may ascend or descend.
We use merge sort to merge sort data.
Split Data
Dividing data into different sub-parts to make the process complete in less
time.
Data Type Data Structure

The data type is the form of a variable to which a


value can be assigned. It defines that the particular Data structure is a collection of different kinds of
variable will assign the values of the given data type data. That entire data can be represented using an
only. object and can be used throughout the program.

It can hold value but not data. Therefore, it is It can hold multiple types of data within a single
dataless. object.

The implementation of a data type is known as Data structure implementation is known as concrete
abstract implementation. implementation.

In data structure objects, time complexity plays an


There is no time complexity in the case of data types. important role.

While in the case of data structures, the data and its


In the case of data types, the value of data is not value acquire the space in the computer’s main
stored because it only represents the type of data memory. Also, a data structure can hold different
that can be stored. kinds and types of data within one single object.

Data type examples are int, float, double, etc. Data structure examples are stack, queue, tree, etc.
• Pointers in C – Using pointers with Arrays, Strings, Functions, Structures;
Double Pointers

• Example Programs

• Dynamic arrays using malloc and calloc

ptr = (cast-type*) malloc(byte-size)


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

ptr = (cast-type*)calloc(n, element-size);


here, n is the no. of elements and element-size is the size of each element.

ptr = (float*) calloc(25, sizeof(float));


This statement allocates contiguous space in memory for 25 elements each with
the size of the float.
Operator Description
Associativity

() Parentheses (function call) (see Note 1)


[] Brackets (array subscript)
. Member selection via object name left-to-right
-> Member selection via pointer
++ – – Postfix increment/decrement (see Note 2)

++ – – Prefix increment/decrement
+– Unary plus/minus
!~ Logical negation/bitwise complement
(type) Cast (convert value to temporary value of type) right-to-left
* Dereference
& Address (of operand)
sizeof Determine size in bytes on this implementation
* / % Multiplication/division/modulus left-to-right

+ – Addition/subtraction left-to-right

Bitwise shift left, Bitwise shift


<< >> left-to-right
right

Relational less than/less than or


< <= equal to
left-to-right
> >= Relational greater than/greater
than or equal to

Relational is equal to/is not equal


== != left-to-right
to
& Bitwise AND left-to-right
^ Bitwise exclusive OR left-to-right
| Bitwise inclusive OR left-to-right
&& Logical AND left-to-right
|| Logical OR left-to-right
?: Ternary conditional right-to-left

Assignment
= Addition/subtraction assignment
+= -= Multiplication/division
*= /= assignment
right-to-left
%= &= Modulus/bitwise AND assignment
^= |= Bitwise exclusive/inclusive OR
<<= >>= assignment
Bitwise shift left/right assignment

, Comma (separate expressions) left-to-right


Advantages of Pointers

• Pointers are used for dynamic memory allocation and


deallocation

• An Array or an structure can be accessed efficiently with


pointers

• Pointers are useful for accessing memory locations

• Pointers are used to form complex data structures such as linked


lists, graphs, trees, etc

• Pointers reduces length of the program and its execution time as


well
Disadvantages of pointers

• Memory corruption can occur if an incorrect value is


provided to pointers

• Pointers are Complex to understand

• Pointers are majorly responsible for memory leaks

• Pointers are comparatively slower than variables in C

• Uninitialized pointers might cause segmentation fault


• Core Dump/Segmentation fault is a specific kind
of error caused by accessing memory that “does
not belong to you.”

• When a piece of code tries to do read and write


operation in a read only location in memory or
freed block of memory, it is known as core dump
Void Pointers

• A Void Pointer in C can be defined as an address of any variable. It has no standard


data type. A void pointer is created by using the keyword void.

// C Program to show void pointer


#include <stdio.h>

int main()
{
// void pointer
void* ptr = NULL;

printf("The size of pointer is:%d\n", sizeof(ptr));

return 0;
}
• Null pointers can be created by assigning a zero value during pointer
declaration. This method is useful when no address is assigned to the pointer.

// C program to show use of Null Pointer


#include <stdio.h>

int main()
{
// null pointer
int* ptr = NULL;

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

return 0;
}
Wild Pointers

• Wild Pointers are pointers that have not been initialized with something yet. These
types of C-pointers can cause problems in our programs and can eventually cause
them to crash. While working with Wild Pointers Programmer must be very careful.

// C Program to show use of wild pointers


#include <stdio.h>

int main()
{
// wild pointer
int* ptr;

printf("\n%d", *ptr);

return 0;
}
Other types of pointers in
C:

• Far pointer – A far pointer is typically 32 bit that can


access memory outside current segment

• Dangling pointer – A pointer pointing to a memory


location that has been deleted (or freed) is called dangling
pointer

• Huge pointer – A huge pointer is 32 bit long containing


segment address and offset address

• Complex pointer – Pointers with multiple levels of


indirection
• Near pointer – Near pointer is used to store 16 bit
addresses means within current segment on a 16 bit
machine

• Normalized pointer – It is a 32 bit pointer, which has as


much of its value in the segment register as possible

• Generic pointer – In C void* acts as a generic pointer

• File Pointer – The pointer to a FILE data type is called


as a stream pointer or a file pointer
Questions:

Q1: Are pointers integer?


Ans: No, pointers are not integers. A pointer is an
address. It is a positive number

Q2: What does the error ‘Null Pointer Assignment’


means and what causes this error?
Ans: As null pointer points to nothing so accessing a
uninitialized pointer or invalid location may cause an
error.
Q3: How pointer variables are initialized?

Ans: Pointer variables are initialized by one of


the following ways
– Static memory allocation
– Dynamic memory allocation
Q4: What is pointer to a pointer?
Ans: If a pointer variable points another pointer
value. Such a situation is known as a pointer to a
pointer.

Example:
int *p1,**p2,v=10;
P1=&v; p2=&p1;
Here p2 is a pointer to a pointer.
Pointers MCQs (Refer Notepad)
• Sorting and Searching – Bubble Sort, Selection
Sort, Linear Search, Binary Search, Hashing.

• Programs.
Hashing…
What is Hashing?

• Hashing is a technique or process of mapping


keys, and values into the hash table by using a
hash function

• It is done for faster access to elements

• The efficiency of mapping depends on the


efficiency of the hash function used
• Let a hash function H(x) maps the value x at
the index x%10 in an Array

• For example if the list of values is


[11,12,13,14,15] it will be stored at positions
{1,2,3,4,5} in the array or Hash table
respectively
What is Collision?

• Since a hash function gets us a small number for a


key which is a big integer or string, there is a
possibility that two keys result in the same value

• The situation where a newly inserted key maps to an


already occupied slot in the hash table is called
collision and must be handled using some collision
handling technique
How to handle Collisions?

• There are mainly two methods to handle


collision:
– Separate Chaining
– Open Addressing
Separate Chaining:

• The idea behind separate chaining is to


implement the array as a linked list called a
chain

• Separate chaining is one of the most popular


and commonly used techniques in order to
handle collisions
Example:
Let us
consider a
simple hash
function as
“key mod
7” and a
sequence of
keys as 50,
700, 76, 85,
92, 73, 101
Advantages:

• Simple to implement

• Hash table never fills up, we can always add more


elements to the chain

• Less sensitive to the hash function or load factors

• It is mostly used when it is unknown how many and


how frequently keys may be inserted or deleted
Disadvantages:

• The cache performance of chaining is not good as keys are


stored using a linked list

• Open addressing provides better cache performance as


everything is stored in the same table.

• Wastage of Space (Some Parts of the hash table are never used)

• If the chain becomes long, then search time can become O(n) in
the worst case

• Uses extra space for links


Open Addressing:
• Like separate chaining, open addressing is a method for
handling collisions

• In Open Addressing, all elements are stored in the hash


table itself

• So at any point, the size of the table must be greater than or


equal to the total number of keys (Note that we can increase
table size by copying old data if needed)

• This approach is also known as closed hashing

• This entire procedure is based upon probing


• We will understand the types of probing ahead:

• Insert(k): Keep probing until an empty slot is found. Once an empty


slot is found, insert k.

• Search(k): Keep probing until the slot’s key doesn’t become equal to
k or an empty slot is reached.

• Delete(k): Delete operation is interesting. If we simply delete a key,


then the search may fail. So slots of deleted keys are marked
specially as “deleted”.

• The insert can insert an item in a deleted slot, but the search
doesn’t stop at a deleted slot.
Different ways of Open Addressing
1. Linear Probing:

• In linear probing, the hash table is searched


sequentially that starts from the original location of
the hash

• If in case the location that we get is already occupied,


then we check for the next location

• The function used for rehashing is as follows:


rehash(key) = (n+1)%table-size.
• Let hash(x) be the slot index computed using a hash
function and S be the table size

• If slot hash(x) % S is full, then we try (hash(x) + 1) % S

• If (hash(x) + 1) % S is also full, then we try (hash(x) + 2) % S

• If (hash(x) + 2) % S is also full, then we try (hash(x) + 3) % S


…………………………………………..
…………………………………………..
Let us consider a simple hash function as “key mod
7” and a sequence of keys as 50, 700, 76, 85, 92, 73,
101,
Applications of linear probing

• Linear probing is a collision handling


technique used in hashing, where the
algorithm looks for the next available slot in
the hash table to store the collided key

• Some of the applications of linear probing


include:
1. Symbol tables:

• Linear probing is commonly used in symbol tables,


which are used in compilers and interpreters to store
variables and their associated values

• Since symbol tables can grow dynamically, linear


probing can be used to handle collisions and ensure
that variables are stored efficiently.
2. Caching:

• Linear probing can be used in caching systems to


store frequently accessed data in memory

• When a cache miss occurs, the data can be loaded


into the cache using linear probing, and when a
collision occurs, the next available slot in the cache
can be used to store the data
3. Databases:
• Linear probing can be used in databases to store
records and their associated keys

• When a collision occurs, linear probing can be used


to find the next available slot to store the record
4. Compiler design:
• Linear probing can be used in compiler design to
implement symbol tables, error recovery
mechanisms, and syntax analysis

5. Spell checking:
• Linear probing can be used in spell-checking software
to store the dictionary of words and their associated
frequency counts

• When a collision occurs, linear probing can be used


to store the word in the next available slot
Overall, linear probing is a simple and efficient
method for handling collisions in hash tables,
and it can be used in a variety of applications
that require efficient storage and retrieval of
data
Challenges in Linear Probing:

• Primary Clustering: One of the problems with linear


probing is Primary clustering, many consecutive
elements form groups and it starts taking time to find
a free slot or to search for an element

• Secondary Clustering: Secondary clustering is less


severe, two records only have the same collision
chain (Probe Sequence) if their initial position is the
same
• Example: Let us consider a simple
hash function as “key mod 5” and a
sequence of keys that are to be
inserted are 50, 70, 76, 93.

• Step1: First draw the empty hash


table which will have a possible
range of hash values from 0 to 4
according to the hash function
provided.
• Step 2: Now insert all the
keys in the hash table one by
one

• The first key is 50

• It will map to slot number 0


because 50%5=0

• So insert it into slot number


0.
• Step 3: The next key is
70. It will map to slot
number 0 because
70%5=0 but 50 is
already at slot number
0 so, search for the
next empty slot and
insert it.
• Step 4: The next key is
76. It will map to slot
number 1 because
76%5=1 but 70 is
already at slot number
1 so, search for the
next empty slot and
insert it.
• Step 5: The next
key is 93 It will map
to slot number 3
because 93%5=3,
So insert it into slot
number 3
2. Quadratic Probing

• If you observe carefully, then you will understand


that the interval between probes will increase
proportionally to the hash value

• Quadratic probing is a method with the help of which


we can solve the problem of clustering that was
discussed above.

• This method is also known as the mid-square


method
• In this method, we look for the i2‘th slot in
the ith iteration

• We always start from the original hash


location

• If only the location is occupied then we check


the other slots
• let hash(x) be the slot index computed using hash
function.

• If slot hash(x) % S is full, then we try (hash(x) + 1*1) % S


If (hash(x) + 1*1) % S is also full, then we try (hash(x) +
2*2) % S
If (hash(x) + 2*2) % S is also full, then we try (hash(x) +
3*3) % S
…………………………………………..
…………………………………………..
• Example: Let us consider
table Size = 7, hash function
as Hash(x) = x % 7 and
collision resolution strategy
to be f(i) = i2 . Insert = 22, 30,
and 50.

• Step 1: Create a table of size


7.
• Step 2 – Insert 22 and
30

– Hash(22) = 22 % 7 = 1,
Since the cell at index 1
is empty, we can easily
insert 22 at slot 1.

– Hash(30) = 30 % 7 = 2,
Since the cell at index 2
is empty, we can easily
insert 30 at slot 2.
• Step 3: Inserting 50
– Hash(50) = 50 % 7 = 1
– In our hash table slot 1 is
already occupied. So, we
will search for slot 1+12,
i.e. 1+1 = 2,
– Again slot 2 is found
occupied, so we will
search for cell 1+22,
i.e.1+4 = 5,
– Now, cell 5 is not
occupied so we will place
50 in slot 5.
3. Double Hashing

• The intervals that lie between probes are computed by


another hash function

• Double hashing is a technique that reduces clustering in


an optimized way

• In this technique, the increments for the probing


sequence are computed by using another hash function

• We use another hash function hash2(x) and look for the


i*hash2(x) slot in the ith rotation
• let hash(x) be the slot index computed using hash
function
• If slot hash(x) % S is full, then we try (hash(x) +
1*hash2(x)) % S
If (hash(x) + 1*hash2(x)) % S is also full, then we try
(hash(x) + 2*hash2(x)) % S
If (hash(x) + 2*hash2(x)) % S is also full, then we try
(hash(x) + 3*hash2(x)) % S
…………………………………………..
…………………………………………..
Example:

• Insert the keys 27, 43, 692, 72 into the Hash Table
of size 7, where first hash-function is h1​(k) = k
mod 7 and second hash-function is h2(k) = 1 + (k
mod 5)
• Step 1: Insert 27
– 27 % 7 = 6,
location 6 is
empty so
insert 27 into 6
slot.
• Step 2: Insert
43

• 43 % 7 = 1,
location 1 is
empty so
insert 43 into 1
slot.
• Step 3: Insert 692
– 692 % 7 = 6, but location 6 is
already being occupied and this is
a collision
– So we need to resolve this
collision using double hashing.

• hnew = [h1(692) + i * (h2(692)] %


7
= [6 + 1 * (1 + 692 % 5)] % 7
= 9% 7
=2
• Now, as 2 is an empty slot,
so we can insert 692 into 2nd
slot.
• Step 4: Insert 72
– 72 % 7 = 2, but location 2 is
already being occupied and
this is a collision.
– So we need to resolve this
collision using double hashing.
• hnew = [h1(72) + i * (h2(72)] %
7
= [2 + 1 * (1 + 72 % 5)] % 7
=5%7
= 5,
• Now, as 5 is an empty slot,
so we can insert 72 into 5th
slot.
S.No. Separate Chaining Open Addressing

1. Chaining is Simpler to implement. Open Addressing requires more


computation.

2. In chaining, Hash table never fills up, we can always add more In open addressing, table may
elements to chain. become full.

Open addressing requires extra


3. Chaining is Less sensitive to the hash function or load factors. care to avoid clustering and load
factor.

Chaining is mostly used when it is unknown how many and how Open addressing is used when the
4. frequently keys may be inserted or deleted. frequency and number of keys is
known.

Open addressing provides better


5. Cache performance of chaining is not good as keys are stored cache performance as everything
using linked list. is stored in the same table.

Wastage of Space (Some Parts of hash table in chaining are never In Open addressing, a slot can be
6. used). used even if an input doesn’t map
to it.

7. Chaining uses extra space for links. No links in Open addressing


Problems:

1. Find whether an array is a subset of another array using Hashing

• Given array arr1[] = { 11, 1, 13, 21, 3, 7 } and arr2[] = { 11, 3, 7, 1 }.

• Step 1: We will store the array arr1[] elements in HashSet

• Step 2: We will look for each element in arr2[] in arr1[] using hashing.

• arr2[] = { 11, 3, 7, 1 }, 11 is present in the HashSet = { 1, 3, 7, 11, 13, 21}


• arr2[] = { 11, 3, 7, 1 }, 3 is present in the HashSet = { 1, 3, 7, 11, 13, 21}
• arr2[] = { 11, 3, 7, 1 }, 7 is present in the HashSet = { 1, 3, 7, 11, 13, 21}
• arr2[] = { 11, 3, 7, 1 }, 1 is present in the HashSet = { 1, 3, 7, 11, 13, 21}

• As all the elements are found we can conclude arr2[] is the subset of
arr1[].
2. Group words with same set of characters

• Given a list of words with lower cases. Implement a function to find all Words that have the same unique
character set.

Example:

Input: words[] = { "may", "student", "students", "dog",


"studentssess", "god", "cat", "act",
"tab", "bat", "flow", "wolf", "lambs",
"amy", "yam", "balms", "looped",
"poodle"};
Output :
looped, poodle,
lambs, balms,
flow, wolf,
tab, bat,
may, amy, yam,
student, students, studentssess,
dog, god,
cat, act,

All words with same set of characters are printed together in a line.
• The idea is to use hashing

• We generate a key for all words

• The key contains all unique characters (The size of the


key is at most 26 for lowercase alphabets)

• We store indexes of words as values for a key

• Once we have filled all keys and values in the hash table,
we can print the result by traversing the table
Stack Data Structure

• Stack is a linear data structure that follows a


particular order in which the operations are
performed

• The order may be LIFO(Last In First Out) or FILO(First


In Last Out)

• LIFO implies that the element that is inserted last,


comes out first and FILO implies that the element
that is inserted first, comes out last
Basic Operations on Stack

• In order to make manipulations in a stack,


there are certain operations provided to us

– push() to insert an element into the stack


– pop() to remove an element from the stack
– top() Returns the top element of the stack.
– isEmpty() returns true if stack is empty else false.
– size() returns the size of stack.
Application of Stack Data Structure:

• Function calls and recursion: When a function is called,


the current state of the program is pushed onto the
stack. When the function returns, the state is popped
from the stack to resume the previous function’s
execution

• Undo/Redo operations: The undo-redo feature in


various applications uses stacks to keep track of the
previous actions. Each time an action is performed, it is
pushed onto the stack. To undo the action, the top
element of the stack is popped, and the reverse
operation is performed.
• Expression evaluation: Stack data structure is used to
evaluate expressions in infix, postfix, and prefix
notations. Operators and operands are pushed onto the
stack, and operations are performed based on the stack’s
top elements.

• Browser history: Web browsers use stacks to keep track


of the web pages you visit. Each time you visit a new
page, the URL is pushed onto the stack, and when you hit
the back button, the previous URL is popped from the
stack.
• Balanced Parentheses: Stack data structure is used to
check if parentheses are balanced or not. An opening
parenthesis is pushed onto the stack, and a closing
parenthesis is popped from the stack. If the stack is
empty at the end of the expression, the parentheses are
balanced.

• Backtracking Algorithms: The backtracking algorithm


uses stacks to keep track of the states of the problem-
solving process. The current state is pushed onto the
stack, and when the algorithm backtracks, the previous
state is popped from the stack.
Advantages of Stack:

• Easy implementation: Stack data structure is easy to


implement using arrays or linked lists, and its
operations are simple to understand and implement

• Efficient memory utilization: Stack uses a contiguous


block of memory, making it more efficient in memory
utilization as compared to other data structures
• Fast access time: Stack data structure provides fast
access time for adding and removing elements as the
elements are added and removed from the top of
the stack

• Helps in function calls: Stack data structure is used


to store function calls and their states, which helps in
the efficient implementation of recursive function
calls
• Supports backtracking: Stack data structure supports
backtracking algorithms, which are used in problem-solving
to explore all possible solutions by storing the previous
states.

• Used in Compiler Design: Stack data structure is used in


compiler design for parsing and syntax analysis of
programming languages.

• Enables undo/redo operations: Stack data structure is used


to enable undo and redo operations in various applications
like text editors, graphic design tools, and software
development environments.
Disadvantages of Stack:

• Limited capacity: Stack data structure has a limited


capacity as it can only hold a fixed number of elements. If
the stack becomes full, adding new elements may result
in stack overflow, leading to the loss of data

• No random access: Stack data structure does not allow


for random access to its elements, and it only allows for
adding and removing elements from the top of the stack.
To access an element in the middle of the stack, all the
elements above it must be removed
• Memory management: Stack data structure uses a
contiguous block of memory, which can result in
memory fragmentation if elements are added and
removed frequently.

• Not suitable for certain applications: Stack data


structure is not suitable for applications that require
accessing elements in the middle of the stack, like
searching or sorting algorithms.
• Stack overflow and underflow: Stack data structure
can result in stack overflow if too many elements are
pushed onto the stack, and it can result in stack
underflow if too many elements are popped from the
stack

• Recursive function calls limitations: While stack data


structure supports recursive function calls, too many
recursive function calls can lead to stack overflow,
resulting in the termination of the program
Convert Infix expression to Postfix
expression
• Infix expression: The expression of the form a operator b (a
+ b). When an operator is in-between every pair of operands

Postfix expression: The expression of the form a b operator
(ab+). When an operator is followed by every pair of
operands.

• Input: A + B * C + D
Output: ABC*+D+

• Input: ((A + B) – C * (D / E)) + F


Output: AB+CDE/*-F+
• Scan the infix expression from left to right.
• If the scanned character is an operand, put it in the postfix
expression.
• Otherwise, do the following
– If the precedence and associativity of the scanned operator are
greater than the precedence and associativity of the operator in
the stack [or the stack is empty or the stack contains a ‘(‘ ], then
push it in the stack. [‘^‘ operator is right associative and other
operators like ‘+‘,’–‘,’*‘ and ‘/‘ are left-associative].
• Check especially for a condition when the operator at the top of the
stack and the scanned operator both are ‘^‘. In this condition, the
precedence of the scanned operator is higher due to its right
associativity. So it will be pushed into the operator stack.
• In all the other cases when the top of the operator stack is the same
as the scanned operator, then pop the operator from the stack
because of left associativity due to which the scanned operator has
less precedence.
– Else, Pop all the operators from the stack which are greater than
or equal to in precedence than that of the scanned operator.
• After doing that Push the scanned operator to the stack. (If you
encounter parenthesis while popping then stop there and push the
scanned operator in the stack.)
• If the scanned character is a ‘(‘, push it to the stack.
• If the scanned character is a ‘)’, pop the stack and output it
until a ‘(‘ is encountered, and discard both the parenthesis.
• Repeat steps 2-5 until the infix expression is scanned.
• Once the scanning is over, Pop the stack and add the
operators in the postfix expression until it is not empty.
• Finally, print the postfix expression.
• Consider the infix
expression exp =
“a+b*c+d”
and the infix expression
is scanned using the
iterator i, which is
initialized as i = 0.
• 1st Step: Here i = 0 and
exp[i] = ‘a’ i.e., an
operand. So add this in
the postfix expression.
Therefore, postfix =
“a”.
exp = “a+b*c+d”
• 2nd Step: Here i =
1 and exp[i] = ‘+’
i.e., an operator.
Push this into the
stack. postfix =
“a” and stack =
{+}.
exp = “a+b*c+d”
• 3rd Step: Now i = 2
and exp[i] = ‘b’ i.e.,
an operand. So add
this in the postfix
expression. postfix
= “ab” and stack =
{+}.
exp = “a+b*c+d”
• 4th Step: Now i =
3 and exp[i] = ‘*’
i.e., an operator.
Push this into the
stack. postfix =
“ab” and stack =
{+, *}.
exp = “a+b*c+d”
• 5th Step: Now i = 4
and exp[i] = ‘c’ i.e.,
an operand. Add
this in the postfix
expression.
postfix =
“abc” and stack = {+,
*}.
• 6th Step: Now i = 5 exp = “a+b*c+d”

and exp[i] = ‘+’ i.e.,


an operator. The
topmost element of
the stack has higher
precedence. So pop
until the stack
becomes empty or
the top element has
less precedence. ‘*’ is
popped and added in
postfix. So postfix =
“abc*” and stack =
{+}.
exp = “a+b*c+d”
• Now top
element is
‘+‘ that also
doesn’t have
less
precedence.
Pop
it. postfix =
“abc*+”.
exp = “a+b*c+d”
• Now stack is
empty. So
push ‘+’ in
the
stack. stack =
{+}.
exp = “a+b*c+d”
• 7th Step: Now i = 6
and exp[i] = ‘d’ i.e.,
an operand. Add
this in the postfix
expression. postfix =
“abc*+d”.
exp = “a+b*c+d”
• Final Step: Now
no element is
left. So empty
the stack and
add it in the
postfix
expression. post
fix = “abc*+d+”.

Refer the C Program


Prefix to Infix Conversion

• Example : *+AB-CD
• (Infix : (A+B) * (C-D) )

Input : Prefix : *+AB-CD


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

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


Output : Infix : ((A-(B/C))*((A/K)-L))
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 the end of Prefix expression.
• At the end stack will have only 1 string i.e resultant string
Prefix to Postfix
Conversion

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.
Evaluation of Postfix
Expression

• Create a stack to store operands (or values)


• Scan the given expression from left to right and do the
following for every scanned element.
– If the element is a number, push it into the stack
– If the element is an operator, pop operands for the
operator from the stack. Evaluate the operator and
push the result back to the stack
• When the expression is ended, the number in the stack is
the final answer
Next Greater Element
(NGE) for every element in
given Array

• The Next greater Element for an element x is the first


greater element on the right side of x in the array

• Elements for which no greater element exist,


consider the next greater element as -1.
Example:

Input: arr[] = [ 4 , 5 , 2 , 25 ]
Output: 4 –> 5
5 –> 25
2 –> 25
25 –> -1
Explanation: except 25 every element has an element greater than them
present on the right side

Input: arr[] = [ 13 , 7, 6 , 12 ]
Output: 13 –> -1
7 –> 12
6 –> 12
12 –> -1
Explanation: 13 and 12 don’t have any element greater than them present on
the right side
• Follow the steps mentioned below to implement the idea:

• Push the first element to stack.


• Pick the rest of the elements one by one and follow the following
steps in the loop.
– Mark the current element as next.
– If the stack is not empty, compare top most element of stack with next.
– If next is greater than the top element, Pop element from the stack. next is
the next greater element for the popped element.
• Keep popping from the stack while the popped element is smaller
than next. next becomes the next greater element for all such popped
elements.
• Finally, push the next in the stack.
• After the loop in step 2 is over, pop all the elements from the stack
and print -1 as the next element for them.
Queue

• Ordinary Queue

• Circular Queue

• Priority Queue – Ascending and Descending


Priority Queue

• Deque
Heaps and Heapsort

• A heap can be defined as a binary tree with keys


assigned to its nodes, one key per node, provided the
following two conditions are met:
– 1. The shape property—the binary tree is essentially
complete (or simply complete), i.e., all its levels are full
except possibly the last level, where only some rightmost
leaves may be missing.
– 2. The parental dominance or heap property—the key in
each node is greater than or equal to the keys in its
children.
Important properties of heaps
Bottom-up heap construction
How efficient is this algorithm in
the worst case?

• Assume, for simplicity, that n = 2k − 1 so that a heap’s


tree is full

• Let h be the height of the tree

• According to the first property of heaps in the list at


the beginning of the section, h= log2 n or just log2 (n
+ 1) − 1= k − 1 for the specific values of n we are
considering
• Each key on level i of the tree will travel to the leaf level h
in the worst case of the heap construction algorithm

• Since moving to the next level down requires two


comparisons—one to find the larger child and the other
to determine whether the exchange is required—the
total number of key comparisons involving a key on level
i will be 2(h − i). Therefore, the total number of key
comparisons in the worst case will be
Top down heap construction

• First, attach a new node with key K in it after the last leaf
of the existing heap

• Then sift K up to its appropriate place in the new heap as


follows

• Compare K with its parent’s key: if the latter is greater


than or equal to K, stop (the structure is a heap);
otherwise, swap these two keys and compare K with its
new parent.
• This swapping continues until K is not greater than its
last parent or it reaches the root

• Since the height of a heap with n nodes is about log2


n, the time efficiency of insertion is in O(log n)
Maximum Key Deletion from a heap

• Step 1 Exchange the root’s key with the last key K of the heap

• Step 2 Decrease the heap’s size by 1

• Step 3 “Heapify” the smaller tree by sifting K down the tree


exactly in the same way we did it in the bottom-up heap
construction algorithm. That is, verify the parental dominance
for K: if it holds, we are done; if not, swap K with the larger of
its children and repeat this operation until the parental
dominance condition holds for K in its new position
• The efficiency of deletion is determined by the
number of key comparisons needed to “heapify” the
tree after the swap has been made and the size of
the tree is decreased by 1

• Since this cannot require more key comparisons than


twice the heap’s height, the time efficiency of
deletion is in O(log n) as well.
Heapsort

• This is a two-stage algorithm that works as follows:

– Stage 1 (heap construction): Construct a heap for a


given array

– Stage 2 (maximum deletions): Apply the root-deletion


operation n − 1 times to the remaining heap
Time efficiency of heapsort is, Ɵ(n log n) in both
the worst and average cases
Linked Lists

• Singly Linked List operations


– Insert front, rear, stack, queue
– Insert after a node
– Delete a node
– Insert an element in an ordered list
– Reverse a singly linked list
• Circular linked list
– Operations
– Josephus problem
• Doubly linked list
– Insert front, rear, stack, queue
– Insert a node, delete a node
• Adding two long integers
Remove duplicates from a sorted linked list
void removeDuplicates(struct Node* head)
{
/* Pointer to traverse the linked list */
struct Node* current = head;

/* Pointer to store the next pointer of a node to be deleted*/


struct Node* next_next;

/* do nothing if the list is empty */


if (current == NULL)
return;

/* Traverse the list till last node */


while (current->next != NULL)
{
/* Compare current node with next node */
if (current->data == current->next->data)
{
/* The sequence of steps is important*/
next_next = current->next->next;
free(current->next);
current->next = next_next;
}
else /* This is tricky: only advance if no deletion */
{
current = current->next;
}
}
}
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;

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


new_node->next = (*head_ref);

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


(*head_ref) = new_node;
}

/* Function to print nodes in


a given linked list */
void printList(struct Node* node)
{
while (node != NULL) {
printf("%d ", node->data);
node = node->next;
}
}
/* Driver program to test above functions*/
int main()
{
/* Start with the empty lists */
struct Node* a = NULL;
struct Node* b = NULL;
struct Node* intersect = NULL;

/* Let us create the first sorted


linked list to test the functions
Created linked list will be
1->2->3->4->5->6 */
push(&a, 6);
push(&a, 5);
push(&a, 4);
push(&a, 3);
push(&a, 2);
push(&a, 1);

/* Let us create the second sorted linked list


Created linked list will be 2->4->6->8 */
push(&b, 8);
push(&b, 6);
push(&b, 4);
push(&b, 2);

/* Find the intersection two linked lists */


intersect = sortedIntersect(a, b);

printf("\n Linked list containing common items of a & b \n ");


printList(intersect);

getchar();
What will be the output of the following code snippet for the
list 1->2->3->4->5->6?

void solve(struct node* start)


{
if(start == NULL)
return;
printf("%d ", start->data);
if(start->next != NULL )
solve(start->next->next);
printf("%d ", start->data);
}
• 1 3 5 5 3 1
void solve(ListNode* root) {
/*
The LinkedList is defined as:
root-> val = value of the node
root-> next = address of next element from the node
The List is 1 -> 2 -> 3 -> 4 -> 5
*/
int sum = 0;
while (root != NULL) {
sum += root -> val;
root = root -> next;
}
cout << sum << endl;
}

Ans 15
What does the following code snippet do?

ListNode* solve(ListNode* head) {


ListNode* prev = NULL;
if(head == NULL) {
return head;
}
if(head -> next == NULL) {
return head;
}
ListNode* curr = head -> next;
while(head != NULL) {
head -> next = prev;
prev = head;
head = curr;
if(curr != NULL) {
curr = curr -> next;
}
}
return prev;
}

Reversing the linked list


• A linked list in which none of the nodes
contains a NULL pointer is?

• Circular Linked List


What does the following code snippet do?

int solve (ListNode* list) {


ListNode* fast = list;
ListNode* slow = list;
while(fast -> next != NULL && fast -> next -> next != NULL) {
fast = fast -> next -> next;
slow = slow -> next;
}
return slow -> data;
}

Finds the middle element of the linked list


Binary tree

Binary Tree is defined as a tree data structure


where each node has at most 2 children. Since each
element in a binary tree can have only 2 children,
we typically name them the left and right child.
Binary Tree Representation

• A Binary tree is represented by a pointer to the


topmost node (commonly known as the “root”) of
the tree. If the tree is empty, then the value of the
root is NULL. Each node of a Binary Tree contains the
following parts:
– Data
– Pointer to left child
– Pointer to right child
Basic Operation On Binary Tree:

– Inserting an element.
– Removing an element.
– Searching for an element.
– Traversing the tree.
• Strictly binary tree

• Complete Binary tree

• Almost Complete binary tree


• 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 into binary search tree

• Searching in binary search tree

• Traversal – Inorder, Preorder and Postorder

• Deleting an element from BST


Deleting an element from BST

1) Node to be
deleted is
the leaf: Simply
remove it from the
tree.
2) Node to be
deleted has only
one child: Copy the
child to the node
and delete the
node.
3) Node to be deleted
has two children: Find
inorder successor of
the node. Copy
contents of the inorder
successor to the node
and delete the inorder
successor.

Note: Inorder
predecessor can also
be used.
Follow the below steps to solve the problem:
• If the root is NULL, then return root (Base case)
• If the key is less than the root’s value, then set root->left =
deleteNode(root->left, key)
• If the key is greater than the root’s value, then set root-
>right = deleteNode(root->right, key)
• Else check
– If the root is a leaf node then return null
– else if it has only the left child, then return the left child
– else if it has only the right child, then return the right child
– else set the value of root as of its inorder successor and recur to
delete the node with the value of the inorder successor
• Return
Second largest element in BST

• The second largest element is second last element in


inorder traversal and second element in reverse
inorder traversal

• We traverse given Binary Search Tree in reverse


inorder and keep track of counts of nodes visited

• Once the count becomes 2, we print the node.


void secondLargestUtil(Node *root, int &c)
{
// Base cases, the second condition is important to
// avoid unnecessary recursive calls
if (root == NULL || c >= 2)
return;

// Follow reverse inorder traversal so that the


// largest element is visited first
secondLargestUtil(root->right, c);

// Increment count of visited nodes


c++;

// If c becomes k now, then this is the 2nd largest


if (c == 2)
{
cout << "2nd largest element is "
<< root->key << endl;
return;
}

// Recur for left subtree


secondLargestUtil(root->left, c);
}
// Function to find 2nd largest element
void secondLargest(Node *root)
{
// Initialize count of nodes visited
as 0
int c = 0;

// Note that c is passed by


reference
secondLargestUtil(root, c);
}
Check if two BSTs contain same set
of elements

• Given two Binary


Search Trees
consisting of unique
positive elements,
we have to check
whether the two
BSTs contain the
same set of elements
or not.
• Note: The structure
of the two given BSTs
can be different.
• The most simple method will be to traverse first tree
and store its element in a list or array

• Now, traverse 2nd tree and simultaneously check if


the current element is present in the list or not

• If yes, then mark the element in the list as negative


and check for further elements otherwise if no, then
immediately terminate the traversal and print No.
• If all the elements of 2nd tree is present in the list
and are marked negative then finally traverse the list
to check if there are any non-negative elements left

• If Yes then it means that the first tree had some extra
element otherwise both trees consist the same set of
elements
• Construct Tree from given Inorder and
Preorder traversals

• Inorder sequence: D B E A F C
• Preorder sequence: A B D E C F
Threaded Binary Tree
Advanced Data Structures
Trie – Data Structure

• Trie data structure is defined as a Tree based data


structure that is used for storing some collection of
strings and performing efficient search operations on
them

• The word Trie is derived from reTRIEval, which


means finding something or obtaining it.
Need for Trie Data Structure?

• A Trie data structure is used for storing and retrieval of


data and the same operations could be done using
another data structure which is Hash Table but Trie can
perform these operations more efficiently than a Hash
Table

• Moreover, Trie has its own advantage over the Hash table

• A Trie data structure can be used for prefix-


based searching whereas a Hash table can’t be used in
the same way
Advantages of Trie Data
Structure over a Hash Table:

Trie data structure has the following advantages over a


hash table:

• We can efficiently do prefix search (or auto-


complete) with Trie

• We can easily print all words in alphabetical order


which is not easily possible with hashing
• There is no overhead of Hash functions in a Trie data
structure

• Searching for a String even in the large collection of


strings in a Trie data structure can be done
in O(L) Time complexity, Where L is the number of
words in the query string

• This searching time could be even less than O(L) if


the query string does not exist in the trie.
Properties of a Trie Data Structure

Below are some important properties of the Trie data


structure:

• There is one root node in each Trie

• Each node of a Trie represents a string and each edge


represents a character

• Every node consists of hashmaps or an array of pointers,


with each index representing a character and a flag to
indicate if any string ends at the current node
• Trie data structure can contain any number of characters
including alphabets, numbers, and special characters

• But for this article, we will discuss strings with characters a-


z

• Therefore, only 26 pointers need for every node, where


the 0th index represents ‘a’ and the 25th index
represents ‘z’ characters

• Each path from the root to any node represents a word or


string
How does Trie Data Structure
work?

• We will discuss strings with characters a-z

• Therefore, only 26 pointers need for every


node, where the 0th index represents ‘a’ and
the 25th index represents ‘z’ characters
• Any lowercase English word can start with a-z, then the
next letter of the word could be a-z, the third letter of
the word again could be a-z, and so on

• So for storing a word, we need to take an array


(container) of size 26 and initially, all the characters are
empty as there are no words and it will look as shown
below.
• Let’s see how a word “and” and “ant” is stored in the
Trie data structure:

• Store “and” in Trie data structure:


– The word “and” starts with “a“, So we will mark the position
“a” as filled in the Trie node, which represents the use of “a”.
– After placing the first character, for the second character
again there are 26 possibilities, So from “a“, again there is an
array of size 26, for storing the 2nd character.
– The second character is “n“, So from “a“, we will move to “n”
and mark “n” in the 2nd array as used.
– After “n“, the 3rd character is “d“, So mark the position “d” as
used in the respective array.
• Store “ant” in the Trie data structure:
– The word “ant” starts with “a” and the position of
“a” in the root node has already been filled. So, no
need to fill it again, just move to the node ‘a‘ in Trie.
– For the second character ‘n‘ we can observe that
the position of ‘n’ in the ‘a’ node has already been
filled. So, no need to fill it again, just move to node
‘n’ in Trie.
– For the last character ‘t‘ of the word, The position
for ‘t‘ in the ‘n‘ node is not filled. So, filled the
position of ‘t‘ in ‘n‘ node and move to ‘t‘ node.
After storing the word “and” and “ant” the Trie
will look like this:
Basic Operations on Trie Data Structure:
– Insertion
– Search
– Deletion
1. Insertion in Trie Data
Structure:
Let us try to Insert “and” & “ant” in this Trie:
From the above representation of insertion, we can see
that the word “and” & “ant” have shared some
common node (i.e “an”) this is because of the property
of the Trie data structure that If two strings have a
common prefix then they will have the same ancestor
in the trie
• Now let us try to Insert “dad” & “do”:
2. Searching in Trie Data Structure:

Search operation in Trie is performed in a similar way as


the insertion operation but the only difference is that
whenever we find that the array of pointers in curr
node does not point to the current character of
the word then return false instead of creating a new
node for that current character of the word
• This operation is used to search whether a string
is present in the Trie data structure or not

• There are two search approaches in the Trie data


structure
– Find whether the given word exists in Trie.
– Find whether any word that starts with the given
prefix exists in Trie.
2.1 Searching Prefix in Trie
Data Structure:
• Search for the prefix “an” in the Trie Data Structure.
2.2 Searching Complete word
in Trie Data Structure:
• It is similar to prefix search but additionally, we have to check
if the word is ending at the last character of the word or not.
3. Deletion in Trie Data Structure

• This operation is used to delete strings from the Trie


data structure

• There are three cases when deleting a word from Trie


– The deleted word is a prefix of other words in Trie
– The deleted word shares a common prefix with other
words in Trie
– The deleted word does not share any common prefix
with other words in Trie
3.1 The deleted word is a prefix
of other words in Trie
• As shown in the following figure, the deleted word “an” share
a complete prefix with another word “and” and “ant“.
An easy solution to perform a delete operation
for this case is to just decrement
the wordCount by 1 at the ending node of the
word
3.2 The deleted word shares a
common prefix with other words
in Trie
As shown in
the figure,
the deleted
word “and”
has some
common
prefixes
with other
words ‘ant’.
They share
the prefix
‘an’.
The solution for this case is to delete all the
nodes starting from the end of the prefix to the
last character of the given word
3.3 The deleted word does not share
any common prefix with other words
in Trie

As shown in
the following
figure, the
word “geek”
does not
share any
common
prefix with
any other
words.
The solution for this case is just to delete all the
nodes
Applications of Trie data structure:

1. Autocomplete
Feature: Autoco
mplete provides
suggestions
based on what
you type in the
search box. Trie
data structure is
used to
implement
autocomplete
functionality.
2. Spell Checkers: If the word typed does not appear in the
dictionary, then it shows suggestions based on what you
typed.

It is a 3-step process that includes :


– Checking for the word in the data dictionary.
– Generating potential suggestions.
– Sorting the suggestions with higher priority on top.
– Trie stores the data dictionary and makes it easier to build an
algorithm for searching the word from the dictionary and
provides the list of valid words for the suggestion.
3. Longest Prefix Matching Algorithm(Maximum Prefix
Length Match): This algorithm is used in networking by the
routing devices in IP networking

• Optimization of network routes requires contiguous


masking that bound the complexity of lookup a time to
O(n), where n is the length of the URL address in bits

• To speed up the lookup process, Multiple Bit trie


schemes were developed that perform the lookups of
multiple bits faster
Advantages of Trie data structure:

• Trie allows us to input and finds strings in O(l) time,


where l is the length of a single word. It is faster as
compared to both hash tables and binary search trees

• It provides alphabetical filtering of entries by the key of


the node and hence makes it easier to print all words in
alphabetical order

• Trie takes less space when compared to BST because


the keys are not explicitly saved instead each key
requires just an amortized fixed amount of space to be
stored
• Prefix search/Longest prefix matching can be efficiently done
with the help of trie data structure

• Since trie doesn’t need any hash function for its


implementation so they are generally faster than hash tables
for small keys like integers and pointers

• Tries support ordered iteration whereas iteration in a hash


table will result in pseudorandom order given by the hash
function which is usually more cumbersome

• Deletion is also a straightforward algorithm with O(l) as its


time complexity, where l is the length of the word to be
deleted
Disadvantages of Trie data
structure:

• The main disadvantage of the trie is that it takes a lot of


memory to store all the strings

• For each node, we have too many node pointers which


are equal to the no of characters in the worst case

• An efficiently constructed hash table(i.e. a good hash


function and a reasonable load factor) has O(1) as lookup
time which is way faster than O(l) in the case of a trie,
where l is the length of the string
Frequently asked questions
(FAQs) about Trie Data
Structure:
1. Is trie an advanced data structure?
• A Trie is an advanced data structure that is sometimes
also known as a prefix tree

2. What is the difference between trie and tree data


structure?
• A tree is a general structure of recursive nodes. There are
many types of trees. Popular ones are the binary tree
and balanced tree. A Trie is a kind of tree, known by
many names including prefix tree, digital search tree, and
retrieval tree (hence the name ‘trie’).
3. What are some applications of Trie?
• The longest common prefix, pattern searching, autocomplete
and implementation of the dictionary are some of the
common applications of a Trie Data Structure

4. Does Google use trie?


• Google even stores each word/sentence in the form of a trie

5. What is the disadvantage of trie?


• The main disadvantage of Trie is that it takes a lot of memory
to store all the Strings. For each node, we have too many
node pointers (equal to the number of characters of the
alphabet).
Introduction to Segment
Trees

• A Segment Tree is a data structure that stores


information about a range of elements in its nodes

• It also allows users to modify the array and perform


range queries in smaller complexity
Brute force solution

• Sum (L, R) -> O(n)

• Update(pos, value) -> O(1)

• If lots of updates, bruteforce approach is better


otherwise segment tree gives better results
• Is this sum values useful?

• Let us check how to find the sum of range


sum(0, 2) and at leaf node with index
We can find the sum of sum(0, 2) by using the
readymade sum values at node [0,1] and at leaf
node with index 2
• To find the sum (3, 6) use the following segments
in the tree:

– Node 3
– Node at [4-5]
– Node at 6
Update (2, 10)
Sum at node [2-3] will be updated to 15, at node
[0-3] updated to 20, at node [0-7] updated to 40
We can perform a range summation of an array
between the range L to R while also modifying the
array from range L to R all in log(N) time complexity

Refer:
https://www.youtube.com/watch?v=Ic7OO3Uw6J0
Splay Tree

• Splay trees are the self-balancing or self-


adjusted binary search trees

• In other words, we can say that the splay trees


are the variants of the binary search trees

• The prerequisite for the splay trees that we


should know about the binary search trees
• A splay tree contains the same operations as
a Binary search tree, i.e., Insertion, deletion
and searching, but it also contains one more
operation, i.e., splaying

• So all the operations in the splay tree are


followed by splaying
• Splay trees are
not strictly
balanced
trees, but they
are roughly
balanced trees

• Suppose we
want to search
7 element in
the tree, which
is shown:
• To search any element in the splay tree, first, we will perform
the standard binary search tree operation

• As 7 is less than 10 so we will come to the left of the root node

• After performing the search operation, we need to perform


splaying

• Here splaying means that the operation that we are


performing on any element should become the root node after
performing some rearrangements

• The rearrangement of the tree will be done through the


rotations
Rotations

There are six types of rotations used for


splaying:
• Zig rotation (Right rotation)
• Zag rotation (Left rotation)
• Zig zag (Zig followed by zag)
• Zag zig (Zag followed by zig)
• Zig zig (two right rotations)
• Zag zag (two left rotations)
Factors required for
selecting a type of rotation

The following are the factors used for selecting a type


of rotation:

• Does the node which we are trying to rotate have a


grandparent?

• Is the node left or right child of the parent?

• Is the node left or right child of the grandparent?


Cases for the Rotations:

• Case 1: If the node does not have a grand-parent,


and if it is the right child of the parent, then we carry
out the left rotation; otherwise, the right rotation is
performed

• Case 2: If the node has a grandparent, then based on


the following scenarios; the rotation would be
performed:
• Scenario 1: If the node is the left of the parent and
the parent is also left of its parent, then zig zig right
right rotation is performed

• Scenario 2: If the node is left of a parent, but the


parent is right of its parent, then zig zag right left
rotation is performed
• Scenario 3: If the node is right of the parent and the
parent is right of its parent, then zag zag left left
rotation is performed

• Scenario 4: If the node is right of a parent, but the


parent is left of its parent, then zag zig left-right
rotation is performed
The following are the cases that can exist in the splay tree
while searching:

• Case 1: If the search item is a root node of the tree

• Case 2: If the search item is a child of the root node, then


the two scenarios will be there:
– If the child is a left child, the right rotation would be performed,
known as a zig right rotation.
– If the child is a right child, the left rotation would be performed,
known as a zig left rotation.
• We have to search key 7.
We will follow the below
steps:

• Step 1: First, we compare


7 with a root node. As 7 is
less than 10, so it is a left
child of the root node.

• Step 2: Once the element


is found, we will perform
splaying. The right
rotation is performed so
that 7 becomes the root
node of the tree, as
shown:

• It’s a zig rotation


• We have to search
20 element in the
tree

• Step 1: First, we
compare 20 with a
root node. As 20 is
greater than the
root node, so it is a
right child of the
root node.
• Step 2: Once the
element is
found, we will
perform
splaying. The left
rotation is
performed so
that 20 element
becomes the
root node of the
tree.

• It’s a zag
rotation
Suppose we
have to
search 1
element in
the tree
• Step 1: First, we have to perform a standard BST searching
operation in order to search the 1 element. As 1 is less than
10 and 7, so it will be at the left of the node 7. Therefore,
element 1 is having a parent, i.e., 7 as well as a grandparent,
i.e., 10.

• Step 2: In this step, we have to perform splaying. We need to


make node 1 as a root node with the help of some rotations.
In this case, we cannot simply perform a zig or zag rotation;
we have to implement zig zig rotation

• In order to make node 1 as a root node, we need to perform


two right rotations known as zig zig rotations.
• When we
perform
the right
rotation
then 10
will move
downward
s, and
node 7 will
come
upwards
as shown
• Again, we will
perform zig right
rotation, node 7
will move
downwards, and
node 1 will
come upwards
as shown:

• Zig zig right


right rotation
• Suppose we
want to search
20 in the this
tree.

• In order to
search 20, we
need to
perform two
left rotations.
Following are
the steps
required to
search 20 node:
• Step 1: First, we perform the standard BST searching
operation. As 20 is greater than 10 and 15, so it will
be at the right of node 15

• Step 2: The second step is to perform splaying. In this


case, two left rotations would be performed
In the first
rotation, node
10 will move
downwards,
and node 15
would move
upwards as
shown:
• In the second
left rotation,
node 15 will
move
downwards,
and node 20
becomes the
root node of
the tree, as
shown

• Zag zag left


left rotation
• we want to
search 13
element in
the tree
which is
shown

• Follow zig
zag right
left
rotation
• Step 1: First, we perform standard BST searching
operation. As 13 is greater than 10 but less than 15,
so node 13 will be the left child of node 15

• Step 2: Since node 13 is at the left of 15 and node 15


is at the right of node 10, so RL relationship exists
• First, we
perform the
right rotation
on node 15,
and 15 will
move
downwards,
and node 13
will come
upwards, as
shown
• Still, node 13 is
not the root
node, and 13 is at
the right of the
root node, so we
will perform left
rotation known as
a zag rotation.
The node 10 will
move downwards,
and 13 becomes
the root node as
shown

• This is zig zag


right left rotation
• As we can observe in the above tree that node 13
has become the root node; therefore, the searching
is completed

• In this case, we have first performed the zig rotation


and then zag rotation; so, it is known as a zig zag
rotation
• We want to
search 9
element in the
tree, which is
shown

• Since node 9
is to right of
its parent, and
the parent is
left of its
parent,
the zag zig
left-right
rotation is
performed
• Step 1: First, we perform the standard BST searching
operation. As 9 is less than 10 but greater than 7, so
it will be the right child of node 7.

• Step 2: Since node 9 is at the right of node 7, and


node 7 is at the left of node 10, so LR relationship
exists.
First, we
perform the left
rotation on
node 7. The
node 7 will
move
downwards, and
node 9 moves
upwards as
shown
Still the node 9
is not a root
node, and 9 is at
the left of the
root node, so we
will perform the
right rotation
known as zig
rotation. After
performing the
right rotation,
node 9 becomes
the root node,
as shown
• As we can observe in the above tree that node 13 is a
root node; therefore, the searching is completed

• In this case, we have first performed the zag rotation


(left rotation), and then zig rotation (right rotation) is
performed, so it is known as a zag zig rotation
Advantages of Splay tree

• In the splay tree, we do not need to store the extra


information. In contrast, in AVL trees, we need to
store the balance factor of each node that requires
extra space, and Red-Black trees also require to store
one extra bit of information that denotes the color of
the node, either Red or Black

• It is the fastest type of Binary Search tree for various


practical applications

• It is used in Windows NT and GCC compilers


• It provides better performance as the frequently
accessed nodes will move nearer to the root node,
due to which the elements can be accessed quickly in
splay trees

• It is used in the cache implementation as the recently


accessed data is stored in the cache so that we do
not need to go to the memory for accessing the data,
and it takes less time
Drawback of Splay tree

• The major drawback of the splay tree would be that


trees are not strictly balanced, i.e., they are roughly
balanced

• Sometimes the splay trees are linear, so it will take


O(n) time complexity
Insertion operation in Splay tree

• In the insertion operation, we first insert the


element in the tree and then perform the
splaying operation on the inserted element

Example:
15, 10, 17, 7
• Step 1: First, we
insert node 15 in the
tree. After insertion,
we need to perform
splaying. As 15 is a
root node, so we do
not need to perform
splaying.
• Step 2: The next
element is 10. As 10
is less than 15, so
node 10 will be the
left child of node 15

• Now, we
perform splaying.
To make 10 as a
root node, we will
perform the right
rotation,
• Step 3: The next element is 17. As 17 is greater than
10 and 15 so it will become the right child of node 15

• Now, we will perform splaying

• As 17 is having a parent as well as a grandparent so


we will perform zag zag rotations
In the figure,
we can
observe that
17 becomes
the root
node of the
tree;
therefore,
the insertion
is
completed.
• Step 4: The next element is 7. As 7 is less than 17, 15,
and 10, so node 7 will be left child of 10

• Now, we have to splay the tree

• As 7 is having a parent as well as a grandparent so we


will perform two right rotations as shown:
• Still the node 7 is not a root node, it is a left
child of the root node, i.e., 17

• So, we need to perform one more right


rotation to make node 7 as a root node as
shown
Deletion in Splay tree

• As we know that splay trees are the variants of


the Binary search tree, so deletion operation in
the splay tree would be similar to the BST, but the
only difference is that the delete operation is
followed in splay trees by the splaying operation

Types of Deletions:
There are two types of deletions in the splay trees:
– Bottom-up splaying
– Top-down splaying
Bottom-up splaying

• In bottom-up
splaying, first we
delete the element
from the tree and
then we perform
the

• Suppose we want
to delete 12, 14
from the tree
shown splaying on
the deleted node.
• First, we simply perform the standard BST deletion operation to
delete 12 element

• As 12 is a leaf node, so we simply delete the node from the tree.

• The deletion is still not completed

• We need to splay the parent of the deleted node, i.e., 10.

• We have to perform Splay(10) on the tree.

• As we can observe in the above tree that 10 is at the right of


node 7, and node 7 is at the left of node 13. So, first, we perform
the left rotation on node 7 and then we perform the right
rotation on node 13, as shown
Still, node 10 is
not a root node;
node 10 is the
left child of the
root node. So,
we need to
perform the
right rotation on
the root node,
i.e., 14 to make
node 10 a root
node as shown
• Now, we have to delete the 14 element from the tree, which is shown

• As we know that we cannot simply delete the internal node

• We will replace the value of the node either using inorder


predecessor or inorder successor

• Suppose we use inorder successor in which we replace the value with


the lowest value that exist in the right subtree

• The lowest value in the right subtree of node 14 is 15, so we replace the
value 14 with 15

• Since node 14 becomes the leaf node, so we can simply delete it as


shown
• Still, the deletion is not completed

• We need to perform one more operation, i.e.,


splaying in which we need to make the parent of the
deleted node as the root node

• Before deletion, the parent of node 14 was the root


node, i.e., 10, so we do need to perform any splaying
in this case
Top-down splaying

• In top-down splaying, we first perform the


splaying on which the deletion is to be
performed and then delete the node from the
tree

• Once the element is deleted, we will perform


the join operation
Suppose
we want
to delete
16 from
the tree
which is
shown
• Step 1: In top-down splaying, first we perform splaying
on the node 16

• The node 16 has both parent as well as grandparent

• The node 16 is at the right of its parent and the parent


node is also at the right of its parent, so this is a zag
zag situation

• In this case, first, we will perform the left rotation on


node 13 and then 14 as shown
The node 16 is still not a root node, and it is a right
child of the root node, so we need to perform left
rotation on the node 12 to make node 16 as a root
node
• Once the node 16 becomes a root node, we
will delete the node 16 and we will get two
different trees, i.e., left subtree and right
subtree as shown below:
• As we know that the values of the left subtree are
always lesser than the values of the right subtree

• The root of the left subtree is 12 and the root of the


right subtree is 17

• The first step is to find the maximum element in the


left subtree

• In the left subtree, the maximum element is 15, and


then we need to perform splaying operation on 15.
• As we can observe in the above tree that the
element 15 is having a parent as well as a
grandparent

• A node is right of its parent, and the parent node is


also right of its parent, so we need to perform two
left rotations to make node 15 a root node as shown
below:
• After performing two rotations on the tree, node
15 becomes the root node

• As we can see, the right child of the 15 is NULL,


so we attach node 17 at the right part of the 15
as shown below, and this operation is known as
a join operation
Thank You…

You might also like