DS Notes
DS Notes
• A pointer is a variable in a computer program that stores the memory address of another
variable or object. Pointers are commonly used in C and C++ programming languages to
manipulate memory and create dynamic data structures such as linked lists and trees.
They can also be used to pass data by reference to a function, rather than by value.
Pointers can be dereferenced to access the memory location they point to, or they can be
assigned new memory addresses.
• Define algorithm
• An algorithm is a set of instructions or steps for solving a specific problem or achieving a
specific task. Algorithms are used in computer programming to perform various tasks,
such as sorting data, searching for information, and performing mathematical
calculations.
• An algorithm should have the following properties:
• data structure
• A data structure is a way of organizing and storing data in a computer so that it can be
accessed and modified efficiently. Examples of common data structures include arrays,
linked lists, stacks, queues, trees, and graphs. Each data structure has its own strengths
and weaknesses and is suited to different types of problems and applications. The choice
of data structure can have a significant impact on the performance of an algorithm and
the overall design of a software system.
• ADT
• An Abstract Data Type (ADT) is a high-level description of a collection of data and the
operations that can be performed on that data. It defines the behavior of a data type, but
not its implementation.
• An ADT specifies the following:
• For example, a stack is an ADT that has a set of values (items on the stack), and a set of
operations (push, pop, peek) and has a constraint that the last element added is the first
one to come out.
• An ADT allows the implementation details to be hidden, allowing the user to focus on the
functionality provided by the data type, rather than how it is implemented. This promotes
code reusability, ease of understanding and maintainability.
• Common examples of ADT include: Queue, Stack, List, Tree, Graph, Set and Map.
• Array
• An array is a data structure that stores a collection of items of the same data type. Each
item in the array is called an element, and is accessed by its index, which is an integer
value that corresponds to the position of the element in the array.
• Arrays are often used to store large amounts of data in a structured way, and are well
suited for problems that involve indexing, such as searching and sorting.
• Arrays have a fixed size, which means that once an array is created, its size cannot be
changed. Therefore, if the number of elements in an array is going to change frequently,
it is better to use a dynamic data structure such as a linked list.
• In most programming languages, arrays are implemented as a contiguous block of
memory, which allows for efficient access to elements by their index. However, this also
means that inserting or deleting an element from an array can be expensive, as it may
require shifting many elements to make room for the new element or fill the gap left by
the deleted element.
• Arrays have a constant time complexity for random access and O(n) time complexity for
insertion and deletion at a particular position.
• types
• There are several types of arrays, including:
• One-dimensional arrays: These are the most basic type of array, which stores a single list
of elements. They are often used to store a collection of items of the same data type,
such as a list of integers or strings.
• Multi-dimensional arrays: These arrays have more than one dimension and can be
thought of as a table of values, where each element is identified by a set of indices, rather
than just a single index. For example, a two-dimensional array can be thought of as a grid
of values, where each element is identified by a row and a column index.
• Static arrays: These arrays have a fixed size, which means that once an array is created, its
size cannot be changed.
• Dynamic arrays: These arrays can grow or shrink in size as needed, often implemented
using an array along with a data structure like linked list, which allows for efficient
insertion and deletion of elements.
• Jagged arrays: These are arrays of arrays, where each element of the main array is an
array itself. It means the length of each array can be different.
• Sparse arrays: These are arrays that contain mostly empty or null values, with only a few
elements that have actual data. Sparse arrays are often used to represent large, mostly
empty data sets in a memory-efficient way.
• The choice of array type depends on the specific requirements of the problem and the
application. One type may be more efficient than another for a particular task
• methord of sorting
• There are several methods for sorting an array of data, including:
• Bubble sort: repeatedly steps through the list, compares each pair of adjacent items and
swaps them if they are in the wrong order.
• insertion sort: builds the final sorted list one item at a time by repeatedly removing an
element from the input data and inserting it into its correct position in the already-sorted
list.
• selection sort: repeatedly finds the minimum element from the unsorted part of the array
and swaps it with the first element of the unsorted part.
• merge sort: divides the array in half recursively, sorts each half, and then merges the two
sorted halves back together.
• quick sort: selects a "pivot" element from the array and partition the other elements into
two sub-arrays, according to whether they are less than or greater than the pivot.
• heap sort: builds a heap from the input array and repeatedly extracts the maximum
element from the heap, which places it at the end of the sorted array.
• radix sort: sorts the elements by first grouping the individual digits of the same place
value, and then concatenating the groups.
• Each sorting method has its own advantages and disadvantages, and the best sorting
method for a particular problem will depend on the specifics of the data and the
requirements of the application. Quick Sort and Merge Sort are the most widely used
sorting algorithms with average O(nlogn) time complexity while Bubble sort and insertion
sort have O(n^2) time complexity.
• linked list
• A linked list is a data structure that consists of a sequence of elements called nodes, each
of which stores a reference (or pointer) to the next node in the list. The last node in the
list typically contains a null reference, indicating the end of the list. Linked lists can be
singly-linked, where each node has a single reference to the next node, or doubly-linked,
where each node has references to the next and previous nodes.
• Linked lists are useful in situations where the size of the data is unknown or frequently
changing, as they can easily be resized by adding or removing nodes. They do not have
the size limitations of arrays and can continue to grow in size as needed.
• Insertions and deletions of elements in a linked list can be done in constant time O(1) as
we only need to change the pointers of the adjacent nodes. However, the time
complexity for searching an element in a linked list is O(n) as we have to traverse the list
from the head to the desired position.
• Linked lists can also be used to implement other data structures, such as stacks, queues,
and hash tables, and can be used to implement advanced data structures such as skip
lists, and self-balancing trees (AVL, Red-black tree).
• primitive operation
• A primitive operation, also known as a basic operation, is a simple operation that is
fundamental to the functioning of a computer or algorithm. These operations are usually
implemented directly by the hardware or the lowest-level software and are not further
decomposable. Examples of primitive operations include:
• The number and types of primitive operations depend on the architecture of the
computer system and the programming language being used. The time and space
complexity of an algorithm is often analyzed in terms of the number and types of
primitive operations performed. The goal of algorithm design is often to minimize the
number of primitive operations needed to solve a problem.
• static implementaion
• Static implementation refers to the creation of data structures with a fixed size. The size
of the data structure is determined at the time of creation and cannot be changed
afterwards.
• For example, an array is a static data structure, as its size is fixed at the time of creation
and cannot be changed afterwards. Once an array is created, it can no longer be resized
to accommodate more elements.
• A static implementation of a data structure has several advantages:
• It may be wasteful in terms of memory usage if the size of the data structure is much
larger than the number of elements it contains.
• It may be inefficient for data sets that frequently change in size, as inserting or deleting
elements requires shifting the elements and can be slow.
• Dynamic implementation, on the other hand, allows for the size of the data structure to
be changed after it is created. This can be done by allocating and deallocating memory as
needed. Linked List, Stack and Queue are the examples of dynamic data structures.
• dynamic stack
• A dynamic stack is a stack data structure that can change its size during runtime. This is in
contrast to a static stack, which has a fixed size determined at the time of creation and
cannot be changed afterwards.
• In a dynamic stack, elements are pushed and popped as usual, but when the stack
becomes full, it dynamically resizes itself to accommodate more elements. Similarly, when
the stack becomes empty after several pops, it dynamically shrinks its size to free up
unnecessary memory.
• One way to implement a dynamic stack is to use a dynamic array as the underlying data
structure. When the stack becomes full, the array is resized to a larger size, and when the
stack becomes empty, the array is resized to a smaller size. This method provides
constant time complexity for push and pop operations, but resizing the array can be an
expensive operation, with a time complexity of O(n), where n is the number of elements
in the stack.
• Another way to implement a dynamic stack is to use a linked list as the underlying data
structure. Each node in the linked list represents an element in the stack, and the stack
can be resized by adding or removing nodes from the linked list. This method provides
constant time complexity for both push and pop operations and resizing the stack, but it
uses more memory as each node in the list stores a pointer to the next node.
• Dynamic stack allows for efficient use of memory and adapts to the usage pattern of the
program, but it may be more complex to implement and less efficient than a static stack
in terms of time complexity.
• dynamic stack
• A dynamic stack is a stack data structure that can change its size during runtime. This is in
contrast to a static stack, which has a fixed size determined at the time of creation and
cannot be changed afterwards.
• In a dynamic stack, elements are pushed and popped as usual, but when the stack
becomes full, it dynamically resizes itself to accommodate more elements. Similarly, when
the stack becomes empty after several pops, it dynamically shrinks its size to free up
unnecessary memory.
• One way to implement a dynamic stack is to use a dynamic array as the underlying data
structure. When the stack becomes full, the array is resized to a larger size, and when the
stack becomes empty, the array is resized to a smaller size. This method provides
constant time complexity for push and pop operations, but resizing the array can be an
expensive operation, with a time complexity of O(n), where n is the number of elements
in the stack.
• Another way to implement a dynamic stack is to use a linked list as the underlying data
structure. Each node in the linked list represents an element in the stack, and the stack
can be resized by adding or removing nodes from the linked list. This method provides
constant time complexity for both push and pop operations and resizing the stack, but it
uses more memory as each node in the list stores a pointer to the next node.
• Dynamic stack allows for efficient use of memory and adapts to the usage pattern of the
program, but it may be more complex to implement and less efficient than a static stack
in terms of time complexity.
• queues
• A queue is a data structure that follows the First-In-First-Out (FIFO) principle, meaning
that the first element added to the queue will be the first one to be removed. A queue
can be implemented as a dynamic or static data structure.
• A dynamic queue is a queue whose size can change during runtime. It can be
implemented using a dynamic array or a linked list. When elements are dequeued
(removed) from the front of the queue, the dynamic queue can shrink its size to free up
unnecessary memory. When elements are enqueued (added) at the back of the queue,
the dynamic queue can expand its size to accommodate more elements.
• A static queue, on the other hand, has a fixed size determined at the time of creation and
cannot be changed afterwards. It can be implemented using an array. When elements are
dequeued from the front of the queue, the remaining elements are shifted towards the
front to fill the gap. When the array is full and a new element is enqueued, the oldest
element is removed from the front of the queue (overwritten) to make room for the new
element.
• Both dynamic and static queues provide constant time complexity for enqueue and
dequeue operations, but dynamic queues can be more efficient in terms of memory
usage, while static queues are simple and easy to understand.
• In general, dynamic queues are used when the number of elements in the queue is
unknown or frequently changing, while static queues are used when the number of
elements is known and not expected to change.
•
• types of queue
• There are several types of queues, including:
• Simple Queue: It is a basic queue that follows the First-In-First-Out (FIFO) principle.
Elements are added to the back of the queue and removed from the front of the queue.
• Priority Queue: A priority queue is a queue where each element has a priority associated
with it. Elements with higher priority are dequeued before elements with lower priority.
• Double-Ended Queue (Deque): A double-ended queue allows elements to be added or
removed from both ends of the queue. It supports the operations of enqueue, dequeue,
push and pop, both from the front and the rear.
• Circular Queue: A circular queue is a queue that "wraps around" when it reaches the end
of the array. When the end of the array is reached, the next element is added at the
beginning of the array.
• Blocking Queue: A blocking queue is a queue that blocks (or waits) when the queue is
empty during dequeue operation, and when the queue is full during enqueue operation.
• Bounded Queue: A bounded queue is a queue that has a limit on the number of elements
it can hold at any given time. Once the limit is reached, new elements cannot be added to
the queue.
• Multi-level Queue: A multi-level queue is a queue where each element is associated with
a level of priority and the elements are dequeued based on their level of priority.
• Distributed Queue: A distributed queue is a queue that can be accessed and modified by
multiple processes running on different machines in a network.
• The choice of queue type depends on the specific requirements of the problem and the
application. One type may be more efficient than another for a particular task.
A common example of a self-referential data structure is a linked list, where each node of the list
contains a reference to the next node in the list. In this case, each node of the list is self-
referential, as it contains a reference to itself.
Another example is a tree data structure, where each node of the tree contains a reference to its
parent and its children. Each node is self-referential as it contains a reference to itself.
In addition to linked lists and trees, self-referential structures can also be used to implement
other data structures such as graphs and circular linked lists.
Self-referential structures are useful in many algorithms and data structures and they can be used
to implement advanced data structures such as self-balancing trees (AVL, Red-black tree) and
Skip Lists.
1. Undirected Graphs: These are graphs where edges have no direction, meaning that if there is an
edge between vertex A and vertex B, then there is also an edge between vertex B and vertex A.
2. Directed Graphs (Digraphs): These are graphs where edges have a direction, meaning that if there
is an edge from vertex A to vertex B, it does not necessarily mean that there is an edge from
vertex B to vertex A.
3. Weighted Graphs: These are graphs where each edge has a weight or cost associated with it.
These weights can represent distance, time, or any other metric.
4. Unweighted Graphs: These are graphs where edges do not have any weight or cost associated
with them.
5. Cyclic Graphs: These are graphs that contain a cycle, a path that starts and ends at the same
vertex.
6. Acyclic Graphs: These are graphs that do not contain any cycles, also known as a forest.
7. Simple Graphs: These are graphs that do not contain any self-loops or parallel edges.
8. Multi Graphs: These are graphs that can contain self-loops and parallel edges.
9. Regular Graphs: These are graphs where all vertices have the same degree (number of edges).
10. Complete Graphs: These are graphs where every vertex is connected to every other vertex.
The choice of graph type depends on the specific requirements of the problem and the
application. One type may be more efficient than another for a particular task.
1. Memory management: The stack is used to keep track of function calls and to store local
variables.
2. Expression Evaluation: Infix, prefix, and postfix expressions can be evaluated using a stack.
3. Syntax parsing: A stack can be used to check for balanced symbols in programming languages,
such as matching parentheses, brackets, and curly braces.
4. Backtracking: A stack can be used for backtracking, for example, to find a path through a maze or
to solve a problem using a depth-first search.
5. Undo/Redo: A stack can be used to implement undo/redo functionality in applications, such as
text editors and image editors.
6. Recursion: A stack can be used to implement recursion, by keeping track of the state of the
function calls.
7. Browser's history: A stack can be used to keep track of the user's browsing history, so the user
can go back to the previous pages.
8. Compiler design: A stack can be used in the compilation process, for example, to keep track of
the control flow and to generate machine code.
9. Parsing: A stack can be used in parsing, for example, to check for balanced expressions in a
programming language.
10. Other: Stacks can be used in other
1. Binary Tree: A tree where each node has at most two children.
2. Binary Search Tree (BST): A binary tree where the left child of a node has a value less than its
parent node, and the right child has a value greater than its parent node.
3. AVL Tree: A balanced binary search tree where the difference in height between the left and right
subtrees of any node is at most 1.
4. Red-Black Tree: A balanced binary search tree where each node is colored either red or black to
ensure that the tree remains balanced.
5. B-Tree: A self-balancing tree data structure that keeps data sorted and allows searches,
sequential access, insertions, and deletions in logarithmic time.
6. B+ Tree: An extension of a B-Tree, where all data is stored only in leaf nodes, but each leaf node
has a pointer to the next leaf node.
7. Trie: A tree-based data structure that is used for efficient retrieval of a key in a large data set of
strings.
8. K-ary Tree: A tree where each node has at most k children.
9. Heap: A specialized tree-based data structure that satisfies the heap property, where the value of
each node is greater than or equal to the value of its children.
10. Segment Tree: A specialized tree used for solving problems involving ranges, such as finding the
minimum or maximum value in a range.
11. Fenwick Tree(BIT): A specialized tree used for solving problems involving prefix sums, such as
finding the sum of elements in a range.
12. Huffman Tree: A tree-based data structure used for lossless data compression.
Each tree has its own advantages and disadvantages, and the best tree for a particular problem
will depend on the specifics of the data and the requirements of the application.
what is searching
Searching is the process of finding a specific item or a group of items from a collection of data. It
is a fundamental operation in computer science and is used in a wide range of applications,
including databases, file systems, and the internet.
1. Linear Search: This algorithm involves iterating through the data collection one item at a time
until the desired item is found. It has a time complexity of O(n), where n is the number of items in
the collection.
2. Binary Search: This algorithm is used to search for an item in a sorted collection of data. It
repeatedly divides the search interval in half and checks the middle element. It has a time
complexity of O(log n)
3. Depth First Search (DFS): This algorithm is used to search through a tree or a graph by exploring
as far as possible along each branch before backtracking. It has a time complexity of O(n + m)
where n is the number of nodes and m is the number of edges.
4. Breadth First Search (BFS): This algorithm is used to search through a tree or a graph by exploring
all the neighboring nodes before moving to the next level. It has a time complexity of O(n + m)
where n is the number of nodes and m is the number of edges.
5. Hashing: This algorithm uses a hash function to map keys to indices in an array and allows for
constant time O(1) lookups.
The choice of search algorithm depends on the specific requirements of the problem and the
type of data
1. Bubble sort: This algorithm repeatedly steps through the list, compares adjacent elements and
swaps them if they are in the wrong order. It has a time complexity of O(n^2)
2. insertion sort: This algorithm builds the final sorted list one item at a time by repeatedly inserting
the next item in the correct position. It has a time complexity of O(n^2)
3. selection sort: This algorithm repeatedly selects the smallest element from the unsorted part of
the list and moves it to the sorted part of the list. It has a time complexity of O(n^2)
4. merge sort: This algorithm divides the list into two parts, sorts each part separately and then
merges the two sorted parts back together. It has a time complexity of O(n log n)
5. quick sort: This algorithm chooses a 'pivot' element from the list and partition the other elements
into two parts, those less than the pivot and those greater than the pivot, and then recursively
sorts the sub-lists. It has a time complexity of O(n log n) on average, but O(n^2) in worst case.
In an almost complete binary tree, all the leaf nodes are either at the same level or at the level
immediately below the last level of nodes. The last level of nodes may be not completely filled,
but all the preceding levels of the tree are completely filled.
Almost Complete Binary Tree is used in Heap data structure, where the heap is a complete binary
tree except for the rightmost elements on the last level.
Almost complete binary trees have some advantages over complete binary trees, such as:
1. They can be implemented more efficiently by using an array, with the elements stored in level-
order fashion.
2. They can be more memory efficient, as they do not require additional memory to store the
unused nodes.
3. They can be more flexible, as they can adapt to changing data sets more easily.
The Almost Complete Binary Tree is used in various algorithms and data structures such as Heap,
Trie, and Huffman Tree. These trees are efficient in terms of space and time complexity and are
used in many real-world applications such as memory management, data compression and
priority queue operations.
In a Left-Left (LL) rotation, the left subtree of the root node becomes the new root, and the
current root becomes the right child of the new root. This rotation is typically performed when
the left subtree is taller than the right subtree, and the left subtree's left subtree is taller than its
right subtree.
In a Left-Right (LR) rotation, the left subtree of the root node's right child becomes the new root,
and the current root becomes the left child of the new root. This rotation is typically performed
when the left subtree is taller than the right subtree, and the left subtree's right subtree is taller
than its left subtree.
Both LL and LR rotations are used to balance the tree and maintain the height balance property.
The Left-Left (LL) and Left-Right (LR) rotations are used in Self-Balancing Binary Search Trees (AVL
tree, Red-black tree) to maintain the balance of the tree after an insertion or deletion operation.
These rotations are used to re-balance the tree and keep the height of the tree as small as
possible to ensure that the tree remains efficient.
The LL and LR rotations are used to balance the tree and maintain the height balance property.
These rotations are used in various algorithms and data structures such as AVL Trees, Red-Black
Trees and other self-balancing binary search trees to ensure that the tree remains balanced after
insertions and deletions.
It's important to note that this algorithm assumes that the infix expression is well-formed and
contains no errors, such as mismatched parentheses or invalid characters.
In a tree data structure, a leaf node is a node that has no children. It is also known as
a terminal node or an end node. Leaf nodes are the nodes that are at the bottom of
the tree, and they do not have any branches or children. They are the last nodes in
the tree, which means they don't have any further descendants.
For example, in a binary tree, leaf nodes are the nodes that have no left or right
children. In a k-ary tree, leaf nodes are the nodes that have no children at all.
Leaf nodes play an important role in many algorithms and data structures, such as
traversing a tree, searching for a specific item, or calculating the height of a tree.
They also have a significant impact on the performance of a tree data structure, as
the number of leaf nodes in a tree is directly related to the height of the tree.
A cycle can be a simple cycle, where the same vertex is visited twice, or it can be a more complex
cycle, where multiple vertices are visited multiple times. Cyclic graphs can have multiple cycles
and they can have different lengths and shapes.
Cyclic graphs are used in a variety of applications, such as modeling relationships in social
networks, transportation networks, and communication networks. They are also used in
algorithms such as cycle detection, topological sorting and finding strongly connected
components.
On the other hand, an acyclic graph is a graph that does not contain any cycles, also known as a
forest.
The cycle detection in a graph can be done using different algorithms such as DFS, BFS, and
Union-Find Algorithm. The choice of algorithm depends on the specific requirements of the
problem and the type of data.
Define parent node
In a tree data structure, a parent node is a node that has one or more child nodes. It is also
known as an internal node or a non-leaf node. Parent nodes are the nodes that are not at the
bottom of the tree, and they have one or more branches or children. They are not the last nodes
in the tree, which means they have at least one descendant.
For example, in a binary tree, a parent node is a node that has at least one left or right child. In a
k-ary tree, a parent node is a node that has at least one child.
Parent nodes play an important role in many algorithms and data structures, such as traversing a
tree, searching for a specific item, or calculating the height of a tree. They also have a significant
impact on the performance of a tree data structure, as the number of parent nodes in a tree is
directly related to the height of the tree.
A parent node can have one or more child nodes, and it can also have a parent node, which is
called grandparent node and so on. The topmost node in the tree which has no parent is called
the root node.