排序算法名称 | 针对的应用情景 |
---|---|
快速排序 | 无序数组(对于基本有序数组和大量重复键值的数组复杂度上升至O(n2) |
随机速排 | 快速排序的优化,解决普通快排在部分有序数组中进行排序,每次取得的都是趋近最值 |
二路快排 | 随机速排的优化,解决数组中存在大量键值重复的情况以及基本有序数组 |
三路快排 | 二路排序的优化,把等于value的元素放在另一个区间内,不参与下次的排序。 |
快速排序
快速排序算法
思路:
- 从序列中挑选出一个元素(一般是第一个或者是最后一个)作为"基准"元素
- 把序列分成2个部分,其数值大于"基准"元素的元素放在"基准"元素的左边,否在放在"基准"元
素的右边,此时"基准"元素所在的位置就是正确的排序位置,这个过程被称为 partition(分区) - 递归将"基准"元素左边的序列和"基准"元素右边的序列进行partition操作
缺点:
- 当partition操作时,如果每次取得的的第一个元素趋近于最值,使得分治的任务极度不平衡,情况最坏时,快速排序算法的复杂度将上升到O(n2)
- 存在大量重复键值时,同样会出现分治任务很不平衡的情况
package Sort;
/**
* 快速排序
* 时间复杂度:O(nlogn) ;空间复杂度:O(1)
* 原地排序,非稳定排序
*
* 最好情况下时间复杂度为O(nlogn),最坏情况下时间复杂度为O(n2)【在有序的情况下】;平均时间复杂度为O(nlogn)
*快排要点在于找到【哨兵/分界点】,使小于该分界点的数在其左边,大于的其在右边
* 分治思想,自顶向下
*/
public class QuickSort{
public void sort(int[] nums){
quicksort(nums,0,nums.length-1);
}
private void quicksort(int[] nums, int left, int right) {
if(left >= right)
return;
int partition = getPartition(nums,left ,right);
quicksort(nums, left, partition -1);
quicksort(nums, partition+1, right);
}
//找哨兵/分界点
private int getPartition(int[] nums, int left, int right) {
int partition = nums[right];
int i=left , j=left;
for (;i<nums.length-1;i++){
if (nums[i] < partition ){
swap(nums ,i , j);
j++;
}
}
swap(nums, j , right);
return j;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
随机快排
思路:
- 在每次partition的过程中,
将left
到right-1
之间的随机选取一个元素,与right进行位置交换,这样就解决了快排中如果数组部分有序,数组划分不平衡的情况
int ranNum = left + (int)(Math.random()*(right-left+1));
//把随机数作为索引,将索引对应值与最后一位值 right 交换
swap(arr,right,ranNum);
缺点
- 当数组中存在大量重复键值的时候任然会出现算法复杂度上升的情况
双路快排
二路快速排序
二路快速排序
思路:
- 从左边和右边分别遍历,当左边遍历到第i位置时,所对应的元素大于v,从右边遍历的元素遍历到第j个位置时,所对应的元素arr[j]<v,交换i和j的位置直到i>j.适用于有大量重复键值的情形和数组基本有序的问题
public class QuickSort2 {
private void quickSort(int[] arr, int l, int r) {
if (l >= r){
return;
}
int position = partition(arr,l,r);
quickSort(arr,l,position-1);
quickSort(arr,position+1,r);
}
int partition(int[] arr, int left, int right){
int ranNum = left + (int)(Math.random()*(right-left+1));
//把随机数作为索引,将索引对应值与最后一位值 right 交换
swap(arr,right,ranNum);
// arr[left+1...i) <= v; arr(j...right] >= v
int value = arr[left];
int i = left+1, j = right;
while( true ){
while( i <= right && arr[i] < value) {
i ++;
}
while( j >= left+1 && arr[j] > value ) {
j --;
}
if( i > j ) {
break;
}
swap(arr,i,j);
i ++;
j --;
}
swap(arr, left, j);
return j;
}
}
三路快排
三路快速排序算法
三路快速排序算法
三路快速排序算法
三路快速排序算法
思路:
- 之前的快速排序是将数组划分为 分成<=v和>v或者是<v和>=v的两个部分,而三路快排是将数组划分为分成三个部分:`<v、=v、>v
public class QuickSort3 {
private void quickSort(int[] arr, int l, int r) {
if (l >= r)
return;
int position = partition(arr,l,r);
quickSort(arr,l,position-1);
quickSort(arr,position+1,r);
}
int partition(int[] arr,int left,int right){
int ranNum = left + (int)(Math.random()*(right-left+1));
//把随机数作为索引,将索引对应值与最后一位值 right 交换
swap(arr,right,ranNum);
// arr[left+1...i) <= value; arr(j...right] >= value
int value = arr[left];
// 将<v的分界线的索引值lt初始化为第一个元素的位置(也就是<v部分的最后一个元素所在位置)
int lt = left;
//将>value的分界线的索引值gt初始化为最后一个元素right的后一个元素所在位置
// (也就是>value部分的第一个元素所在位置)
int gt = right+1;
// 将遍历序列的索引值i初始化为 left+1
int i = left+1;
while (i < gt){
if (arr[i] < value){
swap(arr, lt+1, i);
//移动指针
i++;
//增加<value的部分
lt++;
}else if ( arr[i] > value){
swap(arr,gt-1,i);
//增加>value的部分
gt--;
//注意,这里不需要移动i,之前移动i是因为需要增加<value的部分
//而保持=i部分不懂,所以i和lt都需要移动
}else{
//增加=v的部分
i++;
}
}
return i;
}
}