Download as pdf or txt
Download as pdf or txt
You are on page 1of 44

CSE 123 – Data Structures & Algorithms

Dr. Amr El Masry | Dr. Nawal El Boghdady


[email protected] | [email protected]

WEEK 1: INTRODUCTION TO DATA STRUCTURES AND ALGORITHMS


Outline
Course Introduction & Class Logistics

Review on Linked Lists, Stacks, and Queues in C++

Complexity analysis and asymptotic notation

Time & Space Complexity


Introduction &
Class Logistics
Introduction to Data Structures
and Algorithms
•This lecture will cover the fundamentals of data structures and algorithms in the C++
programming language

•Topics will include:


• Data structures such as arrays, linked lists, stacks, queues, trees, and graphs
• Algorithms such as sorting, searching, and graph algorithms

•Understanding data structures and algorithms is crucial for writing efficient and effective code
Data Structures
Definitions
•Data structures are the way in which data is organized and stored in a computer

•Different types of data structures are better suited for different types of tasks

•Some common data structures include:


• Arrays
• Linked Lists
• Stacks
• Queues
• Trees
• Graphs

5
Example (adjacent African
countries)
Problem
Definition: adjacency: if two countries share a boundary, the two countries are adjacent.

Given a country X, print a country Z that is not adjacent to X, but is adjacent to a country Y
adjacent to X.
◦ for example,
◦ Input: Egypt
◦ Output: Algeria, Niger, Chad, South Sudan, Central African Republic, Ethiopia, Eritria

7
Come up with Data Structures
Suppose you have only the following information
◦ for each country x, the list of countries that are adjacent to country x.
◦ for example,
◦ Egypt: Libya, Sudan

How are you going to store this adjacency information to solve the problem efficiently?

8
Lessons

Different data structures lead to different


ways to solve a given problem. (algorithms).

Different algorithms may give different


efficiency (space and time).
Data Structures in
a Glance
Arrays
Arrays are a collection of elements stored in contiguous memory locations

In C++, arrays can be declared as follows:

int arr[10]; // Declares an array of 10 integers

Arrays have a fixed size, which must be specified at the time of declaration

Elements of an array can be accessed using their index, which starts at 0


Linked Lists
Linked lists are a collection of nodes, where each node contains a value and a pointer to the next node

In C++, linked lists can be implemented using a struct:

struct Node {

int data;

Node* next;

};

Linked lists have dynamic size, as nodes can be added or removed at any time

Linked lists have a faster insertion and deletion time compared to arrays

12
Stacks

•Stacks are a type of data structure that


follows the Last-In-First-Out (LIFO) principle

•In C++, stacks can be implemented using an


array or a linked list

•Operations performed on stacks include


push (inserting an element), pop (removing
an element), and top (retrieving the top
element)
Queues

•Queues are a type of data structure that


follows the First-In-First-Out (FIFO) principle

•In C++, queues can be implemented using


an array or a linked list

•Operations performed on queues include


enqueue (inserting an element), dequeue
(removing an element), and front (retrieving
the front element)
Trees
• Trees are a type of data structure that represents a hierarchical structure

• There are several types of trees, including binary trees and AVL (self-balancing binary search tree) trees

• Trees are commonly used for searching and sorting algorithms

• In C++, trees can be implemented using a struct:

struct Node {

int data;

Node* left;

Node* right;

};

15
Graphs

Graphs are a collection of nodes and edges


that represent relationships between objects

In C++, graphs can be implemented using


an adjacency list or an adjacency matrix

Graphs are used to model real-world


situations, such as transportation networks
and social networks
Source: https://jcheshire.com/featured-maps/mapping-worlds-biggest-airlines/
Graphs
Not only physical connectivity, but also logical relationship.

17
Algorithms
•Algorithms can be divided into two types:
• Sorting algorithms, which rearrange elements in a specific order
• Searching algorithms, which search for a specific element in a data structure

•Algorithms can also be used on graphs, including graph traversal algorithms and graph search
algorithms

•Understanding algorithms is crucial for writing efficient and effective code

