每天进步一点点2023.3.25

文章详细介绍了TCP协议中的拥塞控制机制,包括慢启动阶段的指数增长和拥塞避免阶段的线性增长,并讨论了超时重传和三次重复ACK的区别。此外,还提到了Epoll的LT和ET工作模式以及其效率差异。最后,文章给出了力扣每日一题的解决方案,涉及双指针技巧和滑动窗口最大值的优先队列解法。
摘要由CSDN通过智能技术生成

每天进步一点点2023.3.25

拥塞控制

慢启动阶段

慢启动阶段:最初拥塞窗口大小为1,表明可以发送一个MSS-最大报文段长度;当成功收到一个ACK确认时,拥塞窗口就会加一;为什么慢启动是指数增长的呢,模拟一下过程:
最开始,拥塞窗口=1,发送一个MSS,发送方接收到一个ACK,那么这个时候拥塞窗口+1=2;
此时,拥塞窗口=2,可以发送2个MSS,发送方接收到2个ACK,这个时候拥塞窗口+2=4;

这样的话我就理解了慢启动算法里拥塞窗口是指数增长的了。
按照这样的增长,拥塞窗口会增长到一个慢启动阈值,这个时候就会进入拥塞避免了。

拥塞避免

这个阶段也是拥塞窗口逐渐增长的一个阶段,每一个RTT(往返时延)轮次,拥塞窗口就会加一,这个轮次就是说发送8个MSS,接收8个ACK的前后时间。同样,也可以是用ACK来看。

拥塞发生

上面拥塞避免也不是无限能增加拥塞窗口的,因为一直加拥塞窗口,最后一定会出现问题,第一个是超时重传,第二个是三次重复ACK。这两个我一直没搞懂区分是什么,看了以往上课的资料。
超时重传是说,发送方在TCP连接过程中,一直是有一个RTO定时器(这个RTO定时器也是和RTT(往返时延)有点关系的),当定时器时间到了,都没有接收到这个阶段界对应MSS的ACK,那么就会触发重传。这个的意义是全网络堵塞。
三次重复ACK,发送方发送了多个段,但是接收端可能没有接收到其中的某一个片段,其他片段接收方收到了,缺失这一个片段的时候,接收方发送的后面几次ACK,都会显示那个片段没有收到,这样就收到了重复的ACK,当发送方接收到三次这样的重复ACK,发送方会重新发送这个片段。这个时候表明网络并不是完全堵塞的,所以是半堵塞。
当发生了上述两种情况之后发送方这边的拥塞窗口会对应做出什么样的措施呢?

  1. 超时重传发生后,表明情况比较严重了,此时的拥塞窗口会重新置为1,那么会重新进入慢启动阶段,同时将慢启动阈值设置为之前拥塞窗口的一半值;
  2. 三次重复ACK之后呢,没那么严重,慢启动阈值也是会被设置为此时拥塞窗口的一半,同时拥塞窗口的值也会减半,这样的话,会重新进入拥塞避免的阶段。

Epoll

epoll的描述符操作模式有两种,LT和ET模式,LT是默认的工作模式,ET是epoll的高效模式。LT模式:某个事件触发,那么epoll_wait会一直通知有事件,直到这个事件被处理;ET模式就是只通知一次,所以这次触发的时候一定要被处理,否则epoll_wait不会再通知这一事件。与此相比,ET模式在触发次数上要少于LT,因此效率要搞一点。
同时,ET模式要设置非阻塞,因为一旦阻塞的话,读或写操作将会因为没有后续事件而一直处于阻塞状态。

力扣

每日一题:

  1. 删除最短的子数组使剩余数组有序
    给你一个整数数组 arr ,请你删除一个子数组(可以为空),使得 arr 中剩下的元素是 非递减 的。
    一个子数组指的是原数组中连续的一个子序列。
    请你返回满足题目要求的最短子数组的长度。
示例 1:

输入:arr = [1,2,3,10,4,2,3,5]
输出:3
解释:我们需要删除的最短子数组是 [10,4,2] ,长度为 3 。剩余元素形成非递减数组 [1,2,3,3,5] 。
另一个正确的解为删除子数组 [3,10,4] 。
示例 2:

