小橙书阅读指南(六)——快速排序和三向切分快速排序

算法描述:快速排序是一种分治的排序算法。它将数组分为两个子数组,并将两部分独立的排列。快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将子数组归并以将整个数组排序;而快速排序将数组排序的方式则是当两个子数组都有序时整个数组也就自然有序了。

算法图示:

算法解释:选择标的元素(5)并且便利数组,将素有小于5的元素都安排在它的左侧,而大于5的元素都安排在它的右侧。之后再通过递归的方法分别处理左边的子数组和右边的子数组。

快速排序的算法难点在于尽量不要使用额外的存储空间(即保证原地切分)以及如何处理与标的元素(5)相等的元素。

Java代码示例:

package algorithms.sorting;

import algorithms.Sortable;
import algorithms.common.Arrays;
import algorithms.common.ArraysGenerator;

/**
 * Created by learnhow on 2018/8/17.
 */
public class Quick extends Arrays<Integer> implements Sortable<Integer> {
    @Override
    public void sort(Integer[] array) {
        sort(array, 0, array.length - 1);
    }

    // 递归体
    private void sort(Integer[] array, int lo, int hi) {
        if (lo >= hi) {
            return;
        }

        int part = partition(array, lo, hi);
        sort(array, lo, part - 1);
        sort(array, part + 1, hi);
    }

    // 切分算法
    private int partition(Integer[] array, int lo, int hi) {
        // 限制数组的遍历范围 array(lo, hi];
        int i = lo;
        int j = hi + 1;
        int temp = array[lo];
        while (true) {
            while (array[++i] < temp) {
                if (i == hi) {
                    break;
                }
            }
            while (array[--j] > temp) {
                if (j == lo) {
                    break;
                }
            }
            if (i >= j) {
                break;
            }
            exchange(array, i, j);
        }
        exchange(array, lo, j);
        return j;
    }

    public static void main(String[] args) {
        Integer[] arr = ArraysGenerator.generate(10, 0, 10);
        Quick quick = new Quick();
        quick.sort(arr);

        System.out.println(java.util.Arrays.toString(arr));
    }
}

Qt/C++代码示例:

void Quick::sort(int *arr, int lo, int hi)
{
    if (lo >= hi) {
        return;
    }
    int part = partition(arr, lo, hi);
    sort(arr, lo, part - 1);
    sort(arr, part + 1, hi);
}

