经典排序算法[1]-快排
1、荷兰国旗问题
荷兰国旗问题又称为三边界问题,先来看看数据分区问题:假设有一个数组arr,给定一个数,将数组中小于该数和大于该数的数据各放在数组的两边
算法流程:
假设数组[5,3,7,5,3,4,1] 给定的数是3
- 假设数组最后会被划分成[小于等于3的区域][大于3的区域],构造小于等于3的数据区的右边界,初始右边界是-1,扫描数组进行如下比较
如果 扫描的数据<=给定数据
当前数据和小于等于3区域的数据右边界的下一个元素交换,并移动边界【也就是边界+1】,意味着小于等于3的数据区变大了
否则 不做操作
扫描下一个数据
- 1)、扫描数据5时,5>3 直接扫描下一个
- 2)、扫描3时,3<=3,此时小于等于3的区域右边界是-1,先将右边界+1变成0,再将3和0位置数据交换变成
[3,5,7,5,3,4,1]
- 3)、重复该操作,不断扩充小于等于3的数据边界,最终能达到数据分散在两侧,但是不是有序的
假设需要分为三个区[小于区][等于区][大于区],其中:小于区的所有元素<等于区的所有元素<大于区的所有元素,解法和上面的相似,算法流程是:
[3,2,4,0,4,6,7] 给定数字是4 //实际情况这个给定数字可以随机从数组选择
- 需要设置小于区的右边界初始值为-1,大于区的左边界初始值为数组的长度,扫描的时候两个边界向中间扩展,以上面的例子为例,起始左边界为-1 右边界为8,只是当数据和右边界交换后,需要重复判断交换后的数
如果 扫描的数据<给定数据
当前数据和小于区域的数据右边界的下一个元素交换,并移动边界【也就是边界+1】
扫描下一个数据
如果 扫描的数据>给定数据
当前数据和大于区域的数据左边界的上一个元素交换,并移动边界【也就是边界+1】
下一次继续扫描比较当前位置的数 //英文当前位置的数为交换回来的数,还未和给定数字比较
否则
扫描下一个数据
代码实现:**
public class ThreeBound {
/**
* 数据分区,假设有一个数组,给定一个数,将数组中小于该数的的大于该数的数据各放在数组的两边
* 要求 空间复杂度O(1) 时间复杂度 O(N)
*
* 假设数组[5,3,7,5,3,4,1] 给定的数是3
*
* 1、构造小于等于3的数据边界,初始边界是-1,扫描数组进行如下比较
* 如果扫描的数据<=给定数据
* 当前数据和小于等于3的数据边界的下一个元素交换,并移动边界【也就是边界+1】
* 扫描下一个数据
*
* 1、扫描数据5时,5>3 直接扫描下一个
* 2、扫描3时,3<=3,此时小于等于3的区域边界是-1,所以3需要和0位置数据交换变成
* [3,5,7,5,3,4,1]
* 3、重复该操作,不断扩充小于等于3的数据边界,最终能达到数据分散在两侧,但是不是有序的
*
*
* 假设需要分为三个区,大于 等于 小于
* 则需要设置小于的边界初始值为-1,大于区的初始边界为数组的长度,扫描的时候边界向中间扩展,只是当数据和右边界交换后,需要重复判断交换后的数
* 例如
* [3,2,4,0,4,6,7] 给定数字是4
*
* 起始左边界为-1 右边界为8
* 扫描到6时,需要和7交换,交换后为[3,2,4,0,4,7,6],由于7原来在6后面,未被扫描,所以扫描指针等从现在的7位置开始扫描才能保证不漏数据
*
*
*/
//实现三边界问题,荷兰国旗问题
//以数组最后一个元素划分
public static int[] ThreeBound(int[] data,int L,int R){
if(L > R){
return new int[]{-1,-1};
}
if(L == R){
return new int[]{L,R};
}
int less = L-1;
int more = R; //先固定最后一个元素,扫描后将其和大于区的边界交换即可
int index = L;
while(index<more){
if(data[index] == data[R]){
index++;
}else if(data[index]<data[R]){
swap(data,index++,++less);
}else{
swap(data,index,--more);
}
}
swap(data,more,R); //
return new int[]{less+1,more};
}
private static void swap(int[] data, int index, int i) {
if(i==index){
return;
}else {
data[index] = data[index] ^ data[i];
data[i] = data[index] ^ data[i];
data[index] = data[index] ^ data[i];
}
}
}
2、 快排
快排的整体思想和荷兰国旗是一样的,整体的思路如下:
/**
* 快排的思想
*
*
* 借用的是三边界问题,假设现在有一个数组,我们以数组最后一个元素进行边界划分
* 得到的是
* [小于区][等于区][大于区][末尾元素]
* 再将末尾元素和大于区的第一个元素交换,得到
* [小于区域][等于区][末尾元素][大于区]
*
* 可以看到入如果将原数组进行排序,等于区在排好序数组的位置就是这样,所以可以递归小于区和大于区就可以完成排序
*
* 优化点:
* 有可能出现的数组是7654321这种逆序数组,如果都拿最后一个元素,最后的时间复杂度是O(N^2),所以递归的时候先随机选择一个元素
* 和末尾交换,再调用三边界问题得到边界,再递归,转化成等概率问题,时间复杂度就是O(NlogN)
*
*/
代码实现:
public class QuickSort {
//实现三边界问题,荷兰国旗问题
//以数组最后一个元素划分
public static int[] ThreeBound(int[] data,int L,int R){
if(L > R){
return new int[]{-1,-1};
}
if(L == R){
return new int[]{L,R};
}
int less = L-1;
int more = R; //先固定最后一个元素,扫描后将其和大于区的边界交换即可
int index = L;
while(index<more){
if(data[index] == data[R]){
index++;
}else if(data[index]<data[R]){
swap(data,index++,++less);
}else{
swap(data,index,--more);
}
}
swap(data,more,R); //
return new int[]{less+1,more};
}
private static void swap(int[] data, int index, int i) {
if(i==index){
return;
}else {
data[index] = data[index] ^ data[i];
data[i] = data[index] ^ data[i];
data[index] = data[index] ^ data[i];
}
}
public static void quickSort(int[] data,int L,int R){
if(L>=R){
return;
}
int length = R-L+1;
int index = new Random().nextInt(length);
swap(data,L+index,R);
int[] tmpResult = ThreeBound(data, L, R);
quickSort(data,L,tmpResult[0]-1);
quickSort(data,tmpResult[1]+1,R);
}
public static void main(String[] args) {
int[] data = {5, 4, 3, 2, 1, 2, 3, 4, 5, 3};
// process(data,0,data.length-1);
// int[] res = ThreeBound(data, 0, data.length - 1);
// System.out.println(Arrays.toString(res));
quickSort(data,0,data.length-1);
System.out.println("-----------------------------------");
System.out.println(Arrays.toString(data));
}
}