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

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

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

相关文章
|
1月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
41 3
|
21天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
18天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
31 1
|
24天前
|
数据采集 存储 编解码
一份简明的 Base64 原理解析
Base64 编码器的原理,其实很简单,花一点点时间学会它,你就又消除了一个知识盲点。
64 3
|
5天前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
|
5天前
|
存储 供应链 安全
深度解析区块链技术的核心原理与应用前景
深度解析区块链技术的核心原理与应用前景
13 0
|
20天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
21天前
|
供应链 安全 分布式数据库
探索区块链技术:从原理到应用的全面解析
【10月更文挑战第22天】 本文旨在深入浅出地探讨区块链技术,一种近年来引起广泛关注的分布式账本技术。我们将从区块链的基本概念入手,逐步深入到其工作原理、关键技术特点以及在金融、供应链管理等多个领域的实际应用案例。通过这篇文章,读者不仅能够理解区块链技术的核心价值和潜力,还能获得关于如何评估和选择适合自己需求的区块链解决方案的实用建议。
37 0
|
16天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
91 9
|
7天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
16 1

推荐镜像

更多