【数据结构】-8种排序解析(详细总结,简洁,含代码,例题)(二)

简介: 【数据结构】-8种排序解析(详细总结,简洁,含代码,例题)

 2.非递归写法(类比层序遍历用队列实现,这里用栈)

学习原因:递归的本质是不断开辟空间,当递归层数过多时可能会出现栈溢出的问题。因而引入非递归写法


实现原理:递归写法本质上是向下不断开辟空间,当达到终止条件时返回并归还空间。不采用递归的写法,即可以在原数组上直接对下标进行划分


1.入尾标,入头标


2.标记begin,end后,进行头删,并算出keyi


3.此时,原数组被分割成【begin,keyi-1】keyi【keyi+1,end】。


分别对存在的区间进行同样的操作(压栈,出栈)即可。


图示:

image.png

PS:数字表示,可视作递归的层数。而实际上没有递归。 

void quicksortnonr(int*a,int left,int right)
{
  ST st;
  StackInit(&st);
  StackPush(&st, right);//表示end的先入栈
  StackPush(&st, left);
  while (!StackEmpty(&st))
  {
    int begin = StackTop(&st);
    StackPop(&st);
    int end = StackTop(&st);
    StackPop(&st);
        //得出keyi
    int keyi = Partsort3(a, begin, end);//三数取中
    //【begin,keyi-1】keyi【keyi+1,end】
    if (keyi + 1 < end)
    {
      StackPush(&st, end);//表示end的先入栈
      StackPush(&st, keyi+1);
    }
    if (keyi -1 >begin)
    {
      StackPush(&st, keyi - 1);//表示end的先入栈
      StackPush(&st, begin);
    }
  }
  StackDestroy(&st);
}

8.归并排序(递归和非递归写法)

1.递归写法

归并原理:两个有序数组进行比较,并尾插到新空间。


PS:结合递归后,即可细分到只剩两个数归并形成有序数组,两两合成新的有序序列,并拷贝到一块新空间(避免覆盖原数组),新空间的位置关系要与原数组对应


形象图示:

image.png

注意点:为提升效率,采用取中间数进行划分

图示:

image.png

void MergeSort(int* a, int begin, int end, int* tmp)
{
  if (begin >= end)
    return;
  int mid = (begin + end) / 2;
  MergeSort(a,begin,mid,tmp);
  MergeSort(a,mid+1,end,tmp);
  //拷贝回与原数组a相对应的位置
  memcpy(a + begin,tmp + begin,sizeof(int) * (end - begin + 1));
}

递归实现的逻辑:后序遍历

PS:后序遍历相关可查看博主相关博客

3.png

 2.非递归写法(注意越界情况的分类讨论)

分析:与快排的非递归算法同理。当递归次数过多时,有可能会导致栈溢出。不妨在原数组的基础上,直接对下标对应区间范围内的数组进行归并,并拷贝回原数组。

形象图示:

image.png

注意点:有时候gap的选取会越界!


分析:本质上是不断选取【begin1,end1】【begin2,end2】


注意点:以下分析是在归并进行前,对下标对应空间进行讨论!


1.当begin1和end2和并后形成新begin1,end1时,若end1临界(begin2越界)/end1越界,则停止归并


2.当end1越界时,则对end1进行修正


形象图示:  

image.png

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail\n");
    return;
  }
  int gap = 1;
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    {
      // [begin1,end1][begin2, end2]
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;//((i+gap)+(gap-1))
      //printf("[%d,%d][%d,%d] ", begin1, end1,begin2,end2);
            //分类讨论情况
      if (end1 >= n || begin2 >= n)
      {
        break;
      }
      if (end2 >= n)
      {
        end2 = n - 1;//修正end2区间
      }
      printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
      int j = i;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        else
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
      // 归并一部门拷贝一部分
      memcpy(a+i, tmp+i, sizeof(int) *(end2-i+1));
    }
    printf("\n");
    gap *= 2;
  }
  free(tmp);
}

三.8种排序方式复杂度/稳定性分析

    1.稳定性的概念

假定再待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种算法是稳定的。

    2.分析

image.png

  *计数排序较为特别,时间复杂度O(n)/O(range),空间复杂度为O(n)

 1.简单选择排序不稳定的原因

特例:替换的数在两相同数同一边时

image.png

 2.复杂度分析综述

1.希尔排序是直接插入排序基础上加了预处理。较为复杂,暂记结论。

image.png

2.直接插入排序,是取每一个数和前面所有数进行比对。无论如何都要先取,所以最好情况即有序情况即是n,最坏情况相当于一个数组的遍历,n^2。


3.快速排序当keyi每次都能取中间值时,接近二叉树,nlogn。keyi每次都取最左/右值时,即相当于一个数组的遍历,n^2。


4.归并排序,接近二叉树,nlogn。由于需要tmp新空间容纳归并后的新空间,空间复杂度为n


5.堆排序,分为堆调整(向上向下)和用删除思想堆排序两部分,根据数学计算知道后者复杂度为nlogn,即堆排整体为nlogn。

————————————————

版权声明:本文为CSDN博主「你的小笔记本YY」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/YYDsis/article/details/130110495

相关文章
|
10月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
339 1
|
11月前
|
算法 PyTorch 算法框架/工具
昇腾 msmodelslim w8a8量化代码解析
msmodelslim w8a8量化算法原理和代码解析
912 5
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
942 29
|
存储 机器学习/深度学习 人工智能
C 408—《数据结构》易错考点200题(含解析)
408考研——《数据结构》精选易错考点200题(含解析)。
1380 27
|
11月前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
438 5
|
12月前
|
人工智能 文字识别 自然语言处理
保单AI识别技术及代码示例解析
车险保单包含基础信息、车辆信息、人员信息、保险条款及特别约定等关键内容。AI识别技术通过OCR、文档结构化解析和数据校验,实现对保单信息的精准提取。然而,版式多样性、信息复杂性、图像质量和法律术语解析是主要挑战。Python代码示例展示了如何使用PaddleOCR进行保单信息抽取,并提出了定制化训练、版式分析等优化方向。典型应用场景包括智能录入、快速核保、理赔自动化等。未来将向多模态融合、自适应学习和跨区域兼容性发展。
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
4123 11
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
349 59
|
8月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
175 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
623 77

推荐镜像

更多
  • DNS