距离上次实现快速排序已经过去很久了,这次再次实现一下快排,并且不按照教程的方式进行实现,自己琢磨了个原型,当然优化到最后就是常见的快排实现方式了。
实现原理:数组里面随便选择一个数(这里选择的是中间那个数),作为比较数,然后把大于比较数的放到左边,把小于比较数的放到右边,然后以比较数的下标为界可以将原数组分为两个数组,这两个数组重复上述操作,直到满足终止条件(数组长度==1时或其他什么的)返回,终止递归。
写这种递归的算法,可以先分步的写出来,也就是像之前一样,先实现递归的第一个步骤:
这里先实现第一步——让指针自动找到符合要求的数,即让指针找到大于比较数的数or小于比较数的数
private static int Hsp(int[] arr, int left, int right) {
int r = right;
int l = left;
//随便选个数作为比较数,这里选择中间那个数
int pick = (right + left) / 2;
int pivot = arr[pick];
//如果条件不满足,那么就不断让指针向下移位,直至满足或者出界
while (arr[l] <= pivot) {
l++;
if (l == right) {
break;
}
}
//此处同理
while (arr[r] >= pivot) {
r--;
if (r == left) {
break;
}
}
}
这样的话,就可以让两个指针指向需要数上面了,那么如何让两个数去到该去的位置(大数放到比较数右边,小数放到比较数右边)呢?方法很简单,就是将这两个数进行交换。为此,可以写一个交换的方法。
static void exchange(int[] arr, int right, int left) {
int temp;
temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
}
但是只要是找到两个满足条件的数就可以交换吗?显然不是的,需要满足一些判断,即如果两个指针相遇或相背(相遇后间隔一段距离),则本次不可交换,而且指针如果相遇或者相背,说明本次的交换也该结束了,该进行下一步的比较数交换了,不过这个放到后面再说。
判断后进行交换的代码:
private static int Hsp(int[] arr, int left, int right) {
int r = right;
int l = left;
//随便选个数作为比较数,这里选择中间那个数
int pick = (right + left) / 2;
int pivot = arr[pick];
//如果条件不满足,那么就不断让指针向下移位,直至满足或者出界
while (arr[l] <= pivot) {
l++;
if (l == right) {
break;
}
}
//此处同理
while (arr[r] >= pivot) {
r--;
if (r == left) {
break;
}
}
//边界判断,如果两个指针相遇或相背(相交后间隔一段距离),则结束,不进行交换
if (l >= r) {
break;
}
//进行交换
exchange(arr, r, l);
}
以上的代码是进行1次交换的过程,将上面的功能再次增强,改为将满足条件的所有数都进行交换,也就是加一个while循环:
private static int Hsp(int[] arr, int left, int right) {
int r = right;
int l = left;
//随便选个数作为比较数
int pick = (right + left) / 2;
int pivot = arr[pick];
//不断地交换
while (l < r) {
//如果条件不满足,那么就不断让指针向下移位,直至满足或者出界
while (arr[l] <= pivot) {
l++;
if (l == right) {
break;
}
}
//此处同理
while (arr[r] >= pivot) {
r--;
if (r == left) {
break;
}
}
//边界判断,如果两个指针相遇或相背(相交后间隔一段距离),则结束
if (l >= r) {
break;
}
exchange(arr, r, l);
}
当所有的非比较数都交换完后,需要做的就是交换比较数了,把比较数放到合适的位置,达到左边都小于它,右边都大于它。
如何做到呢?首先要进行一些分析
指针一共会出现这么三种情况
然后将这三种情况翻译成为代码:
//交换比较数
//根据分析可得,当xx时,需要与左交换,当yy时,需要与右交换,当zz时,不需要交换
//我是拿了几个实例推导了一下,并没有从理论证明,不过把图画出来后,我发现确实如此,只有这三种情况
if (pick > r) {
if(pick < l){
return pick;
}
exchange(arr, pick, l);
//返回比较数所在的下标
return l;
} else {
exchange(arr, pick, r);
return r;
}
然后,把上述的功能代码拼成一个方法:
private static int Hsp(int[] arr, int left, int right) {
int r = right;
int l = left;
//随便选个数作为比较数
int pick = (right + left) / 2;
int pivot = arr[pick];
//不断地交换
while (l < r) {
//如果条件不满足,那么就不断让指针向下移位,直至满足或者出界
while (arr[l] <= pivot) {
l++;
if (l == right) {
break;
}
}
//此处同理
while (arr[r] >= pivot) {
r--;
if (r == left) {
break;
}
}
//边界判断,如果两个指针相遇或相背(相交后间隔一段距离),则结束
if (l >= r) {
break;
}
exchange(arr, r, l);
}
//交换比较数
//根据分析可得,当xx时,需要与左交换,当yy时,需要与右交换,当zz时,不需要交换
//我是拿了几个实例推导了一下,并没有从理论证明,不过把图画出来后,我发现确实如此,只有这三种情况
if (pick > r) {
if(pick < l){
return pick;
}
exchange(arr, pick, l);
//返回比较数所在的下标
return l;
} else {
exchange(arr, pick, r);
return r;
}
}
好了,我们描述一下这个方法吧,这个方法要求传入一个数组以及左右边界的下标,返回一个比较数的下标,且对数组进行了大致排序,即比较数左边都小于比较数,比较数右边都大于比较数。
有了这个方法后我们有什么用呢?答:可以进行递归操作了。
static void hspSort(int[] arr, int lo, int hi) {
//终止条件,即当左右边界相背或者相等时(就是只有一个元素的时候)
if (lo >= hi) {
return;
}
int partition = Hsp(arr, lo, hi);
hspSort(arr, lo, partition - 1);
hspSort(arr, partition + 1, hi);
}
以下是完整代码:
static void hspSort(int[] arr, int lo, int hi) {
//排序方法----把数组中的数同第一个元素比较大小,大的放左边,小的放右边
if (lo >= hi) {
return;
}
int partition = Hsp(arr, lo, hi);
hspSort(arr, lo, partition - 1);
hspSort(arr, partition + 1, hi);
}
private static int Hsp(int[] arr, int left, int right) {
int r = right;
int l = left;
//随便选个数作为比较数
int pick = (right + left) / 2;
int pivot = arr[pick];
//不断地交换
while (l < r) {
//如果条件不满足,那么就不断让指针向下移位,直至满足或者出界
while (arr[l] <= pivot) {
l++;
if (l == right) {
break;
}
}
//此处同理
while (arr[r] >= pivot) {
r--;
if (r == left) {
break;
}
}
//边界判断,如果两个指针相遇或相背(相交后间隔一段距离),则结束
if (l >= r) {
break;
}
exchange(arr, r, l);
}
//交换比较数
//根据分析可得,当xx时,需要与左交换,当yy时,需要与右交换,当zz时,不需要交换
//我是拿了几个实例推导了一下,并没有从理论证明,不过把图画出来后,我发现确实如此,只有这三种情况
if (pick > r) {
if(pick < l){
return pick;
}
exchange(arr, pick, l);
//返回比较数所在的下标
return l;
} else {
exchange(arr, pick, r);
return r;
}
}
//工具方法
static void exchange(int[] arr, int right, int left) {
int temp;
temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
}
过于繁琐了对吗?主要繁琐的地方在于返回比较数下标那个地方,就是说要根据不同的情况去移动比较数,很麻烦,可不可以简单一点?可以的,只要把比较数固定在其他位置,就可以变得简单,比如将比较数固定为数组起始元素。
代码:
private static int partition(int[] arr, int lo, int hi) {
int right = hi + 1;//把指针向右边放一个是为了下面可以正常循环起来
int left = lo;
int i = arr[lo];
while (true) {
//!!如果指针数满足条件,就会不断循环,至不满足条件为止,而不满足条件的指针所指的数,就是我们目标数(自动寻数,很妙)
while (arr[--right] > i) {
if (right == lo) {//防止溢出
break;
}
}
while (arr[++left] < i) {
if (left == hi) {
break;
}
}
if (left >= right) {
break;
} else {
exchange(arr, right, left);
}
}
//将比较数移动至两组数中间
//这个算法很妙的一点在于此,交换结束后,这个交换让比较数移动到中间,和right交换是因为right指向的数是小于比较数的
//此处优化后只需要一行代码即可
exchange(arr, right, lo);
return right;
}