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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理

【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一)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

相关文章
|
27天前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
50 2
|
29天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
42 4
|
1月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
41 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
存储 搜索推荐 算法
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
32 1
|
2月前
|
消息中间件 存储 Java
数据结构之 - 深入探析队列数据结构: 助你理解其原理与应用
数据结构之 - 深入探析队列数据结构: 助你理解其原理与应用
43 4
|
1月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
2月前
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
|
2月前
|
Java C++
【数据结构】探索红黑树的奥秘:自平衡原理图解及与二叉查找树的比较
本文深入解析红黑树的自平衡原理,介绍其五大原则,并通过图解和代码示例展示其内部机制。同时,对比红黑树与二叉查找树的性能差异,帮助读者更好地理解这两种数据结构的特点和应用场景。
40 0
|
2月前
|
存储 算法 搜索推荐
数据结构--堆的深度解析
数据结构--堆的深度解析

推荐镜像

更多