快速排序—数组、数组存储结构的链表排序算法

快速排序思想

快速排序算法是一种划分交换的方法,采用分治法进行排序

基本思想是:任取待排序元素序列中的某个元素作为基准元素(一般取第1个元素),通过基准元素将整个元素序列分别划分为左右两个子序列,左子序列中所有元素都小于等于基准元素,右子序列中的所有元素都大于等于基准元素,并且基准元素就唯一确定了元素最终摆放的位置,然后对左右个序列重复以上处理,直到划分出来的基准元素都摆放在了相应的位置上为止

算法步骤

给定一个数组data[N](且N>1),以实现数组元素从小到大排序为例:

第1轮:

选取第0个元素为基准元素,pivot = data[0],使用2个指针,一个指针i依次指示其后N-1个元素,另一个指针 pivotpos 依次指示比基准元素小的元素,依次比较指针i所指元素与基准元素;

若指针i所指元素比pivot小,则移动指针pivotpos,此时若pivotpos所指元素比指针 i所指元素小,则交换 ipivotpos所指元素 ;

若指针i所指元素比pivot大,则继续判断下一个元素;

当N-1个元素都比较完以后,此时指针移动到pivotpos = k所指元素,最后将data[pivotpos]与基准元素data[0]交换并返回指针pivotpos,此时data[k]位置唯一确定,并将整个序列划分为左右两个子序列A[u] 0 = < u < = k − 1 0=<u<=k-1 0=<u<=k1)和B[v]( k + 1 < = v < = N − 1 k+1<=v<=N-1 k+1<=v<=N1)

第2轮:

对两个子序列AB,子序列A选取的基准元素pivot=data[0],子序列B选取的基准元素为pivot=data[k+1],对两个子序列递归调用第1轮的处理过程,然后不断递归直到子序列全被划分为一个个独立的基准元素并摆放到相应的位置时排序完毕

以下图给定的序列为例,各轮排序结果如下:

在这里插入图片描述

算法复杂度

快速排序过程中,每次划分出一个基准元素并并成为最终摆放的位置,基准元素将整个序列划分为左子序列和右子序列,并存在最理想和最坏的情况

最理想的情况:

基准元素划分的左右子序列长度相同,则下一步是对两个长度减半的子序列进行排序。在一个有n元素的序列中,划分出一个基准元素所需时间为O(n),设T(n)是对整个元素序列排序所需的时间,若每次划分出的基准元素恰好将序列分为两个等长的序列,则总的时间为:

当 n = n 时 , T ( n ) = 2 T ( n 2 ) + c n 当 n = n时,T(n) = 2 T(\frac{n}{2})+cn n=nT(n)=2T(2n)+cn

当 n = n 4 时 , T ( n ) = 4 T ( n 4 ) + 2 c n 当 n = \frac{n}{4}时,T(n) = 4 T(\frac{n}{4})+2cn n=4nT(n)=4T(4n)+2cn

当 n = n 8 时 , T ( n ) = 8 T ( n 8 ) + 3 c n 当 n = \frac{n}{8}时,T(n) = 8 T(\frac{n}{8})+3cn n=8nT(n)=8T(8n)+3cn

当 n = m 时 , T ( n ) = 2 m T ( n 2 m ) + 3 c n ≈ 2 m T ( 1 ) + m c n 当 n = m时,T(n) = 2^m T(\frac{n}{2^m})+3cn \approx 2^mT(1)+mcn n=mT(n)=2mT(2mn)+3cn2mT(1)+mcn

由 T ( 1 ) = T ( n 2 m ) 得 n = 2 m , 因 此 m = log ⁡ 2 n 由 T(1) = T(\frac{n}{2^m}) 得 n = 2^m,因此 m = \log{_2n} T(1)=T(2mn)n=2m,m=log2n

所 以 T ( n ) = 2 log ⁡ 2 n T ( 1 ) + n c log ⁡ 2 n ≈ n T ( 1 ) + n c log ⁡ 2 n ≈ n + n log ⁡ 2 n 所以T(n) = 2^{\log_2n}T(1)+nc\log_2n \approx nT(1)+nc\log_2n \approx n+n\log_2n T(n)=2log2nT(1)+nclog2nnT(1)+nclog2nn+nlog2n

可以得出时间复杂度为O( n log ⁡ 2 n n\log_2n nlog2n),有相关实验表明,就平均时间来计算,快速排序算法时所有内部排序算法中最好得一个,由于快速排序为递归调用,最大递归调用层数与递归树的深度一致,理想情况为 ⌈ log ⁡ 2 ( n + 1 ) ⌉ \lceil \log_2(n+1) \rceil log2(n+1),因此存储开销为 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)

最坏的情况:

当初元素序列已经有序,于是递归树成为单只树,每次划分只能得到一个比上一次少一个元素的子序列,这样必须经过n-1趟才能把所有元素定位,且第1趟需要n-1次比较才能确定一个元素摆放位置,第2趟需要n-2趟才能确定第2个元素的摆放位置,总的比较次数:

