快速排序

概述
  快速排序(Quick Sort)是一种有效的排序算法。虽然算法在最坏的情况下运行时间为O(n^2),但由于平均运行时间为O(nlogn),并且在内存使用、程序实现复杂性上表现优秀,尤其是对快速排序算法进行随机化的可能,使得快速排序在一般情况下是最实用的排序方法之一。
  快速排序被认为是当前最优秀的内部排序方法。
[编辑本段]
实现
  快速排序的实现基于分治法,具体分为三个步骤。假设待排序的序列为L[m..n]。
  分解:序列L[m .. n]被划分成两个可能为空的子序列L[m .. pivot-1]和L[pivot+1 .. n],使L[m .. pivot-1]的每个元素均小于或等于L[pivot],同时L[pivot+1.. n]的每个元素均大于L[pivot]。其中L[pivot]称为这一趟分割中的主元(也称为枢轴、支点)。
  解决:通过递归调用快速排序,对子序列L[m .. pivot-1]和L[pivot+1 .. r]排序。 
  合并:由于两个子序列是就地排序的,所以对它们的合并不需要操作,整个序列L[m .. n]已排好序。
[编辑本段]
性质
  内部排序
  快速排序是一种内部排序方法。也就是说快速排序的排序对象是读入内存的数据。
  比较排序
  快速排序确定元素位置的方法基于元素之间关键字大小的比较。
  所有基于比较方法的排序方法的时间下界不会低于O(nlgn)。这个结论的具体证明,请参考有关算法的书籍,例如《算法导论》(第一版)第8章(第二版在第七章QuickSort)。
  在理想情况下,能严格地达到O(nlgn)的下界。一般情况下,快速排序与随机化快速排序的平均情况性能都达到了O(nlgn)。
  不稳定性
  快速排序是一种不稳定的排序方法。简单地说,元素a1, a2的关键字有a1.key=a2.key,则不稳定的排序方法不能保证a1, a2在排序后维持原来的位置先后关系。
  原地排序
  在排序的具体操作过程中,除去程序运行实现的空间消费(例如递归栈),快速排序算法只需消耗确定数量的空间(即S(1),常数级空间)。
  这个性质的意义,在于在内存空间受到限制的系统(例如MCU)中,快速排序也能够很好地工作。
[编辑本段]
时空复杂度
  快速排序每次将待排序数组分为两个部分,在理想状况下,每一次都将待排序数组划分成等长两个部分,则需要logn次划分。
  而在最坏情况下,即数组已经有序或大致有序的情况下,每次划分只能减少一个元素,快速排序将不幸退化为冒泡排序,所以快速排序时间复杂度下界为O(nlogn),最坏情况为O(n^2)。在实际应用中,快速排序的平均时间复杂度为O(nlogn)。
  快速排序在对序列的操作过程中只需花费常数级的空间。空间复杂度S(1)。
  但需要注意递归栈上需要花费最少logn 最多n的空间。
[编辑本段]
随机化算法
  快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。一位前辈做出了一个精辟的总结:“随机化快速排序可以满足一个人一辈子的人品需求。”
  随机化快速排序的唯一缺点在于,一旦输入数据中有很多的相同数据,随机化的效果将直接减弱。对于极限情况,即对于n个相同的数排序,随机化快速排序的时间复杂度将毫无疑问的降低到O(n^2)。
[编辑本段]
减少递归栈使用的优化
  快速排序的实现需要消耗递归栈的空间,而大多数情况下都会通过使用系统递归栈来完成递归求解。在元素数量较大时,对系统栈的频繁存取会影响到排序的效率。
  一种常见的办法是设置一个阈值,在每次递归求解中,如果元素总数不足这个阈值,则放弃快速排序,调用一个简单的排序过程完成该子序列的排序。这样的方法减少了对系统递归栈的频繁存取,节省了时间的消费。
  一般的经验表明,阈值取一个较小的值,排序算法采用选择、插入等紧凑、简洁的排序。一个可以参考的具体方案:阈值T=10,排序算法用选择排序。
  阈值不要太大,否则省下的存取系统栈的时间,将会被简单排序算法较多的时间花费所抵消。
  另一个可以参考的方法,是自行建栈模拟递归过程。但实际经验表明,收效明显不如设置阈值。
消除递归的快速排序
  传统的快速排序是递归的,这就会受到递归栈深度的限制。比如在一台普通的PC上,当待排序元素达到10^6以上时,传统的递归快排会导致栈溢出异常,或者一个莫名其妙的错误结果。所以,对于巨大的数据规模,将快速排序消除递归是十分必要的。而消除递归,又将带来巨大的性能提升,把系统级的消耗降到最低。
  消除递归的方法,就是模拟栈操作。但是从代码可以看出,这种模拟的消耗几乎可以忽略不计。因此消除递归的快排的效率是有保障的。
  (虽然下面的代码没有使用随机化,但经过测试,它是目前所有快排编写方法中,效率最高,速度最快的!)
  
  #define MAXARRAY 10000
  #define PUSH(A,B) {sl[sp]=A;sr[sp]=B;sp++;}
  #define POP(A,B) {sp--;A=sl[sp];B=sr[sp];}
  void quicksort(int a[],int l,int r){
  static int sl[MAXARRAY], sr[MAXARRAY], sp;
  int i,j,p,t;
  sp=0;
  PUSH(l,r);
  while(sp){
  POP(l,r);
  i=l;j=r;p=a[(i+j)/2];
  while(i<=j){//2.建议改成:while(i<j){
  while(a<p)i++;//应为while(a<p)i++;
  while(a[j]>p)j--;
  if(i<=j){//3.建议改成:if(i<j){
  t=a;a=a[j];a[j]=t;//应为t=a;a=a[j];a[j]=t;
  i++;j--;//1.建议注释掉//不能注释掉!
  } 
  }
  if(l<j)PUSH(l,j);
  if(i<r)PUSH(i,r);
  }
  }
  
