数据结构从入门到精通(第四篇) :排序的入门(插入排序,希尔排序,选择排序,冒泡排序)

简介: 数据结构从入门到精通(第四篇) :排序的入门(插入排序,希尔排序,选择排序,冒泡排序)

排序的概念及其运用

排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。


稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次

序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排

序算法是稳定的;否则称为不稳定的。 内部排序:数据元素全部放在内存中的排序。


外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。


排序运用

高校排名:

image.png

接下来,我会一一介绍几种常见的排序算法


插入排序

直接插入排序

直接插入排序是一种简单的插入排序法


基本思想: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列


代码的实现

//直接插入排序
void InsertSort(int* a, int n)
{
  assert(a);//传入数组不为空指针
  int i;
  for (i = 0; i < n - 1; i++)
  {
  int end = i;
  int x = a[end + 1];
  while (end >= 0)
  {
    //升序
    if (a[end] >x)
    {
    a[end + 1] = a[end];
    end--;
    }
    else
    {
    break;
    }
  }
  a[end + 1] = x;
  }
}


希尔排序


解析

希尔排序在直接排序之前,进行预排列,将某些极端数据更快的排列到数列前面,构成一个接近排列好的序列,最后再进行一次直接插入排序


预排列的原理也是插入排列,只不过这里的将数组分成了gap组,分别对每一个小组进行插入排序

// 希尔排序
void ShellSort(int* a, int n)
{
  int gap = n;
  while (gap > 1)
  {
  gap /= 2;
  for (int i = 0; i < n - gap; i++)
  {
    int end = i;
    int x = a[end + gap];
    while (end >= 0)
    {
    if (a[end] > x)
    {
      a[end + gap] = a[end];
      end-=gap;
    }
    else
      break;
    }
    a[end + gap] = x;
  }
  }
}


当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比


选择排序

直接选择排序

解析

每一次遍历待排序的数据元素从中选出最小(或最大)的一个元素,存放在序列的起始(或者末尾)位置,直到全部待排序的数据元素排完


代码的实现

// 选择排序
void SelectSort(int* a, int n)
{
  int begin = 0, end = n - 1;//记录下标
  while (begin < end)
  {
  int mini = begin;
  for (int i = begin; i <= end; i++)
  {
    //遍历找到最小数据并记录下标
    if (a[i] < a[mini])
    mini = i;
  }
  Swap(&a[begin], &a[mini]);//交换
  begin++;//缩小范围
  }
}


总结


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

空间复杂度:O(1)

稳定性:不稳定


不推荐使用


堆排序

堆排序是指利用堆(数据结构)进行选择数据的一种排序算法


基础思想:


原则:

先将原数组建成堆,需要注意的是排升序要建大堆,排降序建小堆

注:以大堆为例


建堆:

一个根节点与子节点数据如果不符合大堆结构,那么则对根节点数据进行向下调整,而向下调整的前提是左右子树也符合大堆结构,所以从堆尾数据的根节点位置开始向下调整建大堆


排序:

大堆堆顶数据一定是待排数据中最大的,将堆顶数据与堆尾数据交换,交换后将除堆尾数据看成新堆,对现堆顶数据进行向下调整成大堆,以此循环直至排列完毕


向下调整:

找到子节点中的较大数据节点比较,如果父节点数据比大子节点小则交换,直到不符合则停止向下交换,此时再次构成了一个大堆结构.


代码的实现

void Adjustdown(int* a, int n,int parent)
{
  int child = parent * 2 + 1;
  while (child < n)
  {
  //找到数据大的子结点
  if (child + 1 < n && a[child + 1] > a[child])
  {
    child++;
  }
  //父节点数据小于子节点就交换
  if (a[parent] < a[child])
  {
    Swap(&a[parent], &a[child]);
    //更新下标
    parent = child;
    child = parent * 2 + 1;
  }
  else//否则向下调整完毕
    break;
  }
}
// 堆排序(升序)建大堆
void HeapSort(int* a, int n)
{
  int i;
  //建大堆
  for (i = (n - 1 - 1) / 2; i >= 0; i--)
  {
  Adjustdown(a, n, i);
  }
  //交换调整
  for (i = n - 1; i >= 0; i--)
  {
  Swap(&a[0], &a[i]);//与当前堆尾数据交换
  Adjustdown(a, i, 0);//对交换后堆顶数据进行向下调整
  }
}


总结:

堆排序使用堆来选数,效率就高了很多。

时间复杂度:O(N*logN)

空间复杂度:O(1)

稳定性:不稳定

交换排序之冒泡排序


冒泡排序


每次遍历数组,对相邻数据进行比较,不符合排序要求则交换


代码的实现

// 冒泡排序
void BubbleSort(int* a, int n)
{
  int i, j;
  for (i = 0; i < n - 1; i++)//趟数
  {
  for (j = 0; j < n - 1 - i; j++)//比较次数
  {
    if (a[j] > a[j + 1])//满足条件
    Swap(&a[j], &a[j + 1]);//交换
  }
  }
}


总结

排序的第一篇就讲到这里了,下一篇还会讲快速排序和归并排序,希望大家多多支持!!


相关文章
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
1413 29
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
510 10
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
431 7
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1309 10
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
421 59
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
1097 77
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
264 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
583 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
383 11

热门文章

最新文章