欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

快速排序的实战实现及其改进方法探讨

最编程 2024-02-22 09:28:00
...

相比于冒泡排序的改进点:

在前面学过的冒泡排序中,由于扫描过程只对相邻的两个元素进行比较,因此在互换两个相邻元素时,只能消除一个逆序,如果能通过两个(不相邻的)元素的交换,消除待排序记录 中的多个逆序,则会大大加快排序的速度,快速排序方法中的一次交换可能消除多个逆序

文字描述:

a.每一轮排序选择一个基准点(pivot)进行分区:

1:让小于基准点的元素进入一个分区,大于基准点的元素进入另一个分区
2:当分区完成后,基准点元素的位置就是其最终位置

b.在子分区内重复以上过程,直至子分区元素个数少于等于1,这体现的是分而治之的思想(divide-and-conquer)

快速排序的实现方式:

单边循环快排(lomuto洛穆托分区方案)

1:选择最右元素作为基准点元素
2:j指针负责找到比基准点小的元素,一旦找到则与i进行交换
3:i指针维护小于基准点元素的边界,也是每次交换的目标索引
4:最后基准点与i交换,i即为分区位置

一次快速排序代码的实现如下:

package bin_find;
import java.util.Arrays;

public class quick_sort {
    public static void main(String[] args) {
        int[] a={5,3,7,2,1,9,8,4};
        quick(a,0,a.length-1);
    }
    public static int quick(int []a,int l,int h){
        int pv=a[h];//基准点元素
        //i相当于是一个分割线,位于它左边的所有元素都是小于基准元素的,位于它右边的元素都是大于基准元素的
        int i=l;
        //j变量根据索引从左往右一旦找到比当前基准元素小的元素,则与索引值为i的元素进行交换
        for(int j=l;j<h;j++){
            if(a[j]<pv){
                swap(a,i,j);
                //交换完成,i++  --->下一个需要排序的元素
                i++;
            }
        }
        //基准点和i的元素进行交换,一轮分区完成
        swap(a,h,i);
        System.out.println(Arrays.toString(a));
        return 0;
    }
    public static void swap(int arr[],int i,int j){
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
}

输出如下:

[3, 2, 1, 4, 7, 9, 8, 5]

一轮排序的步骤如下:

一轮排序图解如下所示:

pv:表示基准点元素

第一次:由于5比4大,所以不发生交换,j继续向后移动寻找比4小的元素

第二次:

j移动到元素3的位置,3比4小,发生交换,i++,结果如下所示:

第3次:当j移动到元素2的位置,2比4小,所以发生交换,且i++

j再移动直到找到1,元素1小于4,将i和j指向的元素进行交换,如下所示:

此时j已经移动到了基准点前一个位置:

最后只需要将i和pv指向的元素进行交换,则一轮快排结束

通过一轮快排结束的结果我们会发现,以i指向的元素作为基准,位于它左边的元素都是小于它的,位于它右边的元素都是大于它的

完整快速排序代码如下:

package bin_find;
import java.util.Arrays;

public class quick_sort {
    public static void main(String[] args) {
        int[] a={5,3,7,2,1,9,8,4};
        recurrence(a,0,a.length-1);
    }

    //该方法的作用是:递归实现分区排序直至整个数组有序,通过每轮排序返回的下标值进而确定这次排序的范围
    public static void recurrence(int []a,int l,int h){
        if(l>=h){//递归结束的条件:当区间只有一个元素或者没有元素时,有等号的原因是由于当l恰好等于返回的索引值,此时p再减1就使得h<l
            return;
        }
        int p=quick(a,l,h);//基准点的索引值
        recurrence(a,l,p-1);//左边分区范围:第一个元素~基准元素的前一个元素
        recurrence(a,p+1,h);//右边分区范围:基准元素的下一个元素~最后一个元素
    }
    public static int quick(int []a,int l,int h){
        int pv=a[h];
        int i=l;
        for(int j=l;j<h;j++){
            if(a[j]<pv){
                //优化1:
                if(i!=j) {//当j找到小于基准点的值和i指向的元素不是同一个元素时,发生交换,这样能够减少不必要的交换次数
                    swap(a, i, j);
                }
                i++;
            }
        }
        //优化2:
        if(i!=h){//当基准点元素和i指向的元素不是同一个元素时,发生交换,这样能够减少不必要的交换次数
        swap(a,h,i);
        }
        System.out.println(Arrays.toString(a));
        return i;
    }
    public static void swap(int arr[],int i,int j){
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
}

输出:

[3, 2, 1, 4, 7, 9, 8, 5]
[1, 2, 3, 4, 7, 9, 8, 5]
[1, 2, 3, 4, 7, 9, 8, 5]
[1, 2, 3, 4, 5, 9, 8, 7]
[1, 2, 3, 4, 5, 7, 8, 9]
[1, 2, 3, 4, 5, 7, 8, 9]

双边循环快排(并不完全等价于hoare霍尔分区方案)

1:选择最左元素作为基准点元素
2:j指针负责从右向左找比基准点小的元素,i指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至i,j相交
3:最后基准点与i(此时i与j相等)交换,i即为分区位置

一轮双边循环快排图解如下:

j此时指向的元素为4比5小,因此j不需要移动了,此时需要等待i移动直至找到大于基准元素的元素:

此时i找到了大于基准元素的元素,i和j指向的元素进行交换:

j移动找到1,小于基准元素,等待i找到大于基准元素的元素9时:

推荐阅读