C++第八讲:STL--stack和queue的使用及模拟实现

news2024/10/28 22:36:50

C++第八讲:STL--stack和queue的使用及模拟实现

  • 1.stack的使用
  • 2.queue的使用
  • 3.栈和队列OJ题
    • 3.1题目1:最小栈
    • 3.2题目2:栈的压入、弹出序列
    • 3.3题目3:逆波兰表达式求值
    • 3.4题目4:用栈实现队列
  • 4.栈的模拟实现
  • 5.队列的模拟实现
  • 6.deque
    • 6.1什么是deque
    • 6.2deque的底层实现
    • 6.3deque迭代器的讲解
    • 6.4库中deque实现细节
  • 7.prioriry_queue的介绍和使用
    • 7.1什么是priority_queue
    • 7.2在OJ题中的使用
    • 7.3priority_queue的模拟实现
      • 7.3.1仿函数
        • 7.3.1.1仿函数的其它应用场景

1.stack的使用

有了前面的基础之后,栈和队列的使用都很简单,而且其中的接口我们在数据结构中都实现过,所以我们直接看一遍即可:

在这里插入图片描述
不同的是,栈中没有实现迭代器,这其实与栈和队列的实现有关:
在这里插入图片描述
那么它的访问方式只有循环出栈和获取栈顶元素:

#include <stack>
int main()
{
    stack<string> s1;
    s1.emplace("hello world!");
    s1.emplace("Xxxxxxxxxxxxx");

    while (!s1.empty())
    {
        cout << s1.top();
        s1.pop();
    }
    cout << endl;//Xxxxxxxxxxxxxhello world!

    cout << s1.empty() << endl;//1,表示此时栈为空

    return 0;
}

2.queue的使用

queue的使用和stack相同,这里不再看:

在这里插入图片描述

#include <queue>
int main()
{
    queue<string> q1;
    q1.emplace("hello world!");
    q1.emplace("Xxxxxxxxxxxx");

    while (!q1.empty())
    {
        cout << q1.front();
        q1.pop();
    }
    cout << endl;//hello world!Xxxxxxxxxxxx

    cout << q1.empty() << endl;//1,表示队列为空

    return 0;
}

3.栈和队列OJ题

3.1题目1:最小栈

链接: 最小栈
在这里插入图片描述
在这里插入图片描述

class MinStack {
public:
    MinStack() {
        //初始化堆栈操作其实不用实现:
        //1.自己不实现,内置类型:不一定处理,自定义类型:走自己的构造函数,stack中有自己的构造函数
        //2.写了函数不实现:走初始化列表,没有初始化列表,判断是否给了初始值,最后走构造函数
        //所以我们既可以给这个函数删了,也可以置之不管,我们这里直接不管
    }
    
    void push(int val) {
        _stack.push(val);
        if(_minstack.empty() || val <= _minstack.top()) _minstack.push(val);
    }
    
    void pop() {
        if(_stack.top() == _minstack.top()) _minstack.pop();
        _stack.pop();
    }
    
    int top() {
        return _stack.top();
    }
    
    int getMin() {
        return _minstack.top();
    }
private:
    //创建两个栈
    stack<int> _stack;
    stack<int> _minstack;
};

3.2题目2:栈的压入、弹出序列

链接: 栈的压入、弹出序列
在这里插入图片描述

class Solution {
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        if(pushV.size() != popV.size()) return false;

        stack<int> _stack;
        int pushi = 0, popi = 0;
        while(pushi < pushV.size())
        {
            _stack.push(pushV[pushi++]);//先入栈
            while(!_stack.empty() && _stack.top() == popV[popi])
            {
                //当栈顶元素和要删除的元素相等时,要出栈
                _stack.pop();
                popi++;
            }
        }
        return _stack.empty();//如果为空,返回true,否则返回false
    }
};

3.3题目3:逆波兰表达式求值

