Binary Search - Lower Bound,
Upper Bound
Recursion Intro
Dr. Mudasir Ganaie
Assistant Professor
Indian Institute of Technology Ropar
Punjab India
Dr. Mudasir Ganaie MASAI
Upper and Lower bound
• Upper and lower bounds in binary search determine the position
of an element in a sorted array.
• Lower Bound: The smallest index where the element could be
inserted without violating order.
• Upper Bound: The smallest index where an element greater than
the target appears.
Lower Bound
• The lower bound of an element x is the first occurrence of x in
a sorted array.
• If x is not present, it is the index where x could be inserted.
Example:
arr = [1, 2, 4, 4, 4, 6, 7]
Lower Bound of 4 → Index 2
Lower Bound of 5 → Index 5
Lower Bound: Implementation
int lowerBound(vector<int>& arr, int x) {
int left = 0, right = arr.size();
while (left < right) {
int mid = (left + right)/2;
if (x < =arr[mid])
right = mid;
else
left = mid + 1;
}
return left;
}
Upper Bound in Binary Search
• The upper bound of an element x is the index where an element
greater than x appears.
• If x is present multiple times, it returns the index after the last
occurrence.
Example:
arr = [1, 2, 4, 4, 4, 6, 7]
Upper Bound of 4 → Index 5
Upper Bound of 5 → Index 5
Upper Bound: Implementation
int upperBound(vector<int>& arr, int x) {
int left = 0, right = arr.size();
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] < x)
left = mid + 1;
else
right = mid;
}
return left;
}
Why Use Upper and Lower Bounds?
• Efficiently find the first and last occurrence of an element in
O(log n) time.
Introduction to Recursion,
Journey from Iteration to
Recursion
Dr. Mudasir Ganaie MASAI
Iteration
Repeated execution of a set of instructions
Iteration is a bit like a washing machine.
Just as a washing machine has a drum that goes round repeatedly doing the work of
washing the clothes, and a fixed bit that drives and controls it,
Iteration comes in two parts: some code that’s executed repeatedly, and other code that
controls this repeated execution.
There are often three different basic structures that languages give to let you use iteration –
while, for and foreach
Iteration is referred to as the process of repeating a computational or
mathematical method till its regulating circumstance becomes untrue.
Source:https://randomtechthoughts.blog/2021/11/26/recursion-and-iteration-an-introduction-to-iteration/
Dr. Mudasir Ganaie MASAI
Example - Factorial
int main()
{
// number n whose factorial needs to be find
int n = 5;
// initialize fact variable with 1
int fact = 1;
// loop calculating factorial
for (int i = 1; i <= n; i++) {
fact = fact * i;
}
// print the factorial of n
cout << "Factorial of " << n << " is " << fact << endl;
return 0;
}
Example - Fibonacci
A Fibonacci number is a series of numbers in which each Fibonacci number is obtained by adding the two preceding numbers.
It begins with 0 and 1
The sequence of Fibonacci numbers can be defined as:
int fib(int n) {
if (n <= 1)
return n;
int prev1 = 1, prev2 = 0;
int curr;
for (int i = 2; i <= n; i++) {
curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return curr;
}
int main() {
int n = 5;
cout << fib(n);
return 0;
}
Recursion
Imagine you have a large family tree, and you want to count how many people are descended
from someone on the tree. A way of counting this could be described as:
Number of descendants = Number of children they have + number of descendants each of their
children has
This is an example of recursion.
Note that number of descendants is on both sides of the equation.
Recursion means that the solution to a problem includes one or more copies of the problem
Source: https://randomtechthoughts.blog/2021/11/26/recursion-and-iteration-an-introduction-to-recursion/
Dr. Mudasir Ganaie MASAI
Recursion
● A recursive method is a method that calls itself
● There are two key requirements in order to make sure that
the recursion is successful:
○ With each call the problem should become smaller and simpler
○ The method will eventually should lead to a point where no longer call
itself- This point is called the base case
Dr. Mudasir Ganaie MASAI
Example - Factorial
int factorial(int n)
{
if (n == 0 || n == 1)
return 1;
return n * factorial(n - 1);
}
// Driver code
int main()
{
int num = 5;
cout << "Factorial of " << num << " is " << factorial(num) <<
endl;
return 0;
}
Example-Fibonnaci
int Fibonacci(int n){
// Base case: if n is 0 or 1, return n
if (n <= 1){
return n;
}
// Recursive case: sum of the two preceding Fibonacci numbers
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main(){
int n = 5;
int result = Fibonacci(n);
cout << result << endl;
return 0;
}
Why do we need recursion?
1. In general, recursion is best used for problems with a recursive structure, where a problem can be
broken down into smaller versions
2. Recursion also provides code redundancy, making code reading and maintenance much more
efficient.
3. Recursion is useful and required in many applications involving complex algorithms, such as tree
and graph traversal.
Dr. Mudasir Ganaie MASAI
Recursion vs Iteration
Iteration uses conditional loops such as a ‘for loop’ to run a set of instructions repeatedly
until the condition of the iteration statement becomes false.
While recursion, as we know keeps calling a function itself within its own code until it
meets the base case.
Dr. Mudasir Ganaie MASAI
Source: https://www.masaischool.com/blog/how-recursion-works-recursion-vs-iteration/
Dr. Mudasir Ganaie MASAI
Types of Recursion
Direct Recursion
In direct recursion, functions call themselves. This process comprises a
single-step recursive call by the function from within its own body.
int square(int x)
{ int main() {
if (x == 0) // implementation of square function
{ int input=30;
return x; cout << input<<"^2 = "<<square(input);
} return 0;
else }
{
return square(x-1) +
(2*x) - 1;
} Source:
https://www.masaischool.com/blog/how-recursion-works-recursion-vs-iteration/
}
Dr. Mudasir Ganaie MASAI
Indirect Recursion
Indirect recursion is where a function calls another function to call the original function.
This is a two-step recursive process as the function calls another function to make a
recursive call.
Let’s see how to print all the numbers between 15 to 27 using indirect or mutual recursion
int n=15; void foo2()
// declaring functions {
void foo1(void); if (n <= 27)
void foo2(void); {
cout<<n<<" "; // prints n
// defining recursive functions n++; // increments n by 1
void foo1() foo1(); // calls foo1()
{ }
if (n <= 27) else
{ return;
cout<<n<<" "; // prints n }
n++; // increments n by 1
foo2(); // calls foo2()
} The output would display this:
else
return;
} 15 16 17 18 19 20 21 22 23 24 25 26 27
Dr. Mudasir Ganaie MASAI
Tail Recursion
A recursive function is tail-recursive if the recursive call is the last
operation in the function (i.e., there is no computation or operation
performed after the recursive call).
def factorial(n, accumulator=1):
if n == 0:
return accumulator
return factorial(n - 1, n * accumulator) # Tail-recursive call
Dr. Mudasir Ganaie MASAI
Non Tail Recursion
A recursive function is non-tail-recursive if the recursive call is not the
last operation. Additional computations or operations are performed
after the recursive call
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1) # Non-tail-recursive call
Dr. Mudasir Ganaie MASAI
Feature Direct Recursion Indirect Recursion Tail Recursion Non-Tail Recursion
Definition Function calls Function calls another function, which Recursive call is the last Recursive call is not the
itself directly eventually calls the original function operation last operation
Stack Depends on Depends on implementation Constant (if TCO is Grows with each
Usage implementation supported) recursive call
Optimization No inherent No inherent optimization Can be optimized via Cannot be optimized for
optimization TCO constant stack space
Memory Depends on Depends on implementation High (if TCO is Low (risk of stack
Efficiency implementation supported) overflow)
Ease of Easy Harder (due to multiple functions) Easy Easy
Debugging
TCO: Tail Call Optimization
Dr. Mudasir Ganaie MASAI
END
Dr. Mudasir Ganaie MASAI