目录
一、排序方法:
快速排序,双路快速排序,三路快速排序
二、快速排序
1.实现原理:
- 选择一个数作为标定点(如图中的4)
- 同下一个元素进行比较(通过i进行遍历,<v放在l和j 之间,>v放在j之后)
- 完成遍历后,j左右的元素分别是小于和大于它的元素集合
- 最后将j和l处的元素进行交换,完成排序
2.实现代码:
package IMUHERO;
import java.util.*;
public class QuickSort {
public QuickSort(){}
public static void sort(Comparable []arr){
int n=arr.length;
sort(arr,0,n-1);
}
private static void sort(Comparable[]arr,int left,int right){
if (left>=right)return;
int middle=parttion(arr,left,right);//parttion的功能是找到当前元素应该放的位置下标
sort(arr,left,middle);
sort(arr,middle+1,right);
}
private static int parttion(Comparable[]arr,int left,int right){
//方法中:i是用来知识当前遍历到的元素,最终遍历整个数组
//v 表示我们选择的元素下标,比如选择第一个元素,那么下标为0
//j 表示小于v的元素集合的最右边元素
int v=left;
Comparable e=arr[v];//存储选择的选择的元素内容
int j=left;
//整个排序是:v~j~i~right,其中
// v+1~j存贮小于e的元素,
// j+1~i存储大于e的元素,
// i~right是还没有经过比较的元素
for (int i=left+1;i<=right;i++){
if (arr[i].compareTo(e)<0){
j++;
swap(arr,i,j);
}
}
swap(arr,v,j);
return j;
}
private static void swap(Comparable []arr,int i,int j){
Comparable temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
3.存在的问题:
①.可能出现分配极度不均匀的情况
解决办法,随机选择数组中的一个元素
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
②非常多重复元素
造成分割不平衡
退化成O(n^2)
方法:重复元素放在两边:双路快速排序
③性能提升
元素数量较少时可以使用插入排序,提高性能
三、双路快速排序(代码中解决以上三个问题)
1.基本原理
- 设置左右右边界j,使用i来进行遍历
- <v的元素放在l-i之间,>v的元素放在j-r之间
3.使用while()语句判断e是否<v或者e是否>v,是的话i++或者j++;
不是的话代表这i处的元素>=v,或者j处的元素<=v,在这种情况下交换二者的元素
4.这样可以保证在出现重复元素时,i和j也会有一次随机交换的过程,重复元素分配在两边!
完成交换之后i++,j—进行下一轮循环,直到i>j跳出大循环!
2.实现代码:
package IMUHERO;
import java.util.*;
public class QuickSort2 {
public QuickSort2(){ } //构造方法,什么都不做
//这个方法是公用的,给用户用
public static void sort(Comparable []arr){
int length=arr.length;
sort(arr,0,length-1);
}
//这个方法是私有的,程序员在此设计内部函数
private static void sort(Comparable []arr,int begin,int end){
// if(begin>=end)return; //递归结束条件,bengin和end是同一个元素,也就无需排序
if(end-begin<=15)
{InsertionSort.sort(arr,begin,end);
return;}
int div=parttion(arr,begin,end); //找到V应该放在的位置
sort(arr,begin,div-1); //对前半部分进行递归排序
sort(arr,div+1,end); //对后半部分进行递归排序
}
//这个方法用于找到V应该放的位置(从小到大)
private static int parttion(Comparable[]arr,int begin,int end){
//@函数使用:Math.random() 方法生成[0, 1)范围内的double类型随机数;
// @如无特殊需求,则使用(int)(Math.random()*n)的方式生成随机数即可
swap(arr,begin,(int)(Math.random()*(end-begin+1))+begin ) ; //将随机选择的数存入当前数组段的头位置中
Comparable V=arr[begin]; //V作为标记来比较大小
int i=begin+1; //i用于从begin到div向后遍历数组
int j=end; //j用于从end到向前遍历数组
while(true){
while (i<=end&&arr[i].compareTo(V)<0){ //特别注意,此处应该包含=的情况
i++;
}
while (j>begin&&arr[j].compareTo(V)>0){ //特别注意,此处应该包含=的情况
j--;
}
if (i>j)break;
swap(arr,i,j);
i++;
j--;
}
swap(arr,begin,j); //特别注意,此处应该交换j的值,因为上面swap后i++,j--,此时i指向目标位置的下一个位置,j指向目标位置!
return j;
}
//这个方法用于交换数组中的元素
private static void swap(Comparable[]arr,int i,int j){
Comparable temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
四、三路快速排序(更高速的改进)
1.基本原理
- 三路快排主要用于处理多个重复元素的情况
- 声明lt表示左边边界和gt右边边界
- Lt-i的区间存放==v的元素,l-lt之间存放<v的元素,gt-r的元素存放>v的元素
4.使用i进行遍历(i总是指向下一个要判断的位置),当e==v时,i++
4.当e<v时,swap(lt+1,i);
i++;
5.同理,当e>v时,swap(i,gt)
Gt--;
注意此时i不能++,因为更换过来的gt还没有经过判断
注意此时i不能++,因为更换过来的gt还没有经过判断
6.最后一步:交换v的元素swap(l,lt)
7.递归判断,划分成[l+1…lt]、[gt…r],两个位置
因此前面的计算主要是需要计算出lt和gt的位置
Sort(arr,l,lt)
Sort(arr,gt,r)
2.实现代码:
package IMUHERO;
import java.util.*;
public class QuickSort3Way {
public QuickSort3Way(){}
public static void sort(Comparable[]arr){
int length=arr.length;
sort(arr,0,length-1);
}
private static void sort(Comparable[]arr,int begin,int end){
if(end-begin<=15) {
InsertionSort.sort(arr, begin, end); //优化方法,当数值小于15的时候使用插入排序
return;
}
int ran=(int)Math.random()*(end-begin)+begin; //优化方法是,选择随机值作为标定值
swap(arr,begin,ran);
Comparable V=arr[begin]; //头元素用于比较
int lt=begin; //arr[begin+1...lt]<v
int gt=end+1; //arr[gt...end]>v
int i=begin+1; //arr[lt+1...i)=v
while(i<gt){
if (arr[i].compareTo(V)<0){
swap(arr,lt+1,i);
i++;
lt++;
}
if (arr[i].compareTo(V)>0){
swap(arr,i,gt-1);
gt--;
}
else{
i++;
}
}
swap(arr,begin,lt);
//递归调用,排序左右两边的元素
sort(arr,begin,lt);
sort(arr,gt,end);
}
//这个方法用于交换数组中的元素
private static void swap(Comparable[]arr,int i,int j){
Comparable temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
五、剑指offer26 最小K个数
1.题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
K大于数组长度时,返回null;
/**
*快速排序
*/
2.分析
(1)使用partition找到当前选择值对应的下标div
(2)div小于k代表要找的元素div右边,div大于k代表要找的元素div左边
(3)递归判断要找的元素,如果div正好等于k-1(注意题目说的是第k个元素,不是下标为k),就将结果输出到list结果中。
import java.util.ArrayList;
import java.util.List;
public class Solution {
ArrayList<Integer> list=new ArrayList<>();
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
int len=input.length;
if(k<=0)return list;
quickSort(input,0,len-1,k);
return list;
}
private void quickSort(int [] input,int start,int end,int k){
if(start>end)return;
int div=partition(input,start,end);
//注意:本题隐含条件是,k>len则返回null
if(div==k-1){
//此处直接给list赋值,并返回
for(int i=0;i<k;i++){
list.add(input[i]);
}
return;
}
if(div<k-1){
quickSort(input,div+1,end,k);
}
else{
quickSort(input,start,div-1,k);
}
return;
}
//[1,2,3]
private int partition(int [] input,int start,int end){
int V = input[start];
int lt=start+1;
int rt=end;
while(lt<=rt){
while(lt<input.length&&input[lt]<V){
lt++;
}
while(rt>=start&&input[rt]>V){
rt--;
}
//快速排序,此处必须lt<=rt!!!注意
if (lt<=rt) {
swap(input, lt, rt);
lt++;
rt--;
}
}
swap(input,start,lt-1);
return lt-1;
}
private void swap(int[]arr,int a,int b){
int tamp=arr[a];
arr[a]=arr[b];
arr[b]=tamp;
}
// public static void main(String[] args) {
// Sword29_GetLeastNumbers_Solution s29=new Sword29_GetLeastNumbers_Solution();
// int input[]={5,3,8,5,3,7,9,1,2,3};
// List list=s29.GetLeastNumbers_Solution(input,4);
// System.out.println(list);
// }
}
注:关于排序的所有代码都放在我的Github上,有需要的同学可以自行Fork,如果觉得还不错,可以打个☆Star哦~~~