【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(二)

简介: 【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理

【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一)https://developer.aliyun.com/article/1617280

3.4 选择排序(暴力选数)

基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

单趟排序:

void SelectSort(int* a, int n)
{
  int begin = 0,end = n - 1;
  int max = begin, min = begin;
    //max为0,已经参与比较当中,这里begin+1防止冗余
  for (int i = begin + 1; i <= end; i++)//i从下一次开始
    {
      if (a[i] > a[max])
      {
        max = i;
      }
      if (a[i] < a[min])
      {
        min = i;
      }
    }
    Swap(&a[begin], &a[min]);
      Swap(&a[end], &a[max]);
    }

过程解析:这里属于单躺选择排序,将最小(或最大)放在最左边(或最右边),同时begin和end表示最左边(或最右边)的位置发生改变。

瑕疵版本:

void SelectSort(int* a, int n)
{
  int begin = 0,end = n - 1;
  int max = begin, min = begin;
  while (end > begin)
  {
    for (int i = begin + 1; i <= end; i++)//i从下一次开始
    {
      if (a[i] > a[max])
      {
        max = i;
      }
      if (a[i] < a[min])
      {
        min = i;
      }
    }
    Swap(&a[begin], &a[min]);
    Swap(&a[end], &a[max]);
    begin++;
        end--;      
  }
}

注意:这里需要注意当最大的数据在首元素,那么当第一次Swap把最大的数据,放在其他地方,导致了第二次Swap中max为下标的数据不是最大的数据。

进行两次交换会导致两个位置的数值所对应的索引发生改变,会对下一次交换产生影响,需要提前判断。

完整版本:

void SelectSort(int* a, int n)
{
  int begin = 0,end = n - 1;
  int max = begin, min = begin;
  while (end > begin)
  {
    for (int i = begin + 1; i <= end; i++)//i从下一次开始
    {
      if (a[i] > a[max])
      {
        max = i;
      }
      if (a[i] < a[min])
      {
        min = i;
      }
    }
    Swap(&a[begin], &a[min]);
    if (begin == max)
    {
      max = min;
    }
    Swap(&a[end], &a[max]);
    begin++;
    end--;
  }
}

选择排序的特点总结:

  • 选择排序思想非常好理解,但是效率不是很好。(实际中很少使用)
  • 时间复杂度: O(N2)
  • 空间复杂度: O(1)
  • 稳定性:不稳定

3.4 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

void HeapSort(int *a,int n)  
{
    //O(N*logN)
    //for(int i=0;i<n;i++)
    //{
    //  AdjustUp(a,i);
    // }
    //O(N)
    for(int i=(n-1-1)/2;i>=0;--i)
    {
        AdjustDown(a,n,i);//从倒数的第一个非叶子,也就是最后一个结点的父亲
    }
    int end=n-1;//下标--同时调整后,最后一个元素不再改动
    //O(N*logN)
    while(end>0)//利用堆删除思想进行排序
    {
        Swap(&a[0],&a[end]);
        AdjustDown(a,end,0);//要清楚为什么要向下调整
        --end;
    }
}

堆排序的特性总结:

  • 堆排序使用堆来选数,效率就高了很多。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

3.5 冒泡排序

基本思想:比较两个相邻的元素,当不满足某一条件时,发生相邻元素的交换,直到整个序列有序为止。

原始版本:

int main()
{
    int arr[10] = { 0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    for (int i = 0; i < sz; i++)
    {
        scanf("%d ", &arr[i]);///输入数据
    }
    int tap = 0;
    for (int i = 0; i < sz - 1; i++)
    {//注意只需要sz-1趟就行,最后一次是两个相邻最后的排序
        for (int j = 0; j < sz - 1 - i; j++)
        {//完成一趟,少一个元素参与排序所以-i,-1是下标的原因
            if (arr[j] > arr[j + 1])//判断条件
            {//进行交换
                tap = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tap;
            }
        }
    }
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);//打印数据
    }
    return 0;
}

过程解析:序列中两个相邻元素进行比较,当满足条件发生交换操作,导致最小或大元素放到后面位置,不断重复该过程,直到有序。

不足点:目的是直到有序就停下来,但是上面的逻辑是地毯式查找,对此我们需要设置一个标识变量去标识是否有序,如果不需要交换说明有序直接退出。

