快速排序_QUICKSORT

30 篇文章 1 订阅
30 篇文章 6 订阅

快速排序

快速排序是一个最坏情况时间复杂度为 Θ(n2) ,最好情况下时间复杂度为 O(nlogn) 的排序算法。虽然在最坏情况下的时间复杂为 n2 ,但是快速排序的平均性能非常好,只有 Θ(nlogn) ,而且我们也可以主动避免快速排序的最差的情况,所以快速排序在排序作业中还是应用比较广泛的。

快速排序的基本思想

我们现在有一数组 A={ai,ai+1,...,aj} ,假定我们将数组 A 排序得到数组B={bi,bi+1,...,bj} ,数组 A 和数组B里面的元素全都想用,只是元素的顺序不同。现在我们希望找到数组 A 里面的一个任一元素am在数组 B 中的位置k(ikj)。在排序之前,如果我们一开始就知道位置 k 应该放置元素am,那么排序岂不是很简单,我们只需要将各个元素放到对应的位置上不久可以了吗?

那下面我们就来找一个数组 A 里面任意元素am的应该放置的目标位置 k ,这个位置k应该满足这样的特性 ai...k1akak+1...j ,就是位置 k 的左边的元素应该不大于ak,位置 k 右边的元素应该不小于元素ak
即:

ai,ai+1,...,ak1ak,ak,ak+1,...,aj1,ajak

所以我们只需要将大于 ak 的元素移到数组的右边,小于等于 ak 的元素移到数组的左边就可以啦。

让然后我们对剩下的两个子数组 A˙={ai,..,ak1} A¨={ak+1,...,aj} 进行同样的递归操作,直到我们找到所有的元素的目标位置 k ,并将元素放入

