首页 > 其他分享 >学期总结——插入排序(从io,循环到类,时间复杂度,循环不变式)

学期总结——插入排序(从io,循环到类,时间复杂度,循环不变式)

时间:2024-12-24 20:30:15浏览次数:11  
标签:数字 int 插入排序 ++ 不变式 算法 循环 排序 函数

以插入算法的实现为例,从一开始写下思路,到证明循环不变式,从完全在主函数中书写,到把某些步骤写成函数,再到把这一算法写成类,而后分析时间复杂度

目录

算法的实现

思路(循环不变式)

做法

完全在主函数中书写(实现一)

将“交换”写成函数

将“排序”写成函数

将几乎所有步骤都写成函数

(实现二)

将这一过程写成类

(实现三)

分析算法(时间复杂度)

小结


算法的实现

思路(循环不变式)

  • 假定我们有一个已经按照从小到大排好序的n个数字,再增加一个数字,如何将这n+1个数字排序?显然只需要把这新增的数字放到它该在的位置上,而其他的数字相对位置不变,这样就排好序了。对这个第n+1个数字而言,如果它比原来n个数字中最后一个数字也就是最大的那个数字大,那么它的位置就是在最后面。如果不是,那么可以知道这个数字的位置绝不是最后面,后续也不用在和第n个数字比较。这时候它需要和第n-1个数字比较...重复上述过程,最终找到一个数字比它小,那么它的位置也就确定了,这样就实现了n+1个数字的排序
  • 而实际上我们面对的是n个毫无顺序的数字,但即使如此,对于第一个数字而言,这个数字本身是排好序的,第二个数字之于第一个数字相当于增加的那一个数字,按上述完成排序后,第三个数字之于前两个数字又相当于增加的那一个数字...最终第n个数字之于前n-1个数字相当于增加的数字,经比较即可完成排序
  • 举个例子:2,5,4,9,3,第一次是5和2比较,保持不变,前两个数字完成排序,第二次是4和(5,2)比较,4和5交换位置,变为(2,4,5,9,3),第三次9和(2,4,5)比较,位置不变,第四次是3和(2,4,5,9)比较,9和3交换位置,5和3交换位置,4和3交换位置,变为(2,3,4,5,9),排序完成
  • 这种都是每次将“新增的数字”“插入”原来n个已经排好序的数字,这就是循环不变式,下面给出定义:1、初始:保证在初始的时候不变式为真。
    2、保持:保证在每次循环开始和结束的时候不变式都为真。
    3、终止:如果程序可以在某种条件下终止,那么在终止的时候,就可以得到自己想要的正确结果。
    在这三个部分中,前两个是条件,最有一个是结论。(具体可参考本站博主的文章)https://blog.csdn.net/weixin_33720956/article/details/91790171?fromshare=blogdetail&sharetype=blogdetail&sharerId=91790171&sharerefer=PC&sharesource=ftgchtcju&sharefrom=from_link
  • 下面给出本算法的证明:                                                                                                            1.在刚开始,只有第一个数字,而这个数字显然是有序的                                                          2.第一个数字有序到前两个数字有序,再到第三个数字有序...所以当k个数字有序,“插入”第     k+1个数字时也是有序的,当这k+1个数字排好序后也是有序的                                                3.程序终止时,也就是将第n个数字“插入”到n-1个已经排好序的数字中,结束时所有数字都是   有序的,证明完毕

做法

完全在主函数中书写(实现一)

#include <iostream>
using namespace std;
int main()
{
	int n;
	cin >> n;//获取需要创建的数组空间大小
	int* p = new int[n];//创建数组空间
	for (int i = 0; i < n; i++) {
		cin >> *(p + i);//逐个获取数字
	}
	for (int i = 1; i < n; i++) {
		int j = 0;//实现排序,从前往后进行,从后往前比较
			while (*(p + i - j) < *(p + i - 1 - j) && (i - j - 1 >= 0)) {
				int t = *(p + i - j);
				*(p + i - j) = *(p + i - 1 - j);
				*(p + i - 1 - j) = t;
				j++;
				}
			}
	for (int i = 0; i < n; i++) {
		cout << *(p + i) << "  ";
	}
}

先在控制台确定要输入几个数字,创建有相应空间的数组,再逐个输入数字,该过程完全在主函数中书写

注意

while (*(p + i - j) < *(p + i - 1 - j) && (i - j - 1 >= 0)) {
				int t = *(p + i - j);
				*(p + i - j) = *(p + i - 1 - j);
				*(p + i - 1 - j) = t;
				j++;
}