∑ i = 1 n − 1 ( n − 1 ) = 1 2 n ( n − 1 ) ≈ n 2 2 \sum_{i=1}^{n-1}(n-1) = \frac{1}{2}n(n-1)\approx \frac{n^2}{2} i=1n1(n1)=21n(n1)2n2

这样排序速度会退化到简单排序的水平,占用存储栈达到O(n)

快速排序的效率与初始序列中元素的摆放顺序有关,当元素个数很少时,快速排序排序算法的比其他排序方法慢,但对于元素个数较大时,排序比较快

根据一些研究表明,序列长度N取5~25时,采用直接插入排序比快速排序至少快10%,因此对于小规模的子序列建议用插入排序

数组快速排序源码

#include <iostream>
using namespace std;
int arrayPartion(int arr[], int left, int right) {
  int pivot = arr[left];
  int pivotpos = left;
  for (int i = left + 1; i <= right; i++) {
    if (arr[i] < pivot) {
      pivotpos++;
      //当pivotpos!=i时,说明pivotpos所指元素比pivot还大
      //此时arr[i]<pivot说明找到了一个比pivot还小的元素,
      //只需要交换arr[i]与arr[pivotpos]
      if (pivotpos != i) {
        swap(arr[i], arr[pivotpos]);
      }
    }
  }
  arr[left] = arr[pivotpos];
  arr[pivotpos] = pivot;
  return pivotpos;
}
void arrayQuickSort(int arr[], int left, int right) {
  if (left < 0 || left > right) {
    return;
  }
  int pivotpos = arrayPartion(arr, left, right);
  arrayQuickSort(arr, left, pivotpos - 1);
  // pivotpos把数组分为左右两部分,左边比arr[pivotpos],右边比arr[pivotpos]大
  arrayQuickSort(arr, pivotpos + 1, right);
}

int main() {
  // int arr[]={5,6,8,1,2,3,5,99,123,33,77,23,0,-1,2,7,3,1,2};
  int arr[] = {6, 4, 3, 8, 7, 5, 2, 1};
  int sz = sizeof(arr) / sizeof(int);
  arrayQuickSort(arr, 0, sz - 1);
  for (int i = 0; i < sz; i++) {
    cout << arr[i] << ", ";
  }
  cout << endl;
}

数组存储结构的链表—快速排序

数组存储结构的链表采用快速排序,算法的复杂度与简单的数组快速排序一样,而在划分基准元素个过程中有两种方式实现元素的交换:

第1种—直接交换数据域,简单直接,不需要修改指针指向

第2种—交换指针域,实现1次交换需要做2次指针域的交换,即交换的两个元素的指针及各自前驱元素的指针

如下图,这是一个初始序列通过第2种方式交换指针域实现快速排序的图解过程:

在这里插入图片描述

数组存储结构的链表—快速排序源码

#include <stdio.h>
#include <stdlib.h>

struct Element {
  //数据域
  int data;
  //指针
  int link;
};

//封装Element相关属性
struct StaticList {
  int maxSize;
  // elements[0]为附加头节点
  // int *p;
  struct Element *elements;
  // tail指针指向最后一个添加的元素,方便尾部插入
  int tail;
  // avail指针指向备用链表的第一个位置
  int avail;
  // 指示当前元素的个数
  int currentSize;
};

int initStaticList(struct StaticList *p, int maxSize) {
  p->avail = 1;
  p->tail = 0;
  p->currentSize = 0;
  p->maxSize = maxSize > 10 ? (maxSize + 1) : 10;
  p->elements = (struct Element *)malloc(p->maxSize * sizeof(struct Element));
  for (int i = 1; i < p->maxSize; i++) {
    p->elements[i].link = i + 1;
  }
  p->elements[0].link = -1;
  p->elements[maxSize].link = -1;
}

void addElement(struct StaticList *p, int x) {
  int cur = p->avail;
  if (cur == -1) {
    printf("memory overflow");
    return;
  }
  p->avail = p->elements[cur].link;
  p->elements[cur].data = x;
  //新插入元素指针域为-1
  p->elements[cur].link = -1;
  p->elements[p->tail].link = cur;
  p->tail = cur;
  p->currentSize++;
}
void output(struct Element *ems) {
  int cur = ems[0].link;
  while (cur != -1) {
    printf("%d, ", ems[cur].data);
    cur = ems[cur].link;
  }
  printf("\n");
}

int listPartion_swap_data(struct Element ems[], int head, int end) {
  //跳过附加节点
  int begin = ems[head].link;
  int pivot = ems[begin].data;
  int pivotpos = begin;
  int dumy;
  int cur = ems[begin].link;
  while (cur != end) {
    if (ems[cur].data < pivot) {
      pivotpos = ems[pivotpos].link;
      //交换指针pivotpos与cur所指元素, 不交换指针
      if (pivotpos != cur) {
        dumy = ems[pivotpos].data;
        ems[pivotpos].data = ems[cur].data;
        ems[cur].data = dumy;

        // swap(ems[pivotpos].data, ems[cur].data);
      }
    }
    cur = ems[cur].link;
  }
  // povotpos没有移动过,则不需要交换
  if (pivotpos != begin) {
    //交换ems[begin]与ems[pivotpos]的数据
    ems[begin].data = ems[pivotpos].data;
    ems[pivotpos].data = pivot;
  }
  return pivotpos;
}

