From e8be312dd3243e42704f59ddce8a8907eeecd600 Mon Sep 17 00:00:00 2001 From: medusa Date: Thu, 27 Jun 2024 06:11:45 +0000 Subject: [PATCH] Update tech_docs/python/Python_programming.md --- tech_docs/python/Python_programming.md | 278 +++++++++++++++++++++++++ 1 file changed, 278 insertions(+) diff --git a/tech_docs/python/Python_programming.md b/tech_docs/python/Python_programming.md index 0883a1a..9c545a7 100644 --- a/tech_docs/python/Python_programming.md +++ b/tech_docs/python/Python_programming.md @@ -1,3 +1,281 @@ +Sorting is a fundamental operation in computer science and programming, used to arrange the elements of a list or array in a particular order (ascending or descending). There are numerous sorting algorithms, each with its own characteristics and use cases. Below are some common sorting algorithms along with their time complexities and a brief description. + +### Common Sorting Algorithms + +1. **Bubble Sort** + - **Description**: Repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. The process is repeated until the list is sorted. + - **Time Complexity**: + - Worst-case: \(O(n^2)\) + - Best-case: \(O(n)\) (when the list is already sorted) + - **Space Complexity**: \(O(1)\) + - **Stability**: Yes + +2. **Selection Sort** + - **Description**: Divides the list into a sorted and an unsorted region. Repeatedly selects the smallest (or largest) element from the unsorted region and moves it to the sorted region. + - **Time Complexity**: + - Worst-case: \(O(n^2)\) + - Best-case: \(O(n^2)\) + - **Space Complexity**: \(O(1)\) + - **Stability**: No + +3. **Insertion Sort** + - **Description**: Builds the sorted array one element at a time by repeatedly picking the next element and inserting it into its correct position in the already sorted part. + - **Time Complexity**: + - Worst-case: \(O(n^2)\) + - Best-case: \(O(n)\) (when the list is already sorted) + - **Space Complexity**: \(O(1)\) + - **Stability**: Yes + +4. **Merge Sort** + - **Description**: Divides the list into two halves, recursively sorts each half, and then merges the sorted halves back together. + - **Time Complexity**: \(O(n \log n)\) + - **Space Complexity**: \(O(n)\) + - **Stability**: Yes + +5. **Quick Sort** + - **Description**: Selects a 'pivot' element, partitions the array around the pivot (elements less than the pivot to its left, elements greater than the pivot to its right), and recursively sorts the partitions. + - **Time Complexity**: + - Worst-case: \(O(n^2)\) (when the pivot is the smallest or largest element every time) + - Average-case: \(O(n \log n)\) + - **Space Complexity**: \(O(\log n)\) + - **Stability**: No + +6. **Heap Sort** + - **Description**: Builds a binary heap from the list and repeatedly extracts the maximum element from the heap, reconstructing the heap each time. + - **Time Complexity**: \(O(n \log n)\) + - **Space Complexity**: \(O(1)\) + - **Stability**: No + +7. **Counting Sort** + - **Description**: Assumes that the input consists of integers in a fixed range. Counts the occurrences of each integer and uses these counts to place the integers in the correct position. + - **Time Complexity**: \(O(n + k)\) where \(k\) is the range of the input + - **Space Complexity**: \(O(k)\) + - **Stability**: Yes + +8. **Radix Sort** + - **Description**: Sorts the numbers by processing individual digits. Uses a stable sorting algorithm (like Counting Sort) as a subroutine to sort digits. + - **Time Complexity**: \(O(d(n + k))\) where \(d\) is the number of digits and \(k\) is the range of the digits + - **Space Complexity**: \(O(n + k)\) + - **Stability**: Yes + +### Example: Quick Sort Implementation + +Here’s an example of Quick Sort in JavaScript: + +```javascript +function quickSort(arr) { + if (arr.length <= 1) return arr; + + const pivot = arr[Math.floor(arr.length / 2)]; + const left = []; + const right = []; + const equal = []; + + for (let element of arr) { + if (element < pivot) { + left.push(element); + } else if (element > pivot) { + right.push(element); + } else { + equal.push(element); + } + } + + return [...quickSort(left), ...equal, ...quickSort(right)]; +} + +// Example usage +const unsortedArray = [3, 6, 8, 10, 1, 2, 1]; +const sortedArray = quickSort(unsortedArray); +console.log(sortedArray); // [1, 1, 2, 3, 6, 8, 10] +``` + +### Choosing the Right Sorting Algorithm + +- **Small Arrays**: For small arrays, simple algorithms like Insertion Sort can be very efficient. +- **Large Arrays**: For larger arrays, more efficient algorithms like Quick Sort, Merge Sort, or Heap Sort are preferable. +- **Memory Constraints**: If memory usage is a concern, use in-place sorting algorithms like Quick Sort or Heap Sort. +- **Stable Sort Requirement**: If you need a stable sort (where equal elements retain their original order), use Merge Sort, Insertion Sort, or a modified version of Quick Sort that ensures stability. + +Sorting is a broad topic, and the best algorithm depends on the specific requirements and constraints of your problem. + +--- + +Priority queues and hashmaps (or hash tables) are two fundamental data structures in computer science, each serving different purposes and having unique characteristics. Here's an overview of both: + +### Priority Queue + +A priority queue is a type of queue in which each element is associated with a priority. Elements are dequeued in order of their priority, rather than in the order they were enqueued. There are two main types of priority queues: max-priority queues and min-priority queues. + +- **Max-priority queue**: The element with the highest priority is dequeued first. +- **Min-priority queue**: The element with the lowest priority is dequeued first. + +#### Implementations + +1. **Binary Heap**: + - **Min-Heap**: The root is the minimum element. Used for min-priority queues. + - **Max-Heap**: The root is the maximum element. Used for max-priority queues. + - Time Complexity: + - Insertion: \(O(\log n)\) + - Deletion (extract-min or extract-max): \(O(\log n)\) + - Peek (get-min or get-max): \(O(1)\) + +2. **Fibonacci Heap**: + - More complex but can offer better amortized time complexities for some operations. + - Time Complexity (amortized): + - Insertion: \(O(1)\) + - Deletion: \(O(\log n)\) + - Decrease key: \(O(1)\) + +3. **Binary Search Tree (BST)**: + - Can also be used but usually less efficient than binary heaps for priority queues. + +#### Example Usage + +```javascript +class MinHeap { + constructor() { + this.heap = []; + } + + insert(value) { + this.heap.push(value); + this.bubbleUp(); + } + + bubbleUp() { + let index = this.heap.length - 1; + while (index > 0) { + let parentIndex = Math.floor((index - 1) / 2); + if (this.heap[parentIndex] <= this.heap[index]) break; + [this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]]; + index = parentIndex; + } + } + + extractMin() { + const min = this.heap[0]; + const end = this.heap.pop(); + if (this.heap.length > 0) { + this.heap[0] = end; + this.sinkDown(0); + } + return min; + } + + sinkDown(index) { + const length = this.heap.length; + const element = this.heap[index]; + while (true) { + let leftChildIdx = 2 * index + 1; + let rightChildIdx = 2 * index + 2; + let swap = null; + + if (leftChildIdx < length) { + if (this.heap[leftChildIdx] < element) swap = leftChildIdx; + } + + if (rightChildIdx < length) { + if ( + (swap === null && this.heap[rightChildIdx] < element) || + (swap !== null && this.heap[rightChildIdx] < this.heap[leftChildIdx]) + ) { + swap = rightChildIdx; + } + } + + if (swap === null) break; + [this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]]; + index = swap; + } + } +} + +// Example usage +const pq = new MinHeap(); +pq.insert(10); +pq.insert(5); +pq.insert(20); +console.log(pq.extractMin()); // 5 +console.log(pq.extractMin()); // 10 +``` + +### HashMap (Hash Table) + +A hashmap, or hash table, is a data structure that implements an associative array, a structure that can map keys to values. It uses a hash function to compute an index into an array of buckets or slots, from which the desired value can be found. + +#### Characteristics + +- **Average Time Complexity**: + - Insertion: \(O(1)\) + - Deletion: \(O(1)\) + - Lookup: \(O(1)\) +- **Worst-case Time Complexity**: + - \(O(n)\) (if many keys hash to the same index) + +#### Handling Collisions + +1. **Chaining**: Store multiple elements at the same index using a linked list or another data structure. +2. **Open Addressing**: Find another open slot within the array itself using probing (linear probing, quadratic probing, double hashing). + +#### Example Usage + +```javascript +class HashMap { + constructor(size = 53) { + this.keyMap = new Array(size); + } + + _hash(key) { + let total = 0; + let WEIRD_PRIME = 31; + for (let i = 0; i < Math.min(key.length, 100); i++) { + let char = key[i]; + let value = char.charCodeAt(0) - 96; + total = (total * WEIRD_PRIME + value) % this.keyMap.length; + } + return total; + } + + set(key, value) { + let index = this._hash(key); + if (!this.keyMap[index]) { + this.keyMap[index] = []; + } + this.keyMap[index].push([key, value]); + } + + get(key) { + let index = this._hash(key); + if (this.keyMap[index]) { + for (let i = 0; i < this.keyMap[index].length; i++) { + if (this.keyMap[index][i][0] === key) { + return this.keyMap[index][i][1]; + } + } + } + return undefined; + } +} + +// Example usage +const hm = new HashMap(); +hm.set("hello", "world"); +hm.set("foo", "bar"); +console.log(hm.get("hello")); // "world" +console.log(hm.get("foo")); // "bar" +console.log(hm.get("baz")); // undefined +``` + +### Summary + +- **Priority Queue**: Used when you need to process elements based on their priority. Implementations often use binary heaps for efficient operations. +- **HashMap (Hash Table)**: Used for fast key-value lookups. They provide average \(O(1)\) time complexity for insertion, deletion, and lookup operations, with different strategies to handle collisions. + +Each data structure has its specific use cases and choosing the right one depends on the problem you are trying to solve. + +--- + # Python Functions: A Comprehensive Guide Python functions are the building blocks of Python programming, enabling code reusability, organization, and modularity. This guide explores Python functions, their syntax, and how to use them effectively.