中间这一步骤,是在后面数字比前面数字小的情况下,将这两个数字进行交换,其中交换的过程可以写成函数

将“交换”写成函数

//交换函数
void exchange(int &a, int &b) {
	int t = a;
	a = b;
	b = t;
}

这样这一步就可以变成

while (*(p + i - j) < *(p + i - 1 - j) && (i - j - 1 >= 0)) {
				 exchange(*(p + i-j), *(p + i - 1 - j));//调用交换函数
				j++;
}

交换这一步骤可以写成函数,那么同样地,实现这一排序本身自然也是可以写成函数的

将“排序”写成函数

void insert(int* p, int n) {//将原代码写成函数
	for (int i = 1; i < n; i++) {
		int j = 0;
		while (*(p + i - j) < *(p + i - 1 - j) && (i - j - 1 >= 0)) {
			 exchange(*(p + i-j), *(p + i - 1 - j));
			j++;
		}
	}
}

于是在主函数中就简化成了

int main()
{
	int n;
	cin >> n;
	int* p = new int[n];
	for (int i = 0; i < n; i++) {
		cin >> *(p + i);
	}
		insert(p, n);//调用函数
		for (int i = 0; i < n; i++) {
			cout << *(p + i) << "  ";
		}
	}

如果再把其他步骤也写到函数里,对insert函数改造一下

将几乎所有步骤都写成函数

template<typename T>
void insert(T* p, int n) {
	for (int i = 0; i < n; i++) {
		cin >> *(p + i);
	}
	for (int i = 1; i < n; i++) {
		int j = 0;
		while (*(p + i - j) < *(p + i - 1 - j) && (i - j - 1 >= 0)) {
			 exchange(*(p + i-j), *(p + i - 1 - j));
			j++;
		}
	}
	for (int i = 0; i < n; i++) {
		cout << *(p + i) << "  ";
	}
}

这里还用上了一个小小的分支知识点——模板,用了模板后,这个指针的类型就不会卡死,增加其泛用空间

那在主函数里就剩下了

(实现二)

int main()
{
	int n;
	cin >> n;
	int* p = new int[n];
	insert(p, n);
}

这样极其简洁(但实际上一个函数里不应该同时实现如此多的功能),只需要在关键步骤加上注释即可

而既然都简化为函数了,那么自然就能进一步写成类,调用的时候也会更方便

将这一过程写成类

class Insert//含义为“插入排序类”
{
public:
	friend void exchange(int& a, int& b);
	void get_arr();
	void insert();
	void show_arr();

private:
	int _n;
	int* _p;
};

在该类中,它的数据只有空间大小以及一个数组指针,指向一个动态空间,设为private,函数则有负责获取的,实现排序的以及输出在控制台的,由于交换函数并非独属于该类中的功能,在他处书写并在此设为友元函数

各函数的定义

void Insert::get_arr()//获取数组空间的大小,创建动态空间并逐个输入数字
{
	cin >> _n;
	_p = new int[_n];
	for (int i = 0; i < _n; i++) {
		cin >> *(_p + i);
	}
}

void Insert::insert() //排序的实现
{
	for (int i = 1; i < _n; i++) {
		int j = 0;
		while (*(_p + i - j) < *(_p + i - 1 - j) && (i - j - 1 >= 0)) {
			exchange(*(_p + i - j), *(_p + i - 1 - j));//类外的交换函数,在此处使用实际上并不需要设为友元函数
			j++;
		}
	}
}
void Insert::show_arr() //负责将排好序的各数字逐个输出
{
	for (int i = 0; i < _n; i++) {
		cout << *(_p + i) << "  ";
	}
}

之后在主函数中,则只需写成

(实现三)

int main()
{
	Insert ser;//定义
	ser.get_arr();//获取
	ser.insert();//排序
	ser.show_arr();//输出
}

对这一个算法而言,可能写成类是大材小用,但实际上,排序有多种方法,将来完全可以写一个“排序类”用于保存所有实现排序的算法,想用哪个直接调用类中的成员即可,相当方便

分析算法(时间复杂度)

分析算法的结果意味着预测算法需要的资源。虽然有时我们主要关心像内存,通信带宽或计算机硬件这类资源,但是通常我们想度量的是计算时间。一般来说,通过分析求解某个问题的几种候选算法,我们可以选出一种最有效的算法。这种分析可能指出不止一个可行的候选算法,但是在这个过程中,我们往往可以抛弃几个较差的算法,在此就用时间复杂度来表示

考虑最佳情况,在进行排序前就已经拍好了序,那么从第二个数字开始,所有数字都只会比较一次,运行次数为n-1,所以时间复杂度为O(n)

