1. 找第k小的数的分治算法
首先,我们要先去找一个划分点,然后我们要去对划分点左右两边的数进行划分。
划分完之后,我们能得到 pivot 也就是划分点的最终位置,这个位置也是 pivot 最终排序的位置。
当我们发现 pivot 的位置大于我们要找的位置时,我们就去左边的区域重复上面的操作;如果小于我们要找的位置时,我们就去右边的区域重复上面的操作,直到 pivot 的位置就是 k,我们也就找到了第 k 小的数了。
部分算法实现如下。(Partition 的方法有多种,这里只是其中一种)
int Partition(int arr[], int low, int high)
{int pivot = arr[high];int i = low - 1;for(int j=low;j<high;j++){if(arr[j] < pivot){i++;swap(arr[i], arr[j]);}}swap(arr[i+1], arr[high]);return i+1;
}void find(int arr[], int low, int high, int k)
{int p = Partition(arr, low, high);if(k-1 == p) cout<<arr[p]<<endl;else if(k-1 < p) find(arr, low, p-1, k);else find(arr, p+1, high, k);
}
2. 该算法的最好时间复杂度和最坏时间复杂度
最好的时间复杂度
假如我们第一个划分点就我们要找到的数,那么此时只进行了一次循环去进行左右分区,此时的时间复杂度就是最好的。也就是说,最好的时间复杂度是 \(O(n)\)。
最坏的时间复杂度
假设我们每次都没有找到我们要找的数,直到最后完成排序,才刚好找到目标,此时的时间复杂度是最差的。每次分区的时间复杂度是 \(O(n)\),然后要重复这个分区 n 次,也就是说,我们最差的时间复杂度是 \(O(n^2)\)。
3. 对分治法的体会和思考
分治法的核心在于将一个复杂的问题拆解为若干个结构相似但规模更小的子问题,通过解决这些子问题来间接得到原问题的答案。这种思路的关键并非对所有细节都面面俱到,而是有选择性地聚焦于对解决问题真正有用的部分,就像在找第 k 小元素的算法中,每次分区后只需要关注可能包含目标元素的那部分子数组,而非整个数组,这使得计算量大幅减少,效率远高于蛮力处理。
分治法的效率很大程度上取决于分解的平衡性。如果每次分解都能让子问题的规模快速缩小,比如理想情况下将数组均匀拆分,那么算法能在较短时间内得到结果;反之,若分解失衡,比如每次只能将问题规模缩小一点点,效率就会显著下降。这也意味着,在设计分治法时,如何设计合理的分解策略以保证子问题规模有效缩减,是提升效率的核心。
递归是分治法常用的实现方式,但也需要注意其潜在的成本,比如递归调用带来的栈空间占用和函数调用开销。不过,这并不影响分治法在处理具有可分解、子问题独立且易解特性的问题时的优势,它不仅是一种算法技巧,更像是一种处理复杂问题的思维方式,引导我们通过拆解、聚焦和逐步解决的策略,将难题化繁为简。