数据结构笔记-排序算法
记录一下数据结构的排序算法:相关代码放在这里保存:https://gitee.com/lx2035/data_structure
1、插入排序
插入排序的思想是将数据按照一定顺序一个个插入到有序的表中,最终得到的序列就是排好序的数据。插入排序根据插入方式的不同,分为直接插入排序,折半插入排序,2路插入排序和表插入排序这几种,下面逐一进行说明:
1、直接插入排序
直接插入排序就是在添加新的记录的时候,使用顺序查找的方式找到要插入的位置,然后将数据插入。
下面是将{3,1,7,5,2,4,9,6}进行升序排序的过程:
- 第一个3直接插入
- 插入1,1小于3,放到3前面
- 插入7,7大于3,放3后面
- 插入5,5在3和7之间,所以插进去
基本就是这么个流程,后面的部分省略
下面看代码实现:
void InsertSort(int a[], int n)
{
for (int i = 1; i < n; i++) /*一个个插入的动作*/
{
if (a[i] < a[i - 1]) /*当前元素小于前一个元素*/
{
int j = i - 1;
int x = a[i]; /*将当前元素存到临时变量x*/
while (j > -1 && x < a[j]) /*从当前位置向前遍历,找到一个比临时变量还小的值,找不到就是第一个了,这个由j控制循环结尾*/
{
a[j + 1] = a[j]; /*找的过程将每次不符合的都往后移动*/
j--;
}
a[j + 1] = x; /*找到j了,将当前元素插入*/
}
print(a, n, i);
}
}
2、折半插入排序
上面说的直接插入排序,后面的元素插入的时候,采用顺序插入的方式来查找,但是实际上前面的表我们已经排好序了,所以可以节省一点查找量,因此就有折半查找这种思路:
下面直接看代码:
3、2路插入排序
2路插入排序是在折半插入排序的基础上对其进行改进,减少其在排序过程中移动记录的册数来提高效率。
实现上是另外设置一个和储存记录的数组大小相同的数组d,将无序表第一个加到d[0]的位置,之后从无序表的第二个记录开始,同0作比较,如果该值比d[0]大,就加到右侧,反之就是左侧。
还是用这个举例子,{3,1,7,5,2,4,9,6}
- 一开始还是加入3到d[0]
- 插入1,1比3小,所以插入到左边
- 插入7,7比1大,所以插入到右边
- 将5插入,5比3大,插入右边,但是比7小,所以在中间
- 插入2,2很特殊,2比3小,应该插入左边,但是左边是1,所以只能让3整体都移动
- 插入4,和之前5类似,找个中间位置插入
- 插入9,一样的,找个中间位置插入
- 插入6,一样的,找个中间位置插入
下面看代码实现:
void insert(int arr[], int temp[], int n)
{
int i, first, final, k;
first = final = 0; // 分别记录temp数组中最大值和最小值的位置
temp[0] = arr[0]; /*将一个元素放到临时数组的第一个位置*/
for (i = 1; i < n; i++)
{
if (arr[i] < temp[first]) // 待插入元素比最小的元素小
{
first = (first - 1 + n) % n; /*更新最小元素的位置*/
temp[first] = arr[i]; /*将带插入元素放入最小元素的位置,这个就是放到末尾了,就是示意图里面的a【0】左边的*/
}
else if (arr[i] > temp[final]) // 待插入元素比最大元素大
{
final = (final + 1 + n) % n; /*更新最大元素的位置*/
temp[final] = arr[i];/*将带插入元素放入最大元素的位置*/
}
else // 插入元素比最小大,比最大小
{
k = (final + 1 + n) % n; /*k是待插入元素应该插入的位置,从最大的开始找*/
// 当插入值比当前值小时,需要移动当前值的位置
while (temp[((k - 1) + n) % n] > arr[i]) /*没找到之前就一直前移*/
{
temp[(k + n) % n] = temp[(k - 1 + n) % n];
k = (k - 1 + n) % n; /*找到k*/
}
// 插入该值
temp[(k + n) % n] = arr[i];
// 因为最大值的位置改变,所以需要实时更新final的位置
final = (final + 1 + n) % n;
}
}
// 将排序记录复制到原来的顺序表里
for (k = 0; k < n; k++)
{
arr[k] = temp[(first + k) % n];
}
}
4、表插入排序
前面几种插入排序算法,基本都是用数组的形式来存储数据,因此无法避免排序过程中产生数据移动的问题,如果要根本上解决只能改变数据的存储结构,改为使用链表存储。根据链表存储的特点,在存储的过程中,不需要移动记录的存储位置,只需要更改节点间指针的指向。
这个用链表的没怎么看懂,先不记录
5、缩小增量排序(希尔排序)
这个也是插入排序的一种,但是和前面的几种相比,在时间效率上有很大的改进,直接插入排序算法时,如果表中的记录只有个别的是无序的,多数保持有序,这种情况下算法的效率也会比较高;除此之外,如果需要排序的记录总量很少,该算法的效率同样会很高。希尔排序就是从这两点出发对算法进行改进得到的排序算法
这个算法的实现思路就是,先将整个记录表分割成若干个部分,分别进行直接插入排序,然后对整个记录表进行一次直接插入排序
具体实现如下,对这个表进行插入排序{49, 38, 65, 97, 76, 13, 27, 49, 55, 4}
- 先对{49, 13}, {38, 27}, {65, 49}, {97, 55}, {76, 4} 分别进行直接插入排序,其实就是比大小
- 讲过第一次简单排序之后,表中的记录已经基本有序,在进行一次分割
- 最后再来一次完整的直接插入排序,这个过程其实完整插入排序要做的工作其实已经很少了,最后在来一下就是这样了
下面看代码
typedef struct
{
int key;
} SLNode;
typedef struct
{
SLNode r[SIZE]; /*存记录的数组*/
int length; /*记录数组中记录的数量*/
} SqList;
void ShellInsert(SqList *L, int dk) /*dk的增量*/
{
for (int i = dk + 1; i <= L->length; i++) /*每隔dk做一下直接插入排序*/
{
if (L->r[i].key < L->r[i - dk].key)/*新调取的关键字的值,比子表中最后一个还小,就需要调换位置*/
{
int j;
L->r[0] = L->r[i]; /*用0位置暂存需要换位置的值*/
/*下面是直接插入排序*/
for (j = i - dk; j > 0 && (L->r[0].key < L->r[j].key); j -= dk)
{
L->r[j + dk] = L->r[j];
}
L->r[j + dk] = L->r[0]; /*把值插入进去*/
}
}
}
void ShellSort(SqList *L, int dlta[], int t) /*对多个子表进行插入排序,插入排序的增量值由dlta控制*/
{
for (int k = 0; k < t; k++)
{
ShellInsert(L, dlta[k]);
}
}
2、冒泡排序算法
这个应该算是很经典的排序算法了吧,很多学c语言的都是会在正式的课程中学到,这里简单说一下实现的思路
冒泡排序对应一个换钻石的问题,比如一个人做电梯,每层楼都需要停一下,每层楼都有一颗钻石,这个人想要拿到最大的钻石,但是一次只能拿一颗,因此他就每次用自己手里的钻石和这层楼的钻石做比较,比他手里的大就换一下,这就是冒泡排序的思路了,冒泡排序每次每一轮冒泡选出一个最大或者最小值,这样到最后一轮的时候就是有序的了。
下面看代码实现:
3、快速排序算法
需要说明,c语言自带的函数库中就有快速排序,qsort函数,在<stdlib.h>这个头文件中,快速排序的思想是:
通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小,然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就成为了有序序列。
下面使用对无序表{49, 38, 65, 97, 76, 13, 27, 49}进行快速排序作为示例:
- 首先从表中选取一个记录的关键字作为分割点(称为“枢轴” 或者支点,一般选择第一个关键字),例如选取 49;
- 将表格中大于 49 个放置于 49 的右侧,小于 49 的放置于 49 的左侧,假设完成后的无序表为: {27, 38, 13, 49, 65, 97, 76, 49};
- 以 49 为支点,将整个无序表分割成了两个部分,分别为{27, 38, 13}和{65, 97, 76, 49},继续采用此种方法分别对两个子表进行排序;
- 前部分子表以 27 为支点,排序后的子表为{13, 27, 38},此部分已经有序;后部分子表以 65 为支点,排序后的子表为{49, 65, 97,76};
- 此时前半部分子表中的数据已完成排序;后部分子表继续以 65 为支点,将其分割为{49}和{97, 76},前者不需排序,后者排序后的结果为{76, 97};
- 通过以上几步的排序,最后由子表{13, 27, 38}、 {49}、 {49}、 {65}、 {76, 97}构成有序表: {13, 27, 38, 49, 49, 65, 76, 97};
这里也贴代码吧,具体的还没怎么看明白
#include "stdio.h"
#include "stdlib.h"
#define MAX 9
typedef struct
{
int key;
} SqNote;
typedef struct
{
SqNote r[MAX];
int length;
} SqList;
int Partition(SqList *L, int low, int high)
{
L->r[0] = L->r[low];
int pivotkey = L->r[low].key;
while (low < high)
{
while (low < high && L->r[high].key >= pivotkey)
{
high--;
}
L->r[low] = L->r[high];
while (low < high && L->r[low].key <= pivotkey)
{
low++;
}
L->r[high] = L->r[low];
}
L->r[low] = L->r[0];
return low;
}
void QSort(SqList *L, int low, int high)
{
if (low < high)
{
int pivotloc = Partition(L, low, high);
QSort(L, low, pivotloc - 1);
QSort(L, pivotloc + 1, high);
}
}
void QuickSort(SqList *L)
{
QSort(L, 1, L->length);
}
int main()
{
SqList *L = (SqList *)malloc(sizeof(SqList));
L->length = 8;
L->r[1].key = 49;
L->r[2].key = 38;
L->r[3].key = 65;
L->r[4].key = 97;
L->r[5].key = 76;
L->r[6].key = 13;
L->r[7].key = 27;
L->r[8].key = 49;
QuickSort(L);
for (int i = 1; i <= L->length; i++)
{
printf("%d ", L->r[i].key);
}
printf("\n");
exit(0);
}
4、简单选择排序
这个算法的思想为:对于具有 n 个记录的无序表遍历 n-1 次,第 i 次从无序表中第 i 个记录开始,找出后序关键字中最小的记录,然后放置在第 i 的位置上。
简单来说就是第一次遍历找到一个最小的,然后和第一个位置的元素换位置,之后从第二个位置开始继续找最小的,然后和第二个元素换位置(和冒泡排序有点类似)
下面看实现代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!