


Sorting Algorithms || Python || Data Structures and Algorithms
Dec 18, 2024 am 09:08 AMSorting Algoritms
1. Bubble Sort
In this, we swap the higher element with its neighbor until we reach the end of the array. Now the highest element is at the last position. So, we change the boundary and decrease it by 1 from the last. At worst, we have to iterate n times to sort the array.
def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False # Last i elements are already in place, so we don't need to check them for j in range(0, n-i-1): # Swap if the element found is greater than the next element if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] # Swap the elements swapped = True if not swapped: break return arr
Algorithm -
- Iterate over the array and find the maximum element, then swap it to the last.
- Compare all adjacent elements and swap if the larger element is before the smaller element. Keep doing this until you reach the end of the array.
- Maintain a flag: if no element is swapped, then we can break the loop, as the array is sorted.
Time Complexity :
- Best Case - If the array is already sorted then only one iteration is required. Time complexity - O(n)
- Average Case - if the array is randomly sorted, then time complexity - O(n2)
- Worst Case - if the array is in decreasing order then we will require n*2 iterations.
Space Complexity - O(1), No extra memory required.
Advantages -
No extra memory required.
Stable as elements keep their relative order.
Disadvantages -
- Time complexity - O(n2), which is very high for large datasets.
Applications-
- Can be used only when the dataset is very small, and simplicity outweighs inefficiency due to high time complexity.
2. Selection Sort
In this, we find the smallest element in the array and replace it with the first element. Then, we increase the boundary by 1 and repeat the same steps until we reach the end of the array.
def selectionSort(a): i = 0 while i<len(a): smallest = min(a[i:]) index_of_smallest = a.index(smallest) a[i],a[index_of_smallest] = a[index_of_smallest],a[i] i=i+1 return a
Algorithm -
Iterate over the array and find the minimum element.
Swap it with the first element and increase the pointer by 1.
Repeat this process until we reach the end of the array.
Time Complexity : It has a time complexity of O(n2) in all three cases: best, average, and worst. This is because we have to select the minimum element and swap it every time, regardless of whether the array is already sorted or not.
Space Complexity - O(1), No extra memory required.
Advantages -
No extra memory required.
Fewer swaps are done than in bubble sort.
Disadvantages -
Time complexity - O(n2), which is very high for large datasets.
Not Stable, as it does not maintain the relative order of equal elements.
Applications -
It can be used in systems with limited memory as it does not require additional storage.
It is used in systems where minimizing the number of swaps is critical, such as in systems with slow write operations.
3. Insertion Sort
It is an algorithm that works by inserting an unsorted element into its correct position by iteratively checking backwards from the position of the element to the start of the array.
def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False # Last i elements are already in place, so we don't need to check them for j in range(0, n-i-1): # Swap if the element found is greater than the next element if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] # Swap the elements swapped = True if not swapped: break return arr
Algorithm -
Start from the second element of the array and compare it with the first element. If the current element is smaller than the first element, then swap them.
Now increase the pointer and do this for the third element: compare it with the second and first elements.
Repeat the same process for the rest of the elements, comparing them with all the previous elements, and insert them at the suitable position.
Time Complexity :
- Best Case - If the array is already sorted then only one iteration is required. Time complexity is O(n)
- Average Case - if the array is randomly sorted, then time complexity is O(n2)
- Worst Case - if the array is in decreasing order then we will require n2 iterations.
Space Complexity - O(1), No extra memory required.
Advantages -
- No extra memory required.
- Stable, as elements keep their relative order.
Disadvantages -
Time complexity - O(n2), which is very high for large datasets.
Not Stable, as it does not maintain the relative order of equal elements.
Applications-
- It is efficient for scenarios where elements arrive in real-time and need to be sorted, such as live data streams.
4. Merge Sort
Merge Sort is an algorithm that follows the divide and conquer approach. It has two main steps: first, dividing the array recursively, and second, merging the divided arrays in sorted order.
def selectionSort(a): i = 0 while i<len(a): smallest = min(a[i:]) index_of_smallest = a.index(smallest) a[i],a[index_of_smallest] = a[index_of_smallest],a[i] i=i+1 return a
Algorithm -
Divide the array into two halves by calculating the mid-point.
Continue dividing until the length of each sub-array is 1.
Call the merge function on both halves: the left half and the right half.
Use three pointers for the merging process:
- The first pointer for the left half array.
- The second pointer for the right half array.
- The third pointer for the sorted array.
Iterate through both halves and compare their elements. Insert the smaller element into the sorted array and increment the corresponding pointer by 1.
Repeat this process recursively until the entire array is sorted.
Time Complexity : Merge Sort has a time complexity of O(n log n) in all three cases: best, average, and worst. This is because, irrespective of whether the array is already sorted or not, the same steps are followed for each division and merge.
O( log n ) - The array size is halved at each step during the divide phase.
O(n) - During merging process we have to iterate over all the elements once.
So the total time complexity is O (n) * O (log n) = O (n log n)
Space Complexity - O(n), Extra memory is required during the merging process to store the temporary arrays.
Advantages -
Stable, as elements keep their relative order.
Time complexity is O (n log n), even for the large datasets.
Suitable for parallel processing because sub-arrays are merged independently.
Disadvantages -
- Time complexity - O(n2), which is very high for large datasets.
- Extra memory is required for the merging process.
- Not in place as extra memory is required.
- Slower than QuickSort in general for most datasets.
Applications -
- It is used in situations where data is too large to fit in memory, such as merging large files.
- It is used to sort linked lists because random access is not required.
5. Quick Sort
Quick Sort is an algorithm that follows the divide-and-conquer approach. We choose a pivot element and partition the array around the pivot element after placing the pivot in its correct sorted position.
The first step is to choose the pivot element and then partition the array around the pivot. All elements smaller than the pivot will be on the left, and all elements greater than the pivot will be on its right. The pivot is then in its correct sorted position. Recursively, the same process is applied by dividing the array into two halves: the first half contains the elements before the pivot, and the second half contains the elements after the pivot. This process is repeated until the length of each sub-array reaches 1.
def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False # Last i elements are already in place, so we don't need to check them for j in range(0, n-i-1): # Swap if the element found is greater than the next element if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] # Swap the elements swapped = True if not swapped: break return arr
Algorithm -
- Divide the array into two halves by calculating the mid-point.
- Choose a pivot; any element can be chosen as the pivot.
- Iterate over the array and compare each element with the pivot.
- Place all elements smaller than the pivot on the left and all elements greater than the pivot on the right.
- Swap the pivot with the left pointer so that the pivot is at its correct sorted position.
- Repeat this process recursively until the length of the partition is greater than 1.
Time Complexity :
1. Best Case - Time complexity - O(n log n), when the pivot divides the array into two equal halves.
2. Average Case - Time complexity - O(n log n), when the pivot divides the array into two equal halves. But not necessarily equal.
3. Worst case - Time complexity - O(n2) , When -
The smallest element is chosen as the pivot in an already sorted array.
The largest element is chosen as the pivot in an array sorted in decreasing order.
O( log n ) - The array size is halved at each step during the divide phase.
O(n) - During the ordering of elements.
So, the total time complexity is O (n) * O (log n) = O (n log n)
Space Complexity :
Best and Average case - O( log n) - for the recursive stack.
Worst Case - O(n) - for the recursive stack.
Advantages -
- Efficient for large datasets unless the pivot is chosen bad.
- It is cache friendly as we work on the same array to sort and do not copy data to to any auxiliary array.
- One of the fastest general purpose algorithms for large data when stability is not required.
Disadvantages -
- Time complexity - O(n2), which is very high for large datasets.
- Not Stable, as it does not maintain the relative order of equal elements.
Applications -
- It is used in programming libraries and frameworks. For example - Python’s sorted() function and Java’s Array.sort() is based on quick sort.
- It is used indDatabase query optimization by efficiently sorting rows during query execution.
- It works well for in-memory sorting of large datasets due to its cache-friendly properties.
6.Heap Sort
Heap Sort is a comparison-based sorting algorithm. It is an extension of Selection Sort. In Heap Sort, we create a Binary Heap and swap the maximum or minimum element with the last element. Then, we reduce the heap size by 1. This process is repeated until the length of the heap is greater than 1.
def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False # Last i elements are already in place, so we don't need to check them for j in range(0, n-i-1): # Swap if the element found is greater than the next element if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] # Swap the elements swapped = True if not swapped: break return arr
Algorithm -
- Build a max heap using the heapify process. Heapify is a method used to maintain the heap property of a binary heap data structure.
- If the array is of size n, it contains n//2 parent nodes.
- For a parent at index i:
a.Its left child is at index 2i 1
b. Its right child is at index 2i 2
- Iterate over all subtrees with parents from index n//2 to 0 and call the heapify function on them.
- Now, the array becomes a max heap, with the largest element at index 0.
- Swap the first element with the last element of the heap and decrease the size of the heap by 1.
- Repeat this process until the length of the heap is greater than 1
Time Complexity : Heap Sort has a time complexity of O(n log n) in all three cases: best, average, and worst. This is because, irrespective of whether the array is already sorted or not, the same steps are followed each time a max heap is created and an element is swapped.
O( log n ) - To create max heap
O(n) - As the max heap is created and an element is swapped n times.
So the total time complexity is O (n) * O (log n) = O (n log n)
Space Complexity : For all cases - O( log n) - for the recursive stack.
Advantages -
- Time complexity is O (n log n), even for the large datasets.
- Memory usage is almost constant.
Disadvantages -
- Not Stable, as it does not maintain the relative order of equal elements.
- Many swaps are required as compared to merge sort.
Applications -
- It is useful for implementing priority queues where extraction of the maximum or minimum element is frequent.
- Useful in systems where in-place sorting is necessary and memory usage is critical.
- It is used in scenarios where rankings are needed.
7.Counting Sort and Radix Sort
Counting Sort is a non-comparison-based sorting algorithm. It is particularly efficient when the range of input values is small compared to the number of elements to be sorted. The basic idea behind Counting Sort is to count the frequency of each distinct element in the input array and use that information to place the elements in their correct sorted positions.
Radix Sort uses Counting Sort as a subroutine. It applies Counting Sort to each digit place of a number and repeatedly sorts until it processes all the digits of the largest number in the array.
def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False # Last i elements are already in place, so we don't need to check them for j in range(0, n-i-1): # Swap if the element found is greater than the next element if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] # Swap the elements swapped = True if not swapped: break return arr
def selectionSort(a): i = 0 while i<len(a): smallest = min(a[i:]) index_of_smallest = a.index(smallest) a[i],a[index_of_smallest] = a[index_of_smallest],a[i] i=i+1 return a
Algorithm -
Find the maximum number in the array and determine the number of digits (d) in it. If the length of the number is d, Counting Sort is called d times on the array.
Call Counting Sort for each digit place in the array, starting from the ones place, then tens place, and so on.
In Counting sort:
- First, create an index array and map the elements based on their values.For example, if a digit is 4, increment the value at the 4th index of the index array.
- Create a prefix sum array from the index array.
- Using the prefix sum array, create a new sorted array equal to the length of the input array
- For example, if the prefix sum array has a value of 2 at index 1, place the value 1 at position 2 in the sorted array and decrement the value in the prefix sum array by 1
Time Complexity :
Counting Sort has a time complexity of O(n k), where n is the number of elements to sort and k is the range of values (size of the index array).This complexity is valid for all three cases: best, average, and worst.
This is because, irrespective of whether the array is already sorted or not, the same steps are followed each time.
Radix Sort’s time complexity increases by a factor of d, where d is the number of digits in the largest number in the array. Time complexity is O (d * (n k))
So the total time complexity is O (d) * O(n k) = O (d * (n k))
Space Complexity : For all cases - O(n k), where n is the length of the input array and k is the range of values in the index array.
Advantages -
- Stable as elements keep their relative order.
- Counting sort generally performs faster than all comparison-based sorting algorithms, such as merge sort and quicksort, if the range of input is of the order of the number of input.
Disadvantages -
- Counting sort doesn’t work on decimal values.
- Counting sort is inefficient if the range of values to be sorted is very large.
- Not in place sorting algorithm, as it uses extra space O(O(n m)).
Applications -
- It is used in applications like counting character occurrences in strings.
- Useful for sorting integers with a large range of values, such as IDs or phone numbers.
- Efficient for sorting data with multiple keys, like dates or tuples.
The above is the detailed content of Sorting Algorithms || Python || Data Structures and Algorithms. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Python's unittest and pytest are two widely used testing frameworks that simplify the writing, organizing and running of automated tests. 1. Both support automatic discovery of test cases and provide a clear test structure: unittest defines tests by inheriting the TestCase class and starting with test\_; pytest is more concise, just need a function starting with test\_. 2. They all have built-in assertion support: unittest provides assertEqual, assertTrue and other methods, while pytest uses an enhanced assert statement to automatically display the failure details. 3. All have mechanisms for handling test preparation and cleaning: un

