0% found this document useful (0 votes)
13 views

dsa unit 1 (ai)

The document provides an overview of data structures, categorizing them into linear (e.g., arrays, linked lists) and non-linear types (e.g., trees, graphs). It discusses algorithm analysis, including time and space complexity, and introduces abstract data types (ADTs) such as lists and stacks. Additionally, it covers arrays, pointers, string processing, and various linked list types, detailing their operations and comparisons.

Uploaded by

dheerajjha0276
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

dsa unit 1 (ai)

The document provides an overview of data structures, categorizing them into linear (e.g., arrays, linked lists) and non-linear types (e.g., trees, graphs). It discusses algorithm analysis, including time and space complexity, and introduces abstract data types (ADTs) such as lists and stacks. Additionally, it covers arrays, pointers, string processing, and various linked list types, detailing their operations and comparisons.

Uploaded by

dheerajjha0276
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 36

Overview of Data Structures

A data structure is a way of organizing, managing, and storing data efficiently. It


provides the foundation for designing efficient algorithms. Data structures are
categorized into two main types:

1. Linear Data Structures:

Data elements are arranged in a sequential manner. Examples:

●​ Array: Fixed-size, continuous memory allocation.


●​ Linked List: Dynamic size, each node contains data and a pointer.
●​ Stack: Follows LIFO (Last In, First Out) principle.
●​ Queue: Follows FIFO (First In, First Out) principle.

2. Non-Linear Data Structures:

Data elements are stored in a hierarchical or interconnected manner. Examples:

●​ Trees: Hierarchical structure with parent-child relationships.


●​ Graphs: Collection of nodes (vertices) and edges (connections).
●​ Hash Table: Key-value pair mapping for fast data retrieval.

Each data structure has its advantages and is chosen based on the problem
requirements.

Basics of Algorithm Analysis


An algorithm is a step-by-step procedure to solve a problem. The efficiency of an
algorithm is analyzed using:

1.​ Time Complexity: Measures how the execution time of an algorithm changes
with input size.
2.​ Space Complexity: Measures the additional memory required by an algorithm.

Running Time Calculations


The running time of an algorithm depends on the number of operations performed. It is
expressed using Big-O notation, which provides an upper bound on time complexity in
terms of input size nn.

Common Time Complexities:


Complexit Name Example Algorithm
y

O(1)O(1) Constant Time Hash Table Lookup

O(log⁡n)O(\l Logarithmic Binary Search


og n) Time

O(n)O(n) Linear Time Linear Search

O(nlog⁡n)O Linearithmic Merge Sort, Quick Sort (average case)


(n \log n) Time

O(n2)O(n^ Quadratic Time Bubble Sort, Selection Sort


2)

O(2n)O(2^ Exponential Recursive Fibonacci


n) Time

O(n!)O(n!) Factorial Time Traveling Salesman Problem (Brute


Force)

For practical purposes, algorithms with lower time complexities are preferred as they
scale efficiently with large inputs.

Abstract Data Types (ADT)


An Abstract Data Type (ADT) is a mathematical model for a data structure that
specifies:

1.​ The data type and


2.​ The operations that can be performed on it, without specifying implementation
details.

Common ADTs:

●​ List ADT: Supports operations like insertion, deletion, and traversal.


●​ Stack ADT: Follows LIFO (Last In, First Out) principle.
●​ Queue ADT: Follows FIFO (First In, First Out) principle.
●​ Deque ADT: A double-ended queue allowing insertion and deletion at both ends.
●​ Set ADT: Stores unique elements and supports operations like union,
intersection, and difference.
●​ Map (Dictionary) ADT: Stores key-value pairs for fast lookup.

ADTs define the behavior of a data structure, while the actual implementation can vary.

Arrays
An array is a linear data structure that stores elements of the same data type in
contiguous memory locations.

Key Characteristics of Arrays:

●​ Fixed Size: The size of an array is defined at declaration and cannot be


changed.
●​ Random Access: Elements can be accessed directly using an index.
●​ Efficient Retrieval: Access time is O(1) for retrieving elements using an index.

Types of Arrays:

One-Dimensional Array: Stores a linear sequence of elements.​


int arr[5] = {1, 2, 3, 4, 5};

1.​