链接: 逆波兰表达式求值
在这里插入图片描述

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //首先要先取值入栈
        stack<int> s1;
        for(int i = 0; i<tokens.size(); i++)
        {
            string& s = tokens[i];
            //枚举数字很困难,那么我们就枚举符号
            //s是string类型,而s[0]是第一个字符
            if(s == "+" || s == "-" || s == "*" || s == "/")
            {
                //是字符的话,就拿两个数据进行运算
                int right = s1.top();
                s1.pop();

                int left = s1.top();
                s1.pop();
                switch(s[0])
                {
                case '+':
                    s1.push(left + right);
                    break;//注意break别忘记写了
                case '-':
                    s1.push(left - right);
                    break;
                case '*':
                    s1.push(left * right);
                    break;
                case '/':
                    s1.push(left / right);
                    break;
                }
            }
            else
            {
                //是数字,就入栈
                s1.push(atoi(s.c_str()));
            }
        }
        return s1.top();
    }
};

3.4题目4:用栈实现队列

链接: link
在这里插入图片描述

class MyQueue {
public:
    MyQueue() {
        
    }
    
    void push(int x) {
        //入队列就是直接向入栈中插入数据
        Push.push(x);
    }
    
    int pop() {
        //如果出栈中有数据,直接出栈,否则,先入栈,再出栈
        if(Pop.empty())
        {
            while(!Push.empty())
            {
                Pop.push(Push.top());
                Push.pop();
            }
        }
        int ret = Pop.top();
        Pop.pop();
        return ret;
    }
    
    int peek() {
        if(Pop.empty())
        {
            while(!Push.empty())
            {
                Pop.push(Push.top());
                Push.pop();
            }
        }
        return Pop.top();
    }
    
    bool empty() {
        return Push.empty() && Pop.empty();
    }
private:
    //应该是要两个栈,一个是入栈,一个是出栈
    stack<int> Push;
    stack<int> Pop;
};

4.栈的模拟实现

栈的实现较为简单,我们直接来看:

//栈的模拟实现
1.我们可以按照之前数据结构讲的那样,开辟数组来实现栈
//template<class T>
//class Stack
//{
//private:
//	T* _a;
//	size_t _top;
//	size_t _capacity;
//};

//但是库中的栈并不是这样实现的,而是使用了一个适配器:
namespace Mine
{
	template<class T, class container>
	class Stack
	{
	public:
		//入栈
		void push(const T& x)
		{
			_con.push_back(x);//因为使用了之前我们用过的容器来实例化栈,所以我们可以直接使用容器的函数来实现栈!
		}
		//出栈
		void pop()
		{
			_con.pop_back();
		}
		//获取栈顶元素
		const T& top() const
		{
			return _con.back();
		}
		//栈中的数个数
		size_t size() const
		{
			return _con.size();
		}
		//判空
		bool empty() const
		{
			return _con.empty();
		}

	private:
		container _con;//这时我们可以直接使用容器来创建一个对象
	};
}

///

#include "Stack.h"
int main()
{
	Mine::Stack<int, vector<int>> s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	while (!s1.empty())
	{
		cout << s1.top() << " ";
		s1.pop();
	}
	cout << endl << s1.size();//3 2 1      0(size)

	return 0;
}

但是我们可能会有疑惑:为什么我们可以直接使用到std中的stack以及它的函数呢?因为:using namespace std;我们在一开始就已经展开了,如果我们不展开的话,还要加上std::,如果展开在#include "Stack.h"后面,也是没有问题的,因为stack是模板参数,当实例化时才会进行错误的检查

5.队列的模拟实现

队列的实现也比较简单,我们直接看即可:

//队列的模拟实现
//队列的实现也是这样:
namespace Mine
{
	//在模板定义时可以直接为模板参数赋初始值
	template <class T, class container = list<int>>
	class Queue
	{
	public:
		//入队列
		void push(const T& x)
		{
			_con.push_back(x);
		}
		//出队列
		void pop()
		{
			//对于vector来说,它没有相应的头删函数,因为头删的消耗太大了,所以容器不能够使用vector
			_con.pop_front();
		}
		//获取队头元素
		const T& front() const
		{
			return _con.front();
		}
		//获取队尾元素
		const T& back() const
		{
			return _con.back();
		}
		//队列大小
		size_t size() const
		{
			return _con.size();
		}
		//队列判空
		bool empty() const
		{
			return _con.empty();
		}
	private:
		container _con;
	};
}

