数据结构-排序-排序的七种算法(2)

article/2025/8/4 21:20:10

一,七种算法的介绍和比较

二,冒泡排序

  • 原理:重复遍历列表,比较相邻元素,如果顺序错误就交换它们

  • 时间复杂度

    • 最好:O(n)(已有序时)

    • 平均:O(n²)

    • 最坏:O(n²)

  • 空间复杂度:O(1)

  • 稳定性:稳定

  • 特点

    • 实现简单

    • 对部分有序数据效率较高

    • 每轮将最大元素"冒泡"到末尾

1.冒泡排序的基本思想:

  • /*对顺序表L作交换排序(冒泡排序初级版)*/
    void Bubblesort0(SqList *L)
    {int i,j;for(i=1;i<L->length;i++){for(j=i+1;j<L->length;j++){if(L->r[i] > L->r[j]){swap(L,i,j);//交换L->r[i]与L->r[j]的值}}}
    }

    这个思想使用图表示:

2.冒泡排序算法

/*对顺序表L作交换排序(冒泡排序初级版)*/
void Bubblesort(SqList *L)
{int i,j;for(i=1;i<L->length;i++){for(j=L->length-1;j>=i;j--){if(L->r[j] > L->r[j+1]){swap(L,i,j);//交换L->r[i]与L->r[j]的值}}}
}

 具体表现形式如图:

 当i=2时,变量j由8反向循环到2,逐个比较,在将关键字2交换到第二位置的同时,也将关键字4和3有所提升

3.冒泡排序优化

当我们待排序的序列是{2,1,3,4,5,6,7,8,9},也就是说,除了第一和第二的关键字需要交换外,别的都已经是正常的顺序。当i=1时,交换了2和1,此时序列已经有序,但是算法仍然不停的循环将i=2到9以及每个循环中的j循环都执行了一遍,这样还会大大增加了算法计算的时间,如图

当i=2时,我们已经对9与8,8与7,.....3与2作了比较,没有任何数据交换,这就说明此序列已经有序,不需要再继续后面的循环判断工作了。

此时可以增加一个flag来实现这一算法的改进

/*对顺序表L作交换排序(冒泡排序初级版)*/
void Bubblesort2(SqList *L)
{int i,j;Status flag=TRUE;for(i=1;i<L->length&&flag;i++){flag=GALSE;for(j=L->length-1;j>=i;j--){if(L->r[j] > L->r[j+1]){swap(L,i,j);//交换L->r[i]与L->r[j]的值flag=TRUE;}}}
}

三,简单选择排序

1.核心思想

简单排序算法时一种直观但效率不高的比较排序算法。它的核心思想是:每次从未排序的部分中找到最小(或最大)的元素,然后将其放到已排序部分的末尾

简单使用代码说明:

void SelectSort(SqList *L)
{int i,j,min;for(i=1;i<L->Length;i++){min=1;for(j=i+1;j<=L->r[j])min=j;}if(i!=min){swap(L,i,min);}
}

 

2.时间复杂度分析

  • 比较次数:无论数据初始状态如何,选择排序都需要进行大量的比较操作。

    • 第 1 趟:比较 n-1 次

    • 第 2 趟:比较 n-2 次

    • ...

    • 第 n-1 趟:比较 1 次

    • 总比较次数 = (n-1) + (n-2) + ... + 1 = n(n-1)/2

    • 因此,比较操作的时间复杂度为 O(n²)

  • 交换次数:选择排序的优点是交换次数相对较少。

    • 最好情况(数组已有序):每趟循环都会发现 min_index == i,因此交换 0 次

    • 最坏情况(数组逆序):每趟都需要交换一次,共进行 n-1 次交换

    • 平均情况下,也是大约 O(n) 次交换

  • 总时间复杂度:由于 O(n²) 的比较操作占主导地位,简单选择排序的平均和最坏情况时间复杂度都是 O(n²)。最好情况时间复杂度也是 O(n²)(因为比较次数仍然是 O(n²),即使交换次数为 0)

3.优缺点分析

  • 优点

    • 思路简单直观,易于理解和实现。

    • 交换次数少。在数据移动成本较高时(例如要排序的数据是复杂对象),这可能是一个优点(相对于冒泡排序)。

    • 原地排序,不需要额外的存储空间(除了少量临时变量)。

  • 缺点

    • 时间复杂度高。O(n²) 的时间复杂度使其在处理大规模数据时效率低下。

    • 不稳定。如果需要保持相等元素的原始顺序,则不能使用简单选择排序。

    • 无论数据初始状态如何,比较次数几乎相同。即使输入数据已经有序或接近有序,它仍然需要进行 O(n²) 次比较。

四,直接插入排序

1.基本思想

这是一种简单直观且在实际应用中(特别是对小规模或基本有序的数据)表现不错的比较排序算法。其基本操作是:将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数增1的有序表

基本代码如下:

void InsertSort(SqList *L)
{int i,j;for(i=2;i<L->Length;i++){if(L->r[i] < L->r[i-1]){L->r[0]=L->r[i];for(j=i-1;L->r[j] > L->r[0];j--){L->r[i+1] = L->r[j];for(j=i-1;L->r[j] > L->r[0];j--)L->r[j+1]=L->r[j];L->r[j+1]=L->r[0];}}}
}

2.时间复杂度的分析

 3.优缺点分析

  • 优点:

    • 简单直观,易于实现。

    • 对于小规模数据或基本有序的数据效率非常高(特别是 O(n) 的最好情况)。

    • 原地排序,空间效率高 (O(1))。

    • 稳定排序。

    • 适应性 (Adaptive): 当输入数据部分有序时,实际运行时间接近 O(n)。

    • 在线性 (Online): 可以一边接收新数据一边排序(因为排序过程是增量式的)。

  • 缺点:

    • 平均和最坏情况时间复杂度为 O(n²),不适合大规模随机数据。 当 n 较大时,效率显著低于 O(n log n) 的算法(如快排、归并、堆排)。

    • 元素移动操作较多。 每次插入都可能需要移动大量元素。

五,希尔排序

希尔排序是插入排序的高效改进版,由 Donald Shell 于 1959 年提出。它通过将原始列表分割成多个子序列进行预处理,大幅减少数据移动次数,显著提升排序效率。

1.核心思想

  1. 分组插入:按特定增量(gap)将数组分割为若干子序列

  2. 子序列排序:对每个子序列进行插入排序

  3. 逐步缩小增量:重复上述过程,直至增量为 1(此时等同于标准插入排序)

  4. 大跨度移动:早期使用大增量使元素大幅跳跃,减少小范围调整次数

算法代码如下:

void ShellSort(SqList *L)
{int i,j;int increment = L->length;do {increment=increment/3+1;//增量排序for(i=increment+1;i<=L->length;i++){if(i=increment+1;i<=L->length;i++){//需将L->r[i]插入有序增量子表L->r[0] = r[i];for(j=i-increment;j>0&&L->r[0] < L->r[j];j-=increment){L->r[j+increment] = L->r[j];}L->r[j+increment] = L->r[0];}}}while(increment>1);
}

代码解释如下:

 

 2.优缺点

优点

  1. 突破 O(n²) 屏障,中等规模数据效率高

  2. 原地排序,空间效率优异

  3. 代码简洁(约 10 行核心代码)

  4. 对部分有序数组效率接近 O(n)

缺点

  1. 时间复杂度依赖增量序列选择

  2. 不稳定排序(需谨慎处理关键字段)

  3. 大规模数据仍不如 O(n log n) 算法(如快速排序)

六,堆排序

1,基本概念

堆排序(Heap Sort)是一种高效的基于比较的排序算法,利用二叉堆的数据结构实现。它结合了插入排序和归并排序的优点:时间复杂度为 O(n log n)(最优、平均和最坏情况),空间复杂度为 O(1)(原地排序),且不需要递归栈(可迭代实现)。

2.核心概念

  1. 二叉堆(Binary Heap)

    • 完全二叉树结构(除最后一层外,所有层全满,最后一层从左向右填充)

    • 两种类型:

      • 最大堆:父节点值 ≥ 子节点值(根节点为最大值)

      • 最小堆:父节点值 ≤ 子节点值(根节点为最小值)

    • 存储方式:用数组表示,下标从 0 开始:

      • 父节点 i → 左子节点 2i+1,右子节点 2i+2

      • 子节点 i → 父节点 ⌊(i-1)/2⌋

  2. 核心操作

    • 堆化(Heapify):调整子树使其满足堆性质。

    • 建堆(Build Heap):将无序数组初始化为堆。

    • 排序:反复取出堆顶元素(极值)并调整堆。

3.算法步骤

  • 建堆(Build Max Heap)

    • 从最后一个非叶子节点开始(下标 n/2 - 1),向前遍历至根节点。

    • 对每个节点执行 下沉操作(Sift Down),使子树满足最大堆性质。

  • 排序

    • 将堆顶元素(最大值)与当前堆末尾元素交换。

    • 堆大小减 1(排除已排序的最大值)。

    • 对新的堆顶元素执行 下沉操作,恢复最大堆性质。

    • 重复上述过程,直到堆中只剩一个元素。

4.代码示例

#include <stdio.h>// 交换两个元素的值
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 下沉操作(最大堆)
void heapify(int arr[], int n, int i) {int largest = i;        // 初始化最大元素为当前节点int left = 2 * i + 1;   // 左子节点索引int right = 2 * i + 2;  // 右子节点索引// 如果左子节点大于当前节点if (left < n && arr[left] > arr[largest])largest = left;// 如果右子节点大于当前最大值if (right < n && arr[right] > arr[largest])largest = right;// 如果最大值不是当前节点,交换并继续堆化if (largest != i) {swap(&arr[i], &arr[largest]);heapify(arr, n, largest);  // 递归调整被交换的子树}
}// 堆排序主函数
void heapSort(int arr[], int n) {// 1. 构建最大堆(从最后一个非叶子节点开始)for (int i = n / 2 - 1; i >= 0; i--)heapify(arr, n, i);// 2. 逐个提取元素(堆排序核心)for (int i = n - 1; i > 0; i--) {// 将当前最大值(堆顶)移到数组末尾swap(&arr[0], &arr[i]);// 对剩余元素重新堆化(堆大小减1)heapify(arr, i, 0);}
}// 打印数组
void printArray(int arr[], int n) {for (int i = 0; i < n; ++i)printf("%d ", arr[i]);printf("\n");
}int main() {int arr[] = {12, 11, 13, 5, 6, 7};int n = sizeof(arr) / sizeof(arr[0]);printf("原始数组: ");printArray(arr, n);heapSort(arr, n);printf("排序后数组: ");printArray(arr, n);return 0;
}

 

代码解释

1. 关键函数说明
  • swap():交换两个整数的值

  • heapify():核心堆化操作

    • 确保以节点i为根的子树满足最大堆性质

    • 递归比较并交换父节点与子节点

  • heapSort():堆排序主函数

    • 第一步:构建最大堆(自底向上)

    • 第二步:反复提取堆顶元素并调整堆

2. 执行流程

以输入数组 [12, 11, 13, 5, 6, 7] 为例:

  1. 构建最大堆

    • 最后一个非叶子节点:n/2-1 = 6/2-1 = 2(即值13)

    • 从索引2开始向前处理:

      • 索引2(13)已满足堆性质

      • 索引1(11):左子11<右子7 → 无需交换

      • 索引0(12):左子11<13 → 与右子13交换

    • 最终堆:[13, 11, 12, 5, 6, 7]

  2. 排序过程

    步骤操作当前数组状态剩余堆大小
    1交换堆顶13与末尾7[7, 11, 12, 5, 6, 13]5
    堆化剩余元素[12, 11, 7, 5, 6, 13]
    2交换堆顶12与末尾6[6, 11, 7, 5, 12, 13]4
    堆化剩余元素[11, 6, 7, 5, 12, 13]
    3交换堆顶11与末尾5[5, 6, 7, 11, 12, 13]3
    堆化剩余元素[7, 6, 5, 11, 12, 13]
    4交换堆顶7与末尾5[5, 6, 7, 11, 12, 13]2
    堆化剩余元素[6, 5, 7, 11, 12, 13]
    5交换堆顶6与末尾5[5, 6, 7, 11, 12, 13]1
  3. 最终结果[5, 6, 7, 11, 12, 13]

5.优化方向

5.1 迭代版heapify:避免递归调用

void heapify_iterative(int arr[], int n, int i) {int largest = i;while (1) {int left = 2 * i + 1;int right = 2 * i + 2;if (left < n && arr[left] > arr[largest])largest = left;if (right < n && arr[right] > arr[largest])largest = right;if (largest == i) break;swap(&arr[i], &arr[largest]);i = largest;  // 移动到被交换的子节点}
}

5.2.减少交换次数

// 在heapify中使用
void sift_down(int arr[], int start, int end) {int root = start;while (2 * root + 1 <= end) {int child = 2 * root + 1;int swap_idx = root;if (arr[swap_idx] < arr[child])swap_idx = child;if (child + 1 <= end && arr[swap_idx] < arr[child + 1])swap_idx = child + 1;if (swap_idx == root) return;// 仅一次赋值操作int temp = arr[root];arr[root] = arr[swap_idx];arr[swap_idx] = temp;root = swap_idx;}
}

七,归并排序

1.基本思想

归并排序采用分治策略(Divide and Conquer):

  1. :将数组递归地分成两半

  2. :对每个子数组进行排序

  3. :将两个有序子数组合并为一个有序数组

2.算法步骤

  1. 分解:将数组分成两个大小相等的子数组(奇数长度时允许差1)

  2. 递归:对左右子数组递归进行归并排序

  3. 合并:合并两个已排序的子数组

3.代码举例

#include <stdio.h>
#include <stdlib.h>void merge(int arr[], int l, int m, int r) {int n1 = m - l + 1;int n2 = r - m;int *L = malloc(n1 * sizeof(int));int *R = malloc(n2 * sizeof(int));for (int i = 0; i < n1; i++) L[i] = arr[l + i];for (int j = 0; j < n2; j++) R[j] = arr[m + 1 + j];int i = 0, j = 0, k = l;while (i < n1 && j < n2) {if (L[i] <= R[j]) arr[k++] = L[i++];else arr[k++] = R[j++];}while (i < n1) arr[k++] = L[i++];while (j < n2) arr[k++] = R[j++];free(L);free(R);
}void mergeSort(int arr[], int l, int r) {if (l < r) {int m = l + (r - l) / 2;mergeSort(arr, l, m);mergeSort(arr, m + 1, r);merge(arr, l, m, r);}
}

八,快速排序

1.基本思想

快速排序同样采用分治策略

  1. 分区:选择一个基准元素(pivot),将数组分为两部分

    • 左侧:所有元素 ≤ pivot

    • 右侧:所有元素 > pivot

  2. 递归:对左右子数组递归进行快速排序

2.算法步骤

  1. 选择基准元素(pivot)

  2. 分区操作:将数组重新排列,使基准位于正确位置

  3. 递归排序左子数组和右子数组

3.代码举例

#include <stdio.h>void swap(int* a, int* b) {int t = *a;*a = *b;*b = t;
}int partition(int arr[], int low, int high) {int pivot = arr[high];int i = (low - 1);for (int j = low; j <= high - 1; j++) {if (arr[j] < pivot) {i++;swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[high]);return (i + 1);
}void quickSort(int arr[], int low, int high) {if (low < high) {int pi = partition(arr, low, high);quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);}
}


http://www.hkcw.cn/article/WRXnCIZdyw.shtml

相关文章

【目标检测】backbone究竟有何关键作用?

backbone的核心在于能为检测提供若干种感受野大小和中心步长的组合&#xff0c;以满足对不同尺度和类别的目标检测。

JAVA实战开源项目:精简博客系统 (Vue+SpringBoot) 附源码

本文项目编号 T 215 &#xff0c;文末自助获取源码 \color{red}{T215&#xff0c;文末自助获取源码} T215&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

IO流1——体系介绍和字节输出流

什么是io流 io流分类 纯文本文件&#xff1a; windows自带的记事本打开能读懂的 经验证&#xff1a; word&#xff0c;excel不是&#xff0c; txt, md的是纯文本文件 &#xff01;&#xff01;&#xff01;&#xff01; 字节输出流 io流体系 抽象类不能直接创建他们的对象…

告别复杂操作!电脑极简风格计时使用

无论是工作、学习还是日常生活&#xff0c;这款小巧实用的计时工具都能成为你掌控时间的好帮手。特别适合需要频繁切换正计时、倒计时和查看当前时间的场景。界面简洁&#xff0c;操作便捷&#xff0c;助你高效管理每一刻。 这是一款免安装的工具&#xff0c;下载后可直接打开…

湖北理元理律师事务所:个人债务管理的温度与精度

湖北理元理律师事务所&#xff1a;个人债务管理的温度与精度 面对信用卡、网贷、医疗债等多重债务压力&#xff0c;普通人常陷入“拆东墙补西墙”的恶性循环。湖北理元理律师事务所通过计划集团公司服务平台&#xff0c;推出“有温度的债务优化计划”&#xff0c;其人性化设计…

启动你的RocketMQ之旅(七)-Store存储原理

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final。 &#x1f4dd;个人主页&#xff1a; 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;java专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一…

无标注数据如何提升LLM推理能力?熵最小化 提升LLM自信度

熵最小化 提升LLM自信度 ——熵最小化(Entropy Minimization,EM),如何在不使用任何标注数据的情况下,提升大语言模型(LLMs)在数学、物理和编程等复杂推理任务上的表现。 1. 什么是熵最小化? 熵在机器学习中衡量模型输出的不确定性。熵越小,模型对输出越“自信”(概率…

[yolov11改进系列]基于yolov11引入多尺度空洞注意力MSDA的python源码+训练源码

【MSDA介绍】 本文提出了一种新颖的多尺度空洞 Transformer&#xff0c;简称DilateFormer&#xff0c;以用于视觉识别任务。原有的 ViT 模型在计算复杂性和感受野大小之间的权衡上存在矛盾。众所周知&#xff0c;ViT 模型使用全局注意力机制&#xff0c;能够在任意图像块之间建…

LCA(最近公共祖先)与树上差分

LCA&#xff1a; 我们先看一道例题洛谷p3379 这道题就是LCA的模板题 LCA大抵有三种方法处理&#xff0c;我们这里只讲两种 分别是Tarjan和倍增法&#xff0c;也分别是离线和在线算法 我们这里先讲Tarjan Tarjan&#xff1a; 一提到Tarjan这个名字&#xff0c;相信大家都…

PCIe—TS1/TS2 之Polling下的应用(一)

前文 训练序列有序集用于比特对齐、符号对齐以及交换物理层参数。2.5GT/s和5GT/s速率时,训练序列有序集不会加扰,只用8b/10b 编码。但到8GT/s及以上速率时,采用128b/130b编码,符号有可能加扰有可能不加扰,具体参阅SPEC物理层章节,后续可能会写。 训练序列(TS1或…

Spring AI调用Ollama+DeepSeek

文章目录 Spring AI集成DeepSeek申请api_keySpringBoot工程 Spring AI聊天模型概述ChatClient接口角色预设流式响应 ChatModel接口实现简单的对话提示词 函数调用函数调用实现 AI调用Ollama下载并安装 Ollama拉取 DeepSeek 模型代码测试 Spring AI Spring AI是一个AI工程领域的…

maven中的maven-antrun-plugin插件详解

1. 核心功能2. 典型使用场景3. 配置示例4. 关键配置项5. 优缺点分析6. 最佳实践7. 常见问题8. 使用案例1. 基本配置2. 常用 Ant 任务示例文件操作执行系统命令条件判断 3. 绑定到不同生命周期阶段4. 传递参数到 Ant 脚本5. 跳过任务执行6. 调试与日志7. 完整示例 总结 maven-an…

1Remote远程会话管理以及一键启动虚拟机

1Remote远程会话管理以及一键启动虚拟机 前言 vmware中安装的虚拟机命令行没有右键粘贴功能&#xff0c;想用ssh但又得启动虚拟机又得连接SSH&#xff0c;本文使用开源的1Remote以及windows脚本来实现一键启动虚拟机并连接SSH。 实现过程 下载1Remote 下载地址&#xff1a…

Linux基础 文件描述符,重定向及缓冲区理解

&#x1f3d9;️正文 1、文件描述符 在使用 C语言 相关文件操作函数时&#xff0c;可以经常看到 FILE 这种类型&#xff0c;不同的 FILE* 表示不同的文件&#xff0c;实际进行读写时&#xff0c;根据 FILE* 进行操作即可。 #include<iostream> #include <cstdio>…

Vue 核心技术与实战智慧商城项目Day08-10

1.项目演示 2. 项目收获 3. 创建项目 4. 调整初始化目录 5. vant 组件库 6. 其他 Vue 组件库 7. vant 全部导入 和 按需导入 全部导入&#xff1a; 按需导入&#xff1a; 8. 项目中的 vw 适配 记得执行yarn serve module.exports {plugins: {postcss-px-to-viewport: {// vw适…

MacroDroid安卓版:自动化操作,让生活更智能

在智能手机的日常使用中&#xff0c;我们常常会遇到一些重复性的任务&#xff0c;如定时开启或关闭Wi-Fi、自动回复消息、根据位置调整音量等。这些任务虽然简单&#xff0c;但频繁操作会让人感到繁琐。MacroDroid安卓版正是为了解决这些问题而设计的&#xff0c;它是一款功能强…

基于springboot的益智游戏系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

【深度学习】18. 生成模型:Variational Auto-Encoder(VAE)详解

Variational Auto-Encoder&#xff08;VAE&#xff09;详解 本节内容完整介绍 VAE 的模型结构、优化目标、重参数化技巧及其生成机制。 回顾&#xff1a;Autoencoder&#xff08;自编码器&#xff09; Autoencoder 是一种无监督学习模型&#xff0c;旨在从未标注的数据中学习压…

电容的深入探讨

文章目录 6.1.1 概念6.1.2 容抗6.1.3 电容种类6.1.3.1 安规电容6.1.3.2 电解电容6.1.3.3 电容命名 6.1.4 电容作用6.1.4.1 降压6.1.4.2 滤波6.1.4.3 延时6.1.4.4 解耦合6.1.4.5 旁路 6.1.5 电容的充放电6.1.6 电容储能量化6.1.7 电容的特性理解 6.1.1 概念 无源元件。&#xf…

《P3959 [NOIP 2017 提高组] 宝藏》

题目背景 NOIP2017 D2T2 题目描述 参与考古挖掘的小明得到了一份藏宝图&#xff0c;藏宝图上标出了 n 个深埋在地下的宝藏屋&#xff0c; 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。 小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是&#xff0c;每个宝藏屋…