数据结构 || 常见的排序算法 @ 快速排序

本文深入解析快速排序算法,包括Hoare版本、挖坑法和前后标兵法等常见划分方法,探讨了递归与非递归实现,以及基准值选择与元素交换的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快速排序

  • 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法.
  • 基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,
  • 特点:左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值。
  • 结束条件:最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序的流程
  • 这里假设以数组最右面的值做基准值
    在这里插入图片描述
  • 通过上图我们可以看到,我可以通过递归的思想不断的将大区间分成小区间.
  • 那么其终止条件是什么呢?
  • 其实我们可以发现当区间不可再分的时候,递归也就无法再继续下去了.
  • 那么区间不可再分的条件是什么呢?
  • 那就是当区间大小为0 或者 区间大小为1的时候(既left >= right 的时候)

递归的代码实现

void _QuickSort(int *array,int left,int right){
 //终止条件:size==0 || size==1
 //left ==right 区间内还剩一个数
 //left > right 区间内没有数
  if(left>=right){
    return ;
  }
  int div;//比基准值小的放其左边,大的放到后面去,基准值所在的下标
  div=Partion(array,left,right);//遍历array[left,right]区间,把比其小的放左面,大的放右面
  _QuickSort(array,left,div-1);//分治解决左边的小区间
  _QuickSort(array,div+1,right);//分治解决右边的小区间
}

那么问题来了,非递归的方式可不可以实现这个过程呢?

  • 当然可以了,我们可以利用栈的后进先出 思想来完成.也可以通过队列 先进先出的思想来完成.
  • 具体步骤如下
  • 通过上述递归的方式我们发现,要完成排序,我们要先拿到区间两端数值的下标,再进行排序

说明:先拿到左边区间两端的下标值还是右边区间两端的下标值都可以,但是必须是同一区间两端的下标值

使用栈的思想拿到区间两端下标的实现方式
特点:后进先出

void _QuickSort(int* array,int left,int right){
  stack<int> s;
  s.push(right);
  s.push(left);

  while(!s.empty()){
    int _left=s.top();
    s.pop();
    int _right=s.top();
    s.pop();

    if(_left>=_right){
      continue; 
    }

    int div=Partition(array,_left,_right);

    //div+1 right
    s.push(_right);
    s.push(div+1);
    //_left,div -1
    s.push(div-1);
    s.push(_left);
  }
}

通过队列拿到区间两端下标值的实现方式
特点:先进先出

void _QuickSort(int *array,int left,int right){

  
  queue<int> q;
  q.push(left);
  q.push(right);

  while(!q.empty()){
    int _left=q.front();
    q.pop();
    int _right=q.front();
    q.pop();

    if(_left>=_right){
      continue;
    }

    int div=Partition(array,_left,_right);
    //[left,div-1]
    q.push(_left);
    q.push(div-1);
    //[div+1,_right]
    q.push(div+1);
    q.push(_right);   

  } 
}
将区间按照基准值划分为左右两半部分的常见方法
  • 函数Pation的作用说明
  • 1.将区间分成比基准值大的一部分与比基准值小的一部分
  • 2.返回选定的基准值

【说明:下面方式都是将区间最右边的数作为基准值】

1.hoare版本
  • 算法思想
  • 1.标记区间中需要排序的开始和结束位置
  • 2.从两端开始遍历整个排序区间,将两端比基准值小的值和比基准值大的值进行交换,重复该步骤
  • 3.最后将标准值(待排序区间的最右边)与begin停止处的值交换
  • 4.返回选定的标准值
int Partion(int* array,int left,int right){
  int begin=left;
  int end=right;  //不能从right-1开始

  while(begin<end){
    while(begin<end && array[begin]<=array[right]){
      begin++;
      //在遍历过程中对大于基准值的元素进行定位
      //此处的array[begin]<=array[right]中的"=" 必须存在
      //如果不存在的话会造成基准值右边区间的死循环
    }
    

    while(begin<end && array[end]>=array[right]){
      end--;
      //在遍历过程中对小于基准值的元素进行定位
    }

    if(begin!=end){  
      Swap(array+begin,array+end); 
    }//当begin和end两个数相同的时候,两者指向相同的值,所以不需要交换
  }

  	Swap(array+begin,array+right);
    //返回当前基准值所在位置,
    return begin;
}
2.挖坑法版本
  • 算法思想
  • 1.标记区间中需要排序的开始和结束位置
  • 2.保存基准值(避免填坑造成的数据覆盖,导致标准值丢失),挖出填坑的空间
  • 3.从待排序区间前端开始找比标准值大的数,去填右边区间的坑
  • 4.从待排序区间后端开始找比基准值小的数,去填左边区间的坑
  • 5.用标准值去填最后一个坑
  • 6.返回标准值
  • 代码实现
int  Partion_wakeng(int *array,int left,int right){
  int begin=left;
  int end=right;

  int flag=array[right]; //保存基准值,给挖坑目标腾空间
  while(begin<end){

    while(begin<end && array[begin]<= flag){
      begin++;
    }
    //右侧坑
    array[end]=array[begin];

    while(begin<end && array[end] >= flag ){
      end--;
    }
   //左侧坑
   array[begin]=array[end];
  } 
  array[begin]=flag;
  return begin;
}
3.前后标兵法
  • 算法思想
  • 1.使用标兵指向数组前端,
  • 2.遍历整个数组,将标兵指向的数据元素和符合条件的元素进行交换,重复该步骤
  • 3.最后交换标兵和标准值
  • 4.返回标准值
int Partion_Point(int *array,int left,int right){
  int d=left;

  for(int i=left;i<right;i++){
    if(array[i]<array[right]){
    if(d!=i){
      Swap(array+d,array+i);
     }
      d++;
    }
  }
  Swap(array+d,array+right);
  return d;
}
我的实现方式
//挖坑法测试
//自己实现的版本
//在遍历过程中,将基准值和不符合条件的值进行交换
//并不断更新标准值的下标数据
int  Partion_wakeng(int *array,int left,int right){
  int begin=left;
  int end=right;

  int flag=right; //保存基准值,用来挖坑腾空间
  while(begin<end){

    while(begin<end && array[begin]<= array[flag]){
      begin++;
    }

    if(array[begin]>array[flag]){
      Swap(array+begin,array+flag);
      flag=begin;
    }
    while(begin<end && array[end] >= array[flag] ){
      end--;
    }

    if(array[end]<array[flag]){
      Swap(array+end,array+flag);
      flag=end;
    }
  }
  Swap(array+begin,array+flag);
  return begin;
}
总结
特性总结
  • 1.时间复杂度:O(N*lonN)
  • 2.空间复杂度:O(logN)
  • 3.稳定性:不稳定
个人快排思想:(个人理解)
  • 1.选择一个基准值,遍历整个排序数组.把比基准值小的放到基准值前面,把比基准值大的放到基准值后面.多次排序(递归、堆、栈都可以实现)后使得数组变的有序。
  • 难点:基准值的选择和元素与基准值比较后的交换.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值