18
C++ versus C
C vs C++
In CSE 121, we focused on C

Here, we are using C++

They are quite similar, with only a few differences:


#include <stdio.h> #include <iostream>
using namespace std;
// C Structure for a linked list node
struct Node { // C++ Structure for a linked list node
int data; struct Node {
struct Node* next; int data;
}; Node* next;
};
// Function to insert a node at the front of the list
void insert_at_front(struct Node** head_ref, int new_data) { // Function to insert a node at the front of the list
struct Node* new_node = (struct Node*) malloc(sizeof(struct void insert_at_front(Node*& head, int new_data) {
Node)); Node* new_node = new Node();
new_node->data = new_data; new_node->data = new_data;
new_node->next = (*head_ref); new_node->next = head;
(*head_ref) = new_node; head = new_node;
} }

// Function to print the elements of the linked list // Function to print the elements of the linked list
void print_list(struct Node* node) { void print_list(Node* node) {
while (node != NULL) { while (node != NULL) {
printf("%d ", node->data); cout << node->data << " ";
node = node->next; node = node->next;
} }
} }

int main() { int main() {


struct Node* head = NULL; Node* head = NULL;
insert_at_front(&head, 1); insert_at_front(head, 1);
insert_at_front(&head, 2); insert_at_front(head, 2);
insert_at_front(&head, 3); insert_at_front(head, 3);
print_list(head); print_list(head);
return 0; return 0;
} }
Review on Linked
Lists in C++
Linked Lists in C++: Declaration
& Insertion
In C++, linked lists can be implemented using a struct:

struct Node {

int data;

Node* next;

};

To insert a new node at the beginning of the linked list, we can use the following code:

Node* head = NULL; // Initialize head to NULL

Node* newNode = new Node(); // Create a new node

newNode->data = 5; // Assign data to the new node

newNode->next = head; // Point the next pointer of the new node to head

head = newNode; // Update head to point to the new node


Linked List Traversal
•To traverse a linked list, we can use the following code:

Node* temp = head; // Initialize a temp pointer to head