PythonisidealfordataanalysisduetoNumPyandPandas.1)NumPyexcelsatnumericalcomputationswithfast,multi-dimensionalarraysandvectorizedoperationslikenp.sqrt().2)PandashandlesstructureddatawithSeriesandDataFrames,supportingtaskslikeloading,cleaning,filterin

Dynamic programming (DP) optimizes the solution process by breaking down complex problems into simpler subproblems and storing their results to avoid repeated calculations. There are two main methods: 1. Top-down (memorization): recursively decompose the problem and use cache to store intermediate results; 2. Bottom-up (table): Iteratively build solutions from the basic situation. Suitable for scenarios where maximum/minimum values, optimal solutions or overlapping subproblems are required, such as Fibonacci sequences, backpacking problems, etc. In Python, it can be implemented through decorators or arrays, and attention should be paid to identifying recursive relationships, defining the benchmark situation, and optimizing the complexity of space.

To implement a custom iterator, you need to define the __iter__ and __next__ methods in the class. ① The __iter__ method returns the iterator object itself, usually self, to be compatible with iterative environments such as for loops; ② The __next__ method controls the value of each iteration, returns the next element in the sequence, and when there are no more items, StopIteration exception should be thrown; ③ The status must be tracked correctly and the termination conditions must be set to avoid infinite loops; ④ Complex logic such as file line filtering, and pay attention to resource cleaning and memory management; ⑤ For simple logic, you can consider using the generator function yield instead, but you need to choose a suitable method based on the specific scenario.

