自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(92)
  • 收藏
  • 关注

原创 应用层自定义协议与序列化

2.前人栽树,我们乘凉,用现有的方案,比如xml、json、protobuf等,为了增加可读性,我们选择json。如果我们规定,在发送时按照一定规则把结构体转换成字符串,然后收到字符串时再按照相应的规则转换回去,即可保证信息的准确性,这个过程就为序列化。在了解UDP和TCP的代码后,我们发现,我们想传输数据,都是按照字符串的形式发送,然后根据识别字符串得到相应信息,但如果我们想传输结构化的数据该怎么办呢?因此我们需要做一下改进。tcp和网络部分我们已经掌握,关键在于定好协议与加入序列化和反序列化的方案。

2025-08-06 14:39:56 202

原创 手搓TCP服务器实现基础IO

在有了UDP服务器的基础,搓一个TCP服务器就简单很多了,套接字、绑定,结构体协议家族都是套路,我们主要看二者的区别在代码上的体现。其中TCP是有连接的,也就是说当有客户端访问服务器想发送数据包时,需先和服务器进行连接,然后进行相关操作,而UDP的无连接则不需要,直接向服务器发送数据即可。第一个参数是我们负责“揽客”的套接字(最初绑定的),就相当于饭店外面揽客的人,他们并不在里面吃饭,但是会告诉别人让他们来吃饭,因此这个描述符不做IO工作,只是单纯的“揽客”。后两个是输出型参数,代表哪个客户端来访问了。

2025-08-04 18:45:35 658

原创 教你手搓UDP服务器与服务器可视化对话

但是,我们在给服务器发信息时,并不只是把该信息发送给对方,更是让它知道是谁发的这条信息,因此我们的报头中一定也有我们的IP和端口号,前面说过,如果要发送到网络,统一大端,因此我们就可以用之前讲过的接口htons。我们发现,sendto的第一个参数也是sockfd,而我们一开始创建的sockfd恰好能作为参数,也就是它既可以发也可以收(全双工),其他参数和上面的recvfrom大致相同,后两个参数就用到了我们刚创建的输出型参数(目标主机),刚才记录的就是消息的发出者。接口直接在使用中展示。

2025-08-02 13:41:41 855

原创 网络编程基础——套接字socket

我们将数据传到给主机并不是目的,而是手段,而把数据传输到主机中的各个进程才是目的,只有进程获取数据,我们才能通过进程来查看数据,比如聊天、浏览器等。我们一定是启动了相应的进程才能获取对应的信息。在套接字编程中,socket有许多种类,包括网络socket、本地socket等,不同种类有不同的接口(即使是相同的功能),于是OS提供系统调用,把socket的接口统一了。我们统一传的都是sockaddr类型,到时候强转一下就好了,至于如何区分,我们可以看到上面的AF_INET AF_UNIX,即宏标志位。

2025-08-01 01:06:44 401

原创 网络概念基础

主机a想把信息发送给主机b,但他需要把信息先交给路由器,然后路由器把信息交给b(但在这个过程中,所有主机是知道这个消息的传播的但都知道这个消息只属于b)但是,一个路由器可以连好几个主机,路由器拿到信息后怎么知道该交给谁呢?假设我们要给另一台遥远的主机发信息,那么会经过几个路由器,在这个过程中,我们有一个路径(地址)不变:源IP地址->目的IP地址,同时还有另一条地址:源mac->目的mac,但这个地址,每过一个路由器就会发生变化,源mac->路由器1mac,路由器1mac->路由器2mac....。

2025-07-30 14:17:23 420

原创 链表算法综合——重排链表

涉及到双指针找链表中点,头插法逆转链表的基本链表操作,具体将在代码中演示,这里我们选择不把slow指针分割成后半进行逆序而是放在前半,因为最终结果slow和前一个的位置一直不变(如果放在后也可以)此题意就是先把链表一分为二,然后把后半链表逆序并交替插入前半链表中。

2025-07-28 00:03:35 299

原创 链表算法——两两交换链表中的节点