while (temp != NULL) {

cout << temp->data << " "; // Print the data of the current node

temp = temp->next; // Move temp to the next node

•The above code will print the data of all the nodes in the linked list
Linked List Deletion
To delete a node from the linked list, we can use the // Find the node to be deleted
following code:
while (temp != NULL && temp->data !=
Node* temp = head; // Initialize a temp key) {
pointer to head
prev = temp;
Node* prev = NULL; // Initialize a prev
pointer to NULL temp = temp->next;

int key = // Data of the node to be }


deleted
// If the node is found, delete it
if (temp != NULL) {
prev->next = temp->next;
delete temp;
}
Linked List Reverse
To reverse a linked list, we can use the following code: while (current != NULL) {

next = current->next;

Node* prev = NULL; current->next = prev;

Node* current = head; prev = current;

Node* next = NULL; current = next;

head = prev;

The above code will reverse the linked list and update the head pointer to the new first node.
Stacks in C++
Stack Declaration and Push
Operation
In C++, a stack can be implemented using an array or a linked list

Here's an example of stack implementation using an array:

#define MAX 100 // Maximum size of stack


int top = -1; // Initialize top to -1
int stack[MAX]; // Array to store stack elements

void push(int item) {


if (top == MAX - 1) { // If stack is full, show overflow error
cout << "Stack overflow" << endl;
return;
}
top++; // Increment top
stack[top] = item; // Add item to the top of the stack
}
Stack Pop Operation
The pop operation removes the top item from the stack

Here's an example of the pop operation in C++:

int pop() {
if (top == -1) { // If stack is empty, show underflow error
cout << "Stack underflow" << endl;
return -1;
}
int item = stack[top]; // Store the top item
top--; // Decrement top
return item; // Return the removed item
}
Peek Operation
The peek operation returns the top item of the stack without removing it

Here's an example of the peek operation in C++:

int peek() {
if (top == -1) { // If stack is empty, show underflow error
cout << "Stack underflow" << endl;
return -1;
}
return stack[top]; // Return the top item
}
Stack Traversal
void display() {
if (top == -1) { // If stack is empty, show underflow error
cout << "Stack is empty" << endl;
return;
}
cout << "Stack elements: ";
for (int i = 0; i <= top; i++) { // Loop through stack elements
cout << stack[i] << " ";
}
cout << endl;
}
Queues in C++
Queue Declaration & Enqueue
Operation
In C++, a queue can be implemented using an array or a linked list

Here's an example of queue implementation using an array:


#define MAX 100 // Maximum size of queue
int front = 0, rear = -1; // Initialize front and rear to 0 and -1
respectively
int queue[MAX]; // Array to store queue elements

void enqueue(int item) {


if (rear == MAX - 1) { // If queue is full, show overflow error
cout << "Queue overflow" << endl;
return;
}
rear++; // Increment rear
queue[rear] = item; // Add item to the rear of the queue
}
Dequeue Operation
The dequeue operation removes the front item from the queue

Here's an example of the dequeue operation in C++:

int dequeue() {
if (front > rear) { // If queue is empty, show underflow error
cout << "Queue underflow" << endl;
return -1;
}
int item = queue[front]; // Store the front item
front++; // Increment front
return item; // Return the removed item
}
Peek Operation
The peek operation returns the front item of the queue without removing it

Here's an example of the peek operation in C++:

int peek() {
if (front > rear) { // If queue is empty, show underflow error
cout << "Queue is empty" << endl;
return -1;
}
return queue[front]; // Return the front item
}
Queue Traversal
To traverse a queue, we can use the following code:

void display() {
if (front > rear) { // If queue is empty, show underflow error
cout << "Queue is empty" << endl;
return;
}
cout << "Queue elements: ";
for (int i = front; i <= rear; i++) { // Loop through queue
elements
cout << queue[i] << " ";
}
cout << endl;
}
Complexity Analysis
& Asymptotic
Notation
Introduction to Algorithm
Complexity
•Algorithm complexity refers to the amount of resources (such as time and memory) an algorithm
requires to solve a problem of a given size.

•The goal of algorithm complexity analysis is to compare the relative efficiency of different
algorithms for the same problem.
Time Complexity
•Time complexity of an algorithm is the amount of time it takes to run as a function of the size of
the input.

•In C++, the time complexity of an algorithm is often measured in terms of the number of basic
operations (such as additions, multiplications, and comparisons) performed by the algorithm.
Asymptotic Notation
•Asymptotic notation is a way of expressing the behavior of an algorithm in terms of the size of the
input, as it approaches infinity.

•Three common asymptotic notations used in algorithm analysis are O, Ω, and Θ.


O Notation
•The O notation provides an upper bound on the growth rate of a function.

•It describes an algorithm's worst-case time complexity, meaning the maximum time the algorithm
could take for any input size.

•For example, the time complexity of a linear search algorithm is O(n), meaning that the algorithm
takes proportional to the size of the input (n).
Ω and Θ Notation
•The Ω notation provides a lower bound on the growth rate of a function.

•It describes an algorithm's best-case time complexity, meaning the minimum time the algorithm
could take for any input size.

•The Θ notation is used to describe an algorithm's average-case time complexity, and it provides a
tight bound on the growth rate of a function. It means that the algorithm's performance is within a
constant factor of the lower and upper bounds.
Space Complexity of Algorithms
•Space complexity of an algorithm refers to the amount of memory it uses as a function of the size
of the input.

•The goal of analyzing space complexity is to compare the relative efficiency of different algorithms
in terms of their memory usage.
Calculating Space Complexity
•Space complexity can be calculated by considering the amount of memory required by the
algorithm for different parts of the computation, such as:
• Memory required to store input data
• Memory required to store intermediate results
• Memory required for function calls

•The space complexity of an algorithm is usually expressed in terms of the size of the input (n) and
is usually denoted by O(f(n)), where f(n) is a function that describes the memory usage of the
algorithm.

You might also like