[数据结构]———交换排序

简介: [数据结构]———交换排序

1.交换排序



基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。


首先我们先介绍并创建两个函数,后面要用


第一个定义了一个名为Swap的函数

实现了交换两个整数指针所指向的值


void Swap(int* p1, int* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

第二个三数取中

定义了一个名为GetMidi的函数用于确定三个位置begin、midi和end在数组a中的中间值索引,确定并返回中间值的索引


int GetMidi(int* a, int begin, int end)
{
  int midi = (begin + end) / 2;
  // begin midi end 三个数选中位数
  if (a[begin] < a[midi])
  {
  if (a[midi] < a[end])
    return midi;
  else if (a[begin] > a[end])
    return begin;
  else
    return end;
  }
  else // a[begin] > a[midi]
  {
  if (a[midi] > a[end])
    return midi;
  else if (a[begin] < a[end])
    return begin;
  else
    return end;
  }
}


第三个 实现快速排序算法

函数QuickSort采用递归的方式对数组a进行排序。它接收三个参数:数组a、开始索引begin和结束索引end。

调用partSort函数,它用来进行划分操作,将数组中的元素分为两部分,以一个关键元素作为参考,小于等于关键元素的元素放在关键元素的左边,大于关键元素的元素放在关键元素的右边。partSort函数返回关键元素的索引位置keyi。

递归调用 QuickSort 函数对基准值左边的子数组进行排序,起始位置为 begin,结束位置为 keyi - 1。这样可以保证基准值左边的子数组是有序的。

最后,递归调用 QuickSort 函数对基准值右边的子数组进行排序,起始位置为 keyi + 1,结束位置为 end。这样可以保证基准值右边的子数组是有序的

void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
  return;
  int keyi = partSort(a, begin, end);
  QuickSort(a, begin, keyi - 1);
  QuickSort(a, keyi + 1, end);
}

2.冒泡排序



6f8b67933e744c5793680a9f8cdf2230.gif

代码解析

void BubbleSort(int* a, int n)//冒泡排序
{
  for (int j = 0; j < n; j++)
  {
  bool exchange = false;
  for (int i = 1; i < n - j; i++)
  {
    if (a[i - 1] > a[i])
    {
    Swap(&a[i - 1], &a[i]);
    exchange = true;
    }
  }
  if (exchange == false)
    break;
  }
}


image.png


冒泡排序的特性总结:


1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定


3.快速排序



快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,


其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。


// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
if(right - left <= 1)
return;
// 按照基准值对array数组的 [left, right)区间中的元素进行划分
int div = partion(array, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
// 递归排[left, div)
QuickSort(array, left, div);
// 递归排[div+1, right)
QuickSort(array, div+1, right);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

将区间按照基准值划分为左右两半部分的常见方式有:


1. hoare版本

image.png


在快速排序算法中,需要在找到左边比关键值大的元素和右边比关键值小的元素之后才进行元素交换,以确保左边的元素都比关键值小,右边的元素都比关键值大。但是在代码中目前的交换步骤存在问题,因为在while循环结束之后直接进行了一次元素交换,而应该是在找到左右指针位置后再进行交换。


int QuickSort1(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;
}


image.png


2. 挖坑法

6f8b67933e744c5793680a9f8cdf2230.gif


代码解析

挖坑法:

使用挖坑法的思想,即每次在处理完左右指针位置的元素后,将最初选择的“坑”填入正确的位置。

函数逻辑:

首先在数组中选择基准值(pivot)并与起始位置的元素交换,这里选择的基准值为a[begin]。

使用两个指针begin和end分别指向数组的起始和末尾,开始移动指针以找到需要交换的元素。

当begin小于end时,进行以下操作:

从右侧开始,找到第一个比基准值小的元素,将其填入“坑”中,同时更新“坑”的位置和end指针的位置。

从左侧开始,找到第一个比基准值大的元素,将其填入之前右侧留下的“坑”中,同时更新“坑”的位置和begin指针的位置。

最后,将基准值填入最后的“坑”中,返回该“坑”的位置,用于分割左右子数组。

返回值:

函数返回的是基准值在排序后的位置,用于在递归调用中分割左右子数组。

//2.挖坑法
int QuickSort2(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;
}


image.png


3. 前后指针版本

6f8b67933e744c5793680a9f8cdf2230.gif


代码解析

将数组分成两个子数组,左边的子数组中的元素都小于等于中间元素,右边的子数组中的元素都大于等于中间元素,并返回中间元素的索引


int QuickSort3(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;
}

image.png

相关文章
|
2天前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
23 10
|
2天前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
25 10
|
2天前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
22 7
|
3月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
48 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
3月前
|
存储 搜索推荐 算法
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
36 1
|
3月前
|
搜索推荐 索引
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(二)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
3月前
|
搜索推荐 C++
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
3月前
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
05_用一个栈实现另一个栈的排序
05_用一个栈实现另一个栈的排序
|
3月前
|
人工智能 搜索推荐 算法
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(三)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理