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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 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(范围)

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




相关文章
|
7天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
44 13
|
26天前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
56 1
|
2天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
14 1
|
27天前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
51 2
|
29天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
42 4
|
1月前
|
运维 持续交付 虚拟化
深入解析Docker容器化技术的核心原理
深入解析Docker容器化技术的核心原理
46 1
|
26天前
|
存储 供应链 算法
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
52 0
|
1月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
57 0
|
1月前
|
API 持续交付 网络架构
深入解析微服务架构:原理、优势与实践
深入解析微服务架构:原理、优势与实践
31 0
|
1月前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景

推荐镜像

更多