当前位置: 首页 > news >正文

【数据结构与算法】第34篇:选择排序:简单选择排序与堆排序

一、简单选择排序

1.1 算法思想

每次从待排序序列中选出最小元素,放到已排序序列的末尾。

步骤

  1. [0, n-1]范围找最小值,与arr[0]交换

  2. [1, n-1]范围找最小值,与arr[1]交换

  3. 重复,直到所有元素有序

1.2 图解示例

text

初始: [5, 2, 4, 6, 1, 3] 第1趟:找最小值1,与5交换 → [1, 2, 4, 6, 5, 3] 第2趟:找最小值2(已在位)→ [1, 2, 4, 6, 5, 3] 第3趟:找最小值3,与4交换 → [1, 2, 3, 6, 5, 4] 第4趟:找最小值4,与6交换 → [1, 2, 3, 4, 5, 6] 第5趟:找最小值5(已在位)→ [1, 2, 3, 4, 5, 6]

1.3 代码实现

c

#include <stdio.h> void selectionSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int minIndex = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } if (minIndex != i) { int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } } void printArray(int arr[], int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {5, 2, 4, 6, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); printf("原数组: "); printArray(arr, n); selectionSort(arr, n); printf("排序后: "); printArray(arr, n); return 0; }

运行结果:

text

原数组: 5 2 4 6 1 3 排序后: 1 2 3 4 5 6

1.4 复杂度分析

情况比较次数交换次数时间复杂度
最好n(n-1)/20O(n²)
最坏n(n-1)/2n-1O(n²)
平均n(n-1)/2n-1O(n²)

空间复杂度:O(1)
稳定性:不稳定(交换可能打乱相等元素的顺序)


二、堆排序

2.1 堆的概念

堆是一棵完全二叉树,分为:

  • 大根堆:每个节点的值 ≥ 左右孩子节点的值

  • 小根堆:每个节点的值 ≤ 左右孩子节点的值

数组存储:下标i的节点

  • 左孩子:2*i + 1

  • 右孩子:2*i + 2

  • 父节点:(i - 1) / 2

2.2 核心操作:向下调整(Heapify)

作用:当只有根节点不满足堆性质时,将其向下调整到合适位置。

步骤

  1. 找到当前节点和左右孩子中的最大值

  2. 如果最大值不是当前节点,交换,继续向下调整

c

// 向下调整:将i位置的元素向下调整,使以i为根的子树满足大根堆 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) { int temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; heapify(arr, n, largest); // 递归调整 } }

2.3 建堆

从最后一个非叶子节点开始,从下往上执行向下调整。

c