细节三,交换后把四个指针向后的移动,依然是非定义问题,如果交换后后面无节点(偶数个),那么cur已经就是nullptr了,无法为cur->next定义。如果是奇数个节点,那么在最后一次交换后,四个指针向后移动,next、nnext为空,如果是偶数,cur、next、nnext均为空,因此判断条件是,cur、next均不为空(交换后移动前cur、next均不为空)为了不丢节点,我们把每个节点都进行定义,我们发现,每一次交换时,都需要改动4个节点,这样我们就可以顺序任意的来改变指向了。

2025-07-27 19:18:30 325

原创 链表算法常用技巧与操作

先让cur和后面的节点连,且连接的过程中都以prev(第一个节点)为视角,不然可能会在某个方向丢失第二个节点。第一步prev->next->prev(第二个节点的prev)=cur,第二步cur->next=prev->next(cur和第二个结点已经完成双向连接)。new一个虚拟头结点newhead,先让新结点->next=newhead->next,然后newhead->next=新节点。用此方法逆序链表就非常简单了,创建一个只有newhead的空链表然后把原链表的节点从头到尾依次头插在新链表中。

2025-07-27 17:28:07 170

原创 从分治的思想下优化快速排序算法

快速排序是对于数组元素进行排序的一种方法,基本原理是:在数组中随任意选择一个元素作为参考(key),使得数组分为两部分,左部分小于等于key右部分大于key,这样经过一次排序后key的值就会在他最终正确的位置,接下来就是让左右两个子数组继续重复上述操作,知道把数组分成1或2个元素(递归)。但这种方法如果遇到数组中有重复元素会增加时间复杂度,如果数组都是同一个元素,那么每次进行排序后,key都会到最右端,这样时间复杂度就是n平方了。因此,我们需要优化一下,用三指针把数组分成三部分再进行递归。

2025-07-25 16:56:18 133

原创 分治思想算法——颜色分类

如果是0,直接让left的下一个与nums[i]交换即可,然后i++,left++。如果是2,先让right-1和num[i]交换然后right--,此时i不能++,因为交换后i对应的元素仍是未遍历的(一直在第三个区间)所以还需要再遍历一次。使得[0,left]都是0,[left+1,i-1]都是1,[i,right-1]待遍历元素,[right,n-1]都是2。我们采用分治的思想把数组分割,也就是用“指针”——三指针。接下来我们看一下元素如何交换,一共就是三种情况,nums[i]是0,1,2.

2025-07-25 14:39:47 144

原创 位图——求两数之和

因此,我们可以用a&b找到两数要进位的位置,但进位并不是在当前位,而是在进位的前一位。然后我们再通过a^b得到两数的无进位相加的位置。把a^b看成a,(a&b)<<1看成b。直到a&b==0完毕(无进位了,a^b就是两数之和)。我们在讲^运算时有一个记法是无进位相加,也就是如果两个都是1,那么1+1要进位此为变成0,但是我们不做真正的进位(前一位不改动),然后0和1的位数相加为1(自然无进位不做处理)。因此我们发现,只有二者都为1的情况下该位才进位,否则都不处理(有点类似于&的运算律)

2025-05-24 14:36:34 150

原创 位图算法——判断唯一字符

我们可以创建一个整型变量(32位)而一共才26个字母,所以我们只要用到0-25位即可,0代表没有,1代表有,先判断该位是否为1,为1直接返回false,为0把该为标记为1进行下一次判断。这道题有多种解法,可以创建hash数组建立映射关系判断,但不用新的数据结构会加分,因此我们有“加分”办法——用位图。可优化的细节:如果字符串长度>26直接不符(鸽巢原理)

2025-05-22 21:48:44 155

原创 位运算及其算法

计算机中的所有数在内存中都是以二进制形式进行存储的 ,位运算就是直接对整数二进制位进行操作,有些时候在程序中使用位运算进行操作,会得到极高的便利性。我们以int整型为例,每个int占4个字节32个bit位。对于无符号整数,这32位的0、1都可以用来表示数字。而对于有符号整数,其最高位的bit位代表着整数的正负,0为正,1为负。,范围自然就小很多。无符号整数可表示的范围(32位):0~2的32次方-1(4,294,967,295)