优化版本:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  for (int i = 0; i < sz; i++)
  {
    scanf("%d ", &arr[i]);///输入数据
  }
  int tap = 0;
  for (int i = 0; i < sz - 1; i++)
  {
        int flag=1;
    for (int j = 0; j < sz - 1 - i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        tap = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tap;
                flag=0;
      }
    }
        if(flag==1)
        {
          break;
        }
  }
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);//打印数据
  }
  return 0;
}

冒泡排序的特性总结

  • 冒泡排序是一种非常容易理解的排序
  • 时间复杂度: O (N2)
  • 空间复杂度: O(1)
  • 稳定性:稳定

小总结:选择排序与冒泡排序

选择和冒泡时间复杂度都是N2这个量级,但是还是有区别的

  • 选择排序是:n n-2 n-4
  • 冒泡排序是:n n-1 n-2

选择排序的每一轮都包含了选择和交换两个操作。虽然交换是实现排序的具体操作,但选择是排序策略的核心。


3.6 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法(属于前序遍历)

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

3.6.1 三数取中

一般对于基准值keyi取最左值,但是序列是接近有序的话,可能会导致递归程度太深会栈溢出而程序奔溃。对于相对默认直接以最左值作为基准值,更倾向于采用随机数取值或者三数取中(不是最大最小值),这里采用三数取中。

关于keyi还是在最左边取,通过三数取中将得到适合的数与keyi交换。

实现三数取中:

int Getmidi(int* a, int begin, int end)
{
    int midi = (begin + end) + 2;
    if (a[begin] > a[end])
    {
        if (a[midi] > a[begin]) return begin;
        else if (a[midi] > a[end]) return midi;
        else end;
    }
    else//a[begin] < a[end]
    {
        if (a[midi] > a[end]) return end;
        else if (a[midi] > a[begin]) return midi;
        else begin;
    }
};

在实现过程中,需要以第一个条件为前提,再进行下一条语句判断。

3.6.2 小区间优化

面使用递归的方法很像满二叉树,关于满二叉树倒数几层节点占整颗满二叉树大部分。对此到一定数据时,可以不使用递归的方式,采用插入排序会更好一些(付出的代价更少点)。

3.6.3 hoare版本(坑多)

void PartSort1(int* a, int begin, int end)
{
  if (begin >= end)
  {
    return;
  }
  int midi = GetMidi(a, begin, end);//将第一个元素大小取为不大也不小的数
  Swap(&a[midi], &a[begin]);
  int keyi = begin;//得到第一个元素的下标
  int left = begin;
  int right = end;
  while(right > left)//单趟
  {
    // 右边找小
    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和end的位置,不是最左或者最右
  PartSort1(a, begin,keyi-1);
  PartSort1(a, keyi+1,end);
}

过程解析:涉及到二叉树前序遍历方法,使用分治思想将一个整体不断分为两个部分去看待,当所有若干个小部分有序(只存在一个数,一定有序),则整体有序。在每一阶段递归过程中,将基准值keyi对应数值使用三数取中进行Swap交换语句进行调整。

关于内层循环添加right > left条件判断为了保证在查找数据的时候,导致可能出现left和right超过,导致交换数据位置不理想,同时要求leftright相遇时,需要停止跟a[kayi]交换,跟a[keyi]相等的数据,放在哪个位置是无所谓的,没有影响。

3.6.4 相遇位置比keyi小推理(重点)

总结:不断缩小范围或者某一方向找不到,直到相遇的情况。但是相遇位置都是小,如果keyi在右边的话,那么只需要左边先走就可以了


【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(三)https://developer.aliyun.com/article/1617282

相关文章
|
12月前
|
NoSQL 算法 安全
Redis原理—1.Redis数据结构
本文介绍了Redis 的主要数据结构及应用。
Redis原理—1.Redis数据结构
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
943 29
|
存储 机器学习/深度学习 人工智能
C 408—《数据结构》易错考点200题(含解析)
408考研——《数据结构》精选易错考点200题(含解析)。
1382 27
|
11月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
411 10
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
293 10
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
329 7
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
1111 2
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
349 59
|
8月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
178 0
栈区的非法访问导致的死循环(x64)

热门文章

最新文章

推荐镜像

更多
  • DNS