常见排序算法-----快速排序
最好时间复杂度O(nlogn) 最坏时间复杂度O(N^2)
方法一:
左右指针法:
1 /** 2 * 双指针法,将基准点设置为最左端 3 * 4 * @param arr 5 * @param left 6 * @param right 7 */ 8 public static void quicklySort(int[] arr, int left, int right) { 9 10 if (left >= right) { 11 return; 12 } 13 14 int base = arr[left]; 15 int i = left; 16 int j = right; 17 while (i < j) { 18 while (j > i && arr[j] >= base) { 19 j--; 20 } 21 while (j > i && arr[i] <= base) { 22 i++; 23 } 24 if (i < j) { 25 int temp = arr[i]; 26 arr[i] = arr[j]; 27 arr[j] = temp; 28 } 29 } 30 arr[left] = arr[i]; 31 arr[i] = base; 32 quicklySort(arr, left, i - 1); 33 quicklySort(arr, i + 1, right); 34 35 }
当基点设置在最左端时,要让右指针先移动(当基准设置在最右端时,要让左指针先移动)
S1 右指针移动找到第一个比基准小的数停止.
S2 左指针开始移动,找到第一个比基准大的数停止
S3 将左右指针所指的数进行交换
S4 继续上述步骤直到左右指针相遇,将其与基准进行交换
S5 按照基准的位置,分为左右两侧分别进行上述步骤,直到拆分到只剩一个元素 排序完毕
当基点设置在最左端时,要让右指针先移动(当基准设置在最右端时,要让左指针先移动)原因:
因为右指针最后停止的时候会和左指针发生交换,导致右指针停止的时候所指向的数要大于基准
若此时让左指针先进行移动,最后和两个指针相遇 (相遇在右指针的位置,此时右指针指向的数要大于基准)
若基准设置在最左端,会导致右指针指向的大于基准的数移动到左端,导致左端不是全部小于基准的数,排序失败
右指针先移动同理。
所以,当基准在最左端的时候,要右指针先移动,基准在最右端时,要左指针先移动。
方法二 挖坑法 (基准设在左边 右指针先走 和上面一样)
1 // 挖坑法 2 public static void quicklySort2(int[] arr, int left, int right) { 3 4 if(left >=right){ 5 return ; 6 } 7 8 int base = arr[left]; 9 int i = left; 10 int j = right; 11 12 while (i < j) { 13 // 右指针先走 14 while (j > i && arr[j] >= base) { 15 j--; 16 } 17 arr[i] = arr[j]; 18 19 // 左指针再走 20 while (j > i && arr[i] <= base) { 21 i++; 22 } 23 arr[j] = arr[i]; 24 } 25 arr[j] = base; 26 quicklySort2(arr, left, j - 1); 27 quicklySort2(arr, j + 1, right); 28 29 }
取基准的几种方法
详细见https://blog.csdn.net/hacker00011000/article/details/52176100
(1)固定位置
(2)随机选取基准, 选取后 将该位置的基准与最左端进行交换,即可以用常规的快排方法进行排序
(3)三数去中法。使用最左端,最右端和中间位置的三个元素,将这个三个元素进行排序,选取中间位置的数做为基准数,将其与最左端进行交换,即可用普通的快排进行处理。
四种优化方法
优化1:当待排序序列的长度分割到一定大小后,使用插入排序
优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割
具体过程:在处理过程中,会有两个步骤
第一步,在划分过程中,把与key相等元素放入数组的两端
第二步,划分结束后,把与key相等的元素移到枢轴周围
void QSort(int arr[],int low,int high) { int first = low; int last = high; int left = low; int right = high; int leftLen = 0; int rightLen = 0; if (high - low + 1 < 10) { InsertSort(arr,low,high); return; } //一次分割 int key = SelectPivotMedianOfThree(arr,low,high);//使用三数取中法选择枢轴 while(low < high) { while(high > low && arr[high] >= key) { if (arr[high] == key)//处理相等元素 { swap(arr[right],arr[high]); right--; rightLen++; } high--; } arr[low] = arr[high]; while(high > low && arr[low] <= key) { if (arr[low] == key) { swap(arr[left],arr[low]); left++; leftLen++; } low++; } arr[high] = arr[low]; } arr[low] = key; //一次快排结束 //把与枢轴key相同的元素移到枢轴最终位置周围 int i = low - 1; int j = first; while(j < left && arr[i] != key) { swap(arr[i],arr[j]); i--; j++; } i = low + 1; j = last; while(j > right && arr[i] != key) { swap(arr[i],arr[j]); i++; j--; } QSort(arr,first,low - 1 - leftLen); QSort(arr,low + 1 + rightLen,last);
优化3:优化递归操作
快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化
优点:如果待排序的序列划分极端不平衡,递归的深度将趋近于n,而栈的大小是很有限的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多。优化后,可以缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提高性能。
优化4:使用并行或多线程处理子序列