Introduction to Data Structures
Introduction to Data Structures
Data structures are fundamental concepts in computer science that allow us to efficiently store,
organize, and manage data. They are essential for designing algorithms that operate on data,
optimizing time and space complexity, and solving various computational problems effectively.
1. Arrays
2. Linked Lists
Description: A linked list is a linear data structure where each element (node) points to
the next element. Unlike arrays, linked lists are not stored in contiguous memory
locations.
Types:
o Singly Linked List: Each node points to the next node.
o Doubly Linked List: Each node points to both the next and the previous node.
Operations:
o Access: Linear time O(n)
o Insertion/Deletion: Efficient at the beginning or middle (constant time O(1) at
head or tail)
Use case: Ideal for applications where frequent insertion and deletion operations are
required.
3. Stacks
Description: A stack is a linear data structure that follows the Last In, First Out (LIFO)
principle. It supports two primary operations:
o Push: Add an element to the top of the stack.
o Pop: Remove the top element.
Operations:
o Push/Pop: O(1) time
o Peek: O(1) time
Use case: Used in scenarios like function calls, undo operations, and expression
evaluation.
4. Queues
Description: A queue is a linear data structure that follows the First In, First Out (FIFO)
principle. It supports two main operations:
o Enqueue: Add an element to the rear.
o Dequeue: Remove an element from the front.
Operations:
o Enqueue/Dequeue: O(1) time
Use case: Ideal for scheduling tasks, buffering, or handling requests in a system.
5. Trees
Description: A tree is a hierarchical data structure consisting of nodes, with each node
containing a value and references to child nodes. The topmost node is the root, and nodes
with no children are leaves.
Types:
o Binary Tree: Each node has at most two children.
o Binary Search Tree (BST): A binary tree with the property that the left child is
smaller and the right child is larger than the parent node.
o AVL Tree, Red-Black Tree: Self-balancing binary search trees that maintain
their balance to ensure efficient searching, insertion, and deletion.
Operations:
o Search: O(log n) for balanced trees (worst-case O(n) for unbalanced)
o Insertion/Deletion: O(log n) for balanced trees
Use case: Used in hierarchical data storage, searching, and manipulation of sorted data.
6. Hash Tables
Description: A hash table is a data structure that stores key-value pairs. It uses a hash
function to map keys to indices in an array, where values are stored.
Operations:
o Insert: O(1) average time
o Search: O(1) average time
o Deletion: O(1) average time
Use case: Ideal for fast lookups, such as implementing dictionaries, caching, or counting
occurrences.
7. Graphs
Efficiency: Using the right data structure can drastically reduce the time complexity of
algorithms, leading to more efficient software.
Optimization: Data structures help in optimizing both time and space in computational
tasks.
Flexibility: They enable the efficient handling of a variety of tasks like searching,
sorting, updating, or manipulating large amounts of data.
Data structures are fundamental concepts in computer science that allow us to efficiently store,
organize, and manage data. They are essential for designing algorithms that operate on data,
optimizing time and space complexity, and solving various computational problems effectively.
1. Arrays
2. Linked Lists
Description: A linked list is a linear data structure where each element (node) points to
the next element. Unlike arrays, linked lists are not stored in contiguous memory
locations.
Types:
o Singly Linked List: Each node points to the next node.
o Doubly Linked List: Each node points to both the next and the previous node.
Operations:
o Access: Linear time O(n)
o Insertion/Deletion: Efficient at the beginning or middle (constant time O(1) at
head or tail)
Use case: Ideal for applications where frequent insertion and deletion operations are
required.
3. Stacks
Description: A stack is a linear data structure that follows the Last In, First Out (LIFO)
principle. It supports two primary operations:
o Push: Add an element to the top of the stack.
o Pop: Remove the top element.
Operations:
o Push/Pop: O(1) time
o Peek: O(1) time
Use case: Used in scenarios like function calls, undo operations, and expression
evaluation.
4. Queues
Description: A queue is a linear data structure that follows the First In, First Out (FIFO)
principle. It supports two main operations:
o Enqueue: Add an element to the rear.
o Dequeue: Remove an element from the front.
Operations:
o Enqueue/Dequeue: O(1) time
Use case: Ideal for scheduling tasks, buffering, or handling requests in a system.
5. Trees
Description: A tree is a hierarchical data structure consisting of nodes, with each node
containing a value and references to child nodes. The topmost node is the root, and nodes
with no children are leaves.
Types:
o Binary Tree: Each node has at most two children.
o Binary Search Tree (BST): A binary tree with the property that the left child is
smaller and the right child is larger than the parent node.
o AVL Tree, Red-Black Tree: Self-balancing binary search trees that maintain
their balance to ensure efficient searching, insertion, and deletion.
Operations:
o Search: O(log n) for balanced trees (worst-case O(n) for unbalanced)
o Insertion/Deletion: O(log n) for balanced trees
Use case: Used in hierarchical data storage, searching, and manipulation of sorted data.
6. Hash Tables
Description: A hash table is a data structure that stores key-value pairs. It uses a hash
function to map keys to indices in an array, where values are stored.
Operations:
o Insert: O(1) average time
o Search: O(1) average time
o Deletion: O(1) average time
Use case: Ideal for fast lookups, such as implementing dictionaries, caching, or counting
occurrences.
7. Graphs
Efficiency: Using the right data structure can drastically reduce the time complexity of
algorithms, leading to more efficient software.
Optimization: Data structures help in optimizing both time and space in computational
tasks.
Flexibility: They enable the efficient handling of a variety of tasks like searching,
sorting, updating, or manipulating large amounts of data.
Pointers
Pointers in C are variables that store the memory address of another variable. Instead of holding
a data value directly (like an integer or a character), a pointer holds the location in memory
where the value is stored.
Key Concepts:
1. Memory Address: Every variable in a program is stored in memory, and each location in
memory has a unique address (just like an address for a house). Pointers allow us to work
with these addresses directly.
2. Dereferencing: Dereferencing a pointer means accessing the value stored at the memory
address it points to. In C, this is done using the * operator.
3. Pointer Variables: A pointer variable is declared to hold the address of a specific data
type, such as an int, float, or char.
Syntax:
Pointer Declaration:
c
Copy
type *pointer_name;
o Here, type is the data type the pointer will point to, and * indicates that the
variable is a pointer.
Pointer Initialization: You assign the memory address of a variable to a pointer:
c
Copy
int num = 5;
int *ptr = # // ptr now points to the memory address of num
o The & operator is used to get the memory address of the variable.
Dereferencing: You access the value stored at the memory address by using *:
c
Copy
printf("%d", *ptr); // Dereferencing ptr gives the value of num (5)
Example of Pointers in C:
c
Copy
#include <stdio.h>
int main() {
int num = 10; // Regular integer variable
int *ptr = # // Pointer that stores the memory address of num
// Printing values
printf("Value of num: %d\n", num); // Direct value
printf("Address of num: %p\n", (void*)&num); // Memory address of num
printf("Value of ptr (address stored): %p\n", (void*)ptr); // Address
stored in pointer
printf("Value pointed to by ptr: %d\n", *ptr); // Dereferencing ptr to get
the value
return 0;
}
Output:
Value of num: 10
Address of num: 0x7ffeefbff4ac // This is the memory address of num (it will
vary)
c
Copy
int num = 10;
int *ptr = # // ptr now holds the address of num
2. Dereference operator (*): Used to get the value stored at the memory address a pointer
is pointing to.
c
Copy
int num = 10;
int *ptr = #
printf("%d", *ptr); // Output will be 10
c
Copy
int arr[] = {1, 2, 3};
int *ptr = arr;
printf("%d", *(ptr + 1)); // Output will be 2 (accessing arr[1] via
pointer)
1. Efficiency: Pointers allow direct access to memory, which can make certain operations
faster. For example, when passing large data structures (like arrays) to functions, passing
a pointer is more efficient than passing a copy of the entire structure.
2. Dynamic Memory Allocation: Pointers are used in dynamic memory management with
functions like malloc(), calloc(), and free() to allocate and deallocate memory at
runtime.
3. Arrays and Strings: In C, arrays are essentially pointers to the first element of the array,
making pointers very useful for working with arrays and strings.
4. Function Arguments: Pointers allow functions to modify variables outside their scope
(by passing the memory address of a variable), making them essential for passing by
reference.
Introduction to Arrays in C
An array in C is a collection of elements of the same data type stored in contiguous memory
locations. It allows you to group multiple variables of the same type under a single name, making
it easier to manage and process a collection of data.
Arrays are widely used in C for tasks such as handling lists of numbers, storing characters in
strings, and working with data in a structured way.
Array Declaration
In C, an array is declared by specifying the data type, followed by the array name and the size of
the array (the number of elements it can hold).
c
Copy
type array_name[size];
type: The data type of the elements (e.g., int, char, float).
array_name: The name of the array.
size: The number of elements the array can store.
Example:
c
Copy
int numbers[5]; // Declares an integer array 'numbers' of size 5
This creates an array that can store 5 integer values, with indices ranging from 0 to 4.
Initializing Arrays
Arrays can be initialized at the time of declaration by providing a list of values enclosed in curly
braces {}.
Example:
c
Copy
int numbers[5] = {1, 2, 3, 4, 5}; // Initializes the array with values
If the number of values provided is fewer than the size, the remaining elements will be initialized
to 0 for numeric types.
Example:
c
Copy
int numbers[5] = {1, 2}; // numbers will be {1, 2, 0, 0, 0}
If no values are specified, the array will contain garbage values unless explicitly initialized.
Array elements are accessed using an index. In C, the index of an array starts at 0, so the first
element is accessed with index 0, the second element with index 1, and so on.
Example:
c
Copy
int numbers[5] = {1, 2, 3, 4, 5};
You can modify the value of an element in an array by accessing it using its index.
Example:
c
Copy
numbers[2] = 10; // Modifies the third element to 10
printf("%d", numbers[2]); // Prints 10
Multidimensional Arrays
Here, matrix is a 2D array with 2 rows and 3 columns. You can access elements like this:
c
Copy
printf("%d", matrix[0][1]); // Prints 2, the second element of the first row
Array Size
The size of an array must be specified at the time of declaration. The size cannot be changed
after the array has been declared.
Example:
c
Copy
int arr[5]; // Fixed size, cannot add more elements
If you need a dynamic array whose size can change at runtime, you must use dynamic memory
allocation with pointers (using malloc(), calloc(), or realloc()).
You can pass arrays to functions in C, but when you pass an array, it is actually passing the
memory address (the pointer to the first element) rather than a copy of the array. This allows the
function to modify the original array.
#include <stdio.h>
}
printf("\n");
int main() {
return 0;
1. Iterating through an array: You can use loops to iterate through the elements of an
array.
Example:
c
Copy
int numbers[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
printf("%d ", numbers[i]); // Prints all elements of the array
}
2. Finding the length of an array: To find the length of an array, you can divide the total
size of the array by the size of one element.
Example:
c
Copy
int numbers[5] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]); // 5
printf("Length of array: %d\n", length);
Fixed Size: Once an array is declared with a size, it cannot be resized dynamically unless using
dynamic memory allocation techniques (like malloc() or realloc()).
Indexing: Array indices in C start at 0. Accessing an index out of bounds (e.g., array[5] for an
array of size 5) leads to undefined behavior.
Array Passing to Functions: Arrays are passed by reference to functions, so changes made to the
array within the function will affect the original array.
Introduction to Linked Lists
A linked list is a linear data structure that consists of a sequence of elements, called nodes,
where each node contains two components:
Unlike arrays, linked lists are not stored in contiguous memory locations, allowing for efficient
insertion and deletion of elements at any position.
c
Copy
struct Node {
int data; // The data held by the node
struct Node* next; // Pointer to the next node in the list
};
1. Singly Linked List: Each node points to the next node in the sequence. The last node's
next pointer is NULL, marking the end of the list.
2. Doubly Linked List: Each node has two pointers: one pointing to the next node and
another pointing to the previous node. This allows traversal in both directions.
3. Circular Linked List: The last node's next pointer points back to the first node, forming
a circle.
1. Traversal: Visiting each node of the list and processing its data.
2. Insertion:
o Insert a node at the beginning of the list.
o Insert a node at the end of the list.
o Insert a node at a specific position in the list.
3. Deletion:
o Delete the first node.
o Delete the last node.
o Delete a node at a specific position in the list.
Node Definition
c
Copy
#include <stdio.h>
#include <stdlib.h>
To create a new node dynamically, we use malloc() to allocate memory for it and initialize the
data and next parts.
c
Copy
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL; // Initially, the next pointer is NULL
return newNode;
}
To insert a node at the beginning, we make the next pointer of the new node point to the current
head of the list, then update the head to point to the new node.
c
Copy
void insertAtBeginning(struct Node** head, int data) {
struct Node* newNode = createNode(data);
newNode->next = *head; // Point the new node to the current head
*head = newNode; // Update the head to the new node
}
Traversing the List
To traverse the list and print its elements, we start from the head and move through each node
using the next pointer.
c
Copy
void traverseList(struct Node* head) {
struct Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
To delete the first node, we simply update the head to point to the second node and free the
memory allocated for the first node.
c
Copy
void deleteFirstNode(struct Node** head) {
if (*head == NULL) {
printf("List is empty.\n");
return;
}
struct Node* temp = *head;
*head = (*head)->next; // Update the head to the next node
free(temp); // Free the memory of the old head node
}
int main() {
struct Node* head = NULL; // Initially, the list is empty
return 0;
}
Output:
rust
Copy
Linked List: 30 -> 20 -> 10 -> NULL
Linked List after deleting first node: 20 -> 10 -> NULL
1. Dynamic Memory Allocation: Unlike arrays, linked lists do not require contiguous
memory. Memory is dynamically allocated using malloc() or calloc().
2. Efficient Insertions/Deletions: Linked lists allow for efficient insertions and deletions at
the beginning and middle without needing to shift other elements, unlike arrays.
3. Traversal: Traversing a linked list requires starting from the head and following the
next pointer of each node.
4. No Random Access: Linked lists do not support random access like arrays. To access an
element, you must traverse the list from the head.
Dynamic Size: Linked lists can grow or shrink in size dynamically, unlike arrays, where the size is
fixed.
Efficient Insertions and Deletions: Inserting or deleting elements at the beginning or middle of
the list is more efficient compared to arrays (no shifting required).
Memory Usage: Each node in a linked list requires additional memory for the pointer (next).
No Random Access: You cannot directly access an element by its index like you can in arrays.