/

#include <list>
#include "Queue.h"
int main()
{
	Mine::Queue<int, list<int>> q1;
	q1.push(1);
	q1.push(2);
	q1.push(3);
	while (!q1.empty())
	{
		cout << q1.front() << " ";
		q1.pop();
	}
	cout << endl;//1 2 3

	return 0;
}

6.deque

上面我们已经实现了栈和队列,但是我们看库中的栈和队列时,会发现:

在这里插入图片描述
库中实现的栈和队列的适配器传入的都是一个叫deque的容器,那么这个容器究竟是什么呢?这个容器的底层实现是什么呢?:

6.1什么是deque

在这里插入图片描述
我们可以看一下deque实现的功能:
在这里插入图片描述

6.2deque的底层实现

在这里插入图片描述
所以说deque的缺陷在于[]访问这里,通过代码验证可知,在对10000个数据进行排序时,使用vector比使用deque快将近两倍多,而且如果将deque中的数据拷贝到vector中进行排序,排序之后再拷贝过来的效率也要比单独在deque中进行排序快两倍左右!
所以deque的访问是一个大问题,而拷贝其实是不怎么消耗时间的

6.3deque迭代器的讲解

所以说,对于栈和队列那种不经常插入删除的容器来说,使用deque是再好不过的了

下面我们来看一下deque的迭代器是怎么实现的:
在这里插入图片描述

6.4库中deque实现细节

在这里插入图片描述

7.prioriry_queue的介绍和使用

7.1什么是priority_queue

在这里插入图片描述

翻译为优先级队列,包含在queue头文件中,可以按照数据的优先级对数据进行排序,默认是较大值,它的第一个元素总是大于它所包含的其它元素,它的使用如下:
在这里插入图片描述

int main()
{
	priority_queue<int> pq1;
	pq1.push(5);
	pq1.push(1);
	pq1.push(2);
	pq1.push(9);

	//默认是按照大的优先级排列
	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;//9 5 2 1

	return 0;
}

我们看到这个是不是感觉很熟悉,那就是堆,大堆、小堆和这个相似,所以我们可以使用堆的方法来实现这个容器

int main()
{
	//如果我们想要让小的数据优先级高,需要这样使用:
	//priority_queue<int, vector<int>, less<int>> pq1;
	priority_queue<int, vector<int>, greater<int>> pq1;
	pq1.push(5);
	pq1.push(1);
	pq1.push(2);
	pq1.push(9);

	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;//1 2 5 9

	return 0;
}

7.2在OJ题中的使用

链接: 数组中的第k个最大元素
有了这个容器,该题目实现起来很简单:
在这里插入图片描述

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq1;
        //先插入数据
        for(int i = 0; i<nums.size(); i++)
        {
            pq1.push(nums[i]);
        }
        while(--k)//再将k前边的数据进行删除
        {
            pq1.pop();
        }
        return pq1.top();//最后只剩下数据k了,直接返回
    }
};

7.3priority_queue的模拟实现

模拟实现需要使用堆中的算法,所以需要先进行复习:

链接: link
向上调整算法:
在这里插入图片描述
上面的图是按照小的优先级高来画的,但是默认是大的优先级大,但是实现思路相同
在这里插入图片描述

7.3.1仿函数

我们先实现一下优先级队列的迭代器区间构造:
在这里插入图片描述

//迭代器区间构造
template<class Inputiterator>
priority_queue(Inputiterator begin, Inputiterator end)
	:_con(begin, end)
{
	//迭代器区间插入之后,要将_con设置成为大堆
	for (int i = (_con.size()-1-1)/2; i >= 0; i--)
	{
		AdjustDown(i);
	}
}