总的来说,可以分为下面几个步骤:

  • 找到元素aj(这里选择的 am 为最优一个元素)的目标位置 k
  • 将位置j和位置 k 的元素交换,因为位置k就是 aj 的目标位置,只是现在被元素 ak 占用着。

    • 得到两个子问题 A˙={ai,..,ak1} A¨={ak+1,...,aj}
    • 对这两个子问题采用相同的方法,直到每一个元素都放在了目标位置上
    • 快速排序的代码实现

      我们首先采用两种代码实现

      /*************************************************
      * @Filename:    quickSort.cc
      * @Author:      qeesung
      * @Email:       qeesung@qq.com
      * @DateTime:    2015-05-14 14:48:35
      * @Version:     1.0
      * @Description: 快速排序的算法实现
      **************************************************/
      
      #include <iostream>
      #include <cstdlib>
      #include <ctime>
      
      using namespace std;
      
      
      void exchange(int array[] , int pos1 , int pos2)
      {
          if(array == NULL)
              return;
          int temp = array[pos1];
          array[pos1] = array[pos2];
          array[pos2] = temp;
      }
      
      
      /**
       * 快速排序的递归算法
       * @param array       将要排序的数组
       * @param leftBorder  排序的左边界
       * @param rightBorder 排序的右边界
       */
      void quickSortKernel(int array[] , int leftBorder , int rightBorder)
      {
          if(array == NULL || leftBorder >= rightBorder )
              return;
          /** 现在对数据进行原址排序 */
          int i = leftBorder-1;
          int j = leftBorder;
          int x = array[rightBorder];
          for(; j < rightBorder ; ++j)
          {
              if(array[j] <= x)
              {
                  exchange(array,++i, j);
              }
          }
          exchange(array ,++i , rightBorder);
          /** 递归的排序数组剩下的部分,将合适的数据放在适合的位置上 */
          quickSortKernel(array , leftBorder , i-1);
          quickSortKernel(array , i+1, rightBorder);
      }
      
      /**
       * 快速排序接口
       * @param array 输入的数组
       */
      void quickSort(int array[] , int arraySize)
      {
          if(array == NULL)
              return ;
          quickSortKernel(array , 0 , arraySize-1);
      }
      
      /**
       * 打印数组
       * @param array     数组指针
       * @param arraySize 数组大小
       */
      void printArray(int array[] , int arraySize)
      {
          if(array == NULL)
              return;
          for (int i = 0; i < arraySize; ++i)
          {
              cout<<array[i]<<"\t";
          }
          cout<<endl;
      }
      
      int main(int argc, char const *argv[])
      {
          int array[10];
          int arraySize = sizeof(array)/sizeof(int);
      
          srand((int)(time(NULL)));
          for (int i = 0; i < arraySize; ++i)
          {
              array[i] = rand()%100;
          }
      
          cout<<"before sort the array:"<<endl;
          printArray(array , arraySize);
      
          quickSort(array , arraySize);
      
          cout<<"after sort the array:"<<endl;
          printArray(array ,arraySize);
      
          while(1);
          return 0;
      }

      程序运行结果为:

      before sort the array:
      24 71 30 26 16 23 56 21 68 30
      after sort the array:
      16 21 23 24 26 30 30 56 68 71

      下面采用第二种方法实现了quickSortKernel()函数

      /**
       * 采用d递归来排序的数组
       * @param array       数组
       * @param leftBorder  左边界
       * @param rightBorder 右边界
       */
      void quickSortKernel(int array[] , int leftBorder , int rightBorder)
      {
          if(array == NULL || leftBorder >= rightBorder)
              return;
          int i=leftBorder;
          int j = rightBorder-1;
          while(1)
          {
              // i向右边寻找比array[rightBorder]大的元素
              while(i < rightBorder && array[i] <= array[rightBorder])
                  ++i;
              // j 向左边寻找比array[rightBorder小的元素]
              while(j >= i && array[j] >= array[rightBorder])
                  --j;
              if(j+1 != i)
              {
                  exchange(array , i , j);
              }
              else
              {
                  break;
              }
          }
          exchange(array , i , rightBorder);
          quickSortKernel(array , leftBorder , i-1);
          quickSortKernel(array , i+1 , rightBorder);
      }

      运行结果为:

      before sort the array:
      36 14 24 79 0 71 9 33 77 5
      after sort the array:
      0 5 9 14 24 33 36 71 77 79

      上面两种方法起始很相似,都是采用了递归的方法,只是前一种方法找目标位置 k 的方法时候从左边位置开始找,下面这种方法是从两边开始向中间寻找对应的目标位置k

      快速排序的性能分析

      如果快速排序输入的数组时一个按照降序排序的数组,那么快速排序的将达到最差运行时间 O(n2) ,和插入排序一样,如果输入的数组时一个升序排序的数组,快速排序的运行时间还是 O(n2) ,而插入排序输入一个升序排序的数组,运行时间仅有 O(n) ,连插入排序都比不上了。
      那怎么避免快速排序的最差情况呢?我们来分析一下最坏运行时间的发生情况,那就是子数组的极度不平衡。我们知道在每次找到目标位置 k 以后,都会生成两个子数组A˙={ai,..,ak1} A¨={ak+1,...,aj} ,如果这两个数组里面有一个数组为空,而且是保证每一次迭代子数组都必然有一个为空,那么我们得到递归表达式为:

      T(n)=T(n1)+O(1)+O(n)

      上面式子里面的 O(1) 为交换两个元素的代价, O(n) 为遍历一边所有元素需要的代价,由上面的递归式子我们得到
      T(n)=O(n2)

      那最优的运行时间在什么情况下发生呢?假设两个字数组的元素个数比为 A˙:A¨=1:1 ,即两个数组里面的元素个数相同,那么我们可以得到递归式:
      T(n)=2T(n/2)+O(1)+O(n)

      由主定理可得出:
      T(n)=O(nlogn)

      我们得出最优情况下的运行时间

      为什么上面两种情况会有如此大的区别呢?我们可以将问题划分为两个子问题的情形用二叉树表示出来,如果每次划分数组,其中一个子数组为空,那么每次只能使问题的规模减小1,那么这棵二叉树的深度也将会是 n ,而二叉树每一层的代价就是遍历一遍数组的代价O(n),于是最差的代价就是 nO(n)=O(n2) ,在最优情况下,由于两个子数组时等同大小的,于是每次都能使问题的规模减少为原来的一半,那么二叉树的深度也就是 logn ,每一层的遍历代价都是 O(n) ,所以最优情况下的运行后时间代价为 O(nlogn)

      我们推广到一般情况下来看,假设我们得到的两个子数组里面的元素比为 |A˙={ai,..,ak1|:|A¨={ak+1,...,aj}|=1α:α(α0.5) ,那么假设这棵二叉树的最浅深度为 h1 ,最深深度为 h2 ,于是我们得到下面的不等式

      n×αh1αh1h111nlognlogα

      我们知道二叉树的最浅深度至少为 lognlogα

      同理可知

      n×1α)h21α)h2h211nlognlog(1α)

      我们知道二叉树的最深深度至少为 lognlog(1α) 于是我们按照最深深度来计算在此情况下的运行时间的上界为 O(n)×lognlog(1α)=O(nlogn) 可见只要 α1 的任何情况下,都能保证运行时间 O(nlogn) ,最多只是常数因子大一点小一点而已。

      通过上面分析可知只要我们保证两个子数组里面任何一个都不为空,那么运行时间都是 nlogn ,所以我们改进上面的算法:

      /**
       * 采用d递归来排序的数组
       * @param array       数组
       * @param leftBorder  左边界
       * @param rightBorder 右边界
       */
      void quickSortKernel(int array[] , int leftBorder , int rightBorder)
      {
          if(array == NULL || leftBorder >= rightBorder)
              return;
          // 为了防止是一个纯升序或者是降序的数组,
          // 现在选取数组中前中后三个元素,得到第二大的那个
          int center = (leftBorder + rightBorder)/2;
          if(array[leftBorder] > array[center] && array[rightBorder] < array[center])
              exchange(array , center , rightBorder);
          else if(array[leftBorder] > array[center] && array[rightBorder] > array[leftBorder])
              exchange(array , leftBorder , rightBorder);
      
          int i=leftBorder;
          int j = rightBorder-1;
          while(1)
          {
              // i向右边寻找比array[rightBorder]大的元素
              while(i < rightBorder && array[i] <= array[rightBorder])
                  ++i;
              // j 向左边寻找比array[rightBorder小的元素]
              while(j >= i && array[j] >= array[rightBorder])
                  --j;
              if(j+1 != i)
              {
                  exchange(array , i , j);
              }
              else
              {
                  break;
              }
          }
          exchange(array , i , rightBorder);
          quickSortKernel(array , leftBorder , i-1);
          quickSortKernel(array , i+1 , rightBorder);
      
      }

      输入一个数组,首先取出数组头部,数组尾部,数组中部的三个元素,得到这三个元素的第二大的那个元素作为主元,你也可以不这么选,你选出前面三个元素其实也可以。我们只要保证没有一个子数组为空即可!

      运行结果为:

      before sort the array:
      92 17 47 13 89 13 58 25 42 10
      after sort the array:
      10 13 13 17 25 42 47 58 89 92

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
快速排序是一种常用的排序算法,其核心思想是通过选择一个基准元素,将待排序序列分成两个子序列,小于基准的在左边,大于基准的在右边,然后对子序列分别进行递归排序,最终得到有序序列。 在C语言中,我们可以使用以下的代码来实现快速排序: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 寻找基准元素的位置,并返回 int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选择最后一个元素为基准 int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); // 将小于基准的元素放到左边 } } swap(&arr[i + 1], &arr[high]); // 将基准元素放到正确的位置 return (i + 1); } // 快速排序函数 void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); // 找到基准元素的位置 quickSort(arr, low, pi - 1); // 对左边子序列进行递归排序 quickSort(arr, pi + 1, high); // 对右边子序列进行递归排序 } } // 主函数 int main() { int arr[] = {64, 25, 12, 22, 11}; int n = sizeof(arr) / sizeof(arr[0]); quickSort(arr, 0, n - 1); printf("排序结果:"); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } return 0; } ``` 以上的代码首先定义了一个交换函数`swap`,用于交换两个元素的值。接下来,定义了一个寻找基准元素位置的函数`partition`,通过选择最后一个元素作为基准,将小于基准的元素放到左边,大于基准的元素放到右边,并返回基准元素的位置。 最后,定义了快速排序函数`quickSort`,首先通过`partition`函数找到基准元素的位置,然后递归地对基准元素左边的子序列和右边的子序列进行排序。 在主函数中,先定义一个待排序的数组`arr`,然后通过`quickSort`函数对其进行排序。最后,输出排序结果。 快速排序的时间复杂度为O(nlogn),是一种高效的排序算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值