Linked List
Linked List
• A linked list is a data structure that is used to store and organize collections of data. It
consists of a series of nodes, where each node contains data and a reference (or pointer) to the
next node in the sequence. The nodes are connected in a linear fashion, forming a "linked"
chain.
• The elements in a linked list are linked using pointers.
• Unlike arrays, linked lists do not require contiguous memory locations, as each node can
be allocated dynamically in memory. This makes linked lists more flexible in terms of size, as
nodes can be added or removed at any point in the list without needing to reallocate the entire
list.
• Linked lists can be singly linked, where each node only has a reference to the next node,
or doubly linked, where each node has references to both the next and previous nodes. Doubly
linked lists provide more flexibility, but require additional memory for the previous node
references.
Implementation of Linked list in C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct node {
int data;
struct node *next;
};
struct node *head = NULL;
struct node *current = NULL;
while(linkedlist->next != NULL)
linkedlist = linkedlist->next;
linkedlist->next = lk;
}
• Circular Linked List: In a circular linked list, the last node's next pointer/reference points
back to the head, creating a circular structure. This allows for continuous traversal from the last
node to the first node, forming a loop.
struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};
// Function to create a new node with given data
struct Node* createNode(int data)
{
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("Memory allocation failed\n");
return NULL;
}
newNode->data = data; // Set the data of the new node
newNode->next = NULL; // Set the next pointer to NULL
return newNode; // Return the newly created node
}
// Function to insert a new node at the beginning
void insertAtBeginning(struct Node** head, int data)
{
struct Node* newNode = createNode(data);
if (*head == NULL) {
newNode->next = newNode;
*head = newNode;
return;
}
struct Node* temp = *head;
while (temp->next != *head) {
temp = temp->next;
}
temp->next = newNode;
newNode->next = *head;
*head = newNode;
}
STACK
A Stack is a linear data structure that follows the LIFO (Last-In-First-Out) principle. Stack has
one end, whereas the Queue has two ends (front and rear). It contains only one pointer top
pointer pointing to the topmost element of the stack. Whenever an element is added in the
stack, it is added on the top of the stack, and the element can be deleted only from the stack. . It
can be implemented using an array or a linked list.
Standard Stack Operations
The following are some common operations implemented on the stack:
• push(): When we insert an element in a stack then the operation is known as a push. If
the stack is full then the overflow condition occurs.
• pop(): When we delete an element from the stack, the operation is known as a pop. If the
stack is empty means that no element exists in the stack, this state is known as an underflow
state.
• isEmpty(): It determines whether the stack is empty or not.
• isFull(): It determines whether the stack is full or not.'
• peek(): It returns the element at the given position.
• count(): It returns the total number of elements available in a stack.
Implementation of stack
#include <stdio.h>
int MAXSIZE = 8;
int stack[8];
int top = -1;
int isempty()
{
if(top == -1)
return 1;
else
return 0;
}
int isfull()
{
if(top == MAXSIZE)
return 1;
else
return 0;
}
int pop()
{
int data;
if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf("Stack is empty.\n");
}
}
Queue
A queue is a data structure that represents a collection of elements arranged in a linear order,
where elements are added to the rear (enqueue) and removed from the front (dequeue)
according to the First-In-First-Out (FIFO) principle. In other words, the first element that is added
to the queue is the first one to be removed, and new elements are always added to the rear of
the queue.
The basic operations of a queue include enqueue, which adds an element to the rear of the
queue; dequeue, which removes the front element from the queue; peek, which retrieves the
front element without removing it; and isEmpty, which checks if the queue is empty.
Implementation of queue
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX 6
int intArray[MAX];
int front = 0;
int rear = -1;
int itemCount = 0;
bool isFull(){
return itemCount == MAX;
}
bool isEmpty()
{
return itemCount == 0;
}
int removeData(){
int data = intArray[front++];
if(front == MAX) {
front = 0;
}
itemCount--;
return data;
}
void insert(int data){
if(!isFull()) {
if(rear == MAX-1) {
rear = -1;
}
intArray[++rear] = data;
itemCount++;
}
}
Basic Operations
The basic operations that can be performed on a queue are:
• Enqueue: Also known as "insert" or "push", this operation adds an element to the rear
(end) of the queue. The element becomes the last element in the queue.
• Dequeue: Also known as "remove" or "pop", this operation removes the front (first)
element from the queue. The element that was added first is the one to be removed, following
the First-In-First-Out (FIFO) principle.
• Peek: This operation retrieves the front element from the queue without removing it. It
allows you to inspect the element at the front of the queue without modifying the queue.
• IsEmpty: This operation checks if the queue is empty or not. It returns a boolean value,
typically "true" if the queue is empty and "false" if it contains one or more elements.
Dequeue
The deque stands for Double Ended Queue. Deque is a linear data structure where the insertion
and deletion operations are performed from both ends. We can say that deque is a generalized
version of the queue.
Though the insertion and deletion in a deque can be performed on both ends, it does not follow
the FIFO rule. The representation of a deque is given as follows -
#include <stdio.h>
#define size 5
int deque[size];
int f = -1, r = -1;
// insert_front will insert the value from the front
void insert_front(int x)
{
if((f==0 && r==size-1) || (f==r+1))
printf("Overflow");
else if((f==-1) && (r==-1))
{
f=r=0;
deque[f]=x;
}
else if(f==0)
{
f=size-1;
deque[f]=x;
}
else
{
f=f-1;
deque[f]=x;
}
}
void insert_rear(int x)
{
if((f==0 && r==size-1) || (f==r+1))
printf("Overflow");
else if((f==-1) && (r==-1))
{
r=0;
deque[r]=x;
}
else if(r==size-1)
{
r=0;
deque[r]=x;
}
else
{
r++;
deque[r]=x;
}
Tree
A tree is a non-linear data structure. Tree data structure is a hierarchical structure that is used to
represent and organize data in a way that is easy to navigate and search. It is a collection of
nodes that are connected by edges and has a hierarchical relationship between the nodes.
The topmost node of the tree is called the root, and the nodes below it are called the child
nodes. Each node can have multiple child nodes, and these child nodes can also have their own
child nodes, forming a recursive structure. Each node can have zero or more child nodes
connected to it. Nodes with no children are called leaf nodes.
Implementation
struct Node
{
int data;
struct Node *left_child;
struct Node *right_child;
};
Types of Tree
1. Binary Tree: A binary tree is a tree structure where each node has at most two child
nodes. The left child node contains a smaller value than the parent node, and the right child
node contains a greater value. Binary trees are used for fast searching, insertion, and deletion
of items, and are the foundation of many other types of trees.
2. AVL Tree: An AVL tree is a self-balancing binary search tree where the difference in
height between the left and right subtrees of any node is at most one. When a node is inserted
or deleted, the tree may need to be rebalanced to maintain this property. AVL trees are used for
fast searching, insertion, and deletion operations, and are particularly efficient for large datasets.
3. Red-Black Tree: A red-black tree is a self-balancing binary search tree that uses colored
nodes to ensure balance. Each node is either red or black, and the tree is balanced in such a
way that the longest path from the root to any leaf is no more than twice as long as the shortest
path. Red-black trees are used for efficient searching, insertion, and deletion operations, and
are particularly efficient for large datasets.
4. B-Tree: A B-tree is a tree structure that is designed for efficient external storage and
retrieval of data. B-trees are used in databases and file systems to store large amounts of data
on disk, and are designed to minimize the number of disk accesses needed to retrieve data.
5. Trie: A trie is a tree-like data structure used to store and retrieve associative data. Tries
are particularly useful for storing strings and other text-based data, and are commonly used in
spell-checking and autocomplete applications.
6. Heap: A heap is a tree-like data structure used for efficient priority queue operations.
Heaps are used in many applications that require prioritization of data, such as scheduling
algorithms and graph algorithms. The most common type of heap is a binary heap, where the
parent node is always greater (or less) than its child nodes.
BFS Traversal
• Breadth-first search (BFS) is an algorithm that helps you traverse or visit all the nodes in
a graph, starting from a specified node, by visiting all the neighbors of that node before moving
on to the neighbors of its neighbors
• In other words, BFS visits all nodes at the same distance or depth from the starting node
before moving on to nodes at a greater depth. This makes it useful for finding the shortest path
between two nodes in an unweighted graph or for performing topological sorting.
• BFS can be implemented using a queue data structure. The algorithm starts by
enqueuing the starting node and marking it as visited. Then, it dequeues the first node from the
queue and visits all of its unvisited neighbors, adding them to the queue and marking them as
visited. This process continues until all nodes have been visited.
The steps of the BFS algorithm:
1. Enqueue the root node into a queue data structure.
2. Mark the root node as visited.
3. While the queue is not empty, dequeue a node from the front of the queue.
4. For each unvisited neighbor of the dequeued node, mark it as visited and enqueue it into
the queue.
5. Process the dequeued node (for example, print its value or add it to a result list).
DFS Traversal
Depth-first search (DFS) is an algorithm that helps you traverse or visit all the nodes in a graph
by exploring as far as possible along each branch before backtracking. DFS can be
implemented using recursion or a stack data structure.
DFS starts from a source node and recursively applies DFS on each unvisited neighbor of the
current node. If there are no unvisited neighbors, it backtracks to the previous node and repeats
the process. DFS is useful for finding cycles, paths, or connected components in a graph, but
may not find the shortest path between two nodes.
DFS can be implemented recursively or using a stack data structure. In recursive
implementation, the algorithm applies DFS on each unvisited neighbor of the current node. In
stack implementation, it starts by pushing the source node onto the stack and repeatedly pops
the top node and pushes its unvisited neighbors onto the stack.
The steps of the DFS algorithm:
1. Start at a source node and mark it as visited.
2. For each unvisited neighbor of the current node, recursively apply DFS on that neighbor.
3. If all neighbors of the current node have been visited or there are no neighbors,
backtrack to the previous node.
4. Repeat steps 2-3 until all nodes have been visited.