- 博客(52)
- 收藏
- 关注
原创 常见排序算法以及实现
常规快排:在待排序区间中任取一个元素作为枢轴(pivot,或称基准值,通常选取区间首元素),然后按照基准值大小将区间中元素分割成左右两部分,左侧区间中元素小于基准值,右侧区间中元素大于等于基准值,即基准值已经放在其该放的位置上,该过程称为一次划分。它的工作原理是每次检查相邻两个元素,如果前面的元素与后面的元素满足给定的排序条件,就将相邻两个元素交换。那么,当整个序列有序的时候,每次递归就会划分出特别长的一段右区间,递归的层数是 n次,每次要遍历整个数组一遍,时间复杂度就退化成 n**2。
2025-02-25 21:58:51
1347
原创 哈希表和 unordered_set 与unordered_map
除留余数法,顾名思义,假设哈希表的大小为 M,那么通过 key 除以 M 的余数作为映射位置的下标,也就是哈希函数为:hash(key) = key % M。我们还可以发现,因为我们在第二种方法中哈希表插入元素是采用头插的,所以我们直接使用范围for遍历时,结果是和我们存的时候相反的。从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置。哈希函数的本质也是一个函数,它的作用是,你给它一个关键字,它给你这个关键字对应的存储位 置。
2025-02-24 13:53:39
1364
原创 红黑树和 set 与 map
当然,反过来也是可以的)。它是在搜索树的基础上,使每个结点上增加一个存储位表示结点的颜色,可以是 Red 或者 Black,通过对任意一条从根到叶子的路径上各个结点着色方式的限制,确保没有一条路径会比其他路径长出 2 倍,因而是一棵接近平衡的二叉搜索树。在二叉搜索树中插入新结点之后,插入路径的点中,可能存在很多平衡因子的绝对值大于 1的,此时找到距离插入结点最近的不平衡的点,以这个点为根的子树就是最小不平衡子树。若不等,如果小于根结点的关键字,则在根结点的左子树上查找,否则在根结点的右子树上查找。
2025-02-18 07:39:01
1111
原创 堆和 priority_queue
在算法竞赛中,如果是需要使用堆的题目,我们一般就直接用现成的 priority_queue,很少手写一个堆,因为这样比较省事。而在优先级队列中,元素被赋予优先级,当插入元素时,同样是在队尾,但是会根据优先级进行位置调整,优先级越高,调整后的位置越靠近队头;堆中的所有运算,比如建堆,向堆中插入元素以及删除元素等,都是基于堆中的两个核心操作实现的——向上调整算法以及向下调整算法。因此,在实现堆之前,我们先来掌握两种核心操作。(2)交换完之后,重复 1 操作,直到比儿子结点的权值都大,或者换到叶节点的位置。
2025-02-17 08:02:15
1528
2
原创 二叉树详解
第二部分的思路和宽度优先遍历差不多,通过队列来实现,通过q.size()得出每一行的宽度,这个宽度还有一个作用,就是进行n次操作从而使当前行的元素全部取出,并使下一行的元素进入队列。说白了,就是在满二叉树的基础上,在最后一层的叶子结点上,从右往左依次删除若干个结点,所有的叶子结点都在最左边,剩下的就是一棵完全二叉树。在完全二叉树以及满二叉树的性质那里,我们了解到:如果从根节点出发,按照层序遍历的顺序,由1开始编号,那么父子之间的编号是可以计算出来的。其顺序是固定的,就像我们的左手和右手, 不能颠倒混淆。
2025-02-12 08:45:27
1850
原创 树(Tree)
还需要注意的是,这两个数组开辟的大小应该是两倍,因为我们不知道父子关系时,每组边是需要存两次的。有一个点需要注意,存储树结构的时候,会把相邻的所有结点都存下来,这样在扫描子树的时候会直接扫描到上一层,这不是我们想要的结果。深度优先遍历,英文缩写为 DFS,全称是 Depth First Search,中文名是深度优先搜索,是一种用于遍历或搜索树或图的算法。其中有一个特殊的结点,相当于树的根,称为根结点,根结点没有前驱结点,如图中的A。还有一种结点叫叶子结点,它是没有后驱结点的,如图中的H,E,F,G。
2025-02-11 08:54:27
1131
原创 队列和 queue
因为在内存中的数据是有限的,所以溢出的数据都是先进先出的,这个特点就是队列这个数据结构所具有的,我们可以使用他来解决问题。因为内存查找和外存查找是区分开的,所以我们需要创建一个数组来表示,0就是外存,1就是内存。先进入队列的元素会先出队,故队列具有先进先出(First In First Out)的特性。返回队列里面的第一个元素。需要额外创建一个数组来统计队列里面各个国家的人数,方便在进出队的时候,统计国家的个数。队列也是一种访问受限的线性表,它只允许在表的一端进行插入操作,在另一端进行删除操作。
2025-02-10 16:17:02
930
原创 栈和 stack
当然需要注意一点,就是栈可能为空栈,如果为空栈还进行查找栈顶元素这样的尝试访问无效内存的操作是错误的,所以我们需要对其进行判断,随后若不配对则返回false,如果配对就将栈顶元素拿出。接着就是遍历数组了,如果是左括号就让其下标进栈,如果是右括号就看是否有配对,这又是两种情况:如果配对就根据下标在记录数组中记录,如果不配对就跳过(因为初始化时就是false,不需要处理)。本题看起来可能比较复杂,其实后缀表达式的计算是有规律的:如果是数字就进栈,如果是符号就让距离最近的两个数出栈并进行计算。
2025-02-07 12:40:21
1197
原创 链表和 list
在单链表的模拟实现中,我们可以发现虽然一个节点是由其数据域和指针域组成的,但真正代表该节点信息的是数据域和下标,指针域只是为了连接下一个节点的工具(其实就是下一个节点的下标,本质上是属于下一个节点的)。STL 里面的 list 的底层就是动态实现的双向循环链表,增删会涉及 new 和 delete,因为new 和 delete 是非常耗时的操作,效率不高,在竞赛中一般不会使用。当然第二种方式是有局限性的,一是存的数据过大会导致崩溃,二是不能存在相同的数据,否则这个数组中的部分数据会进行刷新。
2025-02-06 16:06:14
1350
原创 算法题:双指针与三指针
当cur所指向的元素为2时,就将2与right指针自减后所指向的元素进行交换,因为right指针处于2序列的最前一位,所以自减后指向的位置就是我们把2元素放入的位置。若cur指针所指向的元素是非零元素,将cur指针所指向的元素和dest指针自增后所指向的元素进行交换,因为dest指针处于非零序列的最后一位,所以自增后指向的位置就是我们把非零元素放入的位置,这样遍历完数组后,所有的非零元素都处在。我们可以发现,这里所说的双指针和三指针的方式其实都不是真正的指针,而是使用下标定位来实现指针的效果。
2025-02-05 17:00:11
655
原创 顺序表与vector
我们可以利用 C++ 中的结构体和类把我们实现的顺序表封装起来,就能简化操作。旦空间占满,新来的数据就会溢出。而且如果为了保险而申请很大的空间,数据量小的情况下,会浪费很多空间。的底层就是一个会自动扩容的顺序表,其中创建以及增删查改等等的逻辑已经实现好了,并且也完成了封装。按照需要存放的数据的数量,合理的申请大小合适的空间来存放数据。静态分配就是直接向内存申请一大块连续的区域,然后将需要存放的数组放在这一大块连续的区域。用类和结构体将代码进行封装,能够很大程度上减少重复的操作,使代码的复用率大大提升。
2025-02-04 20:54:12
1254
原创 C++:结构体和类
说到排序,我们之前讲过冒泡排序,我们也可以写一个冒泡排序函数来排序一组结构体数据,但是这里给大家介绍一个 C++ 的 STL 中的库函数 sort ,可以直接用来排序数据,只要涉及到数据的排序,又没有明确要求自己实现排序算法的时候,就可以直接使用sort函数。(1)C++的结构体会有一些默认的成员函数,比如:构造函数、析构函数等,是编译器默认生成的,如果觉得不合适,也是可以自己显示的定义这些函数,这些函数都是自动被调用,不需要手动调用。析构函数在结构体变量销毁的时候,被自动调用的。注意:析构函数不能重载。
2025-02-03 20:20:45
1172
原创 C++:函数
函数的返回类型并不影响函数的重载,因为C++编译器不会根据返回类型来区分不同的函数。采用传值调用过程中,函数传参,将实参传递给形参的时候,形参会创建新的空间,再将实参的数据给形参拷贝⼀份;但是引用传参的方式,就不存在数据的拷贝,只是在形参的部分建立引用的关系,形参就是实参。来交换两个变量,也可以交换两个数组(容器的值),我们就不需要自己来实现交换的逻辑,直接使用现成的函数。运行一下就能发现a和它的引用ra的地址是相同的,说明它们处在同一块内存空间,ra只是a的另一个名字罢了。头文件中的一个函数。
2025-01-31 21:30:28
792
原创 STM32单片机:GPIO模式
在它下面的VSS也同理,为0V,当输入电压小于0V时,电流就会流向二极管,从而起到保护作用。1.开漏输出:可输出引脚电平,开漏输出时P-MOS是关闭的,高电平时N-MOS断开,为高阻态;1.上拉输入:可读取引脚电平,内部连接上拉电阻,因为连到了VDD,所以悬空时默认高电平,最后通过TTL肖特基触发器,输出的是数字信号,是一种数字输入。2.下拉输入:可读取引脚电平,内部连接下拉电阻,因为连到了VSS,所以悬空时默认低电平,最后通过TTL肖特基触发器,输出的是数字信号,是一种数字输入。
2025-01-20 22:05:52
1024
原创 C/C++输入输出
当我们输入45时,执行到getchar语句时会读取一个字符,所以会读取'4',因为a为整型,所以输出结果就是它的ASCII码52。式输入和输出的,也就是不管在键盘上输入什么类型的数据,还是将程序中的各种类型的数据输出显示到控制台屏幕上,都是以字符流的形式处理的。所以,输出字符串的头部有两个空格。是 C++ 中提供的标准输入流对象,一般针对的是键盘,也就是从键盘上输入的字符流,使用cin 来进行数据的提取,如果在输入的时候,就是想在整数和字符之间加上空格,那么scanf中的格式串中%c的前面就要加上空格。
2025-01-17 16:47:48
1057
原创 C语言:联合体
思路其实差不多,都是在四个字节的空间中存放一个字节的数据,通过取地址获取首个字节的地址,就能判断机器字节序了。代码1输出的三个地址一模一样,代码2的输出,我们发现将i的第4个字节的内容修改为55了。我们仔细分析就可以画出,un的内存布局图(小端字节序的情况下)。使用联合体是可以节省空间的,举例:比如,我们要搞一个活动,要上线一个礼品兑换单,礼品兑换单中有三种商品:图书、杯子、衬衫。联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
2025-01-15 23:19:39
658
原创 C++:string
关于操作符的重载,只有深入学习C++的类和对象,才能深入理解和掌握,在竞赛中不需要深入挖掘,会使用就行,但是要使用C++进行工程性开发的时候这部分知识一定要补上。字符串中包含大量的方法,这些方法使得字符串的操作变得更加简单。类型的字符串中输入数据的时候,可以输入不带空格的字符串。在实际写代码的过程中经常会涉及到两个字符串比较大小,比如:判断你输入的密码是否正确,就得将输入的密码和数据库中正确的密码比较。类型的字符串比这个要复杂的多,我们可以大概这样去理解,更多的知识需要学习C++的类和对象的知识才能明白。
2025-01-10 23:18:26
3221
原创 C++:字符数组
其实C/C++中有一个库函数叫:strlen,可以求字符串的长度,其实统计的就是字符串中\0之前的字符个数。在这里说明一下它的三个参数,第一个是我们输入的数组的地址,第二个是要输入到数组中的最大字符数(包括终止符'\0'),第三个是输入流的来源,因为我们是使用键盘输入的,所以可以使用stdin用作从。[m]是一个整数,表示读取字符串的最大长度,后面的字符将被丢弃,这样就不会有数组溢出的风险了。字符数组中存放的着字符串,这个字符数组有自己的长度,也就是数组的元素个数,这个可以使用。
2025-01-07 20:27:37
1975
原创 C语言:枚举类型
一、枚举类型的声明枚举顾名思义就是一一列举。我们可以把可能的取值一一列举。比如我们现实生活中:星期一到星期日是有限的7天,可以一一列举 ;性别有:男、女、保密,也可以一一列举 ;月份有12个月,也可以一一列举 。这些数据的表示就可以使用枚举了。enum Day//星期{ Mon, Tues, Wed, Thur, Fri, Sat, Sun};enum Sex//性别{ MALE, FEMALE, SECRET};enum Color//颜⾊{ RED, G
2025-01-06 23:43:34
1095
原创 C++:范围for
是对数组中所有元素进行遍历的,但在平时使用中,可能只需要遍历数组中指定个数的元素,这样范围 for。使用VS进行调试我们就能发现编译器已经自动推导出a,b,c这三个变量的类型。关键字,当你不知道数组中放什么类型的时候,可以使用auto 作为类型,这种类型的for循环使得遍历数组、容器中的元素更加简便和直观。如果明确的知道数组元素的数据类型,也可以将。是一个单独的变量,不是数组的元素,所以对。是在 C++11 这个标准中引入的,,直到遍历完整个数组的元素。数组中的元素,依次放在。的修改,不会影响数组。
2025-01-05 22:36:37
557
原创 C语言:结构体
是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,编译器是不认识这个类型的,这是不行的。下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。结构体变量的初始化有二种。我们可以通过反证法得出这是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。
2025-01-04 23:24:14
1064
1
原创 C语言:数据在内存中的存储
那我们再来看c的结果是怎么出来的呢,-1赋值给了c,而整数(字符是一种特殊的整型)在内存中是一补码来存储的,也就是下图的11111111,就是结果中的255,%d是以整型进行打印,再进行整型提升时也不会改变数值大小的,a和b在这一步也是同理。char的范围是-128到127,所以这里的赋值操作中类型转换,由unsigned char转为char,所以a的补码就为128的补码——10000000,因为本身是有符号的,所以在整型提升时前面都加符号位1,因为以无符号整数的形式打印,所以答案就是整型提升后的结果。
2025-01-03 11:24:22
835
原创 数组创建的注意事项
在数组创建时还有个注意的点,因为不同的题目可能会有不一样的需求,有时候空间超限就会出错,所以数组空间的开辟要足够,以免数据越界,所以经常题目需要存放n个数据,我们就开辟n+10个空间,这样空间就非常充足,比较保险。这道题在洛谷这个平台中的测试结果虽然是AC(不同的编译器可能会有不同的结果,不同的平台也可能会有不同的结果),但是当我们把元素量巨大的数组定义为局部变量确实是存在某些问题的,题目中的序列下标从0开始,很容易想到使用数组去存储这n个元素,顺着题目的思路,我们就能写出下面的代码。
2025-01-02 18:04:36
343
原创 C语言:内存函数
memcpy函数其实我们需要关注的就是重叠的情况,我们不难发现重叠时,当源内存块的末端在目标内存块中时(首段在目标内存块的前面),我们只要将源内存块从后往前复制,就可以规避重叠所带来的问题;和strcpy不同的是,这个函数不仅仅是用于操作字符串的,所以我们需要使用许多void指针来进行处理,又因为数据是以一个字节为单位进行操作的,所以我们在拷贝过程中使用了char*指针来处理(char类型在内存中占一个字节)。和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2024-12-24 21:40:41
547
1
原创 C语言:字符函数和字符串函数
这个头文件中说明的,C语言程序启动的时候就会使用一个全局的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码,存放在errno中,而一个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提前发现不一样,就提前结束,大的字符所在的字符串大于另外⼀个。结尾,返回一个指向这个标记的指针。
2024-12-23 18:18:08
1002
1
原创 C++入门(2)
定义常量的时候是不关注类型的,只关注常量的名字叫啥,常量的值是啥,编译在处理这种常量的时候就是直接替换,在出现常量名字的地方,全部替换成常量的内容。在使用 C/C++ 写代码的过程中,不同类型的数据进行混合计算的时候,或者赋值时等号两边的类型不统⼀的时候,都会发生类型转换,这时就需要根据类型的转换规则转换成合适的类型。那我们换一种方式写,在调试的时候,赋值的细节就可以很方便的观察了。(2)增加了程序的可维护性,如果改变常量的值,只要在定义的部分修改,使用的地方也就随之改变了,做到了"一改全改"的效果。
2024-12-15 22:08:01
1190
原创 C++入门(1)
前面的知识已经让我们了解到了很多的数据类型,不同的数据类型所创建的变量的长度是有差异的,这个长度差异又决定了这种变量中能存储的值的大小。这里选取一个进行说明,我们知道int所占内存为四个字节,也就是32个比特,每个比特位存着0或1,所以int的取值范围为2^32,平分正负数就是下面的结果,而unsigned int因为不包括负数,所以是从0开始。当有一个类型比较复杂的时候,可以简化类型。每⼀种数据类型都有自己的长度,使用不同的数据类型,能够创建出长度不同的变量,变量长度的不同,存储的数据范围就有所差异。
2024-12-14 22:08:28
1451
2
原创 C语言:指针详解续
p是变量名,*表示这个变量是指针,前面的void是函数的返回值,后面加的是函数的参数。当我们使用char数组存放字符串时,就会为数组分配独立的内存空间来存放字符串,所以str1和str2都指的是这两个数组的首字符的地址,即使字符串相同,所对应的地址也不相同。字符串所在的内存位置,所以地址是相同的。(*)(),知道这是一个函数指针,参数为空,返回值为void,后面还跟着一个0,那我们就可以知道,这里是将0强制类型转换了,将它变成了函数指针类型,然后将整体解引用,后面再加上空参,就是在调用函数。
2024-12-11 23:30:13
1159
原创 C语言:指针与数组
在一维数组传参中我们传的其实是这个数组的首元素的地址,所以在test()中得到的是arr这个数组中第一个元素的地址,在x64环境下它就占八个字节。而在它后面的arr[0]就像前面说的一样,相当于*arr,也就是arr数组的首元素的值,因为它的类型是int,所以占四个字节。因为数组元素的地址是递增的,所以随着这个地址的增加,出现的地址就变成了数组中其他元素的地址了,也就是arr+i相当于&arr[i]。&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素。
2024-12-03 23:07:45
1767
4
原创 python:类的创建和继承
如下面的代码,我们在__init__添加了cat_name的变量,所以我们可以通过输入定义它的属性,在这里就是在创建cat1这个对象时,为它的属性赋值,也就是添加了"hello"这个名字属性,所以当我们打印它名字属性是出来的就是"hello"。我们来验证一下,我们在原有的基础上添加了一个cat1的对象,这就是我们创建出的类的实例。我们需要尽量少写重复代码,所以对于一些拥有部分相同属性或方法的类进行特殊地处理,就是我们接下来要讲的在面对对象中很重要的一个特征——继承,意思是可以创建有层次的类。
2024-12-01 15:09:43
841
原创 python:文件操作
相对路径是从一个参照位置出发,我们把参照位置的路径用.来表示,用..来表示更高一级的父目录,再上就用..\..来表示父目录的父目录。但是在我们写入文件时并不会,如果路径不存在,会直接创建该路径下的一个文件,并写入。那我们将"w"改为"a"之后再运行一遍,我们会发现文件中有两个一样的字符串,一个就是我们刚才write方法写入的,另一个是我们刚刚通过添加写入的。当我们第一次运行这个程序时,因为该文件内容为空,所以打印空字符,接下来才执行写入,只要在第二次再执行时,才可以打印出文本中的内容。
2024-11-30 21:21:34
1121
原创 C语言:回型矩阵
这段代码实现了在一个n行m列的矩阵中,按照顺时针方向螺旋填充数字的功能。每次输入一对n和m值(分别代表矩阵的行数和列数),程序就会生成并打印出一个按照上述规则填充的矩阵。这里使用了固定的9x9大小,但实际上只使用了前n行和前m列。4. 通过循环结构按顶部行、右侧列、底部行和左侧列的顺序依次填充矩阵中的数字,每填充完一边就更新相应的边界变量。3. 使用四个变量top, bottom, left,和 right来跟踪当前要填充的矩阵边界。1. 从标准输入读取两个整数n和m,分别代表矩阵的行数和列数。
2024-11-25 15:06:05
494
原创 C语言:深入理解指针
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是 8GB/16GB/32GB 等,那这些内存空间如何高效的管理呢?在上面的代码中,int *是该指针变量的类型,int表示的是该指针指向的对象是整型类型的,而*表示这个变量为指针变量。上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n进行修改,就不符合语法规则,就报错,致使没法直接修改n。
2024-11-24 23:33:01
2342
原创 C语言:操作符详解
在方法一中每次采取将n除于2(其实在前面讲过了,相当于将数字算术右移一位),这样每次判断最后一位是不是1,直到n变为0,也就是二进制中的1全部游历完之后,从而实现1的计数。这是怎么实现的呢,当这一位数为二进制中最后一个1时,它后面一位肯定为0,这两位就为10,将这个数减1,前面的数不变,这两位就为01,后面虽然也为1,但是因为n的后面位全是0,在按位与之后后面还都是0,所以我们只要看这两位就可以了,n为10,n-1为01,按位与之后就变成了,其实就是上文所说的,将二进制中最后一个1改为0。
2024-11-21 18:21:09
1040
原创 C语言:函数递归
不仅如此,在C语言中每一次函数调用,都需要为本次函数调用内存的栈区,申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。递归分成递推和回归。在这段代码中,当我们输入n为3时,Fact(3)函数返回时需要得到Fact(2)的值,再不断推到Fact(1)的值,这就是递归中的递推。
2024-11-14 15:57:23
438
1
原创 python第二天笔记
s.remove(2) #[1, 3, 4, 2]删除了第一个出现的符合条件的元素。s.insert(0,6) #前一位表示下标,后一位表示插入的元素。print(ord('A')) #将字符转化成ASCII编码。print(chr(65)) #将ASCII编码转化成字符。print('hello'[1]) #索引。print(min(s)) #返回最小值。print(max(s)) #返回最大值。a=[1,2,3,4,5] #创建列表。a=(1,2,3,4) #创建元组。
2024-11-12 23:25:28
519
原创 python第一天笔记
s="-".join(["one","two","three"]) 引号中是什么字符字符间的连接就是什么字符。#如果字符串中有单引号可以使用两个双引号,有双引号可以使用两个单引号,如果都有可以使用转义字符。#print(s.swapcase()) 把所有字母翻转(大写变小写,小写变大写)#返回值:返回一个左对齐的字符串,如果指定的宽度小于原字符串的长度,则返回原字符串。#在字符串操作中+指的是将字符串连接在一起,*指的是将字符串重复若干次。#print(s.upper()) 把所有字母变成大写。
2024-11-11 22:33:27
646
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人