输入:arr = [5,4,3,2,1]
输出:4
解释:由于数组是严格递减的,我们只能保留一个元素。所以我们需要删除长度为 4 的子数组,要么删除 [5,4,3,2],要么删除 [4,3,2,1]。
示例 3:

输入:arr = [1,2,3]
输出:0
解释:数组已经是非递减的了,我们不需要删除任何元素。
示例 4:

输入:arr = [1]
输出:0

题解,看了评论区才知道可以用双指针:

class Solution {
public:
    int findLengthOfShortestSubarray(vector<int>& arr) {
        int left=0,right=0;
        for(int i=1;i<arr.size();i++) {
            if(arr[i]<arr[i-1]) {left=i-1;break;}
        }
        for(int i=arr.size()-2;i>=0;i--) {
            if(arr[i]>arr[i+1]) {right=i+1;break;}
        }
        int min_=INT_MAX;
        min_=min(min_,right);
        for(int i=left;i>=0;i--) {
            for(int j=right;j<arr.size();j++) {
                if(arr[i]<=arr[j]) {min_=min(min_,j-i-1);break;}
            }
            min_=min(min_,(int)arr.size()-i-1);
        }
        if(min_==INT_MAX) return arr.size()-1;
        if(min_==-1) return 0;
        return min_;
    }
};

在这里插入图片描述
自己写的不算双指针,再去看一边官网的题解,好好学习了一下双指针的用法,进一步改进题解:

class Solution {
public:
    int findLengthOfShortestSubarray(vector<int>& arr) {
        int right=0;
        for(int i=arr.size()-2;i>=0;i--) {
            if(arr[i]>arr[i+1]) {right=i+1;break;}
        }
        if(right==0) return 0;
        int min_=right;
        
        for(int i=0;i<arr.size();i++) {
            while(right<arr.size()&&arr[right]<arr[i]) {
                right++;
            }
            min_=min(min_,right-i-1);
            if(i+1<arr.size()&&arr[i]>arr[i+1]) {       //左边有顺序的序列到头了
                break;
            }
            // cout<<min_<<endl;
        }
        return min_;
    }
};

在这里插入图片描述

剑指 Offer 59 - I. 滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

看到这种题总是下意识使用C++数据结构,方便存储,对set和map的自排序功能一直印象深刻,首先尝试使用map来当作窗口来滑动存储值。
题解如下:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        map<int,int,greater<int>> res;
        for(int i=0;i<k;i++) {
            res[nums[i]]++;
        }
        vector<int> result;
        auto it=res.begin();
        result.push_back(it->first);
        for(int i=k;i<nums.size();i++) {
            res[nums[i-k]]--;
            res[nums[i]]++;
            if(res[nums[i-k]]<=0) {
                res.erase(nums[i-k]);
            }
            auto it=res.begin();
            result.push_back(it->first);
        }
        return result;
    }
};

这样勉强能够过。因为之前思路一版超过时间限制了,在这个基础上减少二次遍历的时间,但是map的erase功能占O(logn), set、map、multiset、multimap的底层结构都是红黑树,插入、删除、修改时间复杂度都在O(logn)。
现在再去看一下官方解答:
方法一,官方解的思路是优先队列,那么根据官方给我的思路,我的解答:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        priority_queue<pair<int,int>,vector<pair<int,int>>,less<pair<int,int>>> p; //pair默认的比较规则,先比较第一个,再比较第二个
        for(int i=0;i<k;i++) {
            p.push(pair<int,int>(nums[i],i));
        }
        vector<int> result={p.top().first};
        int num=0;
        for(int i=k;i<nums.size();i++) {
            p.emplace(nums[i],i);
            while(p.top().second<=i-k) {    //找到范围内最大的值
                p.pop();
            }
            result.push_back(p.top().first);
        }
       
        return result;

    }
};

还是仿写了一遍,也是第一遍超过时间限制,然后需要看官方解,for循环里面比之第一遍的优化了很多,不需要大量的插入、删除,或者是将对应top的坐标之后的k个位置上的值都应该是top这个最大值,但是这样的话,时间上超过了,自己写的for循环大约是O(nk) 太大了,需要做优化。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值