一、概念
快速排序由C.A.R.Hoare在1962年提出,是冒泡排序的一种改进。其基本思想为:通过一趟排序将待排序数据分割成独立的两部分,其中一部分的所有值都比另一部分的所有值都小,然后再对分割的两部分分别进行快速排序,整个过程可以递归进行,最终所有数据变为有序序列。
二、算法要点
假设待排序数组为a[0], a[1],…a[n-1],快速排序步骤以下:
1、初始化两个变量i、j,刚开始i = 1,j=n-1。
2、将第一个元素a[0]作为基准数。
3、从i开始向后搜索,找到第一个大于基准数的元素a[i]。
4、从j开始向前搜索,找到第一个小于等于基准数的元素a[j]。
5、将a[i]与a[j]互换。
6、重复3到5步骤,直到i = j + 1,此时,j指向的元素是最后一个(从左边算起)小于等于中轴的元素,然后将a[0]与a[j]对换。
7、序列被基准数分割成两个分区,前面分区全部小于等于基准数,后面分区全部大于基准数。
8、递归对分区子序列进行快速排序,最终完成整个排序工作。
每趟快速排序的核心工作是:
选一个元素作为基准数,然后将所有比它小的数都放到它前面,大于它的数都放在它后面。
三、算法实现
实现一:
1 using System; 2 using System.Collections.Generic; 3 4 namespace QuickSort 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 QuickSortUnitTest(13); 11 12 } 13 private static void QuickSort(List<int>dataList, int left, int right) 14 { 15 if(left < right)//递归的边界条件,当left等于right时,元素个数为1个 16 { 17 int pivot = dataList[left];//最左边的元素作为中轴 18 int i = left + 1; 19 int j = right; 20 int tmp = 0; 21 //当i == j时,i和j同时指向的元素还没有与中轴元素判断, 22 //小于等于中轴元素,i++,大于中轴元素j--, 23 //当循环结束时,一定有i = j + 1, 且i指向的元素大于中轴,j指向的元素小于等于中轴 24 while (i <= j) 25 { 26 while(i <= j && dataList[i] <= pivot)//从前向后找到第一个大于pivot的元素 27 { 28 i++; 29 30 } 31 while(i <= j && dataList[j] > pivot) 32 { 33 j--; 34 35 } 36 if(i < j) 37 { 38 tmp = dataList[i]; 39 dataList[i] = dataList[j]; 40 dataList[j] = tmp; 41 } 42 } 43 //当循环结束时,j指向的元素是最后一个(从左边算起)小于等于中轴的元素 44 //将中轴元素和j所指的元素互换 45 tmp = dataList[left]; 46 dataList[left] = dataList[j]; 47 dataList[j] = tmp; 48 Console.WriteLine(); 49 foreach (var item in dataList) 50 { 51 Console.Write($" {item} "); 52 } 53 Console.WriteLine(); 54 QuickSort(dataList, left, j - 1); 55 QuickSort(dataList, j + 1 , right); 56 } 57 } 58 private static void QuickSortUnitTest(int loopNum) 59 { 60 Random random = new Random(); 61 List<int> dataList = new List<int>(); 62 for (int i = 0; i < loopNum; i++) 63 { 64 dataList.Add(random.Next(100)); 65 } 66 Console.Write("Original data:"); 67 foreach(var item in dataList) 68 { 69 Console.Write($" {item} "); 70 } 71 72 QuickSort(dataList, 0, dataList.Count - 1); 73 Console.Write("Quick sorted data:"); 74 75 foreach (var item in dataList) 76 { 77 Console.Write($" {item} "); 78 } 79 Console.WriteLine(); 80 } 81 } 82 }
实现二:
基本思想:“挖坑填坑”
使用两个变量i和j分别指向最左边和最右边的元素,我们将首元素作为中轴,并复制到变量pivot中,这时我们可以将首元素i所在的位置看成一个坑,我们从j的位置从右向左扫描,找一个小于等于中轴
的元素A[j],来填补A[i]这个坑,填补完成后,拿去填坑的元素所在的位置j又可以看做一个坑,这时我们在以i的位置从前往后找一个大于中轴的元素来填补A[j]这个新的坑,如此往复,直到
i和j相遇(i == j,此时i和j指向同一个坑)。
最后我们将中轴元素放到这个坑中。然后,我们按照相同的方法分别对左右两个数组执行相同的操作。
private static void QuickSort2(List<int>dataList, int left, int right) { if(left < right) { //最左边的元素作为中轴复制到pivot,这时最左边的元素可以看做一个坑 int pivot = dataList[left]; //注意这里 i = L,而不是 i = L+1, 因为i代表坑的位置,当前坑的位置位于最左边 int i = left; int j = right; while(i < j) { //下面面两个循环的位置不能颠倒,因为第一次坑的位置在最左边 while (i < j && dataList[j] > pivot) { j--; } //填A[i]这个坑,填完后A[j]是个坑 //注意不能是A[i++] = A[j],当因i==j时跳出上面的循环时 //坑为i和j共同指向的位置,执行A[i++] = A[j],会导致i比j大1, //但此时i并不能表示坑的位置 dataList[i] = dataList[j]; while(i < j && dataList[i] <= pivot) { i++; } dataList[j] = dataList[i]; } //循环结束后i和j相等,都指向坑的位置,将中轴填入到这个位置 dataList[i] = pivot; QuickSort2(dataList, left, i - 1); QuickSort2(dataList, i + 1, right); } }
更多方法请参考: