【C++】通过stack、queue、deque理解适配器模式

news2024/12/28 5:15:58

破镜不能重圆,枯木可以逢春。

在这里插入图片描述

文章目录

  • 一、stack
    • 1.stack的介绍
    • 2.stack相关OJ题(巧妙利用stack数据结构的特征)
    • 3.stack的模拟实现
  • 二、queue
    • 1.queue的介绍
    • 2.queue的相关OJ题(巧妙利用queue数据结构的特征)
    • 3.queue的模拟实现
  • 三、deque(双端队列容器,叫队列,但和队列没关系)
    • 1.vector和list的优缺点→stack和queue的适配容器
    • 2.deque的底层结构
    • 3.deque的优缺点
    • 4.为什么选择deque作为stack和queue的适配容器?(vector排序快,list中间插入删除牛,deque吸取两个容器的部分优点)



一、stack

1.stack的介绍

在这里插入图片描述

1.
stack和queue的设计实际是一种模式,这种模式叫做适配器模式,设计理念就是用已有的东西封装转换出你想要的东西。我们前面还学习过的一种模式叫做迭代器模式,这种模式的设计理念就是封装底层实现的细节,对所有底层数据结构不同的容器,都能提供统一的访问方式

2.
stack的实现就是一种适配器的设计理念,适配器也可以叫做配接器,stack是一种只允许在某一端进行数据的插入和删除元素的容器,其他位置均不可以直接访问,所以对于stack来说不需要实现迭代器。

3.
在利用某种容器实现stack时,容器应该支持back()取到容器尾部元素,push_back()尾插,pop_back()尾删,empty()判空等操作,分别对应stack的top(),push(),pop(),empty()等操作。
对于stack来说,底层的适配容器为deque,但从其接口来看,stack实际就是一种特殊的vector,所以在模拟实现时,我们倾向于用vector来作stack的适配容器。

2.stack相关OJ题(巧妙利用stack数据结构的特征)

最小栈

1.
最小栈的解决思路就是利用两个栈,普通栈用来一直入栈所有的数据,minstack负责只入栈比上一次入栈元素小于或等于的元素,在出栈时,普通栈元素一定出栈,但minstack只有在普通栈的top元素和自身栈的top元素相等的时候才会去出栈,minstack的top元素就是当前普通栈的所有元素中最小的元素。

2.
解释一下为什么只有在普通栈top元素和minstack的top元素相等的时候,minstack才会出栈。
因为有可能在某一次入栈的元素是最小元素之后,入栈的剩余元素都是大于这个最小元素的,那么在pop的时候,你的minstack不能pop,因为minstack的top元素是现在栈所有元素最小的元素,只有说普通栈的top元素达到和minstack的top元素一样时,也就是现在pop的元素是栈中所有元素中最小的元素时,minstack才会跟着普通栈一样将最小的元素给pop掉。

class MinStack {
public:
    MinStack() {

    }
    void push(int val) {
        if(st.empty() || val <= minst.top())
            minst.push(val);
        st.push(val);//st是一直要入栈的。
    }
    void pop() {
        if(st.top() == minst.top())
            minst.pop();
        st.pop();
    }
    int top() {
        return st.top();
    }
    int getMin() {
        return minst.top();
    }
private:
    stack<int> minst;
    stack<int> st;
};

栈的压入弹出序列

1.
我们依次遍历入栈序列,拿入栈后的元素和出栈序列的第一个元素进行比较,如果相等,说明这个元素在入栈之后就立马出栈了,那我们就将入栈之后的元素给pop掉。如果不相等,说明这个元素还没有被pop掉,那我们就继续将入栈序列的后面的元素再次进行入栈,入栈之后再次和出栈序列的剩余元素进行比较。

2.
所以如果入栈序列和出栈序列是相匹配的话,遍历完入栈元素序列之后的结果应该是,st栈为空,并且下标i走到出栈序列的最后一个元素了。但如果两个序列是不匹配的话,st栈中一定还有剩余元素,无法被pop掉,并且i也无法走到出栈序列的最后一个元素的下标位置。

class Solution {
public:
    stack<int> st;
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        int i = 0;//代表出栈序列元素的下标。
        for(auto e:pushV)
        {
            st.push(e);
            while(!st.empty() && st.top() == popV[i])
            {
                st.pop();
                ++i;//出栈之后,该和出栈序列的下一个元素进行比较了。
            }
        }
        return st.empty();
    }
};