int Quick::partition(int *arr, int lo, int hi)
{
    int i = lo;
    int j = hi + 1;
    int targetValue = arr[lo];
    while (true) {
        while (arr[++i] < targetValue) {
            if (i == hi) {
                break;
            }
        }
        while (arr[--j] > targetValue) {
            if (j == lo) {
                break;
            }
        }
        if (i >= j) {
            break;
        }
        // 交换元素位置
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    int temp = arr[lo];
    arr[lo] = arr[j];
    arr[j] = temp;

    return j;
}

算法性能分析:

快速排序速度优势子啊与它的比较次数很少,但是排序效果还是依赖切分数组的情况,并且如果在数组中存在大量重复元素。算法性能还可以大幅度提升。下面我们给出三向切分的快速排序算法。大家作为了解即可。

算法图示:

算法解释:在三向切分快速排序算法中我们定义了三个指针变量:gt 小于 V 的数组的上界,i 等于 V 的数组的上界,gt 大于 V 的数组的下界。以及一段中间数组array[i, gt]。一次遍历的判断过程如下:

  • array[i]小于V,将array[lt]和array[i]交换,并将lt和i分别加1;
  • array[i]大于V,将array[gt]和array[i]交换,并将gt减1;
  • array[i]等于V, 将i单独加1;

Java代码示例:

package algorithms.sorting;

import algorithms.Sortable;
import algorithms.common.Arrays;
import algorithms.common.ArraysGenerator;

public class Quick3way extends Arrays<Integer> implements Sortable<Integer> {
    @Override
    public void sort(Integer[] array) {
        sort(array, 0, array.length - 1);
    }

    private void sort(Integer[] array, int lo, int hi) {
        if (hi <= lo) {
            return;
        }
        int temp = array[lo]; // 比较标的
        int lt = lo; // 小于temp的index记录
        int eq = lo + 1; // 等于temp的index记录
        int gt = hi; // 大于temp的index记录

        while (eq <= gt) {
            if (array[eq] < temp) {
                exchange(array, lt++, eq++);
            } else if (array[eq] > temp) {
                exchange(array, eq, gt--);
            } else {
                eq++;
            }
        }
        sort(array, lo, lt - 1);
        sort(array, gt + 1, hi);
    }

    public static void main(String[] args) {
        Integer[] arr = ArraysGenerator.generate(10, 0, 10);
        Quick3way quick3way = new Quick3way();
        quick3way.sort(arr);

        System.out.println(java.util.Arrays.toString(arr));
    }
}

Qt/C++代码示例(略)

三向切分快速排序针对特定数组能够起到非常优秀的效果,但是在处理下标越界的事情往往容易弄错。因此在考虑算法性能的同时,保证正确应该优先考虑。

相关链接:

Algorithms for Java

Algorithms for Qt

原文地址:https://www.cnblogs.com/learnhow/p/9497069.html

时间: 2024-09-30 07:31:44

小橙书阅读指南(六)——快速排序和三向切分快速排序的相关文章

小橙书阅读指南(五)——归并排序的两种实现

算法描述:将两个较小的有序数组合并成为一个较大的有序数组是比较容易的事情.我们只需要按照相同的顺序依次比较最左侧的元素,然后交替的放进新数组即可.这就是自顶向下的归并排序的实现思路.与之前的算法不同的是,归并排序需要使用额外的存储空间,用空间换时间的做法也是在排序算法中经常需要做的选择. 算法图示: 算法解释:把一个较大的数组不断划分为较小的两个数组,直到无法再切分之后再做逆向合并,并再合并的过程中调整顺序.归并算法的难点是如何尽可能的减少额外存储空间的使用. Java代码示例: package

小橙书阅读指南(九)——红黑平衡树(2)

从标准二叉树的极端情况我们推导出2-3树这样的数据结构具备自平衡的特性,但是要实现这个特性在算法上相当复杂.考虑在大部分情况下,对于检索的指数级时间消费O(lgN)要求并不严格.因此,我们会看到如何将一颗标准的2-3树转变成红黑树的过程. 一.局部变换 考虑如果在2-节点上挂新的键并不会破坏2-3树的平衡结构.可是在3-节点上挂新的键,可能的变化却多达6种.这个临时的4-节点可能是根节点,可能是一个2-节点的左子节点或者右子节点,也可能是3-节点的左子节点.中子节点或者右子节点.2-3树插入算法

小橙书阅读指南(二)——选择排序

算法描述:一种最简单的排序算法是这样的:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置.再次,再剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置.如此往复,知道将整个数组排序.这种方法叫做选择排序,因为它在不断地选择剩余元素之中的最小者. 算法图示: Java代码示例: import common.ArraysGenerator; import common.Sortable; import java.io.IOException; import java.uti

小橙书阅读指南(三)——插入排序

算法描述:通常人们在整理扑克的方法是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置.在算法的实现中,为了给要插入的元素腾出1个空间,我们需要将其余所有元素在插入之前都向右移动1位.这种算法叫插入算法. 算法图示: 算法解释:在基础版本中通常的做法是,当新元素需要被插入有序数组的时候,从右向左依次交换.直到新元素到达它合适的位置. Java代码示例: import common.ArraysGenerator; import common.Sortable; import java.i

小橙书阅读指南(七)——优先队列和索引优先队列

算法描述:许多应用程序都需要按照顺序处理任务,但是不一定要求他们全部有序,或是不一定要一次就将他们排序.很多情况下我们只需要处理当前最紧急或拥有最高优先级的任务就可以了.面对这样的需求,优先队列算法是一个不错的选择. 算法图示: 算法解释:上图所展示的是最大优先队列(大顶堆)的算法逻辑,在这个标准的二叉树中,任意节点的元素都大于其叶子节点的元素.利用数组表示该二叉树即Array[2]和Array[3]是Array[1]的叶子节点,Array[4]和Array[5]是Array[2]的叶子节点,A

小橙书阅读指南(十)——二叉查找树

算法描述:二叉查找树时一种能够将链表插入的灵活性和有序数组查找的高效性结合起来的符号表(SymbolTable)实现.具体来说,就是使用每个节点含有两个链接的二叉树来高效地实现符号表.一颗二叉查找树时一颗二叉树,其中每个节点都含有一个Comparable的键且每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键. 一.查找 一般来说,在符号表中查找一个键只可能出现命中和未命中两种情况.一般通过递归算法在二叉树中查找,如果树时空的则查找未命中:如果被查找的键和根节点相等,查找命中,

小橙书阅读指南(十一)——散列表

算法描述:散列表是一种在时间和空间上做出权衡的查找算法.使用查找算法分为两步.第一步是通过散列函数将被查找的键转化未数组的一个索引.理想情况下,不同的键都能转为不同的索引值.当然,这只是理想情况,所以我们需要面对两个或多个键都被散列到相同索引值的情况.因此,散列查找的第二部就是处理碰撞冲突的过程. 一个比较令人满意的散列函数能够均匀并独立地将所有键散布于0到M-1之间. 一.基于拉链法的散列表 算法图示: 拉链散列表算法的本质是将哈希值相同的键保存在一个普通链表中,当我们需要调整数组长度的时候,

小橙书阅读指南(十二)——无向图、深度优先搜索和路径查找算法

在计算机应用中,我们把一系列相连接的节点组成的数据结构,叫做图.今天我们将要介绍它的一种形式--无向图,以及针对这种结构的深度优先搜索和路径查找算法. 一.无向图数据结构 接口: /** * 图论接口 */ public interface Graph { /** * 顶点数 * * @return */ int vertexNum(); /** * 边数 * * @return */ int edgeNum(); /** * 向图中添加一条v-w的边 * * @param v * @param

快速排序算法原理及实现(单轴、三向切分、双轴)

欢迎探讨,如有错误敬请指正 如需转载,请注明出处http://www.cnblogs.com/nullzx/ 1. 单轴快速排序的基本原理 快速排序的基本思想就是从一个数组中任意挑选一个元素(通常来说会选择最左边的元素)作为中轴元素,将剩下的元素以中轴元素作为比较的标准,将小于等于中轴元素的放到中轴元素的左边,将大于中轴元素的放到中轴元素的右边,然后以当前中轴元素的位置为界,将左半部分子数组和右半部分子数组看成两个新的数组,重复上述操作,直到子数组的元素个数小于等于1(因为一个元素的数组必定是有