Unit 1
Unit 1
Types of Array
1.One-Dimensional Array: An Array with only one row of data elements is
known as a One-Dimensional Array. It is stored in ascending storage location.
2.Two-Dimensional Array: An Array consisting of multiple rows and columns
of data elements is called a Two-Dimensional Array. It is also known as a
Matrix.
3.Multidimensional Array: We can define Multidimensional Array as an Array
of Arrays. Multidimensional Arrays are not bounded to two indices or two
dimensions as they can include as many indices are per the need.
Array Operations
• Search
• Insert
• Delete
• Search Operation:
• In an unsorted array, the search operation can be
performed by linear traversal from the first element to
the last element
• Time Complexity: O(N)
• // C++ program to implement linear • int arr[] = { 12, 34, 10, 6, 40 };
• // search in unsorted array • int n = sizeof(arr) / sizeof(arr[0]);
• #include <bits/stdc++.h>
• using namespace std; • // Using a last element as search element
• int key = 40;
• // Function to implement search operation
• int findElement(int arr[], int n, int key) • // Function call
• { • int position = findElement(arr, n, key);
• int i;
• for (i = 0; i < n; i++) • if (position == -1)
• if (arr[i] == key) • cout << "Element not found";
• return i; • else
• • cout << "Element Found at Position: "
• // If the key is not found • << position + 1;
• return -1;
• } • return 0;
• }
• int main()
• {
Insert Operation:
• 1. Insert at the end:
• In an unsorted array, the insert operation is faster as
compared to a sorted array because we don’t have to
care about the position at which the element is to be
placed.
• #include <iostream> • int arr[20] = { 12, 16, 20, 40, 50, 70 };
• using namespace std; • int capacity = sizeof(arr) / sizeof(arr[0]);
• int n = 6;
• // Inserts a key in arr[] of given capacity. • int i, key = 26;
• // n is the current size of arr[]. This
• // function returns n + 1 if insertion • cout << "Before Insertion: ";
• // is successful, else n. • for (i = 0; i < n; i++)
• int insertSorted(int arr[], int n, int key, int capacity) • cout << arr[i] << " ";
• {
• // Cannot insert more elements if n is • // Inserting key
• // already more than or equal to capacity • n = insertSorted(arr, n, key, capacity);
• if (n >= capacity)
• return n; • cout << "\nAfter Insertion: ";
• for (i = 0; i < n; i++)
• arr[n] = key; • cout << arr[i] << " ";
• return (n + 1);
• } • return 0;
• }
• int main()
• {
• 2. Insert at any position
• Insert operation in an array at any position can be
performed by shifting elements to the right, which are
on the right side of the required position
• // C++ Program to Insert an element • int n = 5;
• // at a specific position in an Array
• cout<<"Before insertion : ";
• #include <bits/stdc++.h> • for (int i = 0; i < n; i++)
• using namespace std; • cout<<arr[i]<<" ";
• Solution:
• Address of A[8][6] = 100 + 1 * ((8 – 1) * 15 + (6 – 1))
• = 100 + 1 * ((7) * 15 + (5))
• = 100 + 1 * (110)
• Address of A[I][J] = 210
• Column Major Order
• Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
• I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in any array(in byte),
LR = Lower Limit of row/start row index of matrix(If not given
assume it as zero),
LC = Lower Limit of column/start column index of matrix(If not
given assume it as zero),
M = Number of rows given in the matrix.
• Given an array arr[1………10][1………15] with a base
value of 100 and the size of each element is 1 Byte in
memory find the address of arr[8][6] with the help of
column-major order.
• Formula: used
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
Address of A[8][6] = 100 + 1 * ((6 – 1) * 10 + (8 – 1))
= 100 + 1 * ((5) * 10 + (7))
= 100 + 1 * (57)
Address of A[I][J] = 157
• Given an array, arr[1………10][1………15] with base
value 1048 and the size of each element is 1 Byte in
memory. Find the address of arr[2][5] with the help of
row-major order and column-major order.
• Given an array, arr[-6………10][+4………15] with base
value 1048 and the size of each element is 1 Byte in
memory. Find the address of arr[2][5] with the help of
row-major order and column-major order.
Recursion
• The process in which a function calls itself directly or indirectly is
called recursion and the corresponding function is called a recursive
function. Using a recursive algorithm, certain problems can be solved
quite easily. Examples of such problems are Towers of Hanoi (TOH),
Inorder/Preorder/Postorder Tree Traversals, DFS of Graph, etc.
Properties of Recursion:
• Performing the same operations multiple times with
different inputs.
• In every step, we try smaller inputs to make the
problem smaller.
• Base condition is needed to stop the recursion
otherwise infinite loop will occur.
• int fact(int n)
•{
• if (n < = 1) // base case
• return 1;
• else
• return n*fact(n-1);
•}
• // An example of direct recursion • // An example of indirect recursion
• void directRecFun() • void indirectRecFun1()
• { • {
• // Some code.... • // Some code...
• directRecFun(); • indirectRecFun2();
• indirectRecFun1();
• // Some code...
• }
• // C++ code to implement Fibonacci series • }
• #include <bits/stdc++.h>
• using namespace std; • int main()
• // Function for fibonacci • {
• int fib(int n) • // Initialize variable n.
• { • int n = 5;
• // Stop condition • cout<<"Fibonacci series of 5 numbers is:
• if (n == 0) ";
• return 0;
• // for loop to print the fibonacci series.
• // Stop condition • for (int i = 0; i < n; i++)
• if (n == 1 || n == 2) • {
• return 1; • cout<<fib(i)<<" ";
• }
• // Recursion function • return 0;
• else • }
• return (fib(n - 1) + fib(n - 2));
Time complexity and space
complexity
• Upper bound, Lower bound, and Average case
• O(n2), n-?
Time complexity
• Sum =0;
• for(i=1; i<=n; i++)
•{
• Sum = Sum + 1;
•}
• Sum =0;
• for(i=1; i<=n; i = i+2)
•{
• Sum = Sum + 1;
•}
• Sum =0;
• for(i=1; i<=n, i = i*2)
•{
• Sum = Sum + 1;
•}
• Sum =0;
• for(i=1; i<=n, i++)
•{
• for(j=1; j<=n, j++)
• {
• Sum = Sum + j;
• }
•}
Asymptotic Analysis
• Worst case: It defines the input for which the
algorithm takes a huge time.
• Average case: It takes average time for the program
execution.
• Best case: It defines the input for which the algorithm
takes the lowest time
Asymptotic Notations
• The commonly used asymptotic notations used for
calculating the running time complexity of an algorithm
is given below:
• Big oh Notation (O)
• Omega Notation (Ω)
• Theta Notation (θ)
Big oh Notation (O)
• Big O notation is an asymptotic notation that measures
the performance of an algorithm by simply providing
the order of growth of the function.
• This notation provides an upper bound on a function
which ensures that the function never grows faster than
the upper bound. So, it gives the least upper bound on a
function so that the function never grows faster than
this upper bound.
• If f(n) and g(n) are the two functions defined for positive integers,
• then f(n) = O(g(n)) as f(n) is big oh of g(n) or f(n) is on the order of
g(n)) if there exists constants c and no such that:
• f(n)≤c.g(n) for all n≥no and c>0
• This implies that f(n) does not grow faster than g(n), or g(n) is an
upper bound on the function f(n). In this case, we are calculating the
growth rate of the function which eventually calculates the worst time
complexity of a function, i.e., how worst an algorithm can perform.
• Example 1: f(n)=2n+3 , g(n)=n
• Now, we have to find Is f(n)=O(g(n))?
• To check f(n)=O(g(n)), it must satisfy the given condition:
• f(n)<=c.g(n)
• First, we will replace f(n) by 2n+3 and g(n) by n.
• 2n+3 <= c.n
• Let's assume c=5, n=1 then
• 2*1+3<=5*1
• 5<=5
• For n=1, the above condition is true.
• If n=2
• 2*2+3<=5*2
• 7<=10
• For n=2, the above condition is true.
Omega Notation (Ω)
• It basically describes the best-case scenario which is
opposite to the big O notation.
• It is the formal way to represent the lower bound of an
algorithm's running time. It measures the best amount
of time an algorithm can possibly take to complete or
the best-case time complexity.
• It determines what is the fastest time that an algorithm
can run.
• If f(n) and g(n) are the two functions defined for
positive integers,
• then f(n) = Ω (g(n)) as f(n) is Omega of g(n) or f(n)
is on the order of g(n)) if there exists constants c and no
such that:
• f(n)>=c.g(n) for all n≥no and c>0
• If f(n) = 2n+3, g(n) = n,
• Is f(n)= Ω (g(n))?
• It must satisfy the condition:
• f(n)>=c.g(n)
• To check the above condition, we first replace f(n) by 2n+3 and
g(n) by n.
• 2n+3>=c*n
• Suppose c=1
• 2n+3>=n (This equation will be true for any value of n starting
from 1).
• Therefore, it is proved that g(n) is big omega of 2n+3 function.
Theta Notation (θ)
• The theta notation mainly describes the average case
scenarios.
• It represents the realistic time complexity of an
algorithm. Every time, an algorithm does not perform
worst or best, in real-world problems, algorithms mainly
fluctuate between the worst-case and best-case, and
this gives us the average case of the algorithm.
• Big theta is mainly used when the value of worst-case
and the best-case is same.
• It is the formal way to express both the upper bound
and lower bound of an algorithm running time.
• Let f(n) and g(n) be the functions of n where n is the steps required to execute
the program then:
• f(n)= θg(n)
• The above condition is satisfied only if when
• c1.g(n)<=f(n)<=c2.g(n)
• where the function is bounded by two limits, i.e., upper and lower limit, and
f(n) comes in between. The condition f(n)= θg(n) will be true if and only if
c1.g(n) is less than or equal to f(n) and c2.g(n) is greater than or equal to f(n).
• Let's consider the same example where
f(n)=2n+3
g(n)=n
Linked List:
• Data Structure: Non-contiguous
• Memory Allocation: Dynamic
• Insertion/Deletion: Efficient
• Access: Sequential
Array:
• Data Structure: Contiguous
• Memory Allocation: Static
• Insertion/Deletion: Inefficient
• Access: Random
Operations of Linked Lists:
Insertion in Linked List
• At the front of the linked list
• After a given node.
• At the end of the linked list.
Insert a Node at the Front/Beginning
of Linked List
To insert a node at the start/beginning/front of a Linked
List, we need to:
• Make the first node of Linked List linked to the new node
• Remove the head from the original first node of Linked
List
• Make the new node as the Head of the Linked List.
• #include <bits/stdc++.h> • while (node != NULL) {
• using namespace std; • cout << " " << node->data;
• class Node { • node = node->next;
• public: • }
• int data; • cout << "\n";
• Node* next; • }
• }; • int main()
• {
• void insertAtFront(Node** head_ref, int new_data) •
• { • Node* head = NULL;
• // 1. allocate node • insertAtFront(&head, 1);
• Node* new_node = new Node(); • insertAtFront(&head, 2);
• // 2. put in the data • insertAtFront(&head, 3);
• new_node->data = new_data; • insertAtFront(&head, 4);
• // 3. Make next of new node as head • insertAtFront(&head, 5);
• new_node->next = (*head_ref); • insertAtFront(&head, 6);
• // 4. move the head to point • cout << "After inserting Nodes at their front: ";
• // to the new node • // The nodes will be : 6 5 4 3 2 1
• (*head_ref) = new_node; • printList(head);
• }
• return 0;
• void printList(Node* node) • }
• {
Insert Node at the End of a
Linked List
• #include <bits/stdc++.h>
• using namespace std; • // Set the next pointer of the new
• class Node { node as NULL since it
• public: • // will be the last node
• int data; • new_node->next = NULL;
• Node* next;
• }; • // If the Linked List is empty, make
the new node as the
• void append(Node** head_ref, int
new_data) • // head and return
• { • if (*head_ref == NULL) {
• // Create a new node • *head_ref = new_node;
• Node* new_node = new Node(); • return;
• new_node->data = new_data; • }
• // Store the head reference in a • // Else traverse till the last node
temporary variable • while (last->next != NULL) {
• Node* last = *head_ref; • last = last->next;
• } • insertAtFront(&head, 5);
• insertAtFront(&head, 4);
• last->next = new_node; • insertAtFront(&head, 3);
• } • insertAtFront(&head, 2);
• void printList(Node* node)
• { • cout << "Created Linked list is: ";
• while (node != NULL) { • printList(head);
• cout << " " << node->data; • // Insert 1 at the end
• node = node->next; • append(&head, 1);
• } • cout << "\nAfter inserting 1 at the
• } end: ";
• int main() • printList(head);
• {
• Node* head = NULL; • return 0;
• insertAtFront(&head, 6); • }
Insert a Node after a given Node
in Linked List
• // C++ program to show inserting a node • return;
• // after a given node in given Linked List • }
• #include <bits/stdc++.h>
• using namespace std; • // 2. allocate new node
• Node* new_node = new Node();
• // A linked list node
• class Node { • // 3. put in the data
• public: • new_node->data = new_data;
• int data;
• Node* next; • // 4. Make next of new node
• }; • // as next of prev_node
• new_node->next = prev_node->next;
• // Given a node prev_node, insert a new
• // node after the given prev_node • // 5. move the next of prev_node
• void insertAfter(Node* prev_node, int new_data) • // as new_node
• { • prev_node->next = new_node;
• // 1. check if the given prev_node • }
• // is NULL
• if (prev_node == NULL) { • // Function to insert element in LL
• cout << "The given previous node cannot be • void push(Node** head_ref, int new_data)
NULL"; • {
• Node* new_node = new Node(); • Node* head = NULL;
• new_node->data = new_data;
• new_node->next = (*head_ref); • push(&head, 6);
• (*head_ref) = new_node; • push(&head, 5);
• } • push(&head, 4);
• push(&head, 3);
• // This function prints contents of • push(&head, 2);
• // linked list starting from head
• void printList(Node* node) • cout << "Created Linked list is: ";
• { • printList(head);
• while (node != NULL) {
• cout << " " << node->data; • // Insert 1 at the beginning.
• node = node->next; • insertAfter(head, 1);
• }
• cout << "\n"; • cout << "After inserting 1 after 2: ";
• } • printList(head);
• // Print newline after traversal • cout << "Backward Traversal:" << endl;
• cout << endl; • backwardTraversal(third);
• }
• // Free memory allocated for nodes
• int main() { • delete head;
• // Sample usage of the doubly linked list and • delete second;
traversal functions • delete third;
• Node* head = new Node(1);
• Node* second = new Node(2); • return 0;
• Node* third = new Node(3); • }
Finding Length of Doubly Linked
List:
•?
Insertion at the Beginning in
Doubly Linked List:
• // Define the structure for a node in the value) {
doubly linked list • // Create a new node
• struct Node { • Node* newNode = new Node;
• int data; // Data stored in the node
• Node* next; // Pointer to the next node • // Assign the value to the new node
• Node* prev; // Pointer to the previous • newNode->data = value;
node
• };
• // Since it's the first node, the previous
pointer is NULL
• // Function to insert a node at the • newNode->prev = NULL;
beginning of the list
• void insertBeginning(Node*& head, int
• // If the list is empty, make the new node the • newNode->next = head;
head
• if (head == NULL) { • // Update the previous pointer of the
• // Since it's the only node, the next pointer current head to point to the new node
is NULL • head->prev = newNode;
• newNode->next = NULL;
• // Update the head pointer to point to the
• // Update the head pointer to point to the new node
new node • head = newNode;
• head = newNode; • }
• } else { • }
• // Point the new node's next pointer to the
current head
Insertion at the End of Doubly
Linked List
• #include <iostream> • newNode->next = NULL;
• // Define the structure for a node in the doubly linked • // Check if the list is empty
list • if (head == NULL) {
• struct Node { • // If the list is empty, make the new node the
• int data; // Data stored in the node head
• Node* next; // Pointer to the next node • newNode->prev = NULL;
• Node* prev; // Pointer to the previous node
• }; • // Update the head pointer to point to the new
node
• // Function to insert a node at the end of the list • head = newNode;
• void insertEnd(Node*& head, int value) { • } else {
• // Create a new node • // Start from the head of the list
• Node* newNode = new Node; • Node* current = head;
• // Assign the value to the new node • // Traverse to the end of the list
• newNode->data = value; • while (current->next != NULL) {
• current = current->next;
• // Initialize the next pointer to NULL • }
• insertEnd(head, 3);
• // Adjust pointers to insert the new node at the
end • // Print the list
• // Set the next pointer of the last node to point to • Node* current = head;
• // the new node • while (current != NULL) {
• current->next = newNode; • std::cout << current->data << " ";
• current = current->next;
• // Set the previous pointer of the new node to • }
point
• // to the last node
• // Delete dynamically allocated memory
• newNode->prev = current;
• while (head != NULL) {
• }
• Node* temp = head;
• }
• head = head->next;
• delete temp;
• int main() {
• }
• // Test the insertEnd function
• Node* head = NULL;
• return 0;
• insertEnd(head, 1);
• }
• insertEnd(head, 2);
Insertion at a Specific Position
of Doubly Linked List
•?
Deletion at the Beginning of
doubly linked list
Deletion at the End on doubly linked list:
Deletion at a Specific Position in
doubly linked list
Circular Linked List
• The circular linked list is a linked list where all nodes
are connected to form a circle. In a circular linked list,
the first node and the last node are connected to each
other which forms a circle. There is no NULL at the end.