快排主要在于划分这个步骤,即随机选定数组中一个数(pivot),代码中选的是数组的最后一个,将大于这个数的元素放数组左边,小于这个数的元素放数组右边。思路:我们将左边的指针和pivot进行交换,三种边界情况都会符合
边界:
1、pivot恰好是数组中最小的数时,pivot将与第一个数进行交换
2、pivot恰好是最大的数时,pivot相当于和自己做了交换一样
3、当指针i和j交错之后,i指向的元素就大于pivot了,所以直接进行交换就可以了
public int partition(int[] arr,int start,int end) {
int p=end;
int i=start-1,j=end;
while(true) {
while(arr[++i]<arr[p]);
while(j>0&&arr[--j]>arr[p]);
if(i>=j)
break;
else
swap(arr,i,j);
}
swap(arr,p,i);
return i;//一定要注意是返回i不是p,这里仍然是引用和值的问题
}
public void swap(int[] arr,int i,int j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
快排其实就是一个分治法加划分的一个算法,当划分数组只剩下一个元素时,则排序就已完成。
public static void quickSort(int[] arr,int start,int end) {
if(start>=end)
return;
int index=partition(arr,start,end);
quickSort(arr,index+1,end);
quickSort(arr,start,index-1);
}
快排的改进
方法1:pivot取数组中几个元素的中位数,如取头、中、尾三个元素的中位数,代码如下,swap这个函数交换的是编号指向的元素的值,不是真的交换编号
public static int media(int[] arr,int start,int end){
int p=(start+end)/2;
if(arr[p]>arr[end])
swap(arr,p,end);
if(arr[start]>arr[p])
swap(arr,start,p);
if(arr[p]>arr[end])
swap(arr,p,end);
swap(arr,p,end);
return end;
}
public static int partition(int[] arr,int start,int end) {
int p=media(arr,start,end);
int i=start-1,j=end;
while(true) {
while(arr[++i]<arr[p]);
while(j>0&&arr[--j]>arr[p]);
if(i>=j)
break;
else
swap(arr,i,j);
}
swap(arr,p,i);
return i;
}
方法二:就是在处理小划分时,插入排序比快排效率更高,所以在小划分的数组直接用插入排序会更好,切割点可以为3,10,20等,Knuth推荐使用9,在本次代码中我们选定10,当子数组的数据项小于101个时,我们采用插入排序,代码如下
public static void insertSort(int[] arr){
if(arr.length<=1)
return;
for(int i=1;i<arr.length;i++){
for(int j=i;j>0;j--){
if(arr[j]<arr[j-1])
QuickSort.swap(arr, j, j-1);//swap是在QuickSort这个类里
}
}
}
public static void quickSort(int[] arr,int start,int end) {
if(end-start<=10){
InsertSort.insertSort(arr);//inserSort是在InsertSort类里
return;
}
int index=partition(arr,start,end);
quickSort(arr,index+1,end);
quickSort(arr,start,index-1);
}
上面说明了快排主要是希望将partition用在这个题目中,数组中出现次数超过一半的数即为数组的中位数,所以用partition来找到其中位数,partition主要是选中数组中的一个数字,使位于其左边的小于他,位于其右边的大于他,当这个选中的数字位置大于n/2(n为数组大小),则中位数在其左边,小于则在其右边,等于的话刚好就是其中位数。注意几个条件判断,代码如下(时间复杂度O(n))。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(checkInvalidArray(array))
return 0;
int m=partition(array,0,array.length-1);
int start=0,end=array.length-1;
int mid=array.length/2;
while(m!=mid){
if(m<mid){
start=m+1;
m=partition(array,start,end);//递归传参数要尤其注意
}
else{
end=m-1;
m=partition(array,start,end);
}
}
int result=array[mid];
if(!checkMorethanHalf(array,result))
return 0;
return result;
}
//判断中位数出现次数是否超过一半
public boolean checkMorethanHalf(int[] arr,int r){
int times=0;
for(int i=0;i<arr.length;i++){
if(arr[i]==r)
times++;
}
return times*2>arr.length;
}
//判断输入是否有效
public boolean checkInvalidArray(int[] arr){
boolean b=false;
if(arr==null||arr.length==0)
b=true;
return b;
}
public int partition(int[] arr,int start,int end){
int p=end;
int i=start-1,j=end;
while(true){
while(arr[++i]<arr[p]);
while(j>0&&arr[--j]>arr[p]);
if(i>=j)
break;
else
swap(arr,i,j);
}
swap(arr,i,p);
return i;
}
public void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
还有一种方法,遍历数组时保存两个值,一个记录当前值,一个记录次数,当下一个与当前值相等时,次数++,否则次数--,当次数为0时,则取下一值作为当前值,并将次数置1,出现次数最多的值就是最后一个置1的值,再判断其出现次数是否大于数组大小的一半
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(checkInvalidArray(array))
return 0;
int a=array[0];
int times=1;
for(int i=1;i<array.length;i++){
if(times==0){
times=1;
a=array[i];
}
else{
if(array[i]==a)
times++;
else
times--;
}
}
if(!checkMorethanHalf(array,a))
return 0;
return a;
}
//判断中位数出现次数是否超过一半
public boolean checkMorethanHalf(int[] arr,int r){
int times=0;
for(int i=0;i<arr.length;i++){
if(arr[i]==r)
times++;
}
return times*2>arr.length;
}
//判断输入是否有效
public boolean checkInvalidArray(int[] arr){
boolean b=false;
if(arr==null||arr.length==0)
b=true;
return b;
}
}