Future trends in Python include performance optimization, stronger type prompts, the rise of alternative runtimes, and the continued growth of the AI/ML field. First, CPython continues to optimize, improving performance through faster startup time, function call optimization and proposed integer operations; second, type prompts are deeply integrated into languages ??and toolchains to enhance code security and development experience; third, alternative runtimes such as PyScript and Nuitka provide new functions and performance advantages; finally, the fields of AI and data science continue to expand, and emerging libraries promote more efficient development and integration. These trends indicate that Python is constantly adapting to technological changes and maintaining its leading position.

Python's socket module is the basis of network programming, providing low-level network communication functions, suitable for building client and server applications. To set up a basic TCP server, you need to use socket.socket() to create objects, bind addresses and ports, call .listen() to listen for connections, and accept client connections through .accept(). To build a TCP client, you need to create a socket object and call .connect() to connect to the server, then use .sendall() to send data and .recv() to receive responses. To handle multiple clients, you can use 1. Threads: start a new thread every time you connect; 2. Asynchronous I/O: For example, the asyncio library can achieve non-blocking communication. Things to note

Polymorphism is a core concept in Python object-oriented programming, referring to "one interface, multiple implementations", allowing for unified processing of different types of objects. 1. Polymorphism is implemented through method rewriting. Subclasses can redefine parent class methods. For example, the spoke() method of Animal class has different implementations in Dog and Cat subclasses. 2. The practical uses of polymorphism include simplifying the code structure and enhancing scalability, such as calling the draw() method uniformly in the graphical drawing program, or handling the common behavior of different characters in game development. 3. Python implementation polymorphism needs to satisfy: the parent class defines a method, and the child class overrides the method, but does not require inheritance of the same parent class. As long as the object implements the same method, this is called the "duck type". 4. Things to note include the maintenance

The core answer to Python list slicing is to master the [start:end:step] syntax and understand its behavior. 1. The basic format of list slicing is list[start:end:step], where start is the starting index (included), end is the end index (not included), and step is the step size; 2. Omit start by default start from 0, omit end by default to the end, omit step by default to 1; 3. Use my_list[:n] to get the first n items, and use my_list[-n:] to get the last n items; 4. Use step to skip elements, such as my_list[::2] to get even digits, and negative step values ??can invert the list; 5. Common misunderstandings include the end index not
