1. 程式人生 > >資料結構 8 基礎排序演算法詳解、快速排序的實現、瞭解分治法

資料結構 8 基礎排序演算法詳解、快速排序的實現、瞭解分治法

## 快速排序 快速排序與氣泡排序一樣,同樣是屬於`交換排序` 叫做快速排序也是有原因的。因為它採用了**分治法的概念** ![image.png](https://file.chaobei.xyz/blogs/image_1588862421197.png_imagess) 其中最重要的一個概念就是 `基準元素` > 氣泡排序每一輪將一個最大的元素挑選出並移動到右側。 ## 分治法思想 在每一輪當中。通過**確定基準元素**,將元素分為兩部分,分別大於小於基準元素。而後的一輪中。還是通過**原來**的方式,在這兩輪中繼續找尋基準元素,直至不可再細分為止。 ![image.png](https://file.chaobei.xyz/blogs/image_1588862377242.png_imagess) **最重要的兩個地方:** ### 基準元素的選擇 pivot 基準元素的確認一般是選擇當前數列的第一個元素,但這種方法確實不太靠譜,一般情況會通過隨機選擇的方式選擇一個基準元素。這樣一來,也能避免某些`特殊數列` 導致的時間複雜O(N^2); 通過**隨機選擇**的方式,可以將時間複雜度調整至O(Nlogn); ### 元素的移動 通過隨機的方式選擇元素後,接下來就是元素的`移動` > 移動就是將元素分別移動到基準元素兩側,左側比基準元素小,右邊則比基準元素大。 這裡有兩種元素的移動方式: 1. 挖坑法 2. 指標交換法 ## 挖坑法 擬定一個無序數列{4,7,6,5,3,2,9,1}要求將這個數列從小到大依次排列,我們採用挖坑法進行實現。 ![image.png](https://file.chaobei.xyz/blogs/image_1590372934852.png_imagess) > 挖坑法最重要的地點在於:指定左右指標(left,right)基準元素下標index。基準元素Pivot. 假設我們通過隨機法選擇`基準元素Pivot = 4` 它的`下標index=1`表示一個坑,並且選擇了左右的指標`left/right`。 ![image.png](https://file.chaobei.xyz/blogs/image_1590373359752.png_imagess) 1、從右邊指標`right`開始 和基準元素進行比較。若右指標元素大於基準元素,則指標向下移動一位。若小於則將這個元素填入坑裡面。將坑的位置記錄下來。 > 此時我們的右邊指標元素`1<4` 則將右邊指標元素`1` 填入首位的坑`index` 裡面。這個時候因為1已經跑到首位去了。所以當前的位置就又成為了一個新的坑,接下來要操作左邊指標`left` 將左邊指標移動一位。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590374726843.png_imagess) 2、開始操作左指標`left` >
通過比較`7>4` 則移動元素,將7移動到坑裡面。移動完後,原位置又變成一個新的坑。下面需要操作右邊指標,將右邊指標移動一位。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590375272247.png_imagess) 3、按照這樣的思路。再次進行操作右邊邊指標。 > 通過比較`9>4` 則右邊的元素已經大於基準元素。則無需移動位置。將右邊指標移動一位即可。繼續進行比較。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590375638835.png_imagess) >
當前右邊指標`2<4` 則將2填入到坑裡面。原位置變成一個坑。左邊指標移動一位,交換指標。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590375927919.png_imagess) 4、繼續操作左邊指標。 > 元素`6>4` 將元素6移動坑的位置。6位置再次成為一個坑,右邊指標移動一位。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590382139595.png_imagess) > 元素`3<4` 則將3元素移動到坑的位置。原位置變成坑,左邊指標移動一位。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590382194293.png_imagess) > 開始操作左邊指標 `5>4` 將5元素移動到坑的位置。右邊指標移動一位。發生指標重合。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590382292140.png_imagess) > 將基準元素移動到重合位置。交換結束。如下圖所示 ![image.png](https://file.chaobei.xyz/blogs/image_1590382362320.png_imagess) ### 交換總結 1. 左右指標發生元素填坑後才進行交換指標操作(從左指標交換到右指標) 2. 左邊指標的元素值大於基準元素則填坑。否則只做移動。 3. 右邊指標的元素值小於基準元素則填坑。否則只做移動。 4. 發生填坑後,將另一個指標位置移動一位。 5. 指標重合則結束,將基準元素填充到重合位置。 ### 程式碼示例 ```java public static void main(String[] args) { int[] array = {4, 7, 6, 5, 3, 2, 9, 1}; sort(array, 0, array.length - 1); System.out.println(Arrays.toString(array)); } public static void sort(int[] array, int start, int end) { if (start >
= end) { return; } int pivotIndex = partition(array, start, end); //通過分治法將數列分成兩份,各自再次遞迴 sort(array, start, pivotIndex - 1); sort(array, pivotIndex + 1, end); } /** * @return int * @Author MRC * @Description 採用分治法 返回基準元素位置 * @Date 17:52 2020/5/25 * @Param [arr 被操作的陣列, start 分治法起始位置, end 結束位置] **/ private static int partition(int[] arr, int start, int end) { //取首位為基準元素。//也是坑的位置 int pivotIndex = start; //基準元素的值 int pivot = arr[pivotIndex]; //左邊指標 int left = start; //右邊指標 int right = end; /** * 大迴圈用於判斷總體迴圈 * 在左右指標指向同位置後 * 結束迴圈 */ while (right >= left) { /** * 右指標 */ while (right >= left) { if (arr[right] < pivot) { arr[pivotIndex] = arr[right]; pivotIndex = right; left++; break; } right--; } /** * 左指標 */ while (right >= left) { if (arr[left] > pivot) { arr[pivotIndex] = arr[left]; pivotIndex = left; right--; break; } left++; } } arr[pivotIndex] = pivot; return pivotIndex; } ``` ## 指標交換法 指標交換法相比於挖坑法,開局還是和挖坑法一樣,元素的交換次數更少,效率相比於挖坑法有小幅度的提升。我們來了解一下指標交換法的邏輯。 拿到一個數列 {4, 7, 6, 5, 3, 2, 9, 1} 我們定義左指標`left` 右指標`right` 以及首位取出的基準元素`pivot=4` ![image.png](https://file.chaobei.xyz/blogs/image_1590461829065.png_imagess) ### 交換要點 1. 從右指標開始迴圈 2. 右指標指向的元素`大於等於`基準元素,則右指標向左移動一位。`小於`則指標停下。換到左邊指標操作。 3. 左指著指向的元素`小於等於`基準元素,則左指標向右移動一位。`大於`則指標停下、跳出迴圈。 4. 左指標停下後開始交換兩個指標位置的元素。開始下次迴圈。 5. 指標重合大迴圈結束。重合位置和基準元素進行交換。 ### 詳細解說 1、第一次迴圈 > 右指標指向元素`1小於4` 則右邊指標`不移動` 交換到左邊指標,左邊指標指向的元素`4小於等於4(基準元素)` 將左邊指標移動一位。 當前指標指向元素`7>4` 指標停下。 ![image.png](https://file.chaobei.xyz/blogs/image_1590462798155.png_imagess) > 指標停下、開始交換元素。將左右指標位置的元素進行交換 ![image.png](https://file.chaobei.xyz/blogs/image_1590462967131.png_imagess) 2、第二次迴圈 > 重新到右指標,當前`7>4` 左移一位。 > 到達`9`當前`9>4` 左移一位。 > 到達`2` 當前`2<4` 右邊指標停下。 ![image.png](https://file.chaobei.xyz/blogs/image_1590463628787.png_imagess) > 左指標,當前`1<4` 右移一位。 > 到達`6`當前`6>4` 左邊指標停下。 ![image.png](https://file.chaobei.xyz/blogs/image_1590463941209.png_imagess) > 交換兩個位置的元素 ![image.png](https://file.chaobei.xyz/blogs/image_1590464075429.png_imagess) 3、第三次迴圈 > 右邊指標移動到3停下 > 左邊指標移動到5停下 ![image.png](https://file.chaobei.xyz/blogs/image_1590464286656.png_imagess) > 開始交換元素。 ![image.png](https://file.chaobei.xyz/blogs/image_1590464425292.png_imagess) 4、第四次迴圈 > 移動右指標,已經發生指標的重合,則將重合的位置的元素和基準元素進行交換。 ![image.png](https://file.chaobei.xyz/blogs/image_1590464538452.png_imagess) ### 程式碼示例 ```java public static void main(String[] args) { int[] array = {4, 7, 6, 5, 3, 2, 9, 1}; sort(array, 0, array.length - 1); System.out.println(Arrays.toString(array)); } private static void sort(int[] array, int start, int end) { if (start >= end) { return; } int pivotIndex = partition(array, start, end); sort(array, start, pivotIndex - 1); sort(array, pivotIndex + 1, end); } private static int partition(int[] array, int start, int end) { //取首位為基準元素。//也是坑的位置 int pivotIndex = start; //基準元素的值 int pivot = array[pivotIndex]; //左邊指標 int left = start; //右邊指標 int right = end; while (right != left) { /** * 操作右指標 */ while (right > left && pivot < array[right]) { right--; } /** * 操作左指標 */ while (right > left && pivot >= array[left]) { left++; } /** * 交換元素 */ if (right > left) { int item = array[right]; array[right] = array[left]; array[left] = item; } } /** * 交換指標位置和基準元素 */ array[pivotIndex] = array[left]; array[left] = pivot; return left; } ``` ## 小結 通過本節,我們研究了快速排序的兩種實現方式。我們通過遞迴的方式實現分治法。通過挖坑交換元素和指標交換的方式分別實現快速排序。快速排序還是一個很重要的排序方法。通過本節的學習。應該瞭解到`分治法`這個重要的思想 ## 程式碼示例 > https://gitee.com/mrc1999/Data-struc