int main()
{
	int a[] = { 1, 5, 3, 9, 7 };
	//Mine::priority_queue<int> pq(a, a+sizeof(a)/sizeof(int));
	Mine::priority_queue<int> pq;//err:没有合适的默认构造可用

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

	return 0;
}

当我们不写默认构造的话,编译器会自己提供一个默认构造,但是这里我们写了默认构造,但是不想写_con,也就是容器适配器(vector)的默认构造,有没有什么办法?:
在这里插入图片描述
这需要写这个就可以强制生成一个默认构造来使用

但是我们知道,库中的优先级队列的实现可以通过传入greater或less参数来实现优先级的更改的,那么我们如何实现这个操作呢?这时候就要看仿函数这个概念了:

template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

template<class T>
struct Greater
{
	bool operator()(const T& x, const T& y) const
	{
		return x > y;
	}
};

int main()
{
	//仿函数的使用
	Less<int> LessFunc;
	cout << LessFunc(1, 2) << endl;//1
	//如果我们只看LessFunc(1, 2)这个的话,很想一个函数调用,所以被称为仿函数

	return 0;
}

可以看出,仿函数的使用还是很简单的,而仿函数调用的原理其实是:
在这里插入图片描述
仿函数在我们实现的优先级队列中的应用为:
在这里插入图片描述
但是库中在实现优先级队列时,传入的是less,创建的是大堆,传入的是greater,创建的是小堆,所以我们也要做一下更改:
在这里插入图片描述

7.3.1.1仿函数的其它应用场景
//假设我们实现了一个日期类,其中实现了日期类的传参构造,>和<的比较,以及流插入的重载
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d);
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

int main()
{
	Mine::priority_queue<Date> q1;

	q1.push({ 2024, 10, 23 });
	q1.push({ 2024, 5, 27 });
	q1.push({ 2024, 11, 2 });

	while (!q1.empty())
	{
		cout << q1.top() << " ";
		q1.pop();
	}
	cout << endl;

	return 0;
}

这里我们可以看到,该程序成功实现了我们的需求,但是我们这样改一下:

int main()
{
	//这里传入的参数为Date*类型
	Mine::priority_queue<Date*> q1;
	
	q1.push(new Date{ 2024, 10, 23 });
	q1.push(new Date{ 2024, 5, 27 });
	q1.push(new Date{ 2024, 11, 2 });

	while (!q1.empty())
	{
		cout << *q1.top() << " ";
		q1.pop();
	}
	cout << endl;

	return 0;
}

在这里插入图片描述
这时我们看到,因为传入的是地址,而地址在new开辟时是不确定的,所以通过比较地址大小来排序是完全不行的,谁最大都有可能出现,所以我们要解决这个问题:

struct Dateless
{
	bool operator()(const Date* d1, const Date* d2) const
	{
		return *d1 < *d2;
	}
};
struct Dategreater
{
	bool operator()(const Date* d1, const Date* d2) const
	{
		return *d1 > *d2;
	}
};

int main()
{
	//Mine::priority_queue<Date*> q1;
	Mine::priority_queue<Date*, vector<Date*>, Dateless> q1;

	q1.push(new Date{ 2024, 10, 23 });
	q1.push(new Date{ 2024, 5, 27 });
	q1.push(new Date{ 2024, 11, 2 });

	while (!q1.empty())
	{
		cout << *q1.top() << " ";
		q1.pop();
	}
	cout << endl;

	return 0;
}

可以看出,我们的解决方法为:再写出两个比较的类进行传入,如果我们需要比较的是两个指针的话,那么走的就是这个解引用的函数,所以仿函数的优点在于:如果库中不支持这个比较,而且我们访问不到这个类时,我们就可以自己写一个仿函数,实现我们想要的需求,但是如果我们不像每次比较时都要传参的话要怎么办呢?:这个之后会讲到!
我们现在还要看的一个是sort的传参和模板参数的问题:
在这里插入图片描述

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

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