//快速排序,交换指针, 需要附加头节点才能完成
int listPartion_swap_point(struct Element ems[], int head, int end) {
  int begin = ems[head].link;
  int pivot = ems[begin].data;
  int pivotpos = begin;
  int precur = begin;
  int cur;
  int prepos;
  int dumy;
  // cout << "$$$$ head ==> "<<head<<", end ==>"<<end<<", begin ==> "<<begin<<",
  // ems[precur].link ==> " << ems[precur].link<<endl; outputByPosition();
  while (ems[precur].link != end) {
    cur = ems[precur].link;
    if (ems[cur].data < pivot) {
      // prepos作为pivotpos的前驱指针
      prepos = pivotpos;
      pivotpos = ems[pivotpos].link;
      //交换指针pivotpos与cur的指针,pivotpos前驱为prepos, cur前驱为precur
      if (pivotpos != cur) {
        //先交换prepos与precur的指针域
        dumy = ems[prepos].link;
        ems[prepos].link = ems[precur].link;
        ems[precur].link = dumy;
        //再交换pivotpos与cur
        dumy = ems[cur].link;
        ems[cur].link = ems[pivotpos].link;
        ems[pivotpos].link = dumy;
        // cur与pivotpos交换
        dumy = cur;
        cur = pivotpos;
        pivotpos = dumy;
      }
    }
    // cout<<"precur ==>"<<precur<<", cur ==> "<<cur<<endl;
    precur = cur;
  }
  // pivotpos ==
  // begin,表示没有改变过,也就是说此时ems[begin]就是最小元素,此时对于指针则不能交换,否则会问题
  if (pivotpos != begin) {
    //交换pivotpos与begin指针,因为pivotpos的前驱为prepos,begin的前驱为head
    //先交换前驱head与前驱prepos的指针域
    dumy = ems[prepos].link;
    ems[prepos].link = ems[head].link;
    ems[head].link = dumy;
    //再交换pivotpos与begin的指针域
    dumy = ems[pivotpos].link;
    ems[pivotpos].link = ems[begin].link;
    ems[begin].link = dumy;
    // pivotpos与begin交换后,pivotpos修改为begin
    pivotpos = begin;
    // printf("end ### pivotpos ==> %d\n",pivotpos);
  }
  return pivotpos;
}

void listQuickSort(struct Element ems[], int head, int end) {
  // head为附加头节点指针
  int begin = ems[head].link;
  if (begin != end && ems[begin].link != end) {
    // int pivotpos = listPartion_swap_data(ems, head, end);
    int pivotpos = listPartion_swap_point(ems, head, end);
    // cout<<"@@@@@@@@@@ head ==> "<<head<<", pivotpos ==> "<<pivotpos<<", end
    // ==> "<<end<<endl; cout<<"1 ## ems[pivotpos] ==>
    // "<<ems[pivotpos].data<<endl; output();
    // head为附加指针
    listQuickSort(ems, head, pivotpos);
    // cout << "2 ## ems[pivotpos] ==> " << ems[pivotpos].data << endl;
    // output();
    // pivotpos作为基准元素所指的指针
    listQuickSort(ems, pivotpos, end);
    // cout << "3 ## ems[pivotpos] ==> " << ems[pivotpos].data << endl;
    // output();
  }
}

void listQuickSort(struct Element ems[]) {
  int begin = ems[0].link;
  if (begin == -1 || ems[begin].link == -1) {
    return;
  }
  listQuickSort(ems, 0, -1);
}
int main() {
  struct StaticList p;
  // int arr[] = {1, 4, 6, 7, 0, 3, 10, 9, 2, 9,
  //              3, 7, 4, 8, 5, 6, 1, 0, 9999, 44,
  //              9999, 2, 4, 9, 4, 12, 44, 99, -1, 0,
  //              5, 888, 345, 12344, 5555, 9090, 10000, 123, 444, -888,
  //              100, 400, 120000, 899999, -1, -2, 8, 9, 8};
  // int arr[]={6,4,3,8,7,5,2,1};
  int arr[] = {2, 5, 6, 7, 1, 4, 8, 9};
  // int arr[] = {9, 0};
  int n = sizeof(arr) / sizeof(int);
  initStaticList(&p, n);
  for (int i = 0; i < n; i++) {
    addElement(&p, arr[i]);
  }
  printf("#### quick sort before ####\n");
  output(p.elements);
  printf("#### quick sort after ####\n");
  listQuickSort(p.elements);
  output(p.elements);
  free(p.elements);
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值