逆波兰表达式求值

1.
这道题里面有很多的知识点,帮助我复习运用了很多学过的知识,首先字符串有自己的比较运算符的重载函数,所以在比较字符串的时候,string类的字符串比较函数可以帮助我们节省很多代码,让我们无需再调用strcmp这样的函数来进行字符串的比较,下面都是一些非成员函数,在调用时可以直接调用,无需通过string类对象进行调用。

在这里插入图片描述
2.
string类还有字符串转换到其他类型的函数,在下面这道题中就用到了stoi将字符串转换为整数int类型的函数,stoi会返回字符串转换为整型之后的值。

在这里插入图片描述
3.
字符串也重载了[]+下标这样的访问方式,和vector一样,但到了list那里的时候我们就只能使用迭代器来进行访问了,利用[]+下标的访问方式和switch case分支语句的组合可以帮助我们挑选出vector里面字符串为运算符的部分。(有一说一,我做这个题的时候连switch case的格式都忘记了,太吓人了)

4.
这道题也巧妙利用了栈结构的特征,只要字符串是非运算符的那就将其全部转换为int,然后push到栈当中,只要遇到了运算符的字符串,那我们就依次取出栈顶的两个元素,按照取出次序来看,先取出的是右操作数,后取出的是左操作数,然后根据运算符类型将运算结果重新push到栈里面,等到下次遇到运算符字符串时,继续取出栈的两个元素进行计算,最后vector元素遍历完之后,栈中剩余的最后一个元素就是逆波兰表达式计算之后的结果。

class Solution {
public:
    stack<int> st;
    int evalRPN(vector<string>& tokens) {
        for(auto s : tokens)
        {
            //if(s[0] == '+' || s[0] == '-' || s[0] == '*' || s[0] == '/')负数这里就不对了
            if(s == "+" || s == "-" || s == "*" || s == "/")//利用string的运算符重载函数进行比较
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch(s[0])//拿到字符串的第一个字符
                {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            }
            else
                st.push(stoi(s));
        }
        return st.top();

    }
};

用栈实现队列

1.
这道题比较经典了,也是很巧妙的利用栈结构的特征,栈是先进后出,队列是先进先出,如果想要用栈来模拟实现队列,一个栈肯定是不够用的,所以我们用两个栈之间的操作接口的配合,来模拟实现队列。

2.
入队列我们就将元素先都入栈到pushst里面,等到pop时,将pushst里面的元素依次取出来入栈到popst里面,这样popst里面依次取出来的元素序列正好符合出队列的元素序列,所以在出队列元素时,就相当于pop掉popst栈里面的元素,这样就符合了队列元素出队列的操作,peek就相当于queue的front接口的功能,拿到队头元素,实际就是拿popst栈顶的元素。
一旦popst栈为空时,我们就将pushst栈里面的元素倒腾过来,然后popst栈的操作行为就符合队列了。

class MyQueue {
public:
    stack<int> pushst;
    stack<int> popst;
    MyQueue() {
        //对于自定义类型,初始化列表会调用他的默认构造
    }
    
    void push(int x) {
        pushst.push(x);
    }
    
    int pop() {
        if(popst.empty())
        {
            while(!pushst.empty())
            {
                popst.push(pushst.top());
                pushst.pop();
            }
        }

        int ret = popst.top();
        popst.pop();
        return ret;
    }
    
    int peek() {
        if(popst.empty())
        {
            while(!pushst.empty())
            {
                popst.push(pushst.top());
                pushst.pop();
            }
        }
        return popst.top();
    }
    
    bool empty() {
        return pushst.empty() && popst.empty();
    }
};

3.stack的模拟实现

1.
无论是函数模板还是类模板,在声明时都可以给缺省参数,只是在使用上有些不同,当你在使用函数模板时,无论是显式实例化还是隐式实例化,你所传参数是变量或对象,函数模板的实例化推演依靠的便是变量或对象的类型。而在使用类模板的时候,我们所传参数是类型,类模板依靠参数类型来推演实例化出具体的类。

2.
stack的实现颇为简单,利用vector容器就可以模拟实现出stack适配器,因为vector支持所有stack的操作,例如back,尾插尾删,size(),判空等操作。