相关文章

BFS解决最短路问题(4)_为高尔夫比赛砍树

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 BFS解决最短路问题(4)_为高尔夫比赛砍树 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48…

LeetCode-684. 冗余连接

. - 力扣&#xff08;LeetCode&#xff09; 题目 树可以看成是一个连通且 无环 的 无向 图。 给定往一棵 n 个节点 (节点值 1&#xff5e;n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间&#xff0c;且这条附加的边不属于树中已存在的边。图的信息记录于…

传输层UDP

再谈端口号 端口号&#xff1a;标识了主机上进行通信的不同的应用程序 在TCP/IP 协议中我们用“源IP”"源端口号" “目的IP”“目的端口号” “协议号”五元组来标识一个通信 用netstat -n 查看 查看网络信息&#xff0c;我们有两种命令查看网络通信1.用netsta…

Python | Leetcode Python题解之第509题斐波那契数

题目&#xff1a; 题解&#xff1a; class Solution:def fib(self, n: int) -> int:if n < 2:return nq [[1, 1], [1, 0]]res self.matrix_pow(q, n - 1)return res[0][0]def matrix_pow(self, a: List[List[int]], n: int) -> List[List[int]]:ret [[1, 0], [0, …

1 环境配置、创建功能包、编译、Cmake文件及package文件学习笔记

1 基本结构 放张 赵虚左老师的pdf截图 2 环境配置 //每次都需配置 . install/setup.bash//或者一次配置echo "source /path/to/your/workspace_name/install/setup.bash" >> ~/.bashrcsource ~/.bashrc3 创建功能包 ros2 pkg create 包名--build-type 构建类…

气象监测软件的程序设计

老师留了个作业&#xff0c;感觉挺有意思&#xff0c;记录一下 文章目录 气象监测软件的程序设计项目指导书&#xff08;一&#xff09;基本信息&#xff08;二&#xff09;项目目标&#xff08;三&#xff09;任务描述&#xff08;四&#xff09;指导内容任务 1&#xff1a;根…

电磁干扰(EMI)与电磁兼容性(EMC)【小登培训】

电磁干扰&#xff08;EMI&#xff09;和电磁兼容性&#xff08;EMC&#xff09;是每个产品在3C &#xff0c;CE认证过程中必不可少的测试项目&#xff1a; 一、电磁干扰&#xff08;EMI&#xff09; EMI&#xff08;Electromagnetic Interference&#xff09;是指电子设备在工作…

ARM学习(33)英飞凌(infineon)PSOC 6 板子学习

笔者来聊一下psoc62 系列板子的知识 1、PSOC62板子介绍 Psoc6-evaluationkit-062S2 与RT-Thread联合推出的一款32位的双core的板子&#xff0c;基于CortexM4以及CortexM0。 管脚兼容Arduio。板载DAP-Link&#xff0c;可以支持调试以及串口&#xff0c;无需外接2MB的Flash以及…

JavaEE初阶---文件IO总结

文章目录 1.文件初识2.java针对于文件的操作2.1文件系统的操作---file类2.2文件内容的操作---流对象的分类2.4字符流的操作》文本文件2.4.1异常的说明2.4.2第一种文件内容的读取方式2.4.3第二种读取方式2.4.4close的方法的介绍2.4.5close的使用优化操作2.4.6内容的写入 2.3字节…

数据结构与算法汇总整理篇——数组与字符串双指针与滑动窗口的联系学习及框架思考

数组 数组精髓&#xff1a;循环不变量原则 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;通过索引(下标)访问元素&#xff0c;索引从0开始 随机访问快(O(1)时间复杂度)&#xff1b;插入删除慢(需要移动元素)&#xff1b;长度固定(部分语言中可动态调整) 其存…

【CSS】边界三角形