2025-05-22 21:30:09 704

原创 前缀和——和为K的子数组

如果我们正着看,就是0位置从前向后遍历,然后0开始的遍历完再从以1位置为开始的遍历,在我们本题中的遍历方法,无非就是把i作为了结尾,然后从右向左,也可以实现枚举所有子数组。显然,暴力我们是不可取的,但这里我们可以采取一种新的遍历数组形式,从后向前,也就是以i位置为结尾的所有子数组,这个子数组只统计i位置之前的。然后,问题就转换成了,我们要找到以i位置为结尾的所有子数组,且在[0,i-1]内,有多少个前缀和为sum[i]-k,因此,如果上面的成立,一定在此之前就已经被统计过了。

2025-05-19 20:57:21 358

原创 前缀和——中心数组下标

定义两个数组f,g。f[i]表示[0,i-1]所有元素的和 f[i]=f[i-1]+nums[i-1];g[i]表示[i+1,n-1]的和。此题我们不应局限于前缀和的模板,因为该中心下标把数组分为两个部分且每个部分都要求和,我们就一个再创建一个”后缀和”g[i]=g[i+1]+nums[i+1];因为依靠关系,f要从左到右,g要从右到左。注意题干中的边界条件,0和n-1位置出的左、右是0,因此不要越界。

2025-05-18 22:31:06 189

原创 “二维前缀和”算法原理及模板

本文介绍了二维前缀和的概念及其应用。

2025-05-18 19:49:15 317

原创 ”一维前缀和“算法原理及模板

通过这个公式我们就可以说明为什么下标需要从1开始了,如果l为0,也就是想求从最左到r,那么公式里就是dp[r]-dp[-1]。现在的问题就是:如果我们用暴力枚举来记录每次l与r之间的和,那么肯定是会超时的(时间复杂度O(N*q)),我们要另辟蹊径。注意,我们这里的arr和dp中的i都是以1开始记录而不是0,稍后我们解释一下原因,我们先把arr[0]和dp[0]都看成0。这个dp数组的每一个元素dp[i]记录着arr[i]及之前的元素之和。假设我们要求l-r的和,只需要用dp[r]-dp[l-1]即可。

2025-05-16 18:26:20 368

原创 经典算法“二分查找”你能写明白吗

如果要查找数组中间的元素——直接朴素二分如果要查找左右端点,首先判断条件必须是<。如果是左端点,要分成小于和大于等于的区间,然后left=mid+1或right=mid。中点选取采用“不加1”形式如果是右端点,要分成小于等于和大于的区间,然后left=mid或right=mid-1。中点选取采用“加1”形式。

2025-05-15 19:29:38 761

原创 滑动窗口——字符串中所有异位词

我们定义一个变量count来统计滑动窗口中有效字符的个数,以p为例,abc分别只出现一次,那么s用双指针存放时,先判断一下,如果存放的是有效字符且此时该有效字符的存放次数小于等于p中该字符的个数就让count++,这是进窗口的操作。把p的字符串存在hash1中(记录p的长度),把s存放在hash2中,用双指针进行存放,当存放长度≤p.size()就进行比较(hash1,hash2)。此方法可行,但我们发现,每次进行二者比较时,都需要遍历26次才能完成一次比较,我们可以把比较方法进行优化。

2025-05-14 10:20:21 207

原创 滑动窗口——水果成篮

我们需要借助hash表来记录不同数字出现的种类以及每个数字出现的次数。然后先让right数字放hash,判断,如果种类小于等于2,重复上述操作,如果大于2,left++,并在hash中删去对应次数。直到遍历完全(right<size)。根据题意我们转化一下,在数组中求一个最长的子数组,数组中的数字种类不超过2种。借助暴力思想,我们定义双指针进行进窗口、判断、出窗口、更新结果的流程。

2025-05-13 23:28:24 203

原创 滑动窗口——将x减到0的最小操作数

