简介
之所以要写点和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的过程在一些具体的应用中还有不同的实现方法,很多细节针对有重复元素等情况体现出来的效果也是各不相同的。值得去细细的体会。
相关推荐
快速排序(Quick Sort)是由C.A.R. Hoare在1960年提出的,它是一种非常高效的排序算法,其基本思想是分治法。快速排序的基本步骤如下: 1. **选择枢轴元素**:在待排序的数组中选取一个元素作为枢轴,通常选择第一...
快速排序(Quick Sort)是一种高效的排序算法,由C. A. R. Hoare在1960年提出。它采用分治法(Divide and Conquer)的策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。快速排序在实现时,...
在"Data structure quick sort.docx"文档中,可能包含了一个使用某种编程语言实现的快速排序程序示例。该程序可能会有以下关键部分: 1. **主函数(Main Function)**:调用快速排序函数并传递数组和其大小作为参数...
在`Quick sort doubly linked list.c`文件中,可能包含了以下关键代码部分: 1. 定义双向链表节点结构: ```c struct Node { int data; struct Node* prev; struct Node* next; }; ``` 2. 快速排序函数的...
易语言的快速排序算法通常包含以下几个步骤: 1. **选择基准元素(Pivot)**:从待排序的数组中选择一个元素作为基准,通常选择第一个或最后一个元素。 2. **分区操作**:将数组分为两个子数组,使得第一个子数组...
在随机快速排序的实现过程中,主要包括以下几个步骤: 1. **选择基准元素**:随机从待排序的数组中选取一个元素作为基准(pivot)。 2. **分区操作**:重新排列数组,使得所有小于基准的元素位于基准的左边,所有...
### Java 实现几种常见排序方法 #### 泡泡排序(Bubble Sort) 泡泡排序是一种简单的排序算法,它重复地遍历待排序的数列,依次比较相邻的两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复...
这份资料"用php实现几种常见的排序算法共6页.pdf.zip"显然包含了关于如何使用PHP实现几种常见排序算法的教程或笔记。 首先,让我们回顾一下几种常见的排序算法,并讨论它们在PHP中的实现: 1. **冒泡排序**...
根据提供的文件信息,我们可以总结出该文档主要涉及了五种基于Java实现的排序算法:插入排序(Insert Sort)、冒泡排序(Bubble Sort)、选择排序(Selection Sort)、希尔排序(Shell Sort)以及快速排序(Quick ...
2. 掌握递归和循环两种实现方式。 3. 了解算法的时间复杂度和空间复杂度分析。 4. 理解各种查找算法的适用场景和优缺点。 5. 实际编程实现和调试,通过实践巩固理论知识。 通过学习“QuickSortAndSearch”中的内容...
在本压缩包中,我们主要关注的是几种简单的算法,它们都是用C语言编写的,并且配合有HTML动画演示,帮助理解和学习。以下是这些算法的详细解释: 1. 插入排序(Insertion Sort) 插入排序是一种基础且直观的排序...
- 熟悉几种典型的排序方法,并对各种算法的特点、使用范围和效率有进一步的了解。 - 实现两种以上的简单排序和快速排序,并比较它们的时间效率。 #### 3.2 实验环境 - 开发工具:Turbo C 或 VC++ - 实验时长:2学时...
本文将深入探讨由C语言实现的几种常见排序算法,包括它们的基本原理、实现细节和性能特点。 首先,让我们从最经典的排序算法——冒泡排序(Bubble Sort)开始。冒泡排序通过不断地交换相邻的不正确顺序的元素来逐步...
本资源"**C语言几种排序算法实现.zip**"可能包含了一系列用C语言编写的经典排序算法,如冒泡排序、插入排序、选择排序、快速排序、归并排序和堆排序等。下面将对这些常见的排序算法进行详细介绍。 1. **冒泡排序...
在"sort1.m"的代码中,开发者可能实现了以下几种排序算法之一: 1. **冒泡排序(Bubble Sort)**:这是一种简单的排序方法,通过不断交换相邻的未排序元素来逐渐完成排序。虽然效率较低,但在小规模数据或部分有序...
在本文中,我们将深入探讨如何使用Java编程语言实现几种经典的排序算法。这些排序算法包括插入排序、冒泡排序、选择排序、Shell排序、快速排序、归并排序和堆排序。我们还会提到一个名为SortUtil的工具类,它可能...
本文将详细探讨Java中实现几种常见的排序算法,包括它们的工作原理、时间复杂度以及如何在实际代码中应用。 一、冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个...
本文将详细解析标题中的“几种常见的排序动态图+代码”,结合描述,我们将探讨几种主流的排序算法,并展示它们的动态过程及对应的编程实现。 1. 冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法,通过重复...
4. **快速排序(Quick Sort)** 快速排序是一种高效的分治算法,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列...