Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
4 views

Data Structure and Algorithm

The document covers various data structures and algorithms, including arrays vs linked lists, recursion vs iteration, hash table collisions, tree traversals, and time complexity analysis. It provides C++ code implementations for Fibonacci sequences, stacks, binary search trees, graphs, merge sort, and the N-Queens problem, along with explanations of greedy algorithms and dynamic programming. Each section includes definitions, comparisons, and examples to illustrate key concepts.

Uploaded by

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

Data Structure and Algorithm

The document covers various data structures and algorithms, including arrays vs linked lists, recursion vs iteration, hash table collisions, tree traversals, and time complexity analysis. It provides C++ code implementations for Fibonacci sequences, stacks, binary search trees, graphs, merge sort, and the N-Queens problem, along with explanations of greedy algorithms and dynamic programming. Each section includes definitions, comparisons, and examples to illustrate key concepts.

Uploaded by

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

MICROLINK INFORMATION AND BUSINESS COLLAGE

DATA STRUCTURE AND ALGORITHM

INDIVIDUAL ASSIGNMENT

Name soliana adhanom

Id no mdr/1634/19

Department SE(R)

add
1. Explain the differences between arrays and linked lists in terms of memory allocation,
insertion/deletion efficiency, and traversal. Provide examples where each data structure is more suitable

ARRAY LINKED LIST

-Size of array is fixed -Size of list is not fixed

-Memory is allocated from stack -memory is allocated from heap

-It is necessary to specify the number -it is not necessary to specify z number

of elements during declaration of elements during declaration (memory

-It occupies less memory tgan linked list for is allocated during run time)

The same elements

-Inserting new elements at the front is potentially - inserting a new element at any posit-

Expensive because existing elements need to be on can be carried out easily

Shifted over to make room

-Deleting an element froman array is not possible -deleting an element is possible

