【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)

简介: 【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)

交换排序


快速排序

快排的过程图如下:



hoare版代码呈现

void QuickSort(int* a, int begin,int end)
{
  if (begin >= end)
  {
  return;
  }
  int left = begin, right =end;
  int keyi = begin;
  while (left < right)
  {
  //右边找小
  while (left < right && a[right] >= a[keyi])
  {
    right--;
  }
  //左边找大
  while (left < right && a[left] <= a[keyi])
  {
    left++;
  }
  Swap(&a[left], &a[right]);
  }
  Swap(&a[left], &a[keyi]);
  keyi = left;
  // [begin,keyi-1] keyi [keyi+1,end]
  QuickSort(a, begin, keyi - 1);
  QuickSort(a, keyi+1, end);
}


分析:快排过程就是左边找比key大的值,右边找比key小的值,找到后就交换。直到left与right相遇,就交换keyi和left对应的值。这是单趟的,后续过程重复,可以思考二叉树的递归过程,快排递归与其相似(见下图)。




下图中,划红线的地方是容易出错的地方。




理解了前面,这里解释一下为什么相遇位置比keyi位置的值要小?


因为右边先走。


相遇有2种情况


1. R遇L->R没有找到比key小,一直走,直到遇到L,相遇位置是L,比key小。

2.L遇R->R先走,找到小的停下来了,L找大,没有找到,遇到R停下来了,相遇位置是R,比key小。

如果左边做key,R先走。

如果右边做key,L先走。


快排优化

1.三数取中法选key

2.递归到小的子区间时,可以考虑使用插入排序

三数取中法

快排对于有序的数据,效率不是很高。



如上图,我们前面的快排是固定选key的,也就是左边第一幅图,效率很低。理想情况下,每一次都二分,这样效率就能提高。这时就用到三数取中法。


三数取中法指三个数里面取中间大小的数,然后将他与key交换位置,让这个中间大小的数作key。


完整代码如下:


int GetMidi(int* a, int begin, int end)
{
  int midi = (begin + end) / 2;
  //begin end midi三个数中选中位数
  if (a[begin] < a[midi])
  {
  if (a[midi] < a[end])
    return midi;
  else if (a[begin] > a[end])
    return begin;
  else
    return end;
  }
  else
  {
  if (a[midi] > a[end])
    return midi;
  else if (a[begin] < a[end])
    return begin;
  else
    return end;
  }
}
void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
  return;
  int midi = GetMidi(a, begin, end);
  Swap(&a[midi], &a[begin]);
  int left = begin, right = end;
  int keyi = begin;
  while (left < right)
  {
  //右边找小
  while (left < right && a[right] >= a[keyi])
  {
    right--;
  }
  //左边找大
  while (left < right && a[left] <= a[keyi])
  {
    left++;
  }
  Swap(&a[left], &a[right]);
  }
  Swap(&a[left], &a[keyi]);
  keyi = left;
  // [begin,keyi-1] keyi [keyi+1,end]
  QuickSort(a, begin, keyi - 1);
  QuickSort(a, keyi + 1, end);
}


小区间优化


假设在理想情况下,每次递归都像二叉树那样,递归到最后面几层时,假设还剩7个数,我们还得递归7次,这样明显不好。我们就可以在最后几层时,使用其他排序方法进行。这里使用插入排序。


完整代码如下:

void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
  return;
  if (end - begin + 1 <= 10)
  {
  InsertSort(a+begin, end - begin + 1);
  }
  else 
  {
  int midi = GetMidi(a, begin, end);
  Swap(&a[midi], &a[begin]);
  int left = begin, right = end;
  int keyi = begin;
  while (left < right)
  {
    //右边找小
    while (left < right && a[right] >= a[keyi])
    {
    right--;
    }
    //左边找大
    while (left < right && a[left] <= a[keyi])
    {
    left++;
    }
    Swap(&a[left], &a[right]);
  }
  Swap(&a[left], &a[keyi]);
  keyi = left;
  // [begin,keyi-1] keyi [keyi+1,end]
  QuickSort(a, begin, keyi - 1);
  QuickSort(a, keyi + 1, end);
  }
}


挖坑法


我们把不同方法的单趟排序重新弄成一个函数。


hoare版本:

//hoare版本 
int PartSort1(int* a, int begin, int end)
{
  int midi = GetMidi(a, begin, end);
  Swap(&a[midi], &a[begin]);
  int left = begin, right = end;
  int keyi = begin;
  while (left < right)
  {
  //右边找小
  while (left < right && a[right] >= a[keyi])
  {
    right--;
  }
  //左边找大
  while (left < right && a[left] <= a[keyi])
  {
    left++;
  }
  Swap(&a[left], &a[right]);
  }
  Swap(&a[left], &a[keyi]);
  return left;
}


挖坑法完整代码:


//挖坑法
int PartSort2(int* a, int begin, int end)
{
  int midi = GetMidi(a, begin, end);
  Swap(&a[midi], &a[begin]);
  int key = a[begin];
  int hole = begin;
  while (begin < end)
  {
  //右边找小,填到左边的坑
  while (begin < end && a[end] >= key)
  {
    end--;
  }
  a[hole] = a[end];
  hole = end;
  //左边找大,填到右边的坑
  while (begin < end && a[begin] <= key)
  {
    begin++;
  }
  a[hole] = a[begin];
  hole = begin;
  }
  a[hole] = key;
  return hole;
} 
void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
  return;
  int keyi = PartSort2(a, begin, end);
  QuickSort(a, begin, keyi - 1);
  QuickSort(a, keyi+1, end);
}

分析:挖坑法其实跟hoare版本比没啥提升,只不过更易理解,本质上没变。但不同的版本,单趟排序后的结果可能会不同。


前后指针版本



分析:


1.cur遇到比key大的值,cur++

2.cur遇到比key小的值,++prev,交换prev和cur位置的值,++cur

代码实现


//前后指针法
int PartSort3(int* a, int begin, int end)
{
  int midi = GetMidi(a, begin, end);
  Swap(&a[midi], &a[begin]);
  int keyi = begin;
  int prev = begin;
  int cur = prev + 1;
  while (cur <= end)
  {
  if (a[cur] < a[keyi] && ++prev!=cur)
    Swap(&a[prev], &a[cur]);
  ++cur;
  }
  Swap(&a[prev], &a[keyi]);
  keyi = prev;
  return keyi;
}


非递归版本快排

非递归版本的快排需要用到栈。下面给出需要的栈的函数:


void STInit(ST* pst)
{
  assert(pst);
  pst->a = NULL;
  pst->capacity = 0;
  pst->top = 0;
  //pst->top = -1;
}
void STDestroy(ST* pst)
{
  free(pst->a);
  pst->a = NULL;
  pst->capacity = pst->top = 0;
}
//栈顶插入删除
void STPush(ST* pst, STDataType x)
{
  assert(pst);
  if (pst->top == pst->capacity)
  {
  int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
  STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
  if (tmp == NULL)
  {
    perror("realloc fail");
    return;
  }
  pst->a = tmp;
  pst->capacity = newcapacity;
  }
  pst->a[pst->top] = x;
  pst->top++;
}
void STPop(ST* pst)
{
  assert(pst);
  assert(pst->top > 0);
  pst->top--;
}
STDataType STTop(ST* pst)
{
  assert(pst);
  assert(pst->top > 0);
  return  pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)
{
  assert(pst);
  //if (pst->top == 0)
  //{
  //  return true;
  //}
  //else
  //{
  //  return  false;
  //}
  return pst->top == 0;
}


非递归代码实现:


void QuickSortNonR(int* a, int begin, int end)
{
  ST s;
  STInit(&s);
  STPush(&s, end);
  STPush(&s, begin);
  while (!STEmpty(&s))
  {
  int left = STTop(&s);
  STPop(&s);
  int right = STTop(&s);
  STPop(&s);
  int keyi = PartSort3(a, left, right);
  // [left,keyi-1] keyi [keyi+1,right]
 
  if (keyi+1 < right)
  {
    STPush(&s, right);
    STPush(&s, keyi+1);
  }
  if (left < keyi - 1)
  {
    STPush(&s, keyi - 1);
    STPush(&s, left);
  }
  }
  STDestroy(&s);
}



分析:栈是后进先出,这里用栈是模拟递归的过程。先模拟递归左边,像二叉树递归那样,先入右边的数,再入左边,这样出的时候就先出左边的,然后就可以模拟先往左边递归了。


目录
相关文章
|
4天前
|
存储 监控 NoSQL
Redis处理大量数据主要依赖于其内存存储结构、高效的数据结构和算法,以及一系列的优化策略
【5月更文挑战第15天】Redis处理大量数据依赖内存存储、高效数据结构和优化策略。选择合适的数据结构、利用批量操作减少网络开销、控制批量大小、使用Redis Cluster进行分布式存储、优化内存使用及监控调优是关键。通过这些方法,Redis能有效处理大量数据并保持高性能。
22 0
|
3天前
|
C语言
C语言——函数递归
C语言——函数递归
4 0
|
4天前
|
C语言
每天一道C语言编程(递归:斐波那契数,母牛的故事)
每天一道C语言编程(递归:斐波那契数,母牛的故事)
5 0
|
4天前
|
存储 算法 搜索推荐
【Java高阶数据结构】图补充-拓扑排序
【Java高阶数据结构】图补充-拓扑排序
7 1
|
4天前
|
算法 搜索推荐 索引
数据结构与算法 排序(下)
数据结构与算法 排序(下)
12 1
|
4天前
|
缓存 算法 搜索推荐
数据结构与算法 排序(上)
数据结构与算法 排序(上)
11 0
[数据结构]-玩转八大排序(二)&&冒泡排序&&快速排序
[数据结构]-玩转八大排序(二)&&冒泡排序&&快速排序
|
3天前
|
C语言
C语言—内存函数的实现和模拟实现(内存函数的丝绸之路)
C语言—内存函数的实现和模拟实现(内存函数的丝绸之路)
17 0