【数据结构】第十五弹---C语言实现直接插入排序

embedded/2024/10/18 7:45:48/

 

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、排序的概念及其运用

1.1、排序的概念与分类

1.2、排序运用

1.3、常见的算法>排序算法

1.4、常见的算法>排序算法性能测试

2、常见算法>排序算法的实现

2.1、直接插入排序

2.1.1、基本思想

2.1.2、代码实现

2.1.3、代码测试

2.1.4、时空复杂度分析

总结


1、排序的概念及其运用


1.1、排序的概念与分类


排序:

排序是一种将一组对象按照某种特定顺序重新排列的过程。在计算机科学中,排序是数据处理中非常基本且重要的操作,它可以帮助人们更有效地理解和分析数据。排序的顺序通常是升序或降序,也可以按照数字、字母、大小或其他标准进行排序。


稳定性:

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] == r[j],且 r[i] 在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种算法>排序算法稳定的;否则称为不稳定的。


内部排序:

数据元素全部放在内存中的排序。


外部排序:

数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2、排序运用

排序的运用非常常见,在我们购物的软件上面,经常会根据不同的属性来进行排序,我们以前上学时候的成绩通常也会对各科进行排序!!!!


1.3、常见的算法>排序算法

常见的算法>排序算法冒泡排序、选择排序、插入排序、快速排序、希尔排序、堆排序、归并排序等等!!!

注意:在本专栏中,还会讲解计数排序,它的效率非常高,但是局限性也很大。 

如下为常见排序实现的接口:

// 排序实现的接口// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序
void SelectSort(int* a, int n);
// 向下调整算法
void AdjustDwon(int* a, int n, int root);
// 堆排序
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n)
// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right);
// 快速排序挖坑法
int PartSort2(int* a, int left, int right);
// 快速排序前后指针法
int PartSort3(int* a, int left, int right);
void QuickSort(int* a, int left, int right);
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
// 归并排序递归实现
void MergeSort(int* a, int n)
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
// 计数排序
void CountSort(int* a, int n)

1.4、常见的算法>排序算法性能测试

实现好排序之后必不可少要对效率进行测试,前面我们用到的都是时间空间复杂度来评价一个算法的好坏,此处我们再用实际的时间来证明排序的性能!!!

// 测试排序的性能对比
void TestOP()
{srand(time(0));// 生成随机数的种子const int N = 10000000;// 动态开辟空间的元素个数,开辟较大的空间适用于效率高的排序int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);int* a8 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand() + i;// rand()随机数只有几万个,不能达到真正的随机,加上i让它更随机a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];a8[i] = a1[i];}//clock计算程序运行到此时的时间 毫秒int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();SelectSort(a3, N);int end3 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();int begin5 = clock();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();MergeSort(a6, N);int end6 = clock();int begin7 = clock();BubbleSort(a7, N);int end7 = clock();int begin8 = clock();CountSort(a8, N);int end8 = clock();// 计算排序所用时间printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);printf("QuickSort:%d\n", end5 - begin5);printf("MergeSort:%d\n", end6 - begin6);printf("BubbleSort:%d\n", end7 - begin7);printf("CountSort:%d\n", end8 - begin8);// 释放空间free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);free(a8);
}

 排序OJ(可使用各种排序跑这个OJ)   :

排序OJicon-default.png?t=N7T8https://leetcode.cn/problems/sort-an-array/description/

2、常见算法>排序算法的实现


2.1、直接插入排序


2.1.1、基本思想


直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。


实现思路:

当插入第 i (i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

直接插入排序动图如下:


2.1.2、代码实现

第一步:创建函数,确定函数的形参,毋庸置疑的是需要传一个待排序的数组,其次进行排序的时候是需要遍历数组的,因此需要知道元素个数。为什么不能再函数内部计算元素个数呢???

答案是将数组作为形参传给函数,此数组的实质是一个地址,而计算大小是用数组大小/数组元素大小,此时sizeof(数组名) 计算的大小是4或者8,不能准确计算元素个数。

因此函数为:

void InsertSort(int a[], int n);//升序

第二步:分析单趟排序的情况

  情况一:待排序的数大于其中任意一个已经排序好的数( tmp > a[i] )。

默认0 - end区间是有序的,将end + 1位置的数插入到有序数组中。

  • 这里的end代表已排序序列的最后一个元素的索引。将插入的数(tmp)依次往前比较,如果比前面的end位置的数小,则将此end位置的数放到end + 1 位置,并将end --。
  • 如果大于等于前面的数字,说明此位置就是插入的数的位置
  • 将插入的数给end + 1下标即可。

  情况二:待排序的数比所有已经排序好的数要小。

通过上面两种情况可以总结出循环的条件是end >=0。

int end;
int tmp = a[end + 1];
while (end >= 0)
{if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}
}
a[end+1]=tmp;

这里跳出循环有两种情况:

1.找到插入位置:当tmp大于等于a[end],这表明tmp应该插入在a[end]的后面,因为在tmp之前的元素(包括a[end]自己)都已经排序好了,并且都是小于等于tmp的。这就是tmp的正确位置,在这种情况下,我们执行break语句跳出循环,并将tmp放置在end + 1的位置。


2.达到有序序列的起点:当循环保持进行,end值在每次迭代中不断递减,如果tmp小于所有已排序的元素,end可能会变成-1,这意味着tmp比有序序列中所有现有的元素都要小,应该被放在整个序列的最开始位置。


在这两种跳出循环的情况下,我们总是需要执行a[end + 1] = tmp;来将tmp元素放置到正确的位置上。因为无论是找到合适的插入点还是tmp成为新的最小元素,我们都需要将它实际插入到有序序列中,这就是为什么这行代码放在循环之外,确保跳出循环后,我们执行最终的插入动作。