这个题如果我们直接去思考方法是很困难的,因为我们不知道下一步是在数组的左还是右操作才能使其最小。正难则反,思考一下,无论是怎么样的,最终这个数组都会分成三个部分左中右,而左右的组合就是我们进行的所有操作(极端情况下左或右长度为0),但不管怎样,都分成了连续的几块。定义同向双指针,然后“进窗口”,判断(sum是否>target,此处的sum是滑动窗口内之和,target是上面的sum-x),如果满足条件,则出窗口,然后更新结果,判断此时的sum是否=target,符合条件,记录长度,然后一次次取最大值。

2025-05-11 23:41:31 215

原创 滑动窗口——无重复字符最长的字串

但这个题我们有更优化的方法,利用数组代替hash(重点不在这!我们用滑动窗口的原理(同向双指针),先让left和right指向头,然后判断right所对应的数组下标是否为1(利用标记来达到桶的效果初始全为0),如果为0则标记为1,right++;如果为大于1,先更新结果,left++,然后right++,这里right不重新回到left再遍历是我们已经能证明二者之间一定无重复字符了。直到right走到尾。题意不难理解,这个题我们暴力枚举的思路是把每一个字符遍历存到hash桶中,如果放两次就进行结果更新。

2025-05-08 00:06:03 561

原创 滑动窗口——长度最小子数组

二者之间的区域我们就可以看成窗口在之间滑动,left每移动一次(符合条件的子数组),就记录一下当前的窗口长度。我们发现,滑动窗口对于此题就相当于帮我们省去没必要的遍历,就以上图为例,此时sum=8,那么我就没必要枚举让right右移的所有子数组了(枚举的话虽然也一定符合target但是长度不是最短)。根据滑动窗口,我们来说一下思路:先定义两个指针指向头,然后left不变,每次对right及之前的数字求和然后right++(求得left和right之间的和)。

2025-05-07 17:05:23 218

原创 双指针——四数之和

不同的是,我们之前是定一个数,现在需要定两个数(两层for)然后思路不变。且之前不需要我们传参(因为指定和为0),现在指定和需要我们自己决定。我们可以用target-nums[i]-nums[j]作为一个整体,然后就又变成了三数之和。这个题我们可以套用“三数之和”的原理。

2025-05-06 09:07:20 216

原创 双指针——三数之和

至于如何去重,我们可以在指针移动前加一条判断,看移动后的数是否和移动前的数相同,若相同就跳过。(固定的数也需要去重)走两步就要注意越界的问题。这里和有效三角形不同的是,我们固定一个数时,找到一组数不要停,继续缩短区间找下一组,直到两指针相遇。这里有个小优化:如果a>0则直接跳出循环(在a的右侧都是大于0的数,一定找不到符合的组合)先把数组排序,然后固定一个数字a,在该数后面的区间内,利用双指针找到两数之和为a。题目中有一个重点:不能包含重复三元组,解决这个问题最好的方式——去重!

2025-05-06 08:29:43 210

原创 双指针(6)——和为s的两个数字

相加大于s:说明二者之间任一数与right组合都不满足,right--相加小于s:说明二者之间任一数与left组合都不满足,left++

2025-05-04 01:06:04 207

原创 双指针(5)——有效三角形个数

我们先计算arr[left]+arr[right](1+9=10,不构成),如果满足a+b≤c,那么left和right的中间任何一个数和left组合都不能满足三角形,所以此时我们就可以把所有数与left的组合都忽略(不用计算了)即,让left++,这是一次流程。反之如果大于c,那么此次流程中的所有数与right结合都满足要求,则有效三角形个数为right-left,然后让right--进行下一次流程,直到left与right相遇。这道题我们首先可能会想到暴力解法,三个for循环然后进行check()。

2025-05-02 20:30:07 369

原创 Linux——线程(3)线程同步

条件等待是线程间同步的⼀种手段,如果只有⼀个线程,条件不满足,⼀直等下去都不会满足, 所以必须要有⼀个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好 的通知等待在条件变量上的线程。通过上面的抢票系统我们发现,有的线程,进行工作(挂锁),当其马上结束工作(解锁),发现外面有很多线程在排队等着加锁执行任务,这个线程解锁后就立马给自己加锁(可以想象成自己能直到解锁的时间,所以加锁的优势比其他线程大),但是如果这个线程本身并没有做什么工作,就会使工作效率低下,线程饥饿等问题。

2025-05-01 17:30:32 828

原创 双指针(4)——盛水最多的容器

这里首尾比较去小的原理是,在某数组中,我们能确定在该容器宽度变化时,数组的高不变,去宽度最大作为该组的最大值,然后缩短数组(去小)继续重复此操作。那么我们就可以认为,在这几个数遍历到4时,从左到右是越来越小的。也就是我们就没必要再去遍历4(把4删除),因为我们是要求储水量最大值,接着在(6,2,5)之间遍历。如果我们此时让“4”去向内遍历(只看每一个数和4遍历的比较),根据木桶原理,这个容器的高不变,宽度会越来越小,即蓄水量越来越少。这题可以暴力枚举,但会超时,所以我们要用其他方法。

2025-04-30 21:49:03 237

原创 双指针(3)——快乐数

定义两个指针,让后面的指针在数组中遍历,前面的指针先不动,符合题目要求时,让两个指针的数据进行交互,直到后面的指针走到数组结尾。但这里并不是链表,也没有指针。我们并不一定要使用指针,只需要每次把一个数看成指针即可。我们看似是两种情况,实质上是一种情况,都会进入无限循环,只是其中一个循环一直是1。这和判断链表是否有环类似,这里我们使用的方法是:快慢指针法。

2025-04-28 10:27:20 165

原创 Linux——线程(2)线程互斥(锁)

这就需要我们最上面的概念——原子性,在ticket--的过程中一共有三步:把ticket从内存放在CPU,CPU进行--,再放回内存,这是对于每一个线程的过程,但是在这个过程中,如果我们把数据放进CPU那么这个数据就对于线程是私有的了(本身的ticket是全局变量所以共享资源),他们完成第一步时,这时线程突然切换了,结果到下一个线程本来应该拿到比上个线程少一的ticket,但因为上一个还没有--所以拿到的值是一样的,然后当他们都进行--,就会出现为负数的原因。线程在申请锁的时候,其他线程要进行阻塞等待!

2025-04-27 21:18:26 867

原创 双指针算法(2)——复写零

定义两个指针,让后面的指针在数组中遍历,前面的指针先不动,符合题目要求时,让两个指针的数据进行交互,直到后面的指针走到数组结尾。解决方法:我们发现出现这种错误的cur最后指向的一定是0,所以我们先让n-1等于0,然后,dest-=2,cur--。这个题,如果我们用两个数组下标去标记,一个遍历,一个交互(赋值),就会造成下面的结果。其实,从前向后不行,从后向前可以,我们通过结果直到,数组的最后一个是4.第一次复写时,发现数据已经被覆盖掉了,所以不可取。但也有特殊情况比如:[1,0,2,3,0,4]。

2025-04-27 21:15:50 272

原创 双指针算法(1)——移动零

交互步骤:首先让dest处于-1的位置,让cur向后遍历,当cur指向0,cur++,直到cur走到非0,交换dest+1与cur,然后dest++,cur++。定义两个指针,让后面的指针在数组中遍历,前面的指针先不动,符合题目要求时,让两个指针的数据进行交互,直到后面的指针走到数组结尾。我们要让“两个指针”把数组进行分块。(注:形成[0,dest]的原因我们可以想一下数组全非0的时候,那么就是每次自己和自己交换直到走到最后)我们需要让第一个分块全部为非0,第二个分块全部为0,第三个分块是未遍历。

2025-04-27 10:42:22 604

原创 Linux——线程(1)线程概念与控制

上面提到过,这并不是系统调用而是一个库,是用户级别的线程库。参数 :thread: 返回线程 ID (输出型)attr: 设置线程的属性, attr 为 NULL 表示使用默认属性start_routine: 是个函数地址,线程启动后要执行的函数arg: 传给线程启动函数的参数arg参数可以是任意类型(变量,数字,对象等)成功返回0,失败返回错误码。

2025-04-25 10:21:38 924

原创 Linux——信号(2)信号保存与捕捉

就以键盘为例,当我们的按键按下了,键盘的外设就会发起中断,实际上我们的外设并没有直接连接CPU,而是连接到了一个叫中断控制器的东西,当收到中断时,它就会知道是哪个设备发出并知道中断号,然后控制器通知CPU发生中断,此时CPU就可以获得对应的中断号。上次我们提到,PCB中有一个记录信号的表(位图),每一个比特位记录着对应的信号,这个表叫pending,除此之外还有两个表handler和block,handler就是记录每一个信号的递达方法,是一个函数指针数组,每一个下标代表着每一个信号的处理方式。

2025-04-21 22:30:05 711

原创 Linux——信号(1)信号的产生

在生活中,我们也会有很多信号,比如下课铃响了我们知道下课了,绿灯来了我们知道该走了,也就是说,它们在向我们发出某种消息,使得我们去做一些事情,在操作系统中也类似,信号就是其他进程,向目标进程发送异步事件的一种方式。所谓异步,就是指目标进程自己也不知道这个信号什么时候来,突然、随时的情况。二、关于信号你需要知道的一些知识1.我们如何识别信号呢?识别信号是内置的。进程认识信号,是程序内置的特性。就相当于我们从小就被告知红灯停绿灯行2.信号产生后该如何处理知道吗?如果没有产生,信号又该如何处理知道吗?

2025-04-17 23:11:32 773

原创 Linux——进程通信

我们知道,进程具有独立性,各进程之间互不干扰,但我们为什么还要让其联系,建立通信呢?比如:数据传输,资源共享,通知某个事件,或控制某个进程。因此,让进程间建立通信是必不可少的。但如何通信呢?根据进程的独立性,我们需要保证,让不同的进程看到同一份资源!但这个资源若是进程a建立的则b看不到,b建立的a看不到,因此,这份资源必须由操作系统提供!

2025-04-16 21:53:39 676

原创 Linux——文件(3)软硬连接和动静态库

也就是说,如果实际的偏移量是0,那么这些就代表着真正的物理地址,如果是a,那么真正的物理地址就是每一个地址加上偏移量a。我们在进程地址空间部分提到过进程启动时建立PCB与内存关系的映射过程,其中,动态库也是可以映射的,其具体机制为,先从磁盘中获取动态库到内存,然后通过页表把动态库映射到虚拟地址空间的共享区部分,然后我们的task_struct就可以找到对应的库了。在CPU的结构中,有一个叫CR3的寄存器,其记录的是物理地址,它拿到这个物理地址,就会去页表找对应的虚拟地址,进而找到对应的代码并执行!

2025-04-10 00:30:51 518

原创 老师们一笔带过的那些“痛“——缓冲区

我们的计算机想处理数据的信息,必须要交给CPU,而我们的键盘、显示器都属于外设,外设的运行速度相比于CPU是要慢很多很多的,如果我们每一次输入都会让外设和CPU交互的话,那么整体运算速度肯定是接近于外设的,为了提高效率,我们既要完成最初的输入输出操作,同时也要减少交互的次数,即我们在内存留出一块空间,先接收一定量的外设带来的数据,等到收到“信号”再一次性交给CPU。这就是行缓冲的原理。在我们的使用中,我们好像觉得,这个打印函数好像是运行起来就打印了啊,好像看不见缓冲区在其中干了什么,我们看下面的代码。

2025-04-03 20:17:38 965

原创 Linux——文件(2)文件系统

但是,当我们想知道一个文件名时,我需要知道它在哪个目录的数据块下,可是目录也有名字,我们也需要知道目录的名字,一直递归下去,就是路径的逆向解析,知道找到根目录,因为根目录是写死的,我们也就知道文件的具体位置了,所以我们讲,文件必须要有路径。盘片上的每一个扇区看似没有什么存储规律,但我们可以类比于磁带,磁带上面的长条就记录着数据,我们就可以把数据看成线性结构,而磁盘上的数据,我们就可以视为是磁带绕中心一圈圈卷起来的盘,如果把其拉长也可以看成线性结构存储,也就是数组,这样我们就可以通过数组下标定位数据扇区了。

2025-04-03 15:30:01 525

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除