内排序算法小结

article/2025/9/30 15:09:04

  • 针对近两天所复盘的《数据结构》中的内排序部分,做一个小总结。尽力在最大程度上把考点重现出来,以便复盘之需。

  • 本文所有算法均使用 C语言 实现。

  • 本博客仅从个人的考点梳理为基,内容相较全局还有很多缺失,读者请权衡参考。

目录

内排序

基本概念

分类方法

直接插入排序及折半插入排序

选择排序

冒泡排序

谢尔排序

快速排序

堆积排序


内排序


基本概念

  • 对于文件而言,排序是 根据记录关键字值的递增或者递减关系将记录的次序进行重新排列,使得原来一组次序任意的记录转变为按其关键字值有序进行排列的一组记录;

  • 排序操作 (功能 1)—— 将一个按值无序的数据元素序列转换为一个按值有序排列的数据元素序列;

  • 又称为 分类

  • (功能 2)提高查找时间效率

分类方法

  1. 稳定排序 和 非稳定排序

    • 参加排序的项 (关键字)—— 排序码 或 排序项

    • 排序码相同的记录可能只有一个,也可能有多个

    • 对于具有相同排序码的多个记录而言,若采用的排序方法使得排序后记录的相对位置保持不变,则称 此排序为稳定的,否则为不稳定的

  2. 连续顺序文件排序 和 链表排序

    • 取决于文件在存储介质的组织方式

    • 连续顺序文件排序

      • 记录之间的逻辑顺序是通过其物理地址的先后来映射,因而在排序过程中需要移动记录的位置

    • 链表排序

      • 文件中的一个记录对应链表中的一个链结点

      • 记录之间的逻辑顺序通过指针来反映

      • 因此排序过程值无需移动记录的位置,只需改变指针的指向

  3. 按照所需工作量划分

    • 简单排序法

    • 先进排序法

    • 基数排序法

  4. 按照所采用的策略

    • 插入排序

    • 选择排序

    • 交换排序

    • 归并排序

    • 基数排序

  • In-place —— 占用常数级内存,不占用额外内存

  • Out-place —— 占用额外内存

  • k —— “桶”个数

  • n —— 数据规模

  • 就排序方法的全面性而言,不能断言某一种方法为最好,因为各有各自的优势与不足,只有根据所处的环境和情况(序列数据量、序列的初始状态)选择合适的

  • 衡量主要指标

    • 执行排序算法所需要的时间

      • 比较两个元素的大小

      • 将元素从一个位置移动到另一个位置 (或 改变指针的指向)

      • (排序的工作量取决于这两种动作的执行次数,尤其是前一个动作)

    • 执行排序算法所需要的附加空间

  • 在将一个按值任意的序列转换为按值有序排列的序列的过程中,大多排序方法都要经过若干处理才能达到目的

直接插入排序及折半插入排序


基本原理及特点

  • 插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入;

  • 插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入;

代码实现