第三步:封装整个排序

一个元素就是有序数组,因此end从0开始。

// 升序
void InsertSort(int a[], int n)
{// [a,end]有序for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp) // 前面的数大于后面则覆盖前面{a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;// 填充空位置}
}

2.1.3、代码测试

按照我们正常的惯例,实现完之后都需要进行测试,此处有两种测试方式,其一为打印测试,其二为调试测试,我们现在实现的都是算法>排序算法,因此可以直接封装一个打印数组函数进行测试。

 打印函数如下:

void ArrayPrint(int a[], int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}

测试代码:

int main()
{int a[] = { 9,8,7,6,5,4,3,2,1,0 };//给一组数据int sz = sizeof(a) / sizeof(a[0]);//计算数组元素个数printf("排序前:\n");ArrayPrint(a, sz);InsertSort(a, sz);printf("排序后:\n");ArrayPrint(a, sz);return 0;
}

测试结果: 

 

2.1.4、时空复杂度分析

时间复杂度:

插入算法>排序算法的时间复杂度取决于输入数组中元素的初始排序状态

最坏情况 :如果数组是完全逆序的,那么每次插入操作都需要将元素移到已排序部分的开头。这就意味着对于第i个元素,可能需要进行i次比较和移动。这种情况下,算法的时间复杂度是O(N2),因为需要进行总计约1 + 2 + 3 + … + (n-1)次比较,这是一个n(n-1)/2的等差数列

最好情况 :这种情况发生在数组已经完全有序时。在这种情况下,每次比较后,很快就会找到插入位置(在已排序元素的末尾),不需要进行额外的移动。因此,最好情况下插入排序的时间复杂度是O(N),因为外层循环只会遍历一次数组,内层循环不会进行任何实际的比较和移动操作。

我们时间复杂度分析看的是最坏情况,因此直接插入排序的时间为O(N^2)。

空间复杂度:

插入排序的空间复杂度为O(1),因为它是一个原地算法>排序算法,不需要额外的存储空间来排序。

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入算法>排序算法的时间效率越高。
2. 时间复杂度:O(N^2)。
3. 空间复杂度:O(1),它是一种稳定的算法>排序算法
4. 稳定性:稳定。

总结

本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!


http://www.ppmy.cn/embedded/48590.html

相关文章

【Android】实现Recyclerview的Item可以左右侧滑动的效果

项目需要 使用Recyclerview进行列表的数据加载的时候&#xff0c;需要对这个Item进行左右滑动进行操作的功能&#xff0c; 比如这样 需求实现 上面图来源于 https://github.com/anzaizai/EasySwipeMenuLayout 这是一个可以用来进行列表左滑、右滑的项目&#xff0c;可以集…

C# WPF入门学习主线篇(三十二)—— 创建Model、View和ViewModel

C# WPF入门学习主线篇&#xff08;三十二&#xff09;—— 创建Model、View和ViewModel 在前一篇文章中&#xff0c;我们介绍了MVVM&#xff08;Model-View-ViewModel&#xff09;模式的基本概念。本篇将深入探讨如何在实际开发中创建Model、View和ViewModel&#xff0c;并通过…

B+索引的分裂及选择率和索引基数

1、B树索引的分裂 B树索引页的分裂并不总是从页的中间记录开始&#xff0c;这样可能会导致页空间的浪费。 例子 比如下面这个记录&#xff1a; 1、2、3、4、5、6、7、8、9 由于插入是以自增的顺序进行的&#xff0c;若这时插入第10条记录然后进行页的分裂操作&#xff0c;那…

代码解读 | Hybrid Transformers for Music Source Separation[06]

一、背景 0、Hybrid Transformer 论文解读 1、代码复现|Demucs Music Source Separation_demucs架构原理-CSDN博客 2、Hybrid Transformer 各个模块对应的代码具体在工程的哪个地方 3、Hybrid Transformer 各个模块的底层到底是个啥&#xff08;初步感受&#xff09;&#xff1…

Ceph入门到精通-Crimson 和 Classic Ceph OSD 架构之间的区别

Crimson 和 Classic Ceph OSD 架构之间的区别 在典型的 ceph-osd 架构中,messenger 线程从 wire 读取客户端消息,它将消息放在 OP 队列中。然后,osd-op thread-pool 会提取消息,并创建一个事务并将其排队到 BlueStore,当前的默认 ObjectStore 实现。然后,BlueStore 的 k…

Vue项目中,利用iframe在线预览pdf/图片等

在components下新建 src\components\iFrame\index.vue <template><div v-loading"loading" :style"height: height"><iframe :src"src" frameborder"no" style"width: 100%; height: 100%" scrolling"…

江苏哪些行业需要服务器托管?

服务器托管顾名思义就是用户委托具有完善设备的机房、良好网络和丰富运营经验的服务商管理其计算机系统&#xff0c;使企业的服务器能够更加安全、稳定和高效的运行&#xff0c;那在江苏都有哪些行业需要服务器托管服务呢&#xff1f;本文就来大概介绍一下。 首先让我们来一起了…

u盘数据要在哪台电脑上恢复?u盘数据恢复后保存在哪里

在数字化时代&#xff0c;U盘已成为我们日常生活中不可或缺的数据存储设备。然而&#xff0c;由于各种原因&#xff0c;U盘中的数据可能会意外丢失&#xff0c;这时数据恢复就显得尤为重要。但是&#xff0c;很多人对于在哪台电脑上进行U盘数据恢复以及恢复后的数据应保存在哪里…