【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);
}



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


目录
相关文章
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
816 4
|
算法 搜索推荐
快速排序-数据结构与算法
快速排序(Quick Sort)是一种基于分治法的高效排序算法。其核心思想是通过选择基准(pivot),将数组划分为左右两部分,使得左侧元素均小于基准,右侧元素均大于基准,然后递归地对左右两部分进行排序。时间复杂度平均为 O(n log n),最坏情况下为 O(n²)(如数组已有序)。空间复杂度为 O(1),属于原地排序,但稳定性不佳。 实现步骤包括编写 `partition` 核心逻辑、递归调用的 `quickSort` 和辅助函数 `swap`。优化方法有随机化基准和三数取中法,以减少最坏情况的发生。
776 13
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
1411 29
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
818 16
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
508 10
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
368 10
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
431 7
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
663 1
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
1042 7
|
存储 缓存 C语言
数据结构——双链表(C语言)
数据结构——双链表(C语言)