#include <stdio.h>
​
// 直接插入排序
void insertionSort(int a[], int len) {int i, j, key;for (int i = 1; i < len; i++) {key = a[i];j = i - 1;while (j >= 0 && a[j] > key) {a[j + 1] = a[j];j--;}a[j + 1] = key;}
}
​
// 折半插入排序
void binInsertionSort(int a[], int len0) {int low, high, mid;int len = len0 - 1;for (int i = 2; i <= len; i++) {a[0] = a[i]; // a[0] 监视哨 不参与排序low = 1;high = i - 1;while (low <= high) {mid = (low + high) >> 1;if (a[0] >= a[mid]) {low = mid + 1;} else {high = mid - 1;}}for (int j = i - 1; j >= low; j--) {r[j + 1] = r[j];}r[low] = r[0];}
}
​

直接插入

  • 最好情况 —— 初始序列已经有序,比较次数最少,为 \Sigma_{i= 2}^{n}1 = n - 1 ,且无需移动记录

  • 最坏情况 —— 初始序列完全逆序,比较次数最多,为 \Sigma_{i=2}^{n}(i-1) = n(n-1)/2

  • 排序总趟数(最坏情况) —— n - 1

  • 平均时间复杂度 —— O(n^2)

  • 稳定排序方法

折半插入

  • 最坏情况下与直接插入方法一样,但在最好情况下时间复杂度降为 O(n log_2n)

选择排序


基本原理及特点

  • 一种简单直观的排序算法,无论怎样的数据规模,时间复杂度都为 O(n²) ,因此用到它的时候,数据规模越小越好。唯一的好处在于不占用额外的内存空间。

  • 算法步骤

    • 首先在初始序列中找到最小(大)元素,存放到排序序列的起始位置;

    • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾;

    • 重复第二步,直到所有元素均排序完毕;

代码实现

#include <stdio.h>
​
// 原生选择排序(升序)
void selectionSort(int a[], int len) {for (int i = 0; i < len; i++) {int minIndex = i;for (int j = i + 1; j < len; j++) {if (a[i] > a[j]) {minIndex = j;}}int t = a[i];a[i] = a[minIndex];a[minIndex] = t;}
}
  • 与插入排序一样,对于数据规模 n ,需要经过的总趟数为 n - 1

  • 元素移动次数

    • (原始序列为升序时)最少为 0 次

    • (原始序列为降序时)最多为 3 * (n - 1)

      • 其中,3 表示:交换的执行次数

  • 比较总次数恒为 n(n - 1) / 2 —— 与原始序列的排序情况无关

冒泡排序


基本原理和特点

  • 冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

  • 冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序,但这种改进对性能并不会有显著的提升。

  • 算法步骤

    • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;

    • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数;

    • 针对所有的元素重复以上的步骤,除了最后一个;

    • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较;

代码实现

#include <stdio.h>
​
void bubbleSort(int a[], int len) {for (int i = 0; i < len - 1; i++) {for (int j = 0; j < len - 1 - i; j++) {if (a[j] > a[j + 1]) {int t = a[j];a[j] = a[j + 1];a[j + 1] = t;}}}
}
​
// 优化
void optBubbleSort(int a[], int len) {int flag = 1; // 表示是否有交换动作 1:有 0:没有for (int i = 0; i < len - 1; i++) {flag = 0;for (int j = 0; j < len - 1 - i; j++) {if (a[j] > a[j + 1]) {int t = a[j];a[j] = a[j + 1];a[j + 1] = t;flag = 1;}}}
}
  • 最好情况 —— 只需经过一趟 n - 1 次的比较,且=不移动元素,复杂度为 O(n);

  • 最坏情况 —— 初始序列为逆序最小值元素在序列末尾,则需要 n - 1 趟排序,共进行 n(n-1)/2次元素之间的比较;

  • 平均时间复杂度 O(n^2)

  • 适合数据规模小的情况,一般情况下,该算法的排序时间效率最低

  • 稳定的排序

谢尔排序


基本原理及特点

  • 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

  • 希尔排序是基于插入排序的以下两点性质而提出改进方法的:

    • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;

    • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

  • 希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

  • 算法步骤

    • 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

    • 按增量序列个数 k,对序列进行 k 趟排序;

    • 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

代码实现

#include <stdio.h>
​
void shellSort(int a[], int len) {int gap, i, j; // gap 增量/元素间隔数int tmp;for (gap = len >> 1; gap > 0; gap >>= 1) {for (i = gap; i < len; i++) {tmp = a[i];for (j = i - gap; j >= 0 && a[j] > tmp; j -= gap) {a[j + gap] = a[j];}a[j + gap] = tmp;}}
}
  • 排序总趟数 —— \lfloor{log_{2}n}\rfloor

  • 一般情况下,时间复杂度在 O(nlog_2n) 与 O(n^2) 之间

  • 不稳定的排序

    • 不适合用于链表结构的排序

快速排序


  • 又称 划分排序

基本原理及特点

  • 通过一趟排序将原始序列分割为独立子序列,其中一序列的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两子序列的数据分别进行快速排序。

  • 快速排序算法通过多次比较和交换来实现排序,流程如下:

    1、首先设定一个分界值,通过该分界值将数组分成左右两子序列 。

    2、将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左部中各元素都小于或等于分界值,而右部中各元素都大于或等于分界值。

    3、然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

    4、重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

  • 挖坑填数 + 分治

代码实现

非递归

#include <stdio.h>
​
typedef struct _Range {int start, end;
} Range;
​
Range new_Range(int s, int e) {Range r;r.start = s;r.end = e;return r;
}
​
void swap(int *x, int *y) {int t = *x;*x = *y;*y = t;
}
​
void quick_sort(int arr[], const int len) {if (len <= 0)return; // 避免len等于负值时引发段错误(Segment Fault)// r[]模拟列表,p为数量,r[p++]为 push,r[--p]为 pop且取得元素Range r[len];int p = 0;r[p++] = new_Range(0, len - 1);while (p) {Range range = r[--p];if (range.start >= range.end)continue;int mid = arr[(range.start + range.end) / 2]; // 中间数为基准点int left = range.start, right = range.end;do {while (arr[left] < mid) ++left;   // 检测基准点左侧while (arr[right] > mid) --right; // 检测基准点右侧if (left <= right) {swap(&arr[left], &arr[right]);left++;right--;               // 移动指针以继续}} while (left <= right);if (range.start < right) r[p++] = new_Range(range.start, right);if (range.end > left) r[p++] = new_Range(left, range.end);}
}

递归

#include <stdio.h>
​
void swap(int *x, int *y) {int t = *x;*x = *y;*y = t;
}
​
void quick_sort_recursive(int arr[], int start, int end) {if (start >= end)return;int mid = arr[end];int left = start, right = end - 1;while (left < right) {while (arr[left] < mid && left < right)left++;while (arr[right] >= mid && left < right)right--;swap(&arr[left], &arr[right]);}if (arr[left] >= arr[end])swap(&arr[left], &arr[end]);elseleft++;if (left)quick_sort_recursive(arr, start, left - 1);quick_sort_recursive(arr, left + 1, end);
}
​
void quick_sort(int arr[], int len) {quick_sort_recursive(arr, 0, len - 1);
}
  • 初始时已经有序的情况下,耗时最长,总的比较次数为 n(n - 1)/2,时间复杂度 —— O(n^2)

  • 若每趟排序后,分界元素正好定位在序列中间,从而把当前待排序的序列分成大小相等的前后两个子序列,则所需时间为 O(nlog_2n)

  • 因此,平均时间复杂度为 O(nlog_2n)

  • 最坏情况 —— 分界元素的位置都偏向子序列的一端,空间复杂度 O(n),一般情况为 O(logn)

  • 不稳定的排序

  • 各部分的分界元素恰好为最大值元素时,快排就会变成“慢速排序”

堆积排序


堆积的定义

  • (定义 1)具有 n 个数据元素的序列 K = (k_1, k_2, k_3, k_4, . . . , k_n); 当且仅当满足条件

    k[ i ] >= k[i*2] \&\& k[ i ] >= k[i*2+1]

    或者

    k[i]<=k[i*2]\&\&k[i]<=k[i*2+1],i = (1, 2, 3, 4, . . . , n/2) 时称序列K为一个堆积 (heap),简称。有时将满足第一种条件的堆积称为大顶堆积,满足第二种条件的堆积称为小顶堆积。大顶堆积的第一个元素具有最大值。下面的讨论针对大顶堆积而言。

  • 若将序列的元素依次存放于一个一维数组中,并将此一维数组看做是一棵完全二叉树的顺序存储结构,则堆积可以与一棵完全二叉树对应,而且很容易确定该完全二叉树中任意结点i的孩子结点的位置(如果存在孩子结点),因此可以从另外一个角度给堆积下定义(定义 2):

    • 堆积是一棵完全二叉树,其中每个分支结点的值均大于或者等于其左子树和右子树(如果存在)中所有结点的值,并且该完全二叉树的根节点值最大。

基本原理及特点

  • 堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

    1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;

    2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

  • 堆排序的平均时间复杂度为 Ο(nlogn)。

  • 算法步骤(堆积的构造

    • 建立初始堆积

    • 交换堆积的第一个元素(最大值元素)与堆积的最后元素的位置

    • 将移走的最大值元素之后的剩余元素组成的序列再转换为一个堆积

    • 重复上述的第二步和第三步 n - 1 次

代码实现

#include <stdio.h>
#include <stdlib.h>
​
void swap(int *a, int *b) {int temp = *b;*b = *a;*a = temp;
}
​
void adjust(int arr[], int start, int end) {int dad = start;int son = dad * 2 + 1;while (son <= end) {if (son + 1 <= end && arr[son] < arr[son + 1])son++;if (arr[dad] > arr[son])return;else {swap(&arr[dad], &arr[son]);dad = son;son = dad * 2 + 1;}}
}
​
void heap_sort(int arr[], int len) {int i;for (i = len / 2 - 1; i >= 0; i--)adjust(arr, i, len - 1);for (i = len - 1; i > 0; i--) {swap(&arr[0], &arr[i]);adjust(arr, 0, i - 1);}
}
  • 对于数据规模为 n 的输入,堆积排序需要进行 n - 1 趟排序才能完成工作;

  • 适合数据规模很大的数据元素序列;

  • 第一层循环所需时间 —— 各层结点数与结点可移动最大距离之积的总和,耗时 O(n);

  • 第二个循环中每次都要调用一次 adjust(int, int, int),总共调用 n - 1 次,因此共耗时 (n-1)log_2(n+1) = O(nlog_2n)

  • 总效率 =》O(n) + O(nlog_2n) = O(nlog_2n) —— 时间复杂度,无论最好还是最坏的情况;

  • 空间复杂度 —— O(1);

  • 不稳定的排序;


http://chatgpt.dhexx.cn/article/0Vns6K4k.shtml

相关文章

数据结构-考研难点代码突破(C/C++/Java排序算法,性能及其稳定性分析(内部排序))

文章目录 1. 内部排序的基本种类2. 插入排序Ⅰ直接插入排序性能与稳定性分析Ⅱ 折半插入排序性能与稳定性分析Ⅲ 希尔排序性能与稳定性分析 3. 交换排序Ⅰ 冒泡排序性能与稳定性分析Ⅱ 快速排序key值选取&#xff08;三数取中&#xff09;三种交换方法① hoare左右指针② 挖坑法…

排序算法知识点总结和Java实现

排序算法知识点总结和Java实现 前言1. 术语解释2. 排序算法2.1 选择排序2.2 冒泡排序2.3 插入排序2.4 希尔排序2.5 归并排序2.6 快速排序2.7 堆排序2.8 计数排序2.9 桶排序2.10 基数排序 参考材料 前言 文章会有一些从参考材料中转载来的动图&#xff0c;如果构成侵权&#xf…

八大排序算法总结与java实现

原文链接&#xff1a; 八大排序算法总结与java实现 - iTimeTraveler 概述 因为健忘&#xff0c;加上对各种排序算法理解不深刻&#xff0c;过段时间面对排序就蒙了。所以决定对我们常见的这几种排序算法进行统一总结。首先罗列一下常见的十大排序算法&#xff1a; 直接插入排序…

C/C++最全排序算法汇总,原理+代码

1、简介 排序是计算机内经常进行的一种操作&#xff0c;其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序。若整个排序过程不需要访问外存便能完成&#xff0c;则称此类排序问题为内部排序。反之&#xff0c;若参加排序的记录数量很大&#x…

采用回调函数的内部排序算法-插入排序,希尔排序,冒泡,快排,堆排,归并排,基数排序

采用回调函数的内部排序算法-插入排序&#xff0c;希尔排序&#xff0c;冒泡&#xff0c;快排&#xff0c;堆排&#xff0c;归并排&#xff0c;基数排序 1.回调函数(callback function) :简而言之&#xff0c;回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针&…

排序算法总结---C语言

排序算法总结---建议收藏 排序算法概述一&#xff1a;冒泡排序&#xff08;Bubble Sort&#xff09; 1、冒泡排序简要概述 2、冒泡排序图解 3、代码实现 二&#xff1a;选择排序&#xff08;Select Sort&#xff09; 1、选择排序简要概述 2、选择排序图解 3、代码实现 三…

C语言实现基础排序算法

排序算法平均时间复杂度最差时间复杂度空间复杂度数据对象稳定性冒泡排序O( n 2 n^{2} n2)O( n 2 n^{2} n2)O(1)稳定快速排序O(n * l o g 2 n log_{2n} log2n​)O( n 2 n^{2} n2)O( l o g 2 n log_{2n} log2n​)不稳定插入排序O( n 2 n^{2} n2)O( n 2 n^{2} n2)O(1)稳定希尔排…

排序算法-总结(c语言)

排序算法有很多种&#xff0c;但是我看网络上大多数都是十大经典排序算法&#xff0c;分别有&#xff1a; 目录 冒泡排序 1. 算法步骤 2.代码 选择排序 1. 算法步骤 2.代码 插入排序 1. 算法步骤 2.图解&#xff08;源自我主页文章&#xff09; 3.代码 希尔排序 1.…

C语言实现八种排序算法

文章目录 排序算法选择排序冒泡排序插入排序归并排序希尔排序快速排序堆排序计数排序 排序算法 平时用惯了高级语言高级工具高级算法&#xff0c;难免对一些基础算法感到生疏。但最基础的排序算法中实则蕴含着相当丰富的优化思维&#xff0c;熟练运用可起到举一反三之功效。 …

c语言八大排序算法详细版

概述排序有内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 我们这里说说八大排序就是内部排序。 当n较大&#xff0c;则应采用时…

C语言各大排序算法整理及动画演示

&#xff08;一&#xff09;插入排序 插入排序基本思想&#xff1a;从初始的子集合开始&#xff0c;不断地将新的元素插入到已排序好的子集合当中的合适位置。&#xff08;未排序的插入到已排序当中&#xff09;具体分为直接插入排序和希尔排序两种。 ①直接插入排序 void Ins…

浅谈排序算法:冒泡排序法和选择排序法的区别

之前学习了冒泡排序法和选择排序法&#xff0c;最近被老师问某个道题用的是什么排序法。自己居然答不出来&#xff0c;才发现自己没有真正弄懂&#xff0c;这两个算法的原理和区别&#xff0c;所以 1冒泡排序法 1.1什么是冒泡排序法&#xff1f; 顾名思义&#xff0c;冒泡排…

Java语言实现常用的十种排序算法

排序问题一直都是程序工作或面试中的重点&#xff0c;特别是对于排序算法而言&#xff0c;在一些公司的笔试中&#xff0c;手写个冒泡啥的也不足为奇&#xff0c;因此今天抽空整理一下Java语言实现的各类排序算法&#xff0c;互勉学习一下。根据排序的不同方式大概可归为一下五…

排序算法(二)—— 选择法排序算法

1、选择法排序简介 选择法排序算法是一种常用的排序算法&#xff0c;他的实现方法是遍历数组所有元素&#xff0c;找出最小的元素&#xff0c;将它与第一个元素交换&#xff1b;然后遍历剩下的元素&#xff0c;找出最小的元素并与第二个元素交换&#xff1b;接下来再遍历剩下的…

Java编程:排序算法——选择排序

基本介绍 选择式排序也属于内部排序法&#xff0c;是从欲排序的数据中&#xff0c;按指定的规则选出某一元素&#xff0c;再依规定交换位置后达到排序的目的。 选择排序思想: 选择排序&#xff08;select sorting&#xff09;也是一种简单的排序方法。它的基本思想是&#x…

Java常用排序算法/程序员必须掌握的8大排序算法

本文由网络资料整理而来&#xff0c;如有问题&#xff0c;欢迎指正&#xff01; 参考链接&#xff1a;维基百科-排序算法 // 排序原始数据 private static final int[] NUMBERS {49, 38, 65, 97, 76, 13, 27, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15,…

排序算法:选择排序

1. 什么是选择排序&#xff1f;&#xff08;摘抄自百度百科&#xff09; 选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。 它的工作原理是&#xff1a; 第一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c…

排序算法——选择排序

目录 &#x1f43e;基本介绍 &#x1f31e;算法思想&#xff1a; &#x1f330;实例&#xff1a; ⛅思路分析&#xff1a; &#x1f308;总结&#xff1a; &#x1f6f4;代码实现: &#x1f6f9;算法性能分析 &#x1f355;时间复杂度 &#x1f367;空间复杂度 &…

基本算法-选择排序

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 本文介绍一种经典排序算法——选择排序&#xff0c;是入门级的排序算法之一。以下是本篇文章正文内容&#xff0c;包括算法简…

程序员八大排序算法之直接选择排序算法(java版)

一&#xff0c;选择排序算法思路&#xff1a;每趟选择序列的最小值/最大值&#xff0c;采取贪心选择策略。 二&#xff0c;选择排序算法有两种&#xff1a;1.直接选择排序 2.堆排序&#xff08;基于二叉树&#xff09;。 &#xff08;这里讲解直接选择排序&#xff09; 三&a…