有三角形 Unicode 字符。您可以在 SVG 中绘制三角形。但还有另一种在 Web 上绘制三角形的方法&#xff0c;只需使用 border 属性和一些 CSS 技巧即可。 想象一个具有粗边框的元素&#xff1a; .triangle {width: 200px;height: 200px;border: 10px solid black; }现在想象一下…

公园客流统计系统根据游客数量安排清洁人员

公园作为人们休闲娱乐的公共场所&#xff0c;游客流量时常处于动态变化中。而公园客流统计系统的应用&#xff0c;为依据游客数量合理安排清洁人员提供了有效途径&#xff0c;主要体现在以下几个方面。 一、精准掌握游客分布&#xff0c;按需调配清洁力量 公园客流统计系统能够…

Es可视化界面 ElasticHd

前言 在开发的过程中&#xff0c;有一个可视化界面工具&#xff0c;以及一个可执行的es相关语句的工具十分重要&#xff0c;主要有以下这些 1. Kibana‌&#xff1a;○ Kibana是Elastic官方提供的数据可视化工具&#xff0c;功能强大&#xff0c;支持多种图表类型&#xff0c…

REST APIs与微服务:关键差异

在构建基于微服务的应用程序时RESYful API和微服务这两个术语经常相伴出现。然而&#xff0c;它们指的是截然不同的东西。 了解 RESTful API 和微服务之间差异的最简单方式是这样&#xff1a; 微服务&#xff1a;它们是构成更大规模基于微服务的应用程序的单个服务和功能&…

雷池社区版OPEN API使用教程

OPEN API使用教程 新版本接口支持API Token鉴权 接口文档官方没有提供&#xff0c;有需要可以自行爬取&#xff0c;爬了几个&#xff0c;其实也很方便 使用条件 需要使用默认的 admin 用户登录才可见此功能版本需要 > 6.6.0 使用方法 1.在系统管理创建API TOKEN 2.发…

参会记录|2024 中国计算机大会(CNCC 2024)

前言&#xff1a;2024年10月24-26日&#xff0c;有幸在横店参加了2024年度的中国计算机大会&#xff08;CNCC&#xff09;&#xff0c;本篇博客总结本人在会议期间收听的主要报告内容及收获。 2024.10.24 上午 夏苑海岳开襟 如何实现人工智能在现实场景中的落地&#xff1f; …

解锁V2G:电动汽车如何化身电网“充电宝”,最重要的是将成为一种赚钱的方式!

解锁V2G&#xff1a;电动汽车如何化身电网“充电宝” 随着新能源汽车的普及&#xff0c;电动汽车不仅仅是交通工具&#xff0c;还能成为电网的重要补充。今天&#xff0c;我们来聊聊V2G&#xff08;Vehicle-to-Grid&#xff09;技术&#xff0c;看看它是如何实现车辆与电网之间…

Git相关介绍

基本概念 关注&#xff08;watch&#xff09; 关注项目&#xff0c;当项目更新可以接收到通知 事物卡片&#xff08;Issue&#xff09; 发现代码BUG&#xff0c;但是目前没有成型代码&#xff0c;需要讨论时用 Git工作区域 工作区 添加、编辑、修改文件等动作 暂存区 …

蓝海创意云入选中国夏衍电影学会工业与科技影视专业委员会成员单位

党的二十届三中全会指出&#xff0c;科技自立自强是中国式现代化的有力保障。科技兴则民族兴&#xff0c;科技强则国家强。为深入的贯彻落实这一重要部署&#xff0c;推动工业与科技领域的融合发展充分发挥电影艺术在传播科技创新精神、展现工业发展成就方面的独特作用&#xf…

【AI大模型】使用谷歌 Gemini API 构建自己的 ChatGPT(二)

上一篇文章 【AI大模型】使用谷歌 Gemini API 构建自己的 ChatGPT&#xff08;一&#xff09;&#x1f680;我们介绍了 Gemini 是什么&#xff0c;以及如何使用Gemini 构建一个多模态的聊天场景示例。这一篇我们使用 langchain 与 Gemini 集成构建应用&#xff1a; 将 Langcha…