`
frank-liu
  • 浏览: 1686916 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

quick sort的几种实现

 
阅读更多

简介

    之所以要写点和quick sort相关的,主要是因为我们很多时候只是关注一下某些问题的一个标准答案。实际上在我们碰到不同的情形,在原有问题的基础上做一点小小的变动,会带来更理想的结果。这里针对传统的实现,有相同元素的实现和非递归的实现做了一个探讨。

 

第一种实现

    我们知道quick sort的过程其实描述还是比较简单的。它主要就是挑选一个中间值,通过partition方法将整个数组划分成小于这个值和大于这个值的两个部分。然后再针对这两个部分递归的去排序。因此从一个宏观的角度来说,一个典型的实现如下:

void quickSort(int[] a, int l, int r){
    if(l < r) {
        int q = newPartition(a, l, r);
        quickSort(a, l, q - 1);
        quickSort(a, q + 1, r);
    }
}

    因为是递归调用的,我们每次取值的范围就是在中间值划分后的结果里。partition的实现如下:

int partition(int[] a, int l, int r) {
    int x = a[r];
    int i = l - 1;
    for(int j = l; j < r; j++) {
        if(a[j] <= x) {
            i++;
            swap(a, i, j);
        }
    }
    swap(a, i + 1, r);
    return i + 1;
}

   这里我们是取的这个数组里最右边的那个值来划分整个数组。这里的数字i表示小于或等于给定值的范围。每次碰到小于等于给定值的时候就递增i,然后交换当前元素和i所在元素的位置。我们都知道,quick sort的时间复杂度平均为O(NlogN),在最坏的情况下为O(N * N)。

    除了上面的实现方式,还要一种实现的思路,就是设定两个索引,一个指向数组的头一个指向数组的尾。假设它们分别定义为i, j,分别表示小于划分元素的索引位置和大于划分元素的索引。假设以数组的第一个元素作为划分元素的话。我们可以采用两头凑的方式。每次从左到右的去遍历比较数组,如果碰到大于划分元素的则停止,然后再看右边从右到左的去比较,碰到比划分元素小的也暂停,交换i, j两个位置的元素。继续上述的过程直到i >= j。按照这个思路,这种实现的代码如下:

int partition(int[] a, int lo, int hi) {
    int i = lo, j = hi + 1;
    while(true) {
        while(a[++i] < a[lo])
            if(i == hi) break;
        while(a[lo] < a[--j])
            if(j == lo) break;
        if(i >= j) break;
        swap(a, i, j);
    }
    swap(a, lo, j);
    return j;
}

 

 

    这种默认的情况还是比较好理解的,可是,在遇到一些拥有相同元素的情况下, 我们是否有什么办法来改进一下呢?因为这些元素如果和划分值是相同的,我们完全可以将他们集中在一块,这样可以直接将它们整个都剥离出来不用参加后面的排序,这不就间接使得需要排序的数据范围缩小了吗?这样也可以提高一点效率啊。

 

有相同值元素的实现

    按照前面的思路,我们这里需要一个元素来记录小于划分值的范围。这里肯定也需要一个元素来记录大于划分值的范围。而在它们两个值中间的不正好就是等于划分值的么?于是我们可以实现如下的代码:

public static void sort(int[] a, int lo, int hi) {
        if(hi < lo) return;
        int lt = lo, i = lo + 1, gt = hi;
        int v = a[lo];
        while(i <= gt) {
            if(a[i] < v) swap(a, lt++, i++);
            else if(a[i] > v) swap(a, i, gt--);
            else i++;
        }
        sort(a, lo, lt - 1);
        sort(a, gt + 1, hi);
    }

    这里的代码没有使用前面的那个划分方法,但是基本的思路是差不多的。我们用lt表示小于划分值的范围,gt表示大于划分值的范围。于是当给定值小于划分值的时候,lt++, i++。因为这里是取的数组最左边的元素作为划分中间值,所以lt表示等于中间值的最左边的那个元素的索引。这里最难理解清楚的是针对a[i] < v, a[i] > v和a[i] = v这3种情况。尤其要注意的就是为什么我们当a[i] < v的时候, lt,i都要加1而a[i] > v的时候只要gt--。因为我们知道当a[i] < v的时候lt++,这之后lt指向的其实已经是最左边的那个和划分值相等的元素了,而之前lt指向的元素就是划分元素v,每次递增后得到的值不可能大于v。而右边的gt所在的元素则还有可能小于v,所以每次gt--的同时还不能i++。

    详细的情况我们可以针对下图来分析:

 

非递归实现

    除了前面的两种实现,还有一个比较有意思的实现就是非递归实现。一般我们都习惯于用递归的方式去实现。但是用一些辅助存储,我们也可以用非递归的方式来实现。

    基本的思路如下,我们用一个额外的栈来保存每次划分的开头和结尾部分。每做一次划分就将两边的划分边界都保存起来。然后不断的出栈,在取出的出栈序列里如果表示右边界已经小于等于左边界了,表示这一步的划分结束。

    按照这个思路,得到的代码实现如下:

public static void iterSort(int[] a, int l, int r) {
        Stack<Integer> stack = new Stack<Integer>();
        push2(stack, l, r);
        while(!stack.empty()) {
            int left = stack.pop();
            int right = stack.pop();
            if(right <= left) continue;
            int i = partition(a, left, right);
            if(right - i > i - left) {
                push2(stack, i + 1, right);
                push2(stack, left, i - 1);
            } else {
                push2(stack, left, i - 1);
                push2(stack, i + 1, right);
            }
        }
    }

    这里还有用到一个改进,每次将划分后比较长的那一段先压栈。然后push2的实现如下:

public static void push2(Stack<Integer> stack, int l, int r) {
        stack.push(r);
        stack.push(l);
    }

    我们要注意首先入栈的是右边界,因为栈是先入后出的。

 

总结

    Quick sort的过程在一些具体的应用中还有不同的实现方法,很多细节针对有重复元素等情况体现出来的效果也是各不相同的。值得去细细的体会。

 

参考材料

Algorithms

  • 大小: 50.4 KB
分享到:
评论

相关推荐

    sort_exp.rar_quick sort_桶排序

    快速排序(Quick Sort)是由C.A.R. Hoare在1960年提出的,它是一种非常高效的排序算法,其基本思想是分治法。快速排序的基本步骤如下: 1. **选择枢轴元素**:在待排序的数组中选取一个元素作为枢轴,通常选择第一...

    PHP排序算法之快速排序(Quick Sort)及其优化算法详解

    快速排序(Quick Sort)是一种高效的排序算法,由C. A. R. Hoare在1960年提出。它采用分治法(Divide and Conquer)的策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。快速排序在实现时,...

    Data-structure-quick-sort.zip..zip_Quick

    在"Data structure quick sort.docx"文档中,可能包含了一个使用某种编程语言实现的快速排序程序示例。该程序可能会有以下关键部分: 1. **主函数(Main Function)**:调用快速排序函数并传递数组和其大小作为参数...

    Quick-sort-doubly-linked-list.zip_Quick

    在`Quick sort doubly linked list.c`文件中,可能包含了以下关键代码部分: 1. 定义双向链表节点结构: ```c struct Node { int data; struct Node* prev; struct Node* next; }; ``` 2. 快速排序函数的...

    易语言快速排序(quick sort)算法源码

    易语言的快速排序算法通常包含以下几个步骤: 1. **选择基准元素(Pivot)**:从待排序的数组中选择一个元素作为基准,通常选择第一个或最后一个元素。 2. **分区操作**:将数组分为两个子数组,使得第一个子数组...

    random-quick-sort.zip_Quick

    在随机快速排序的实现过程中,主要包括以下几个步骤: 1. **选择基准元素**:随机从待排序的数组中选取一个元素作为基准(pivot)。 2. **分区操作**:重新排列数组,使得所有小于基准的元素位于基准的左边,所有...

    Java实现几种常见排序方法

    ### Java 实现几种常见排序方法 #### 泡泡排序(Bubble Sort) 泡泡排序是一种简单的排序算法,它重复地遍历待排序的数列,依次比较相邻的两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复...

    用php实现几种常见的排序算法共6页.pdf.zip

    这份资料"用php实现几种常见的排序算法共6页.pdf.zip"显然包含了关于如何使用PHP实现几种常见排序算法的教程或笔记。 首先,让我们回顾一下几种常见的排序算法,并讨论它们在PHP中的实现: 1. **冒泡排序**...

    用Java实现几种常见的排序算法.txt

    根据提供的文件信息,我们可以总结出该文档主要涉及了五种基于Java实现的排序算法:插入排序(Insert Sort)、冒泡排序(Bubble Sort)、选择排序(Selection Sort)、希尔排序(Shell Sort)以及快速排序(Quick ...

    QuickSortAndSearch

    2. 掌握递归和循环两种实现方式。 3. 了解算法的时间复杂度和空间复杂度分析。 4. 理解各种查找算法的适用场景和优缺点。 5. 实际编程实现和调试,通过实践巩固理论知识。 通过学习“QuickSortAndSearch”中的内容...

    几种简单算法的C语言源代码和演示页面(插入排序,冒泡排序等).zip

    在本压缩包中,我们主要关注的是几种简单的算法,它们都是用C语言编写的,并且配合有HTML动画演示,帮助理解和学习。以下是这些算法的详细解释: 1. 插入排序(Insertion Sort) 插入排序是一种基础且直观的排序...

    查找排序的几种算法的实现

    - 熟悉几种典型的排序方法,并对各种算法的特点、使用范围和效率有进一步的了解。 - 实现两种以上的简单排序和快速排序,并比较它们的时间效率。 #### 3.2 实验环境 - 开发工具:Turbo C 或 VC++ - 实验时长:2学时...

    几种排序算法整理

    本文将深入探讨由C语言实现的几种常见排序算法,包括它们的基本原理、实现细节和性能特点。 首先,让我们从最经典的排序算法——冒泡排序(Bubble Sort)开始。冒泡排序通过不断地交换相邻的不正确顺序的元素来逐步...

    C语言几种排序算法实现.zip

    本资源"**C语言几种排序算法实现.zip**"可能包含了一系列用C语言编写的经典排序算法,如冒泡排序、插入排序、选择排序、快速排序、归并排序和堆排序等。下面将对这些常见的排序算法进行详细介绍。 1. **冒泡排序...

    matlab开发-sort1m

    在"sort1.m"的代码中,开发者可能实现了以下几种排序算法之一: 1. **冒泡排序(Bubble Sort)**:这是一种简单的排序方法,通过不断交换相邻的未排序元素来逐渐完成排序。虽然效率较低,但在小规模数据或部分有序...

    用Java实现几种常见的排序算法

    在本文中,我们将深入探讨如何使用Java编程语言实现几种经典的排序算法。这些排序算法包括插入排序、冒泡排序、选择排序、Shell排序、快速排序、归并排序和堆排序。我们还会提到一个名为SortUtil的工具类,它可能...

    Java实现几种常见的排序算法

    本文将详细探讨Java中实现几种常见的排序算法,包括它们的工作原理、时间复杂度以及如何在实际代码中应用。 一、冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个...

    几种常见的排序动态图+代码

    本文将详细解析标题中的“几种常见的排序动态图+代码”,结合描述,我们将探讨几种主流的排序算法,并展示它们的动态过程及对应的编程实现。 1. 冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法,通过重复...

    c++ 实现的好几种排序算法

    4. **快速排序(Quick Sort)** 快速排序是一种高效的分治算法,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列...

Global site tag (gtag.js) - Google Analytics