Multi-Dimensional Arrays: Used to store tabular or matrix-like data.​


int matrix[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};

2.​
3.​ Dynamic Arrays: Implemented using pointers and memory allocation
(malloc()/new).

Basic Operations on Arrays:

●​ Insertion: O(1)O(1) if added at the end, O(n)O(n) if inserted at the beginning.


●​ Deletion: O(1)O(1) if removed from the end, O(n)O(n) if from the start.
●​ Searching: O(n)O(n) in an unsorted array (Linear Search), O(log⁡n)O(\log n) in a
sorted array (Binary Search).

Arrays and Pointers


In C and C++, arrays and pointers are closely related. The array name itself acts as a
pointer to the first element.

Pointer Representation of Arrays:

int arr[5] = {10, 20, 30, 40, 50};

int *ptr = arr; // ptr now points to arr[0]

●​ arr[i] is equivalent to *(arr + i).


●​ ptr++ moves the pointer to the next element in the array.

Pointer Arithmetic with Arrays:

#include <iostream>

using namespace std;

int main() {

int arr[] = {10, 20, 30, 40};

int *ptr = arr;

cout << "First Element: " << *ptr << endl; // Output: 10

ptr++;

cout << "Second Element: " << *ptr << endl; // Output: 20
return 0;

Dynamic Arrays using Pointers:

Dynamic memory allocation is useful when the array size is unknown at compile time.

int *arr = new int[5]; // Allocating memory dynamically

delete[] arr; // Freeing allocated memory

This prevents memory wastage when handling unknown or varying input sizes.

Multidimensional Arrays
A multidimensional array is an array of arrays, meaning it can store data in multiple
dimensions (2D, 3D, etc.).

Types of Multidimensional Arrays

1. Two-Dimensional Arrays (2D Arrays)

A 2D array is often used to represent matrices or tables.

Declaration and Initialization

int matrix[3][3] = { {1, 2, 3},

{4, 5, 6},

{7, 8, 9} };

Accessing Elements in a 2D Array

Each element in a 2D array is accessed using row index and column index.

cout << matrix[1][2]; // Output: 6 (2nd row, 3rd column)


2. Three-Dimensional Arrays (3D Arrays)

A 3D array is useful for handling 3D grids, gaming maps, or image processing.

Declaration

int cube[2][3][4]; // A 2x3x4 3D array

Each element is accessed using three indices: cube[depth][row][column].

Pointer Representation of Multidimensional Arrays

Since arrays in C++ are stored in row-major order, a 2D array can be represented
using pointers.

int (*ptr)[3] = matrix; // Pointer to a 2D array

String Processing
A string is a sequence of characters stored in contiguous memory. In C++, strings can
be handled using:

1.​ Character Arrays


2.​ string Class (from <string> library)

1. String Representation Using Character Arrays

A string in C can be stored as a character array, ending with a null character (\0).

char str[] = "Hello"; // Stored as {'H', 'e', 'l', 'l', 'o', '\0'}

Input and Output for Character Arrays

char name[20];
cin >> name; // Reads a single word

cout << "Hello, " << name;

To read a full sentence, use cin.getline()

cin.getline(name, 20);

Common String Functions (cstring Library)

#include <iostream>

#include <cstring>

using namespace std;

int main() {

char str1[] = "Hello";

char str2[] = "World";

strcat(str1, str2); // Concatenation

cout << str1; // Output: HelloWorld

cout << strlen(str1); // Output: 10 (length of the string)

return 0;

}
Function Description

strlen(str) Returns string length

strcpy(dest, Copies src to dest


src)

strcat(str1, Concatenates str2 to


str2) str1

strcmp(str1, Compares two strings


str2)

2. String Processing Using string Class (C++)

The string class in C++ provides an easier way to work with strings.

#include <iostream>

#include <string>

using namespace std;

int main() {

string str1 = "Hello";

string str2 = "World";


cout << str1 + " " + str2; // Concatenation

cout << str1.length(); // Length of the string

return 0;

Function Description

s.length() Returns length of


string

s.append("text Appends text to string


")

s.substr(start Extracts substring


, len)

s.find("word") Finds the first


occurrence

General Lists and List ADT


A list is a sequential collection of elements where insertion, deletion, and access
operations can be performed dynamically. Lists provide more flexibility than arrays
because they do not have a fixed size.

List Abstract Data Type (List ADT)


A List ADT defines a linear collection of elements where operations like insertion,
deletion, and traversal are performed. The List ADT does not specify how the list is
implemented but focuses on what operations are possible.

Operations on List ADT:

1.​ Insert (element, position) – Adds an element at a specified index.


2.​ Delete (position) – Removes an element from the specified index.
3.​ Search (element) – Finds and returns the index of an element.
4.​ Update (position, new_element) – Replaces an element at a given position.
5.​ Traverse – Iterates over all elements in the list.
6.​ Sort – Arranges elements in ascending or descending order.
7.​ Reverse – Reverses the order of elements.

Types of Lists:

1.​ Array-Based Lists (Static Lists) – Implemented using arrays. Fixed-size.


2.​ Linked Lists (Dynamic Lists) – Implemented using nodes and pointers. Flexible
size.

List Manipulations
List manipulations involve performing various operations on lists, such as insertion,
deletion, searching, and updating.

1. Insertion in a List

(a) Inserting at the beginning

void insertAtBeginning(Node** head, int newData) {

Node* newNode = new Node();

newNode->data = newData;

newNode->next = *head;

*head = newNode;

}
(b) Inserting at the end

void insertAtEnd(Node** head, int newData) {

Node* newNode = new Node();

newNode->data = newData;

newNode->next = NULL;

if (*head == NULL) {

*head = newNode;

return;

Node* temp = *head;

while (temp->next != NULL)

temp = temp->next;

temp->next = newNode;

2. Deletion in a List

(a) Deleting a specific element

void deleteNode(Node** head, int key) {

Node* temp = *head, *prev = NULL;

if (temp != NULL && temp->data == key) {

*head = temp->next;

delete temp;
return;

while (temp != NULL && temp->data != key) {

prev = temp;

temp = temp->next;

if (temp == NULL) return;

prev->next = temp->next;

delete temp;

3. Searching in a List

bool search(Node* head, int key) {

Node* current = head;

while (current != NULL) {

if (current->data == key)

return true;

current = current->next;

return false;

4. Reversing a List
void reverseList(Node** head) {

Node* prev = NULL, *current = *head, *next = NULL;

while (current != NULL) {

next = current->next;

current->next = prev;

prev = current;

current = next;

*head = prev;

5. Sorting a List (Bubble Sort Example)

void bubbleSort(Node* head) {

bool swapped;

Node* ptr1;

Node* lptr = NULL;

do {

swapped = false;

ptr1 = head;

while (ptr1->next != lptr) {

if (ptr1->data > ptr1->next->data) {

swap(ptr1->data, ptr1->next->data);

swapped = true;
}

ptr1 = ptr1->next;

lptr = ptr1;

} while (swapped);

Comparison of Array-Based Lists vs Linked Lists

Feature Array-Based List Linked List

Memory Fixed size, pre-allocated Dynamic, grows as


needed

Insertio Costly (O(n) for shifting) Efficient at head/tail


n (O(1))

Deletio Costly (O(n) for shifting) Efficient at head/tail


n (O(1))

Search O(n) (Linear), O(1) (if O(n) (Linear)


sorted)

Access O(1) (Random access) O(n) (Sequential)


Single, Double, and Circular Linked Lists
A linked list is a dynamic data structure where elements (nodes) are connected using
pointers. Unlike arrays, linked lists can grow and shrink in size dynamically.

1. Singly Linked List (SLL)

A singly linked list consists of nodes where each node contains:

1.​ Data (the actual value)


2.​ Pointer (next) to the next node in the list

Structure of a Singly Linked List Node

struct Node {

int data;

Node* next;

};

Basic Operations on Singly Linked List

(a) Inserting at the beginning

void insertAtBeginning(Node** head, int newData) {

Node* newNode = new Node();

newNode->data = newData;

newNode->next = *head;

*head = newNode;

(b) Traversing a Singly Linked List

void printList(Node* head) {


Node* temp = head;

while (temp != NULL) {

cout << temp->data << " -> ";

temp = temp->next;

cout << "NULL\n";

2. Doubly Linked List (DLL)

A doubly linked list is similar to a singly linked list but has an additional pointer to the
previous node.​
Each node consists of:

1.​ Data (the actual value)


2.​ Pointer (next) to the next node
3.​ Pointer (prev) to the previous node

Structure of a Doubly Linked List Node

struct Node {

int data;

Node* next;

Node* prev;

};

Basic Operations on Doubly Linked List

(a) Inserting at the Beginning


void insertAtBeginning(Node** head, int newData) {

Node* newNode = new Node();

newNode->data = newData;

newNode->next = *head;

newNode->prev = NULL;

if (*head != NULL)

(*head)->prev = newNode;

*head = newNode;

(b) Traversing a Doubly Linked List

void printList(Node* head) {

Node* temp = head;

while (temp != NULL) {

cout << temp->data << " <-> ";

temp = temp->next;

cout << "NULL\n";

Advantages of DLL over SLL

Feature Singly Linked Doubly Linked List


List
Memory Usage Less (single More (extra pointer)
pointer)

Insertion/Deletio O(1) at the head O(1) at both head and


n tail

Reverse Not possible Possible (via prev


Traversal pointer)

3. Circular Linked List (CLL)

A circular linked list is a variation where:

●​ The last node's next pointer points back to the first node (for singly circular list).
●​ In a doubly circular list, the prev pointer of the first node points to the last node.

Structure of a Circular Linked List Node (Singly Circular)

struct Node {

int data;

Node* next;

};

Basic Operations on Circular Linked List

(a) Inserting at the End

void insertAtEnd(Node** head, int newData) {

Node* newNode = new Node();

newNode->data = newData;
newNode->next = *head;

if (*head == NULL) {

*head = newNode;

newNode->next = *head;

} else {

Node* temp = *head;

while (temp->next != *head)

temp = temp->next;

temp->next = newNode;

newNode->next = *head;

(b) Traversing a Circular Linked List

void printList(Node* head) {

if (head == NULL) return;

Node* temp = head;

do {

cout << temp->data << " -> ";

temp = temp->next;

} while (temp != head);

cout << "HEAD\n";


}

Advantages of Circular Linked Lists

●​ No NULL values; always loops back to the start.


●​ Useful for applications like round-robin scheduling, multiplayer games, and
buffer management.

Comparison of Linked Lists

Feature Singly Linked Doubly Linked Circular Linked List


List List

Memory Usage Less More Same as SLL/DLL

Traversal One direction Both directions Circular (loops back)

Insert/Delete at O(1) O(1) O(1)


Head

Insert/Delete at O(n) O(1) O(n) (O(1) with tail


Tail pointer)

Use Cases Basic lists, Complex Round-robin, buffering


stacks structures

Stacks and Stack ADT


A stack is a linear data structure that follows the LIFO (Last In, First Out) principle. It
allows operations at only one end, called the top of the stack.
Stack Abstract Data Type (Stack ADT)

The Stack ADT defines a collection of elements with the following operations:

1.​ Push (x) – Inserts an element xx at the top.


2.​ Pop () – Removes and returns the top element.
3.​ Peek (Top or TopValue) – Returns the top element without removing it.
4.​ isEmpty () – Checks if the stack is empty.
5.​ isFull () – Checks if the stack is full (in case of a fixed-size array
implementation).

Stack Manipulation
1. Implementation of Stack

Stacks can be implemented using:

●​ Arrays (Static Stack) – Fixed size, uses an array to store elements.


●​ Linked List (Dynamic Stack) – Uses dynamic memory allocation with nodes.

2. Stack Implementation Using Arrays

Structure of Stack (Array Implementation)

#define MAX 100

struct Stack {

int top;

int arr[MAX];

};

Basic Operations on Stack (Array Implementation)

(a) Initialize the Stack


void initStack(Stack* s) {

s->top = -1;

(b) Push Operation

void push(Stack* s, int value) {

if (s->top == MAX - 1) {

cout << "Stack Overflow\n";

return;

s->arr[++s->top] = value;

(c) Pop Operation

int pop(Stack* s) {

if (s->top == -1) {

cout << "Stack Underflow\n";

return -1;

return s->arr[s->top--];

(d) Peek (Top Element)


int peek(Stack* s) {

if (s->top == -1) {

cout << "Stack is Empty\n";

return -1;

return s->arr[s->top];

(e) Check if Stack is Empty

bool isEmpty(Stack* s) {

return s->top == -1;

3. Stack Implementation Using Linked List

A stack using a linked list allows dynamic memory allocation and avoids fixed-size
limitations.

Structure of a Stack Node (Linked List Implementation)

struct Node {

int data;

Node* next;

};

Basic Operations on Stack (Linked List Implementation)


(a) Push Operation (Insert at Head)

void push(Node** top, int value) {

Node* newNode = new Node();

newNode->data = value;

newNode->next = *top;

*top = newNode;

(b) Pop Operation (Delete from Head)

int pop(Node** top) {

if (*top == NULL) {

cout << "Stack Underflow\n";

return -1;

Node* temp = *top;

int popped = temp->data;

*top = (*top)->next;

delete temp;

return popped;

(c) Peek Operation

int peek(Node* top) {


if (top == NULL) {

cout << "Stack is Empty\n";

return -1;

return top->data;

4. Applications of Stacks

1.​ Expression Evaluation – Used in infix to postfix conversion and postfix


evaluation.
2.​ Undo/Redo Operations – Used in text editors and applications.
3.​ Backtracking – Used in algorithms like Maze Solving and Depth-First Search
(DFS).
4.​ Function Call Stack – Manages function calls and recursion in programming.
5.​ Balanced Parentheses Checking – Used in compilers and parsers.

Prefix, Infix, and Postfix Expressions


In mathematical expressions, operators can be placed in different positions relative to
their operands. The three common notations are infix, prefix, and postfix.

1. Infix Notation
Definition: The operator is placed between the operands.

Example:​
A+B

(A + B) * C

●​
●​ This is the standard way humans write expressions. However, computers find it
difficult to process because it requires operator precedence and parentheses.

Operator Precedence (PEMDAS Rule)

1.​ Parentheses ()
2.​ Exponents ^
3.​ Multiplication *, Division /
4.​ Addition +, Subtraction -

Example Evaluation:​
Expression: 3 + 5 * 2

1.​ Multiplication (5 * 2 = 10)


2.​ Addition (3 + 10 = 13)​
Result: 13

2. Prefix Notation (Polish Notation)


Definition: The operator is placed before the operands.

Example:​
+ A B → (A + B)

* + A B C → (A + B) * C

●​
●​ No need for parentheses because the order is clear.

Example Conversion from Infix to Prefix:

(A + B) * C

1.​ Identify the operator precedence:


○​ + has lower precedence than *

Convert:​
*+ABC
2.​

3. Postfix Notation (Reverse Polish Notation - RPN)


Definition: The operator is placed after the operands.

Example:​
A B + → (A + B)

A B + C * → (A + B) * C

●​
●​ No need for parentheses, and it is easier for computers to evaluate using
stacks.

Example Conversion from Infix to Postfix:

(A + B) * C

1.​ Identify the operator precedence:


○​ + has lower precedence than *

Convert:​
AB+C*

2.​

4. Converting Infix to Prefix & Postfix

Step-by-Step Conversion (Example: A + B * C)

1. Convert Infix to Prefix (+ A * B C)

●​ B * C is computed first.
●​ A + (result) follows.
●​ Prefix order: + A * B C
2. Convert Infix to Postfix (A B C * +)

●​ B * C is computed first.
●​ A + (result) follows.
●​ Postfix order: A B C * +

5. Evaluating Postfix Expressions Using Stack


To evaluate 3 4 + 2 *, follow these steps:

1.​ Push operands (3 and 4) onto the stack.


2.​ Encounter +, pop 3 and 4, compute 3 + 4 = 7, push 7.
3.​ Push operand 2.
4.​ Encounter *, pop 7 and 2, compute 7 * 2 = 14, push 14.
5.​ Final result: 14.

Postfix Evaluation Code (C++)

#include <iostream>

#include <stack>

using namespace std;

int evaluatePostfix(string expr) {

stack<int> s;

for (char ch : expr) {

if (isdigit(ch))

s.push(ch - '0'); // Convert char to int

else {

int val2 = s.top(); s.pop();


int val1 = s.top(); s.pop();

switch (ch) {

case '+': s.push(val1 + val2); break;

case '-': s.push(val1 - val2); break;

case '*': s.push(val1 * val2); break;

case '/': s.push(val1 / val2); break;

return s.top();

int main() {

string expr = "34+2*"; // Equivalent to (3+4) * 2

cout << "Result: " << evaluatePostfix(expr); // Output: 14

return 0;

6. Advantages of Prefix and Postfix Notations

Feature Infix Prefix Postfi


x
Readability Easy for Harde Harde
humans r r

Parentheses Yes No No
Required?

Stack-based Harder Easie Easie


Evaluation? r st

Used in Compilers? No Yes Yes

Recursion
Recursion is a programming technique where a function calls itself to solve a problem.
It is useful for problems that can be broken down into smaller, similar subproblems.

1. Basic Structure of Recursion

void recursiveFunction() {

if (base_condition)

return;

recursiveFunction(); // Recursive call

2. Example: Factorial using Recursion

int factorial(int n) {

if (n == 0) return 1; // Base Case

return n * factorial(n - 1);


}

Calling factorial(5) results in:

5 * factorial(4)

4 * factorial(3)

3 * factorial(2)

2 * factorial(1)

1 * factorial(0) -> returns 1

3. Types of Recursion

●​ Direct Recursion – A function calls itself directly.


●​ Indirect Recursion – Function A calls Function B, and Function B calls Function
A.
●​ Tail Recursion – Recursive call is the last operation in the function.
●​ Non-Tail Recursion – Further operations are performed after the recursive call.

4. Example: Fibonacci Using Recursion

int fibonacci(int n) {

if (n <= 1) return n;

return fibonacci(n - 1) + fibonacci(n - 2);

Queues and Queue ADT


A queue is a linear data structure that follows the FIFO (First In, First Out) principle.
●​ Elements are added at the rear and removed from the front.
●​ Used in scheduling, buffering, and breadth-first search (BFS).

Queue ADT (Abstract Data Type)

A queue supports the following operations:

1.​ Enqueue (x) – Inserts element x at the rear.


2.​ Dequeue () – Removes and returns the front element.
3.​ Front () – Returns the front element without removing it.
4.​ isEmpty () – Checks if the queue is empty.
5.​ isFull () – Checks if the queue is full (for array-based implementation).

Queue Manipulation
1. Queue Implementation Using Arrays

Structure of Queue

#define MAX 100

struct Queue {

int front, rear;

int arr[MAX];

};

Basic Queue Operations (Array Implementation)

(a) Initialize the Queue

void initQueue(Queue* q) {

q->front = q->rear = -1;

}
(b) Enqueue Operation

void enqueue(Queue* q, int value) {

if (q->rear == MAX - 1) {

cout << "Queue Overflow\n";

return;

if (q->front == -1) q->front = 0;

q->arr[++q->rear] = value;

(c) Dequeue Operation

int dequeue(Queue* q) {

if (q->front == -1 || q->front > q->rear) {

cout << "Queue Underflow\n";

return -1;

return q->arr[q->front++];

(d) Peek (Front Element)

int front(Queue* q) {

if (q->front == -1 || q->front > q->rear) return -1;

return q->arr[q->front];
}

2. Queue Implementation Using Linked List

Structure of Queue Node

struct Node {

int data;

Node* next;

};

Basic Queue Operations (Linked List Implementation)

(a) Enqueue Operation (Insert at Rear)

void enqueue(Node** front, Node** rear, int value) {

Node* newNode = new Node();

newNode->data = value;

newNode->next = NULL;

if (*rear == NULL) {

*front = *rear = newNode;

return;

(*rear)->next = newNode;

*rear = newNode;

}
(b) Dequeue Operation (Delete from Front)

int dequeue(Node** front, Node** rear) {

if (*front == NULL) {

cout << "Queue Underflow\n";

return -1;

Node* temp = *front;

int removedData = temp->data;

*front = (*front)->next;

if (*front == NULL) *rear = NULL;

delete temp;

return removedData;

Types of Queues
1.​ Simple Queue – Standard FIFO queue.
2.​ Circular Queue – Rear wraps around when it reaches the end of the array.
3.​ Priority Queue – Elements are dequeued based on priority.
4.​ Deque (Double-Ended Queue) – Insertion and deletion can occur from both
ends.

You might also like