在这里插入图片描述

namespace wyn
{
	//前一个参数代表数据类型,后一个参数代表适配的容器
	//template<class T,class Container = vector<T>>
	template<class T,class Container = deque<T>>
	class stack
	{
	public:
		void push_back(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		T& top()
		{
			return _con.back();
		}
		const T& top()const
		{
			return _con.back();
		}
		//const对象只能调用const成员函数,不能调用非const成员函数,因为权限不能放大。
		//非const对象既能调用const成员函数,又能调用非const成员函数,因为权限可以平移或缩小。
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

二、queue

1.queue的介绍

在这里插入图片描述
1.
队列也是一种容器适配器,队列数据结构的接口功能要求有出队列,入队列所以,取队头元素,取队尾元素等重要接口,所以队列的底层容器需要支持头删,尾插,front,back等接口,list,deque,vector其实都可以作为queue的底层容器,但vector在出队列时,需要调用erase头删,而erase头删需要挪动数据,代价较大,所以queue的常见底层容器为list或deque。

2.
队列和栈一样,都不需要实现迭代器,因为队列只支持在容器的头尾两个位置进行元素的访问,所以无需实现迭代器。

2.queue的相关OJ题(巧妙利用queue数据结构的特征)

用队列实现栈

1.
这道题在queue本身有限的功能接口下实现栈的结构,是一道加深对于队列数据结构理解并且熟练运用其操作接口的题目。需要明确的原则就是,队列只能在队头出数据,在队尾入数据,在队头取数据,在队尾取数据。

2.
如果要实现栈的操作,我们可以用两个队列来实现,q负责将入栈的所有数据入到队列里面,等pop的时候,我们将q队尾的数据也就是back数据挪动到q的队头上面去,挪动的过程其实就是出队列数据,然后将数据再入队列,直到队尾数据挪动到队头后,我们将队头数据保存后,将q的队头数据pop掉,然后将刚刚保存的数据入队列到stackQ队列里面。
将上面的逻辑搞成一个循环,直到q队列为空,此时stackQ出队列的操作就是出栈操作了,栈的top元素就是stackQ的队头元素,如果stackQ为空,则栈顶元素就是q的队尾元素。

3.
在具体实现时,我遇到了一点点问题,就是在写队尾数据挪到队头位置的while循环条件的时候,我当时用两个变量标识队头和队尾数据,然后让这两个变量不相等作为判断条件,在力扣上面通过了16个测试用例,最后一个没有通过。
在拿最后一个测试用例进行画图分析之后,我发现这个逻辑如果对于有重复数据的测试用例来看,在挪数据时会有问题,所以我们舍弃这样的用法,用队列大小来作为while循环条件。

在这里插入图片描述

class MyStack {
public:
    queue<int> q;
    queue<int> stackQ;
    MyStack() {

    }
    
    void push(int x) {
        q.push(x);
    }
    
    int pop() {
        if(stackQ.empty())
        {
            int tail = q.back();
            int head = q.front();
            int size = q.size();
            while(!q.empty())
            {
                //一旦push里面出现重复元素,head!=tail这样的判断条件,会让逻辑出现问题
                //所以不要用这样的判断逻辑来换队尾到队头位置,用次数来作为判断条件吧!

                // while(head != tail)
                // {
                //     q.pop();
                //     q.push(head);
                //     head = q.front();
                // }
                while(--size)//循环次数为size-1次
                {
                    q.pop();
                    q.push(head);
                    head = q.front();
                }
                //出来之后head==tail,将tail入stackQ队列,并且更新head和tail
                stackQ.push(tail);
                q.pop();
                //q出队列之后,重新更新head和tail的值,用size作为判断条件后,size也要更新
                tail = q.back();
                head = q.front();
                size = q.size();
            }
        }
        int ret = stackQ.front();
        stackQ.pop();//让stackQ直接出队列
        return ret;
    }
    
    int top() {
        if(stackQ.empty())
            return q.back();
        return stackQ.front();

    }
    
    bool empty() {
        return q.empty() && stackQ.empty();
    }
};

3.queue的模拟实现

1.
模拟实现queue也是非常简单的,只要底层容器有头删,尾插,back,front等接口功能就可以,list就可以作为queue的适配容器。所以模拟实现queue时,只要复用list的接口即可。模拟实现代码过于简单,大家一看就懂。

在这里插入图片描述

namespace wyn
{
	//template<class T, class Container = list<T>>
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_front();
		}
		const T& front()
		{
			return _con.front();
		}
		const T& back()
		{
			return _con.back();
		}
		//const对象只能调用const成员函数,不能调用非const成员函数,因为权限不能放大。
		//非const对象既能调用const成员函数,又能调用非const成员函数,因为权限可以平移或缩小。
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};

}

三、deque(双端队列容器,叫队列,但和队列没关系)

1.vector和list的优缺点→stack和queue的适配容器

1.
vector作为一种由动态数组实现的容器,他的缺点就是头删和头插会进行数据的挪动,并且如果发生扩容,还要对应产生扩容带来的消耗,比如开空间和拷贝数据。但他也有优点,支持随机访问,这一点保证了在vector进行数据访问时,无需进行遍历容器的操作,直接利用下标访问即可,而且尾插尾删的效率高,这其实也是由于其支持下标访问所带来的优点,所以这个优点可以合并到支持下标随机访问里。另一个优点是由于其空间结构连续,CPU高速缓存命中率高。

2.
list作为一种结构体结点链接而成的数据结构,他的缺点就是空间结构不连续,CPU高速缓存命中率低,并且由于他的结构是不连续的,无法支持下标的随机访问,因为结点之间的地址并没有确切的相关联系,而vector能够支持是因为地址与地址之间相差存储元素类型个字节,通过地址的±整数就可以支持数组中任意位置数据的访问。但其最大的优点就是任意位置插入删除数据的时间复杂度都是O(1),并且不会由于空间扩容带来性能的消耗,这也是他的优势。

3.
通过上面所阐述的优点和缺点就可以看出为什么我们在模拟实现stack和queue的时候,分别采用vector和list来作为其适配容器,因为stack会频繁进行尾插、尾删、取vector尾元素,所以正好符合了vector的优点。而queue会频繁进行头删、所以正好符合list的优点。

4.
但是呢,vector作为stack的适配容器来讲,stack扩容的时候,会带来空间扩容的消耗,而list无法支持下标随机访问,那么能不能有一个容器将vector和list的优点都兼顾到呢?答案是有的,他就是双端队列deque。

2.deque的底层结构

1.
双端队列不仅支持了头尾的插入删除,还支持了下标的随机访问相比list,并且头插头删相比vector效率极高。
但deque并不是真正连续的空间,他是由一段段连续的空间组成的,你可以将它看作动态的二维数组。

在这里插入图片描述

2.
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,重担子就落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

3.
deque实际是通过一个中控指针数组来控制多段连续空间buffer的。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3.deque的优缺点

1.
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

2.
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目目前能看到的一个应用场景就是,STL用其作为stack和queue的底层数据结构。

3.
deque在中间插入删除时,也是需要挪动数据的,只不过挪动数据的代价没有vector大而已。deque为什么头插头删效率高不用挪动数据呢?实际就是因为在头插的时候,deque又重新开辟了一块儿空间,让中控数组去控制这个新开辟的buffer,所以如果发生大量中间插入删除时,deque的效率相比list不够极致。
deque支持随机访问的效率实际也不够极致,因为他需要遍历中控数组判断数据在哪个缓冲区,然后在确定缓冲区的具体位置,所以他的随机访问效率相比vector来说也不够极致

在这里插入图片描述

4.为什么选择deque作为stack和queue的适配容器?(vector排序快,list中间插入删除牛,deque吸取两个容器的部分优点)

1.
虽然deque与vector和list的优点来比较,哪个都比不过,但比他们的缺点时,又比vector和list强一些,所以这是一个比较中庸的容器,比上不足,比下有余。

2.
但用deque去作stack和queue的默认适配容器还是不错的,只要中间插入删除少,偶尔进行下标的随机访问,避开deque的缺点,deque用起来还是不错的。
而如果在使用时进行大量的随机访问,我们还是用vector容器,如果要进行中间位置大量的插入删除,还是用list容器。

在这里插入图片描述
3.
切记一点,不要用deque容器来进行排序,因为大量的随机访问会导致deque的效率极低,而库里面的sort算法用的又是快排,快排会进行三数取中从而导致大量的随机访问,所以不要用deque来进行排序,如果非要排序,建议将deque中的数据拷贝到vector,然后用vector来进行sort快排

4.
这里也可以透露出另外一个知识点,vector的排序比list快的原因就是vector支持大量的随机访问,对于快排来说,vector这样的容器非常的友好,这也正是为什么我们喜欢用vector来进行排序的原因。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/401732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多图片怎么转换成PDF?这招教你轻松转换

多图片怎么转换成PDF&#xff1f;我们经常会传输图片文件给同事或者朋友&#xff0c;但是多张图片的传输比较麻烦&#xff0c;有的时候传输比较慢&#xff0c;而且也不便于查看&#xff0c;所以我们就可以将需要传输的多张图片转换成一个PDF文件&#xff0c;这样查看文件时就可…

mxnet版本与numpy,requests等都不兼容问题

简介 跟着李沐学AI时遇到的mxnet环境问题。 问题 使用pip install mxnet时会重新安装相匹配的numpy和requests&#xff0c;而这新安装的这两个版本不满足d2l所需的版本。 然后报错&#xff1a; ERROR: pips dependency resolver does not currently take into account all …

常用docker命令

帮助启动类命令 镜像命令 docker images options -a :列出本地所有的镜像&#xff08;含历史映像层) -q :只显示镜像ID。docker search 某个XXX镜像名字 网站 Docker命令 docker search [OPTIONS] 镜像名字 案例 docker search redis option…

【AI绘图学习笔记】奇异值分解(SVD)、主成分分析(PCA)

这节的内容需要一些线性代数基础知识&#xff0c;如果你没听懂本文在讲什么&#xff0c;强烈建议你学习【官方双语/合集】线性代数的本质 - 系列合集 文章目录奇异值分解线性变换特征值和特征向量的几何意义什么是奇异值分解&#xff1f;公式推导SVD推广到任意大小矩阵如何求SV…

【C++的OpenCV】第十二课-OpenCV图像常用操作(九):找到图像的边界(轮廓)findContours()和drawContours()

&#x1f389;&#x1f389;&#x1f389;欢迎各位来到小白piao的学习空间&#xff01;\color{red}{欢迎各位来到小白piao的学习空间&#xff01;}欢迎各位来到小白piao的学习空间&#xff01;&#x1f389;&#x1f389;&#x1f389; &#x1f496;&#x1f496;&#x1f496…

PMP项目管理项目整合管理

目录1 项目整合管理概述2 制定项目章程3 制定项目管理计划4 指导与管理项目工作5 管理项目知识6 监控项目工作7 实施整体变更控制8 结束项目或阶段1 项目整合管理概述 项目整合管理包括对隶属于项目管理过程组的各种过程和项目管理活动进行识别、定义、组合、统一和协调的各个…

Linux服务器还有漏洞?建议使用 OpenVAS 日常检查!

几乎每天都会有新的系统漏洞产生&#xff0c;系统管理员经常忙于管理服务器&#xff0c;有时候会忽略一些很明显的安全问题。扫描 Linux 服务器以查找安全问题并不是很简单的事情&#xff0c;所以有时候需要借助于一些专门的工具。 OpenVAS 就是这样一种开源工具&#xff0c;它…

Easy Deep Learning——池化层

池化是什么&#xff1f;它有什么作用&#xff1f; 还是草地的场景&#xff0c;把草地分成一块块的网格&#xff0c;数量还是太多了&#xff0c;如何继续简化输入数据呢? 这时候可以只取一块网格中所有的小草的大小形状的平均值或者最大值作为一个输入数据&#xff0c;这样就大…

Tik Tok新手秘籍,做好五点可轻松起号

新手做TikTok需要有一个具体的规划布局&#xff0c;如果没有深思熟虑就上手开始的话&#xff0c;很有可能会导致功亏一篑&#xff0c;甚至是浪费时间。因此&#xff0c;想要做好 TikTok&#xff0c;就必须从最基本的运营细节开始&#xff0c;一步一步来&#xff0c;下面为大家分…

近红外荧光染料修饰氨基IR 825 NH2,IR 825-Amine,IR-825 NH2

IR 825 NH2&#xff0c;IR 825-NH2&#xff0c;IR825 Amine&#xff0c;IR825-Amine&#xff0c;新吲哚菁绿-氨基&#xff0c;荧光染料修饰氨基产品规格&#xff1a;1.CAS号&#xff1a;N/A2.包装规格&#xff1a;10mg&#xff0c;25mg&#xff0c;50mg&#xff0c;包装灵活&am…

数据结构(3)— 线性表之顺序存储详解介绍(含代码)

&#xff08;1&#xff09;博客代码在数据结构代码---GitHub仓库&#xff1b;线性表介绍线性表的基础概念&#xff08;1&#xff09;甲骨文表示&#xff1a;线性表是零个或多个数据元素的有限序列。&#xff08;2&#xff09;线性表&#xff0c;顾名思义&#xff0c;就是说这个…

git | git 2023 详细版

文章目录一、Git命令1.2 设计用户签名1.3 初始化本地库1.4 查看本地库状态1.5 添加至暂存区1.6 从暂存区删除1.7 将暂存区的文件提交到本地库1.8 查看版本信息二、Git分支2.1 查看分支2.2 创建分支2.3 切换分支2.4 合并分支三、GitHub3.1 代码克隆clone3.2 给库取别名3.3 推送本…

【服务器管理】Wordpress服务器内存占用太高(优化方案详解)

简述 在刚刚配置完服务器之后&#xff0c;想着试一试wordpress这个功能&#xff0c;结果打开服务器后台&#xff0c;发现本来就不多的内存被占用了一大半。 我真的服了&#xff0c;我还啥都没干呢&#xff0c;就这么多的内存占用&#xff0c;那之后我开始弄了还得了。因此有必…

masstransit的message几个高级用法

1&#xff09;问题&#xff0c;Class MessageA 基类&#xff0c;Class MessageB继承自MessageA&#xff1b; 用bus.Publish方法本想把有些消息只发给B队列&#xff0c;结果由于其继承关系A队列也获得了消息&#xff1b; 解决方法用send&#xff0c; Uri uri new Uri(RabbitM…

专项攻克——二叉树

文章目录一、二叉树定义、分类二、二叉树的存储结构三、创建二叉树四、遍历方式一、二叉树定义、分类 二叉树&#xff1a;是N个结点的有序集合&#xff0c;该集合或者为空集&#xff0c;或者由一个根节点跟两棵互不相交的、分别称为根节点的左子树或者右子树的二叉树组成。每个…

vue项目打包Gzip压缩,IIS发布

什么是gzip、有何用&#xff1f; gzip是GNUzip的缩写&#xff0c;最早用于UNIX系统的文件压缩。HTTP协议上的gzip编码是一种用来改进web应用程序性能的技术&#xff0c;web服务器和客户端&#xff08;浏览器&#xff09;必须共同支持gzip。目前主流的浏览器&#xff0c;Chrome…

18 个重要的 JavaScript 字符串方法

1. trim()它删除了两侧的空白。ECMAScript 2019 中还包含 trimStart() 和 trimStart() 方法。它们与 trim() 相同&#xff0c;但 trimStart() 和 trimEnd() 分别从字符串的开头和结尾删除空格。2. match()match() 方法返回匹配字符串的数组。3. split()split() 方法将字符串转换…

5大GPU厂商共建 | openKylin社区GPU SIG首次例会召开!

3月8日&#xff0c;openKylin社区GPU SIG首次例会以线上形式召开。此次会议由长沙景美集成电路设计有限公司、摩尔线程智能科技&#xff08;北京&#xff09;有限责任公司、格兰菲智能科技有限公司、象帝先计算技术&#xff08;重庆&#xff09;有限公司等GPU厂商的多位SIG Mai…

JavaScript 中的全部对象

宿主对象&#xff08;host Objects&#xff09;&#xff1a;由 JavaScript 宿主环境提供的对象&#xff0c;它们的行为完全由宿主环境决定。 【 浏览器环境宿主&#xff0c;全局对象window&#xff0c;window 上又有很多属性&#xff0c;如 document。 全局对象 window 上的属…

ROS1学习笔记:tf坐标系广播与监听的编程实现(ubuntu20.04)

参考B站古月居ROS入门21讲&#xff1a;tf坐标系广播与监听的编程实现 基于VMware Ubuntu 20.04 Noetic版本的环境 文章目录一、创建功能包二、创建代码2.1 以C为例2.1.1 配置代码编译规则2.1.2 编译整个工作空间2.1.2 配置环境变量2.1.4 执行代码2.2 以Python为例2.2.1 配置代码…