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

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

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

3.6.5 挖坑法

void PartSort2(int* a, int left, int right)
{
  if (left >= right)
  {
    return;
  }
  int midi = GetMidi(a, left, right);//将首位大小取为不大也不小的数
  Swap(&a[midi], &a[left]);
  int keyi = a[left];
    
  int hole = left;
  int begin = left;
  int end = right;
  while (right > left)
  {
    while (a[right] >= keyi && right > left)//找小
    {
      right--;
    }
    a[hole] = a[right];//移数值
    hole = right;
    while (a[left] <= keyi && right > left)//找大
    {
      left++;
    }
    a[hole] = a[left];
    hole = left;
  }
  a[hole] = keyi;
  PartSort1(a, begin, hole - 1);
  PartSort1(a, hole + 1, end);
}

跟hoare思想是相同的,在实现方面更加便捷。

过程解析:先将第一个数据存放在临时变量keyi中,形成一个坑位,同样L找大、R找小,当L(或R)停下来时,交换停下位置和当前坑位的数据,并且当前位置形成新的坑位,不断重复该过程,直到L和R相遇,将keyi赋值给该坑位。

3.6.6 前后指针法

void PartSort3(int* a, int begin, int end)
{
  if (begin >= end)
  {
    return;
  }
    
    //编译器优化很厉害,可以不使用 
  if (end - begin + 1 <= 10)//小区间优化
  {
    InsertSort(a + begin, end - begin + 1);
  }
    
  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;
  PartSort1(a, begin, keyi - 1);
  PartSort1(a, keyi + 1, end);
  return 0;
}

过程解析:这里使用同相双指针法,具体可以通过动图推导规律。

  • cur遇到比keyi大的值,++cur
  • cur遇到比keyi小的值,++prev,交换prevcur位置的值,++cur

小总结:

对于不同的方法,单趟的结果都是不一样的。当有多选题时,可以使用不同方式选出答案。对此三个方法,如果需要使用快速排序,推荐使用后面两个方法更快(将两个调用自身函数注释掉,就是单趟排序)。


3.4 归并排序

基本思想:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序序列即先使每个子序列有序,再使子序列间有序。若加你个两个有序表合并成一个有序表,称为二路归并。

实现过程

void MergeSort(int *a,int n) 
{
    int *tmp=(int *)malloc(sizeof(int)*n);
    if(tmp==NULL)
    {
        perror("malloc fail!");
        return 1;
    }
    _MergeSort(a,0,n-1,tmp);
    free(tmp)
        tmp=NULL;
}
void _MergeSort(int* a, int begin, int end, int* tmp)
{
  if (begin >= end)
  {
    return;
  }
  int mid = (begin + end) / 2;
  //[begin,mid][mid+1,end]
  _MergeSort(a, begin, mid, tmp);
  _MergeSort(a, mid + 1, end, tmp);//后序递归
  //左边有序 右边有效 合在一起-->合并有序数组问题
  int begin1 = begin;
  int begin2 =  1+ mid;
    
  int end1 = mid;
  int end2 = end;
    
  int i =begin;
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[i++] = a[begin1++];
    }
    else
    {
      tmp[i++] = a[begin2++];
    }
  }//还有部分数据没有放入新数组中
  while (begin1 <= end1)
  {
    tmp[i++] = a[begin1++];
  }
  while (begin2 <= end2)
  {
    tmp[i++] = a[begin2++];
  }
  memcpy(a+begin, tmp+begin, sizeof(int) * (end - begin + 1));//可能是右边归并
}

过程解析:这里同快速排序使用了分治思想,但是不同于快速排序采用前序遍历根 左子树 右子树,而是采用后序遍历左子树 右子树 根归并排序主要是通过已有序的子序列合并,得到完全有序的序列。那么将已有序的左右子树得到完全有序的根序列,完成这项操作需要借助一块空间完成合并,再使用内存函数复制或转移到原本序列中。

注意:将合并好序列拷贝到源序列中,如果为右边归并,开头元素下标需要匹配到相对应的位置,只要a+begin和tmp+begin就可以解决这个问题

归并排序的特点总结

  • 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的使解决在磁盘中的外排序问题。
  • 时间复杂度: O(N*logN)
  • 空间复杂度: O(N)
  • 稳定性:稳定
  • 没有进行交换,更加适合外排序

3.5 计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

void CountSort(int* a, int n)
{
  int min = a[0], max = a[0];
  for (int i = 1; i < n; i++)
  {
    if (a[i] < min)
      min = a[i];
    if (a[i] > max)
      max = a[i];
  }
  int range = max - min + 1;
  int* count = (int*)calloc(range, sizeof(int));
  if (count == NULL)
  {
    printf("calloc fail\n");
    return;
  }
  // 统计次数
  for (int i = 0; i < n; i++)
  {
    count[a[i] - min]++;
  }
    
  // 排序
  int i = 0;
  for (int j = 0; j < range; j++)
  {
    while (count[j]--)
    {
      a[i++] = j + min;//加回去
    }
  }
}

过程解析:将待排序中数据和新数组中下标对应,并且在记录出现的次数。当数据很大时,很难去把握,因此使用相对映射比较好count[a[i]-min]++;

局限性:

  • 不适合分散的数据,更适合集中数据
  • 不适合浮点数,字符串、结构体数据排序、只适合整数

计数排序的特性总结

  • 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  • 时间复杂度:O(MAX(N,范围))
  • 空间复杂度:O(范围)

四、排序算法复杂度及稳定性分析




相关文章
|
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** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
19天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
31 1
|
24天前
|
数据采集 存储 编解码
一份简明的 Base64 原理解析
Base64 编码器的原理,其实很简单,花一点点时间学会它,你就又消除了一个知识盲点。
64 3
|
6天前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
|
6天前
|
存储 供应链 安全
深度解析区块链技术的核心原理与应用前景
深度解析区块链技术的核心原理与应用前景
13 0
|
20天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
21天前
|
供应链 安全 分布式数据库
探索区块链技术:从原理到应用的全面解析
【10月更文挑战第22天】 本文旨在深入浅出地探讨区块链技术,一种近年来引起广泛关注的分布式账本技术。我们将从区块链的基本概念入手,逐步深入到其工作原理、关键技术特点以及在金融、供应链管理等多个领域的实际应用案例。通过这篇文章,读者不仅能够理解区块链技术的核心价值和潜力,还能获得关于如何评估和选择适合自己需求的区块链解决方案的实用建议。
37 0
|
1月前
|
前端开发 JavaScript UED
axios取消请求CancelToken的原理解析及用法示例
axios取消请求CancelToken的原理解析及用法示例
89 0
|
17天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
91 9

推荐镜像

更多