快速排序算法、时间复杂度和稳定性

快速排序

算法原理

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

解法一

算法分析

排序的思想就是先选择一个基数,通过一趟排序将数据分割成两个部分,比基数小的放在基数前面,比基数大的放在基数后面。再对这两个部分采用同样的排序规则进行排序。

代码实现
/**   
 * @Title: quickSort   
 * @Description: 快速排序,使用list实现  
 * @param: items      
 */  
public static void quickSort(List<Integer> items) {
    //用于存放小于基数的元素
    List<Integer> smaller = new ArrayList<Integer>();
    //用于存放等于基数的元素
    List<Integer> same = new ArrayList<Integer>();
    //用于存放大于基数的元素
    List<Integer> larger = new ArrayList<Integer>();

    //选择中间元素作为基数(选择任意元素作为基数都可以)
    Integer choosenItem = items.get(items.size() / 2);
    //遍历所有元素,根据元素和基数的大小关系,将元素添加到三个list中
    for (Integer i : items) {
        if (i < choosenItem) {
            smaller.add(i);
        } else if (i == choosenItem) {
            same.add(i);
        } else {
            larger.add(i);
        }
    }

    if (smaller.size() > 1) {
        //递归排序小于基数的元素
        quickSort(smaller);
    }
    if (larger.size() > 1) {
        //递归排序大于基数的元素
        quickSort(larger);
    }

    //清除list中原来的元素
    items.clear();
    //按照大小顺序把元素添加到list中
    items.addAll(smaller);
    items.addAll(same);
    items.addAll(larger);
}

解法分析

这种解法紧跟快速排序的定义,将小于、等于、大于基数的元素提取出来,最后再将他们组装起来,很容易理解。

但是这种解法会创建很多list,空间复杂度高,所以衍生出第二种解法。

解法二

算法分析

我们可以这样去实现快速排序:
- 1.以第零个元素为基数,设置两个变量i=0,j=n-1
- 2.用一个临时变量k来记录基数的值
- 3.j 向前遍历找到第一个小于k的元素,覆盖array[i]元素
- 4.i 向后遍历找到第一个大于k的元素,覆盖array[j]元素
- 5.再重复上面3、4两个步骤,直到i=j为止
- 6.将k赋值给array[i],这样就实现了array[i]左边的数都比array[i]小,右边的数都比array[i]大
- 7.再将array[i]左边的数和右边的数作为参数递归调用排序方法直到所有元素有序为止

重复上面的插入规则,直到无序部分为空为止。

代码实现

/**   
 * @Title: quickSort   
 * @Description: 快速排序
 * @param: array      
 */  
public static void quickSort(int[] array) {
    sort(array, 0, array.length -1);
}

/**   
 * @Title: sort   
 * @Description: 使用速排序,对array的left~right进行排序
 * @param: array
 * @param: left
 * @param: right      
 */  
private static void sort(int[] array, int left, int right) {
    //定义排序左边界
    int i = left;
    //定义排序右边界
    int j=right;
    //临时变量存放基数的值
    int k=array[i];

    while (i < j) {
        //j从右往左遍历找到第一个小于基数的值
        while (array[j] > k && i<j) {
            j--;
        }
        if (i<j) {
            //将找到的小于基数的值赋值给array[i]
            array[i++] = array[j];
        }
        //j从左往右遍历找到第一个大于基数的值
        while (array[i] < k && i<j) {
            i++;
        }
        if (i<j) {
            //将找到的大于基数的值赋值给array[j]
            array[j--] = array[i];
        }
    }
    //上面循环结束,array[i]左边的数都比array[i]小,右边的数都比array[i]大
    //将临时变量k赋值给array[i]
    array[i] = k;

    if (left < i-1) {
        //递归排序array[i]左边的元素
        sort(array, left, i-1);
    }
    if (i+1 < right) {
        //递归排序array[i]右边的元素
        sort(array, i+1, right);
    }
}

解法分析

这种解法使用两个变量从两端来遍历元素,采用赋值的方式来实现分治的思想,不容易理解。

但是这种解法空间复杂度很低,只需要创建三个变量,所以空间复杂度是O(1)。

时间复杂度和算法稳定性

最优的情况下空间复杂度为:O(logN)

最差的情况下空间复杂度为:O(N^2)

快速排序的平均时间复杂度是:O(N*logN)

因为在快速排序的时候,即使待排序元素可基数相等也需要移动待排序元素的位置使得有序,所以快速排序是不稳定的。

相关代码都在github上:https://github.com/LebronChenX/sort

喜欢这篇文章的朋友,欢迎长按下图关注公众号lebronchen,第一时间收到更新内容。
扫码关注

猜你喜欢

转载自blog.csdn.net/Leon_cx/article/details/81487451