最坏情况,各数字刚好是逆序的,那么第二个数字需要比较1次,第三个需要比较2次...第n个数字需要比较n-1次,运行次数为1+2+……+n-1,最终最高次数为2,所以时间复杂度为O(n²)

一般而言,考虑的时间复杂度是考虑最坏情况,因为更容易发生,这种情况下再用插入排序就不是很合适了,时间会增长的很快,就该考虑别的算法了

小结

在这一学期,学习了io函数,头文件,后来将一些步骤写成函数,再后来用上了指针,使用了数组,也学习了类与对象,本文是对以上所有以插入算法来实现简要概括

标签:数字,int,插入排序,++,不变式,算法,循环,排序,函数
From: https://blog.csdn.net/ftgchtcju/article/details/144700001

相关文章

  • Patroni 流程整理-主循环
    Patroni流程整理目录3.主循环主循环在Patroni类的_run_cycle函数中进行,在这个函数中调用Ha类的循环函数run_cycle,每循环一次调用一次,而不是开启Ha的循环,并且在这里进行重载配置文件。在这个主循环中重点是Ha类的_run_cycle函数,在这个函数中对集群的各种状态做出检查,并且采用......
  • 数据结构与算法 - 排序 #直接插入排序 #希尔排序 #直接选择排序 #堆排序 #冒泡排序 #
    文章目录前言一、插入排序(一)、直接插入排序1、思路2、参考代码:3、复杂度计算:(二)、希尔排序1、思路2、参考代码:3、时间复杂度计算:二、选择排序(一)、直接选择排序1、思路2、参考代码3、时间复杂度计算(二)、堆排序三、交换排序(一)、冒泡排序(二)、快速......
  • 302 最小循环覆盖
    //302最小循环覆盖.cpp:此文件包含"main"函数。程序执行将在此处开始并结束。///*http://oj.daimayuan.top/course/22/problem/909给你一个字符串a,你需要求出这个字符串的最小循环覆盖的长度。b是a的最小循环覆盖,当且仅当a是通过b复制多次并连接后得到的字符......
  • 【深度学习】门控循环单元
    目录一、基本概念和原理二、基本流程三、GRU的简化设计四、应用领域五、改进方法六、技术发展趋势一、基本概念和原理        门控循环单元(GatedRecurrentUnit,GRU)是循环神经网络(RNN)的一种变体,旨在解决标准RNN中的梯度消失或爆炸问题,同时保留序列的长期信息......
  • 插入排序 计数排序 包括 代码 时间复杂度 空间复杂度 稳定性 是否能对代码进行提升
    插入排序代码:voidinsertsort(vector<int>&v){intn=v.size();for(inti=0;i<n;i++){intj=i-1;temp=v[i];for(;j>=0;j--){if(v[j]>temp){v[j+1]=v[j];}else{break;}}v[j+1]=......
  • 学霸带你游戏化玩转 C# 条件语句和循环结构
    控制结构:编程的核心逻辑控制结构是编程语言中的核心元素之一,它决定了程序的流程控制、执行顺序和决策逻辑。无论是简单的条件判断,还是复杂的循环控制,掌握好这些结构能够帮助开发者设计出高效、可靠的程序。在游戏开发中,控制结构不仅仅是编程工具,它们构建了游戏机制的骨架,决定......
  • 第十一章 C++ 循环
    有的时候,可能需要多次执行同一块代码。一般情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的一般形式:......
  • 深度学习——循环神经网络(八)
    序列模型训练生成数据序列importmatplotlib_inlineimporttorchimporttorch.nnasnnimportd2l.torchasd2limportmatplotlib.pyplotaspltimportnumpyasnpT=1000time=torch.arange(1,T+1,1,dtype=torch.float32)x=torch.sin(0.01*time)......
  • 分支与循环8——goto语句与练习题2
     一、先给大家讲一下goto语句呗goto语句,goto接一个对象,就会跳到那个对象那里去,如图,执行gotoagain后,回到到红色框again:后面,继续执行pritnf,完了之后又执行goto,成为一个死循环 goto语句不要随便乱用,可以跳过多个循环,而break一次只可以跳过一个循环,如图,多个for循环嵌套,假设遇......
  • Java知识点——循环、条件语句与BigInteger类
    Java知识点一、循环结构1.for循环2.while循环3.for-each循环二、条件语句1.if-else2.switch-case三、break与continue关键字四、BigInteger类1.创建BigInteger对象2.运算一、循环结构Java提供了多种循环结构,用于多次执行某段代码,直到满足特定条件为止。循环结构......