[编辑本段]
C++例程
  以下是一个用C++编写的快速排序程序。虽然C标准库中提供了快速排序,但作为快速排序的介绍,原理程序的代码更加有助于对快速排序运行过程的分析。
  在这个例程中,对于数组x的0~n-1号元素的排序,初始调用为:quicksort(x, 0, n-1);
  int quicksort_partition(int L[], int Lbb, int Ubb)
  {
  //随机化 
  int iRndPivID;
  srand(unsigned(time(0)));
  iRndPivID = (rand() % (Ubb - Lbb + 1)) + Lbb;
  swap(L[iRndPivID], L[Ubb]);
  //快排 
  int iPivValue;
  int i;
  int iPivPos;
  iPivValue = L[Ubb];
  iPivPos = Lbb - 1;
  for (i=Lbb; i<=Ubb-1; i++)
  {
  if (L[ i ] <= iPivValue)
  {
  iPivPos++;
  swap(L[iPivPos], L[ i ]);
  }
  }
  iPivPos++;
  swap(L[iPivPos], L[Ubb]);
  return iPivPos;
  }
  void quicksort(int L[], int Lbb, int Ubb)
  {
  int iPiv;
  if (Lbb < Ubb)
  {
  iPiv = quicksort_partition(L, Lbb, Ubb);
  quicksort(L, Lbb, iPiv - 1);
  quicksort(L, iPiv + 1, Ubb);
  }
  return;
  }
[编辑本段]
使用C++标准库的快速排序函数
  C++的标准库stdlib.h中提供了快速排序函数。
  请在使用前加入对stdlib.h的引用:#include <cstdlib> 或 #include <stdlib.h>
  qsort(void* base, size_t num, size_t width, int(*)compare(const void* elem1, const void* elem2))
  参数表
  *base: 待排序的元素(数组,下标0起)。
  num: 元素的数量。
  width: 每个元素的内存空间大小(以字节为单位)。可用sizeof()测得。
  int(*)compare: 指向一个比较函数。*elem1 *elem2: 指向待比较的数据。
  比较函数的返回值
  返回值是int类型,确定elem1与elem2的相对位置。
  elem1在elem2右侧返回正数,elem1在elem2左侧返回负数。
  控制返回值可以确定升序/降序。
  一个升序排序的例程:
  int Compare(const void *elem1, const void *elem2) 
  { 
  return *((int *)(elem1)) - *((int *)(elem2));
  } 
  int main()
  {
  int a[100];
  qsort(a, 100, sizeof(int), Compare); 
  return 0;
  }
快速排序的JAVA实现
  package cims.util.arithmetic;
  public class Test {
  public static int[] a = { 10, 32, 1, 9, 5, 7, 12, 0, 4, 3 }; // 预设数据数组(预设数组为{7,6,7}的话能不能正确排序?)
  public static void main(String args[]) {
  System.out.print("排序前: ");
  for (int i = 0; i < a.length; i++)
  System.out.printf("%3s", a);//(java里有这种格式?)
  System.out.println("");
  int Index = a.length;
  QuickSort(0, Index - 1, Index); // 快速排序
  // 排序后结果
  System.out.print("排序后: ");
  for (int i = 0; i < a.length; i++)
  System.out.printf("%3s", a);
  System.out.println("");
  }
  public static void QuickSort(int Left, int Right, int Index) {
  int i, j, k; // 循环计数变量
  int Pivot; // 枢纽变量
  int Temp; // 暂存变量
  i = Left; // 设定左指针
  j = Right; // 设定右指针
  Pivot = a[Left]; // 取最左边的元素
  if (i < j) {
  do {
  while (a < Pivot && i < Right) // 从左往右找比Pivot大的值(这里是指a?)
  {
  i++;
  }
  while (a[j] > Pivot && j > Left) // 从右往左找比Pivot小的值
  {
  j--;
  }
  if (i < j) // 交换a和a[j](交换a和a[j]?)
  {
  Temp = a;
  a = a[j];
  a[j] = Temp;
  }
  } while (i < j);
  if (i > j) {
  Temp = a[Left]; // 交换a[Left]和a[j]
  a[Left] = a[j];
  a[j] = Temp;
  // 打印目前排序结果
  System.out.print("排序中: ");
  for (k = 0; k <= Index; k++) {
  System.out.printf("%3s", a[k]);
  }
  System.out.println("");
  }
  QuickSort(Left, j - 1, Index); // 排序左半边
  QuickSort(j + 1, Right, Index); // 排序右半边
  }
  }
  }

参考自:百度百科 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值