快速排序
從名字可以看出是個(gè)速度很快的排序。快排說穿了就是確定主元的正確位置的過程,其余元素只關(guān)注分布是否正確,而不關(guān)注位置是否正確。
快排的實(shí)現(xiàn)原理:
快排利用雙指針實(shí)現(xiàn)
首先確定一個(gè)主元(隨機(jī)選擇,但一般選擇數(shù)組第一個(gè)元素;實(shí)際上快排不關(guān)心如何選取主元,但主元的選擇確實(shí)關(guān)乎性能),然后low 、high指針分別指向頭尾,從high開始,判斷high指向的元素是否小于主元,若小于,則將該元素的值賦給low指向的位置(將小于主元的元素丟到數(shù)組左邊),同時(shí)low向后移動(dòng)一個(gè)單位,判斷l(xiāng)ow指向的元素是否大于主元,若大于,則將該元素的值賦給high指向的位置(將大于主元的元素丟到數(shù)組右邊)。如此反復(fù),直到low和high指向同一位置,這就是主元的正確位置。這樣將數(shù)組掃描一輪之后,主元就確定在了正確的位置,同時(shí)主元左邊的元素都比它小,右邊的元素都比它大
結(jié)合圖解的具體過程可以參考https://blog.csdn.net/nrsc272420199/article/details/82587933
從數(shù)據(jù)結(jié)構(gòu)上理解快排的時(shí)間復(fù)雜度O(n * log n)
快排可以看成構(gòu)造一棵近似的完全二叉樹的過程。每進(jìn)行一次遞歸,樹就生長(zhǎng)一個(gè)高度。一般情況下,主元會(huì)把數(shù)組分成左右兩部分,這樣樹的高度一般為log n,除非運(yùn)氣差到家,每一次找的主元都是最小值或最大值(這樣每一個(gè)結(jié)點(diǎn)都只有左子樹或者右子樹,而非二叉)。因此一般認(rèn)為需要遞歸log n次。而每一次需要遍歷的次數(shù)都約等于n。因此快排的時(shí)間復(fù)雜度為O(n * log n)
如圖,對(duì)亂序的0-9進(jìn)行快排,快排過程形成了一棵樹。樹的每一層需要遍歷的次數(shù)都約等于n,同時(shí)可認(rèn)為遞歸了log2 N次,那么快排的時(shí)間復(fù)雜度為O(n * log n)
快排的核心代碼實(shí)現(xiàn)如下
def fastSort(self, nums: List[int], low, high) -> int:
if low < high:
base = nums[low]
left = low
right = high
# 核心代碼:3while
while left < right:
while left < right and nums[right] > base:
right -= 1
nums[left] = nums[right]
while left < right and nums[left] <= base:
left += 1
nums[right] = nums[left]
nums[left] = base
self.fastSort(nums, left+1, high)
self.fastSort(nums, low, left-1)
return
BFPRT算法
目的:求一個(gè)亂序數(shù)組中第i大的元素
常規(guī)思路或者說暴力思路:
排序后直接利用i取下標(biāo)得到目標(biāo)元素。就算利用快速排序,時(shí)間復(fù)雜度也至少是N*log N
有沒有時(shí)間復(fù)雜度更小的方法?或者說,我們有沒有什么方式優(yōu)化上面的常規(guī)思路。有的
我們獲取第i大的元素,只需要保證第i大的元素位置正確,不需要對(duì)這個(gè)元素兩側(cè)的元素做具體排序。這和快排的單次操作是一致的。所以,BFPRT算法從快排中誕生了
BFPRT算法的實(shí)現(xiàn)
- 將數(shù)組的n個(gè)元素每5個(gè)一組(取5好像是和cpu寄存器數(shù)量有關(guān)),劃分為n/5組,有多的則多余元素為一組。每組排序后取出每個(gè)組的中位數(shù)
- 找出這些中位數(shù)的中位數(shù),定為主元(如果第一步得到偶數(shù)個(gè)中位數(shù),約定主元是較小的中位數(shù))。
- 按主元對(duì)數(shù)組進(jìn)行劃分。假設(shè)主元是第k小的元素,那么有n-k個(gè)元素在劃分的高區(qū)
- 如果i等于k,返回主元。如果i小于k,則在低區(qū)遞歸調(diào)用算法來找出第i小的元素。如果i大于k,則在高區(qū)遞歸查找第i-k小的元素
我們可以發(fā)現(xiàn),BFPRT算法與快速排序的區(qū)別僅僅在于主元的選取方式不同,是優(yōu)化的快排算法。BFPRT算法中,由于主元由中位數(shù)的中位數(shù)確定,因此主元更偏居中的位置,避免了快排中可能出現(xiàn)的最壞情況--即主元都在排序區(qū)間的邊界
BFPRT算法,作為優(yōu)化的快排,是在快排構(gòu)造的近似完全二叉樹的基礎(chǔ)上進(jìn)行剪枝。它的最壞情況是,總保留7/10 * n 的那一支,剪去3/10 * n 的那一支。這樣,即使最壞的情況下,我們也不斷有3/10的一支直接被剪掉,不需要任何操作,顯然時(shí)間復(fù)雜度是比快排優(yōu)的,即優(yōu)于O(n * log n)
有人證明了BFPRT算法最差的時(shí)間復(fù)雜度也是O(n),數(shù)學(xué)感覺比較復(fù)雜,這里不展開了。。。