`
mintelong
  • 浏览: 396235 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

数据结构--快速排序

    博客分类:
  • java
阅读更多
快速排序 
快速排序(QuickSort)


1、算法思想
     快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

(1) 分治法的基本思想
     分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

(2)快速排序的基本思想
     设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本思想描述为:
①分解:
     在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
  注意:
     划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos]):
     R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
                  其中low≤pivotpos≤high。
②求解:
     通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③组合:
     因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。

2、快速排序算法QuickSort
 
void QuickSort(SeqList R,int low,int high)
   { //对R[low..high]快速排序
     int pivotpos; //划分后的基准记录的位置
     if(low<high){//仅当区间长度大于1时才须排序
        pivotpos=Partition(R,low,high); //对R[low..high]做划分
        QuickSort(R,low,pivotpos-1); //对左区间递归排序
        QuickSort(R,pivotpos+1,high); //对右区间递归排序
      }
    } //QuickSort

  注意:
     为排序整个文件,只须调用QuickSort(R,1,n)即可完成对R[l..n]的排序。

3、划分算法Partition
(1) 简单的划分方法
① 具体做法
  第一步:(初始化)设置两个指针i和j,它们的初值分别为区间的下界和上界,即i=low,i=high;选取无序区的第一个记录R[i](即R[low])作为基准记录,并将它保存在变量pivot中;
  第二步:令j自high起向左扫描,直到找到第1个关键字小于pivot.key的记录R[j],将R[j])移至i所指的位置上,这相当于R[j]和基准R[i](即pivot)进行了交换,使关键字小于基准关键字pivot.key的记录移到了基准的左边,交换后R[j]中相当于是pivot;然后,令i指针自i+1位置开始向右扫描,直至找到第1个关键字大于pivot.key的记录R[i],将R[i]移到i所指的位置上,这相当于交换了R[i]和基准R[j],使关键字大于基准关键字的记录移到了基准的右边,交换后R[i]中又相当于存放了pivot;接着令指针j自位置j-1开始向左扫描,如此交替改变扫描方向,从两端各自往中间靠拢,直至i=j时,i便是基准pivot最终的位置,将pivot放在此位置上就完成了一次划分。

②一次划分过程
     一次划分过程中,具体变化情况【参见动画演示】

③划分算法:
 
int Partition(SeqList R,int i,int j)
    {//调用Partition(R,low,high)时,对R[low..high]做划分,
     //并返回基准记录的位置
      ReceType pivot=R[i]; //用区间的第1个记录作为基准 '
      while(i<j){ //从区间两端交替向中间扫描,直至i=j为止
        while(i<j&&R[j].key>=pivot.key) //pivot相当于在位置i上
          j--; //从右向左扫描,查找第1个关键字小于pivot.key的记录R[j]
        if(i<j) //表示找到的R[j]的关键字<pivot.key
            R[i++]=R[j]; //相当于交换R[i]和R[j],交换后i指针加1
        while(i<j&&R[i].key<=pivot.key) //pivot相当于在位置j上
            i++; //从左向右扫描,查找第1个关键字大于pivot.key的记录R[i]
        if(i<j) //表示找到了R[i],使R[i].key>pivot.key
            R[j--]=R[i]; //相当于交换R[i]和R[j],交换后j指针减1
       } //endwhile
      R[i]=pivot; //基准记录已被最后定位
      return i;
    } //partition 

4、快速排序执行过程
     快速排序执行的全过程可用递归树来描述。

分析:
     (1)递归执行的路线如图中带箭头的包络线所示。
     (2) 递归树上每一结点左旁方括号表示当前待排序的区间,结点内的关键字是划分的基准关键字
  注意:
     叶结点对应的子区间只有一个关键字,无须划分,故叶结点内没有基准关键字
  (3) 划分后得到的左、右两个子区间分别标在该结点的左、右两个孩子结点的左边方括号内。
【例】根结点左旁方括号[49,38,65,97,76,13,27,49]表示初始待排序的关键字,根内的49表示所选的划分基准记录的关键字,划分结果是[27,28,13]49[76,97,65,49_],其左右子区间分别标在根结点的两个孩子的左边。
     (4) 每个分支结点右旁圆括号中的内容表示对该结点左旁区间的排序过程结束之后返回的结果。它是其左右孩子对应的区间排序完成之后,将左右孩子对应的排序结果分别放在该分支结点的关键字前后所得到的关键字序列。
【例】分支结点76的左右孩子对应的区间排序后的结果分别是(49_,65)和(97),将它们分别放在76的前后即得(49,65,76,97),这是对结点76左旁区间[76,97,,65,49]排序的结果。
     (5) 算法的执行顺序是递归树中的箭头顺序,实际上当把划分操作视为访问结点的操作时,快速排序的执行过程相当于是先序遍历其递归树。
  注意:
     任何递归算法均可用递归树来描述其执行过程。

5、快速排序各次划分后的状态变化
[49 38 65 97 76 13 27 49] //初始关键字
[27 38 13] 49 [76 97 65 49] //第1次划分完成之后,对应递归树第2层
[13] 27 [38] 49 [49 65] 76 [97] //对上一层各无序区划分完成后,对应递归树第3层
13 27 38 49 49 [65] 76 97 //对上一层各无序区划分完成后,对应递归树第4层
13 27 38 49 49 65 76 97 //最后的排序结果

6、算法分析
     快速排序的时间主要耗费在划分操作上,对长度为k的区间进行划分,共需k-1次关键字的比较。

(1)最坏时间复杂度
     最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。
     因此,快速排序必须做n-1次划分,第i次划分开始时区间长度为n-i+1,所需的比较次数为n-i(1≤i≤n-1),故总的比较次数达到最大值:
               Cmax = n(n-1)/2=O(n2)
     如果按上面给出的划分算法,每次取当前无序区的第1个记录为基准,那么当文件的记录已按递增序(或递减序)排列时,每次划分所取的基准就是当前无序区中关键字最小(或最大)的记录,则快速排序所需的比较次数反而最多。

(2) 最好时间复杂度
     在最好情况下,每次划分所取的基准都是当前无序区的"中值"记录,划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数:
        0(nlgn)
注意:
     用递归树来分析最好情况下的比较次数更简单。因为每次划分后左、右子区间长度大致相等,故递归树的高度为O(lgn),而递归树每一层上各结点所对应的划分过程中所需要的关键字比较次数总和不超过n,故整个排序过程所需要的关键字比较总次数C(n)=O(nlgn)。
     因为快速排序的记录移动次数不大于比较的次数,所以快速排序的最坏时间复杂度应为0(n2),最好时间复杂度为O(nlgn)。

(3)基准关键字的选取
     在当前无序区中选取划分的基准关键字是决定算法性能的关键。
  ①"三者取中"的规则
     "三者取中"规则,即在当前区间里,将该区间首、尾和中间位置上的关键字比较,取三者之中值所对应的记录作为基准,在划分开始前将该基准记录和该区伺的第1个记录进行交换,此后的划分过程与上面所给的Partition算法完全相同。

  ②取位于low和high之间的随机数k(low≤k≤high),用R[k]作为基准
     选取基准最好的方法是用一个随机函数产生一个取位于low和high之间的随机数k(low≤k≤high),用R[k]作为基准,这相当于强迫R[low..high]中的记录是随机分布的。用此方法所得到的快速排序一般称为随机的快速排序。具体算法【参见教材】
注意:
     随机化的快速排序与一般的快速排序算法差别很小。但随机化后,算法的性能大大地提高了,尤其是对初始有序的文件,一般不可能导致最坏情况的发生。算法的随机化不仅仅适用于快速排序,也适用于其它需要数据随机分布的算法。

(4)平均时间复杂度
     尽管快速排序的最坏时间为O(n2),但就平均性能而言,它是基于关键字比较的内部排序算法中速度最快者,快速排序亦因此而得名。它的平均时间复杂度为O(nlgn)。

(5)空间复杂度
     快速排序在系统内部需要一个栈来实现递归。若每次划分较为均匀,则其递归树的高度为O(lgn),故递归后需栈空间为O(lgn)。最坏情况下,递归树的高度为O(n),所需的栈空间为O(n)。

(6)稳定性
     快速排序是非稳定的,例如[2,2,1]。

算法程序

//快速排序
int findpivot(int a[],int i,int j)//a是线性表,i是起始位置,j是终止位置
//查找关键元素
{
 int k;
 for(k=i+1;k<=j;k++)
 {
  if(a[k]>=a[i])
   return k;
  else if(a[k]<a[i])
   return i;
 }
 return -1;
}

int partition(int a[],int i,int j,int pivot)
//分治
{
 int l,k,x,key;
 l=i;
 k=j;
 key=a[pivot];
  while(l<k){
  x=a[l];
  a[l]=a[k];
  a[k]=x;
  while(a[l]<key)l++;
  while(a[k]>=key)k--;

 }
    return l;
}

void quicksort(int a[],int i,int j)
//递归实现快速排序
{
 int k;
 int pivot=findpivot(a,i,j);
 if(pivot!=-1)
 {
  k=partition(a,i,j,pivot);
  quicksort(a,i,k-1);
     quicksort(a,k,j);
 }
}





/**    
 * 定义一个数组类,封装了对自身数据的排序。    
 */     
class ArrayIns {      
     
    private long[] theArray;//定义数组      
    private int nElems;//数组中的元素个数      
     
    /**    
     * 初始化    
     * @param max    
     */     
    public ArrayIns(int max) {      
        theArray = new long[max];      
        nElems = 0;      
    }      
     
    /**    
     * 为数组赋值    
     * @param value    
     */     
    public void insert(long value) {      
        theArray[nElems] = value;      
        nElems++;      
    }      
     
    /**    
     * 显示数组元素    
     */     
    public void display() {      
        System.out.print("A=");      
        for (int j = 0; j < nElems; j++) {      
            System.out.print(theArray[j] + " ");      
        }      
        System.out.println("");      
    }      
     
    /**    
     * 快速排序主方法    
     */     
    public void quickSort(){      
        recQuickSort(0, nElems-1);      
    }      
    /**    
     * 快速排序需递归调用的方法    
     * @param left    
     * @param right    
     */     
    public void recQuickSort(int left, int right) {      
        if (right - left <= 0) {      
            return;      
        } else {      
            long pivot = theArray[right];      
                  
            int partition = partitionIt(left, right, pivot);      
            recQuickSort(left, partition - 1);      
            recQuickSort(partition + 1, right);      
        }      
    }      
     
    /**    
     * 快速排序划分的核心方法    
     * @param left    
     * @param right    
     * @param pivot    
     * @return    
     */     
    public int partitionIt(int left, int right, long pivot) {      
        int leftPtr = left-1;      
        int rightPtr = right;      
        while (true) {      
            while (theArray[++leftPtr] < pivot)      
                ;      
            while (rightPtr > 0 && theArray[--rightPtr] > pivot)      
                ;      
            if (leftPtr >= rightPtr) {      
                break;      
            } else {      
                swap(leftPtr, rightPtr);      
            }      
        }      
        swap(leftPtr,right);      
        return leftPtr;      
    }      
     
    /**    
     * 交换数据中两个位置的数据    
     * @param dex1    
     * @param dex2    
     */     
    public void swap(int dex1, int dex2) {      
        long temp = theArray[dex1];      
        theArray[dex1] = theArray[dex2];      
        theArray[dex2] = temp;      
    }      
}      
/**    
 * 执行算法的主类    
 */     
public class QuickSort1 {      
     
    public static void main(String[] args) {      
        int maxSize = 16;      
        ArrayIns arr = new ArrayIns(maxSize);      
     
        for (int j = 0; j < maxSize; j++) {      
            long n = (int) (java.lang.Math.random()*99);      
            arr.insert(n);      
        }      
        System.out.println("显示排序前数据");      
        arr.display();      
        arr.quickSort();      
        System.out.println("显示排序后数据");      
        arr.display();      
    }      
}      
/**    
 *     
 * 显示排序前数据:    
 * A=9 14 33 27 66 89 53 32 72 14 46 33 13 79 28 26      
 * 显示排序后数据:     
 * A=9 13 14 14 26 27 28 32 33 33 46 53 66 72 79 89     
 */     
分享到:
评论

相关推荐

    数据结构--快速排序C++源代码

    数据结构--快速排序C++源代码,自己编写调试,代码简单易懂,不长

    数据结构--排序--思维导图.pdf

    "数据结构--排序--思维导图" 数据结构中排序是指将一组无序的记录序列按照一定的规则排列成有序的序列,排序的目的是为了提高数据的存储和检索效率。排序算法的稳定性是指在排序过程中,如果待排序表中有两个元素Ri...

    数据结构-快速排序

    数据结构 经典的快速排序。C6.0调试通过。希望对大家有用。

    数据结构课程设计实例快速排序.rar

    数据结构课程设计实例快速排序 数据结构课程设计实例快速排序 数据结构课程设计实例快速排序 数据结构课程设计实例快速排序 数据结构课程设计实例快速排序 数据结构课程设计实例快速排序 数据结构课程设计实例快速...

    数据结构- C语言 -排序.md

    数据结构- C语言 -排序.md

    漫话数据结构-快速排序.pptx

    在"漫话数据结构-快速排序.pptx"这个文件中,主要讲解了快速排序的基本思想、算法实现以及性能评估。 快速排序的基本思想是选取一个"基准"记录,然后通过一趟排序将待排序的序列分为两部分,一部分的所有记录都比...

    32数据结构-快速排序.rar

    综上所述,"32数据结构-快速排序.rar"压缩包中的内容可能是包含了一个C语言实现的快速排序算法源代码,可以在Linux环境下编译运行,并且由于其基于C语言,具有较好的可移植性,能够适应多种操作系统。通过学习和理解...

    数据结构--排序 很细致

    数据结构中的排序是计算机科学中一个基础且重要的概念,它涉及到如何有效地组织和处理大量数据。排序算法的主要目标是将一组无序的数据元素按照特定的标准(通常是升序或降序)排列成一个有序序列。本篇文章主要介绍...

    数据结构-排序算法的实现(代码+报告)

    ### 数据结构-排序算法的实现知识点详解 #### 实验背景及目标 本次实验的主要目的是让学生深入理解并掌握几种常见的排序算法及其应用场景。通过动手实践,不仅能够加深对各种排序算法工作原理的理解,还能够培养...

    湖南大学-数据结构-期末试题【2016-2019】.zip

    数据结构是计算机科学中的核心课程,它探讨了如何在计算机中高效地组织和管理数据,以便于进行各种操作。湖南大学的数据结构期末试题涵盖了这门课程的重要概念和技术,旨在检验学生对这些知识的理解和应用能力。以下...

    c语言 数据结构 快速排序算法

    c语言版本的数据结构的快速排序算法,适用于新手学习

    数据结构--内部排序

    本文将深入探讨标题"数据结构--内部排序"中涉及的几种主要排序算法,并对描述中提及的插入排序、Shell排序、冒泡排序、快速排序、简单选择排序以及堆排序进行详细解析。 1. 插入排序:插入排序是一种简单的排序算法...

    数据结构课程设计 ---快速排序算法实现 .docx

    快速排序是一种高效的...综上所述,数据结构课程设计中的快速排序项目旨在让学生深入理解分治法和快速排序的原理,同时提升编程和界面设计技能。通过实际操作和分析,学生能更好地掌握这一经典算法的特点和应用场景。

    数据结构-快速排序简单算法

    快速排序本身并不是数据结构的建立过程,而是一种高效的排序算法。在C语言中,快速排序通过选取一个基准元素(pivot),将数组分为两个子数组,一个包含小于基准的元素,另一个包含大于基准的元素,然后递归地对这两...

    快速排序 数据结构 实现

    快速排序是一种高效的排序算法,由英国计算机科学家C.A.R. Hoare在1960年提出。它的基本思想是分治法(Divide and Conquer),通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的...

    数据结构------排序

    常见的集中排序算法 冒泡排序 快速排序 。。。。。。。。。。。。。。。。

    数据结构--九种排序算法 --排序001.cpp

    此文件为数据结构中的九种排序算法,包含一些排序方法的过程,其九种排序包括:直接插入排序,折半插入排序,希尔排序,冒泡排序,快速排序,选择排序,堆排序,归并排序,基数排序!

    数据结构_快速排序

    严蔚敏版的《数据结构》是一本经典的数据结构教材,书中详细介绍了各种数据结构及其操作,包括快速排序在内的各种排序算法。第八章可能涵盖了快速排序的基本概念、算法实现、时间复杂度分析以及优化策略等内容。 ...

    数据结构-C语言-插入排序、快速排序、选择排序

    输入10个数,编程实现插入排序、快速排序、选择排序三类算法

Global site tag (gtag.js) - Google Analytics