算法:快速排序
算法參考:快速排序(三種算法實現和非遞歸實現)
上面這篇文章對快排講解得不錯。快排概念詳述請點上面鏈接。
記錄一下,用lua實現的快排,以及一些註意的地方。
交換函數:
function Swap(tab, i, j) local temp = tab[i]; tab[i] = tab[j]; tab[j] = temp; end
一、左右指針法:
-- 左右指針法 -- 最後一個數為樞軸 function PartSort(tab, left, right) local key = right; whileleft < right do -- 必須先由左向右遍歷 while left < right and tab[left] <= tab[key] do left = left + 1; end -- 然後由右向左遍歷 while left < right and tab[right] >= tab[key] do right = right - 1; end if left < right thenSwap(tab, left, right); else Swap(tab, left, key); end end return left; end
以最後一個值為key的話,必須從左邊開始遍歷。為什麽必須從左邊開始遍歷?因為是以最右的元素做樞軸。回答這個問題之前,首先要知道為什麽內層的while的循環還要 left < right 判斷,這是因為不加的話,left就有可能和key重合了(如果key是當前片段最大的數),然後left再加1就越界了。 好,然後當while循環有了這個判斷之後,外層循環的最後,必然會走到left == right。而如果不考慮特殊情況,一遍排序的最後,就是key跳到片段中間把片段分成小於key和大於key的兩個子片段,而從左邊開始,left和right最終停留的地方就是大於key的結點,交換left和key的位置,剛好把大於key的這個值調到了後面的片段。而如果從右開始,則停留在了比key小的地方,交換後就把一個比key小的值調到了片段後面。(這個地方我也只是意會,不曉得怎麽用通俗語言描述出來,望指教)。同理,如果以最左的元素作為樞軸,那就是要從右邊開始遍歷。
二、挖坑法:
-- 挖坑法 function PartSort(tab, left, right) local sign = tab[left]; while left < right do while left < right and tab[right] >= sign do right = right - 1; end tab[left] = tab[right]; while left < right and tab[left] <= sign do left = left + 1; end tab[right] = tab[left]; end tab[left] = sign; return left; end
挖坑法,其實叫成“填坑法”更好理解?把該數據結構想象成一排箱子,先空出第一個箱子(把裏面的值放旁邊做參考),然後從右邊開始查找,有小於參考的就填到空箱中。此時,右邊就多了一個空箱(此空箱的右邊,如果有,都是大於參考的值),再從左邊開始查找,如果有大於參考的值就填到右邊的空箱中,如此反復。到最後,左邊查找的指針和左邊查找的指針相遇了,就把最先提出來的值填到最後的空箱中。
三、前後指針法:
-- 前後指針 -- 以最右元素為key function PartSort(tab, left, right) if left < right then local cur = left; local pre = left - 1; while cur < right do -- 如果cur比最右的元素大,就不會進入這個if語句,pre就停滯了(停滯在第一個比right大的元素的前一個位置) if tab[cur] < tab[right] then -- pre + 1 有兩種結果 -- 1. pre+1 = cur,說明pre 一直跟在cur後面,沒有停滯過,即pre和cur之間,全是小於right的元素 -- 2. pre+1 < cur, 說明pre 停滯過,沒有跟上cur,也就是說pre和cur之間有大於right的元素,由於是當cur遇到大於right的值pre就停滯了 -- 所以,pre指向的還是小於right的元素,停滯的後一個元素(pre+1)才是大於right的元素。 -- 把cur指向的小於right的元素和pre+1大於right的元素對調。此時的pre=pre+1指向的元素又成了小於right的元素了 pre = pre + 1; if pre ~= cur then Swap(tab, pre, cur); end end cur = cur + 1; end -- 由上可知,pre+1是指向大於right的元素,把它和right交換位置 -- 即把right放到了pre=pre+1的位置,就得到了以right元素為分界的 左邊小於right, 右邊大於right的兩個部分 pre = pre + 1; Swap(tab, pre, right); return pre; end return -1; end
這種方法比較有趣了。註釋比較多,就不啰嗦了。
快排算法有點忘了,復習一下,感謝開頭提到的文章的博主。整理的不錯。
測試代碼:
--local nums = {8, 1, 7, 6, 5, 13, 1, 3, 6, 25, 87, 9, 3, 6, 8, 17, 22, 102, 7, 31, 6}; local nums = {8, 1, 7, 6, 5, 13}; function QuickSort(tab, left, right) if left >= right then return; end local index = PartSort(tab, left, right); -- print(index); QuickSort(tab, left, index - 1); QuickSort(tab, index + 1, right); end QuickSort(nums, 1, #nums);
for _, v in ipairs(nums) do print(v); end
算法:快速排序