算法|快速排序再次实现

距离上次实现快速排序已经过去很久了,这次再次实现一下快排,并且不按照教程的方式进行实现,自己琢磨了个原型,当然优化到最后就是常见的快排实现方式了。


实现原理:数组里面随便选择一个数(这里选择的是中间那个数),作为比较数,然后把大于比较数的放到左边,把小于比较数的放到右边,然后以比较数的下标为界可以将原数组分为两个数组,这两个数组重复上述操作,直到满足终止条件(数组长度==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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值