void buildHeap(int arr[], int n) { // 最后一个非叶子节点的下标 = (n/2) - 1 for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); } }

建堆过程图解(数组[4, 6, 3, 5, 2, 1]):

text

初始完全二叉树: 4 / \ 6 3 / \ / 5 2 1 从下标2(值3)开始调整 → 不变 下标1(值6):左右孩子5、2,6最大 → 不变 下标0(值4):左右孩子6、3,6最大 → 交换4和6 6 / \ 4 3 / \ / 5 2 1 继续调整下标1(值4):左右孩子5、2,5最大 → 交换4和5 6 / \ 5 3 / \ / 4 2 1 建堆完成

2.4 堆排序流程

升序排序用大根堆

  1. 建大根堆

  2. 将堆顶(最大值)与最后一个元素交换

  3. 堆大小减1,对新的堆顶执行向下调整

  4. 重复2-3,直到堆大小为1

c

void heapSort(int arr[], int n) { // 1. 建大根堆 buildHeap(arr, n); // 2. 反复取出堆顶 for (int i = n - 1; i > 0; i--) { // 交换堆顶和末尾 int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; // 调整剩余部分 heapify(arr, i, 0); } }

为什么升序建大堆:每次把最大值放到末尾,最后得到升序序列。


三、完整代码实现

c

#include <stdio.h> #include <stdlib.h> #include <time.h> // 向下调整(大根堆) 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) { int temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; heapify(arr, n, largest); } } // 建堆 void buildHeap(int arr[], int n) { for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); } } // 堆排序(升序) void heapSort(int arr[], int n) { buildHeap(arr, n); for (int i = n - 1; i > 0; i--) { // 交换堆顶和末尾 int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; // 调整剩余部分 heapify(arr, i, 0); } } // 简单选择排序 void selectionSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int minIndex = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } if (minIndex != i) { int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } } // 打印数组 void printArray(int arr[], int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); } // 生成随机数组 void generateRandomArray(int arr[], int n) { for (int i = 0; i < n; i++) { arr[i] = rand() % 10000; } } // 复制数组 void copyArray(int src[], int dst[], int n) { for (int i = 0; i < n; i++) { dst[i] = src[i]; } } int main() { srand(time(NULL)); // 基本测试 printf("=== 基本测试 ===\n"); int arr1[] = {4, 6, 3, 5, 2, 1}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("原数组: "); printArray(arr1, n1); heapSort(arr1, n1); printf("堆排序后: "); printArray(arr1, n1); // 简单选择排序测试 int arr2[] = {5, 2, 4, 6, 1, 3}; int n2 = sizeof(arr2) / sizeof(arr2[0]); selectionSort(arr2, n2); printf("\n简单选择排序后: "); printArray(arr2, n2); // 性能对比 printf("\n=== 性能对比 ===\n"); int sizes[] = {1000, 5000, 10000}; int nTests = sizeof(sizes) / sizeof(sizes[0]); for (int t = 0; t < nTests; t++) { int n = sizes[t]; int *arr = (int*)malloc(n * sizeof(int)); int *arr2 = (int*)malloc(n * sizeof(int)); generateRandomArray(arr, n); copyArray(arr, arr2, n); clock_t start, end; double time1, time2; start = clock(); selectionSort(arr, n); end = clock(); time1 = (double)(end - start) / CLOCKS_PER_SEC * 1000; start = clock(); heapSort(arr2, n); end = clock(); time2 = (double)(end - start) / CLOCKS_PER_SEC * 1000; printf("n=%d:\n", n); printf(" 简单选择排序: %.2f ms\n", time1); printf(" 堆排序: %.2f ms\n", time2); printf(" 堆排序是简单选择的 %.2f 倍\n\n", time1 / time2); free(arr); free(arr2); } return 0; }

运行结果:

text

=== 基本测试 === 原数组: 4 6 3 5 2 1 堆排序后: 1 2 3 4 5 6 简单选择排序后: 1 2 3 4 5 6 === 性能对比 === n=1000: 简单选择排序: 2.35 ms 堆排序: 0.18 ms 堆排序是简单选择的 13.06 倍 n=5000: 简单选择排序: 57.82 ms 堆排序: 1.05 ms 堆排序是简单选择的 55.07 倍 n=10000: 简单选择排序: 230.45 ms 堆排序: 2.31 ms 堆排序是简单选择的 99.76 倍

四、堆排序的详细过程演示

c

// 打印堆排序每一步 void heapSortWithSteps(int arr[], int n) { printf("原数组: "); printArray(arr, n); buildHeap(arr, n); printf("建堆后: "); printArray(arr, n); for (int i = n - 1; i > 0; i--) { printf("交换 arr[0]=%d 和 arr[%d]=%d\n", arr[0], i, arr[i]); int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; heapify(arr, i, 0); printf("调整后: "); printArray(arr, n); } } int main() { int arr[] = {4, 6, 3, 5, 2, 1}; int n = sizeof(arr) / sizeof(arr[0]); heapSortWithSteps(arr, n); return 0; }

运行结果:

text

原数组: 4 6 3 5 2 1 建堆后: 6 5 3 4 2 1 交换 arr[0]=6 和 arr[5]=1 调整后: 5 4 3 1 2 6 交换 arr[0]=5 和 arr[4]=2 调整后: 4 2 3 1 5 6 交换 arr[0]=4 和 arr[3]=1 调整后: 3 2 1 4 5 6 交换 arr[0]=3 和 arr[2]=1 调整后: 2 1 3 4 5 6 交换 arr[0]=2 和 arr[1]=1 调整后: 1 2 3 4 5 6

五、两种选择排序对比

对比项简单选择排序堆排序
时间复杂度O(n²)O(n log n)
空间复杂度O(1)O(1)
稳定性不稳定不稳定
是否原地
实现难度简单较难
适用场景小规模数据大规模数据

六、堆排序的特点

优点缺点
时间复杂度稳定 O(n log n)不稳定排序
空间复杂度 O(1)常数因子较大
适合大数据量对缓存不友好
能处理数据流(TopK问题)实现较复杂

七、小结

这一篇我们学习了两种选择排序:

算法核心思想时间复杂度特点
简单选择排序每次选最小值O(n²)简单但慢
堆排序用堆结构选最大值O(n log n)高效稳定

堆排序的关键

  • 大根堆:每个节点 ≥ 孩子节点

  • 建堆:从最后一个非叶子节点开始向下调整

  • 升序用大根堆:每次把堆顶(最大值)放到末尾

向下调整的递归/非递归

c

// 非递归版本(避免递归深度) void heapifyIter(int arr[], int n, int i) { while (1) { 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) break; int temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; i = largest; } }

下一篇我们讲归并排序与基数排序。


八、思考题

  1. 简单选择排序和冒泡排序相比,哪个交换次数更少?为什么?

  2. 为什么升序排序要用大根堆而不是小根堆?

  3. 堆排序中,建堆的时间复杂度是多少?为什么?

  4. 如何在O(n log k)的时间内找到数组中最小的k个元素?

欢迎在评论区讨论你的答案。

http://www.cnnetsun.cn/news/1774867.html

相关文章:

  • 谷歌更新Gemini心理健康安全防护措施
  • 人脸识别OOD模型真实效果:会议直播截图中关键人物人脸的OOD分标注集
  • 【网络层-ICMP互联网控制报文协议】
  • Hunyuan-MT Pro实际应用:跨国远程医疗问诊记录多语种结构化摘要生成
  • 基于PySide6的YOLO通用检测平台:从零搭建与多场景适配
  • GPU拓扑结构
  • 实测效果惊艳:DeepSeek-R1-Distill-Qwen-1.5B推理能力展示
  • Gemma-3-12b-it真实作品集:10组高质量图片问答对话效果分享
  • nli-distilroberta-base在智能客服中的应用:自动判断用户意图与诉求
  • 和AI一起搞事情#:边剥龙虾边做个中医技能来起号缓
  • 英语时态全解析:从“时”与“态”的底层逻辑到实战应用
  • Z-Image-Turbo-辉夜巫女轻量部署:8GB显存GPU稳定运行的LoRA文生图方案
  • 保姆级教程:用PSIM+Simulink搭建一个移相全桥的联合仿真模型(从电路简化到结果分析)
  • One API中转搭建完整教程(2026最新)
  • 告别复杂配置!mPLUG-Owl3-2B一键部署,小白也能玩转AI识图
  • Transformer 架构学习笔记
  • ModelEngine的‘会话式API’和‘知识库溯源’到底香不香?一个全栈开发者的深度拆解与性能实测
  • OpenClaw技能扩展指南:用Qwen3-4B实现公众号自动发布
  • Python爬虫终极提速:异步IO(asyncio+aiohttp)优化,比多线程还快4倍
  • 一文读懂私有化即时通讯,企业数据安全的“专属防线”
  • Moment-DETR: Revolutionizing Video Moment Retrieval with Transformer-Based Set Prediction
  • AI Coding实战!我用 AI 全程编码了一个企业级后台管理框架 Forge Admin
  • Claude Code 权限 / 安全审查调用流程图
  • 人脸识别OOD模型保姆级教程:GPU加速拒识低质量人脸样本
  • 用 C# 写一个完整的 ReAct 智能体:从命令行输入到任务完成的全链路拆解糜
  • 稳卖AI浏览器怎么做选品:这4个维度提升选品成功率
  • OpenClaw+钉钉机器人:Qwen3-14B镜像搭建团队任务调度中心
  • OpenClaw视觉革命:Qwen2.5-VL-7B实现UI设计稿自动检查
  • DeEAR语音情感识别实操手册:导出Gradio界面结果为PNG报告,含三维雷达图与文字解读
  • OpenClaw性能优化:降低Phi-3-vision-128k-instruct长图文任务的Token消耗