-Use an array when you need fast access by index -use a linked list when u expect freqent
and know the size ahead of time inseriton/deletion (e.g implementing

(e.g., storing fixed-size data) a queue

2. Compare recursion and iteration. Write a C++ function to calculate the Fibonacci sequence using both
approaches and discuss their time/space complexities.

• Recursion involves a function calling itself to solve smaller instances of the problem. It can be more
elegant but may lead to stack overflow if too deep.

• Iteration uses loops to repeat a block of code until a condition is met. It is usually more memory-
efficient.

• Fibonacci Sequence Example:

// Recursive Fibonacci

int fibonacci_recursive(int n) {

if (n <= 1) return n;

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


}

// Iterative Fibonacci

int fibonacci_iterative(int n) {

if (n <= 1) return n;

int a = 0, b = 1;

for (int i = 2; i <= n; i++) {

int temp = a + b;

a = b;

b = temp;

return b;

• Time/Space Complexity:

• Recursive: O(2^n) time, O(n) space due to call stack.

• Iterative: O(n) time, O(1) space.

3. Define hash table collisions and explain two resolution strategies (e.g., chaining, open addressing).
Provide code snippets illustrating each method.

• A hash table collision occurs when two keys hash to the same index in the hash table.

• Resolution Strategies:

• Chaining: Each slot in the hash table contains a linked list of entries that hash to the same index.

struct Node {

int key;

Node* next;

};

class HashTable {

Node** table;
int size;

public:

HashTable(int s) : size(s) { table = new Node*[size](); }

void insert(int key) {

int index = key % size;

Node* newNode = new Node{key, table[index]};

table[index] = newNode;

};

• Open Addressing: All elements are stored in the array itself, and a probing sequence is used to find
an empty slot.

class HashTable {

int* table;

int size;

public:

HashTable(int s) : size(s) { table = new int[size](); }

void insert(int key) {

int index = key % size;

while (table[index] != 0) { // Assuming 0 means empty

index = (index + 1) % size; // Linear probing

table[index] = key;

};

4. Tree Traversals:
Describe in-order, pre-order, and post-order traversal algorithms for binary trees. Manually traverse the
following tree and list the nodes in each order:

A . In-order: Left, Root, Right .Post-order: Left, Right, Root

/\ . Manual Traversal of Given Tree: .Pre-order: Root, Left, Right

BC

/\\ In-order: D, B, E, A, C, F

DEF Pre-order: A, B, D, E, C, F

Post-order: D, E, B, F, C, A

5. Analyze the time complexity of the following code using Big-O notation:

for (int i = 0; i < n; i *= 2) {

for (int j = 0; j < i; j++) {

cout << j << endl;

• The outer loop runs as long as i is less than n. However, i starts at 0 and never changes due to i *= 2,
which means the loop never executes.

• Time complexity: O(1).

6. Implement a stack using a singly linked list in C++. Include push, pop, and peek operations. Handle
edge cases like underflow

struct Node

int data;

Node* next;

};

class Stack {

Node* top;

public:
Stack() : top(nullptr) {}

void push(int value) {

Node* newNode = new Node{value, top};

top = newNode;

void pop() {

if (top == nullptr) throw std::underflow_error("Stack Underflow");

Node* temp = top;

top = top->next;

delete temp;

int peek() {

if (top == nullptr) throw std::underflow_error("Stack Underflow");

return top->data;

};

7. Create a BST in C++ with functions to insert nodes, delete nodes, and search for a value. Demonstrate
inserting [15, 10, 20, 8, 12, 17, 25] and deleting the node with value 15.

struct TreeNode {

int value;

TreeNode* left;

TreeNode* right;

TreeNode(int val) : value(val), left(nullptr), right(nullptr) {}

};

class BST {

TreeNode* root;

void insert(TreeNode*& node, int value) {

if (!node) {
node = new TreeNode(value);

} else if (value < node->value) {

insert(node->left, value);

} else {

insert(node->right, value);

TreeNode* deleteNode(TreeNode* node, int value) {

if (!node) return nullptr;

if (value < node->value) {

node->left = deleteNode(node->left, value);

} else if (value > node->value) {

node->right = deleteNode(node->right, value);

} else {

// Node with one child or no child

if (!node->left) return node->right;

else if (!node->right) return node->left;

// Node with two children

TreeNode* minNode = node->right;

while (minNode && minNode->left) minNode = minNode->left;

node->value = minNode->value;

node->right = deleteNode(node->right, minNode->value);

return node;
}

public:

BST() : root(nullptr) {}

void insert(int value) { insert(root, value); }

void deleteValue(int value) { root = deleteNode(root, value); }

};

// Demonstration

BST tree;

int values[] = {15, 10, 20, 8, 12, 17, 25};

for (int val : values) tree.insert(val);

tree.deleteValue(15)

8. Implement a graph using an adjacency list in C++. Include functions to add edges and perform BFS
traversal starting from a given node.

#include <iostream>

#include <vector>

#include <queue>

class Graph {

std::vector<std::vector<int>> adjList;

public:

Graph(int vertices) : adjList(vertices) {}

void addEdge(int u, int v) {

adjList[u].push_back(v);

adjList[v].push_back(u); // For undirected graph

void BFS(int start) {


std::vector<bool> visited(adjList.size(), false);

std::queue<int> q;

visited[start] = true;

q.push(start);

while (!q.empty()) {

int node = q.front();

q.pop();

std::cout << node << " ";

for (int neighbor : adjList[node]) {

if (!visited[neighbor]) {

visited[neighbor] = true;

q.push(neighbor);

};

9. Write a C++ program to sort an array using the merge sort algorithm. Explain the divide-and-conquer
approach and provide a step-by-step breakdown for sorting [9, 3, 7, 5, 6, 4, 8, 2].

#include <iostream>

#include <vector>

void merge(std::vector<int>& arr, int left, int mid, int right) {

int n1 = mid - left + 1;

int n2 = right - mid;

std::vector<int> L(n1), R(n2)

for (int i = 0; i < n1; ++i)


L[i] = arr[left + i];

for (int j = 0; j < n2; ++j)

R[j] = arr[mid + 1 + j];

int i = 0, j = 0, k = left;

while (i < n1 && j < n2) {

if (L[i] <= R[j]) arr[k++] = L[i++];

else arr[k++] = R[j++];

while (i < n1) arr[k++] = L[i++];

while (j < n2) arr[k++] = R[j++];

void mergeSort(std::vector<int>& arr, int left, int right) {

if (left < right) {

int mid = left + (right - left) / 2;

mergeSort(arr, left, mid);

mergeSort(arr, mid + 1, right);

merge(arr, left, mid, right);

// Example usage:

int main() {

std::vector<int> arr = {9, 3, 7, 5, 6, 4, 8, 2};

mergeSort(arr, 0, arr.size() - 1);

for (int num : arr)

std::cout << num << " ";


return 0;

• Divide-and-Conquer Approach: Merge sort divides the array into halves recursively until single
elements are reached and then merges them back together in sorted order.

10.Design a C++ program to dynamically create a 2D array, populate it with user input, and deallocate
memory properly. Explain pointer arithmetic in your code.

#include <iostream>

int main() {

int rows, cols;

std::cout << "Enter number of rows and columns: ";

std::cin >> rows >> cols;

// Dynamically allocate a 2D array

int** array = new int*[rows];

for (int i = 0; i < rows; ++i)

array[i] = new int[cols];

// Populate the array with user input

for (int i = 0; i < rows; ++i)

for (int j = 0; j < cols; ++j)

std::cin >> array[i][j];

// Display the array

std::cout << "The entered array is:\n";

for (int i = 0; i < rows; ++i) {

for (int j = 0; j < cols; ++j)

std::cout << array[i][j] << " ";

std::cout << std::endl;

}
// Deallocate memory

for (int i = 0; i < rows; ++i)

delete[] array[i];

delete[] array;

return 0;

• Pointer Arithmetic: In C++, pointer arithmetic allows you to navigate through memory locations. For
example:

int *ptr = array[0]; // Points to the first row

11. Greedy Algorithm Application:

Solve the "Coin Change" problem using a greedy approach (e.g., coins = [1, 5, 10]). Explain why this
method works for the given denominations but fails for others like [1, 3, 4]

Problem Statement: Given a set of coin denominations, determine the minimum number of coins
needed to make a specific amount.

Example Denominations: coins = [1, 5, 10]

Greedy Approach:

1. Start with the largest denomination and use as many of those coins as possible without exceeding the
target amount.

2. Move to the next largest denomination and repeat until the amount is reached.

Why It Works for [1, 5, 10]:

• For any amount, you can always use the largest coin available first. This ensures that you minimize the
total number of coins used because larger denominations cover more value with fewer coins.

Example:

• To make 28:

• Use 2 coins of 10 (20)

• Use 1 coin of 5 (5)

• Use 3 coins of 1 (3)


• Total coins = 2 + 1 + 3 = 6

Why It Fails for [1, 3, 4]:

• Consider making 6 with denominations [1, 3, 4]: • Greedy would choose one 4 (leaving 2), then it
would need two 1s (total of 3 coins).

• The optimal solution is two 3s (total of 2 coins).

12. Backtracking: N-Queens Problem in C++

Implement the N-Queens problem using backtracking in C++. Print one valid configuration for a 4x4
chessboard and explain how backtracking avoids invalid placements Problem Statement: Place N queens
on an N x N chessboard such that no two queens threaten each other.

C++ Implementation:

#include <iostream>

#include <vector>

using namespace std;

bool isSafe(vector<vector<int>>& board, int row, int col, int N) {

for (int i = 0; i < col; i++) {

if (board[row][i] == 1) return false; // Check row

for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {

if (board[i][j] == 1) return false; // Check upper diagonal

for (int i = row, j = col; j >= 0 && i < N; i++, j--) {

if (board[i][j] == 1) return false; // Check lower diagonal

return true;

bool solveNQueensUtil(vector<vector<int>>& board, int col, int N) {

if (col >= N) return true;


for (int i = 0; i < N; i++) {

if (isSafe(board, i, col, N)) {

board[i][col] = 1; // Place queen

if (solveNQueensUtil(board, col + 1, N)) return true;

board[i][col] = 0; // Backtrack

return false;

void solveNQueens(int N) {

vector<vector<int>> board(N, vector<int>(N, 0));

if (!solveNQueensUtil(board, 0, N)) {

cout << "Solution does not exist" << endl;

return;

for (const auto& row : board) {

for (int val : row) cout << val << " ";

cout << endl;

int main() {

solveNQueens(4);

return 0;

Explanation:
• The function isSafe checks if placing a queen at a specific position is safe from attacks.

• The function solveNQueensUtil uses recursion to place queens in columns one by one.

• If placing a queen leads to a solution, it returns true; otherwise, it backtracks by removing the queen.

13. Dynamic Programming: Fibonacci Sequence

Use dynamic programming to solve the Fibonacci sequence problem. Compare the time complexity of
your solution with the recursive approach and explain memoization.

Problem Statement: Calculate the nth Fibonacci number.

#include <iostream>

#include <vector>

using namespace std;

int fibonacci(int n) {

vector<int> fib(n + 1);

fib[0] = 0;

fib[1] = 1;

for (int i = 2; i <= n; i++) {

fib[i] = fib[i - 1] + fib[i - 2];

return fib[n];

int main() {

int n = 10; // Example input

cout << "Fibonacci(" << n << ") = " << fibonacci(n) << endl;

return 0;

Comparison with Recursive Approach:

• Recursive Approach: O(2^n) time complexity due to repeated calculations.


• Dynamic Programming Approach: O(n) time complexity as it stores results of previous computations
(memoization).

Memoization Explanation:

• Memoization involves storing the results of expensive function calls and returning the cached result
when the same inputs occur again. This avoids redundant calculations and speeds up the execution
significantly.

14. Queue Using Stacks in C++

Design a queue using two stacks. Provide C++ code for enqueue and dequeue operations and analyze
the time complexity.
Designing a Queue Using Two Stacks:

#include <iostream>

#include <stack>

using namespace std;

class QueueUsingStacks {

private:

stack<int> stack1, stack2;

public:

void enqueue(int x) {

stack1.push(x);

int dequeue() {

if (stack2.empty()) {

while (!stack1.empty()) {

stack2.push(stack1.top());

stack1.pop();

}
if (stack2.empty()) throw runtime_error("Queue is empty");

int front = stack2.top();

stack2.pop();

return front;

};

int main() {

QueueUsingStacks q;

q.enqueue(1);

q.enqueue(2);

cout << q.dequeue() << endl; // Output: 1

q.enqueue(3);

cout << q.dequeue() << endl; // Output: 2

return 0;

Time Complexity Analysis:

• Enqueue Operation: O(1) since we just push onto stack1.

• Dequeue Operation: Amortized O(1). If stack2 is empty, we transfer elements from stack1 to stack2,
which takes O(n), but this happens infrequently.

15. Binary Search:

Write a C++ function to perform binary search on a sorted array. Test it with the input [2, 5, 8, 12, 16,
23, 38, 56] and search for 23. Explain why binary search requires a sorted dataset.

Binary Search Function:

#include <iostream>

using namespace std;

int binarySearch(int arr[], int size, int target) {


int left = 0, right = size - 1;

while (left <= right) {

int mid = left + (right - left) / 2;

if (arr[mid] == target) return mid;

else if (arr[mid] < target) left = mid + 1;

else right = mid - 1;

return -1; // Not found

int main() {

int arr[] = {2, 5, 8, 12, 16, 23, 38, 56};

int target = 23;

int result = binarySearch(arr, sizeof(arr)/sizeof(arr[0]), target);

if (result != -1)

cout << "Element found at index: " << result << endl;

else

cout << "Element not found" << endl;

return 0;

You might also like