【数据结构】无向图的最小生成树(Prime,Kruskal算法)

文章目录

  • 前言
  • 一、最小生成树
  • 二、Kruskal算法
    • 1.方法:
    • 2.判断是否成环
    • 3.代码实现
  • 三、 Prim算法
    • 1.方法:
    • 2.代码
  • 四、源码


前言

连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图

强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图

生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边

连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树就不在连通;反之,在其中引入任何一条新边,都会形成一条回路

一、最小生成树

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:

  1. 只能使用图中的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接图中的n个顶点
  3. 选用的n-1条边不能构成回路

构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略

二、Kruskal算法

里面涉及一些图的代码,不了解的可以参考之前我写的【数据结构】图的创建(邻接矩阵,邻接表)以及深度广度遍历(BFS,DFS)

1.方法:

任给一个有n个顶点的连通网络N={V,E},
首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分
量,
其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分
量,则将此边加入到G中。如此重复,直到所有顶点在同一个连通分量上为止。

核心每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树 在这里插入图片描述

2.判断是否成环

我们要保证不能成环需要保证选入某条边之前,这条边两个顶点本身就不连通,换种说法就是两个顶点没有公共的顶点或者不在同一个集合当中,这个时候我们就可以考虑之前学的并查集的知识来解决了,并查集就是解决两个元素的联合与判断是否在一个结合中的数据结构。

不知道小伙伴可以去看看之前我写的【数据结构】并查集的简单实现,合并,查找(C++)

3.代码实现

typedef Graph<V, W, MAX_W, false> Self;

		//建立边的类,保存边的两个顶点下标和权值
		struct Edge {
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci,size_t dsti,W w)
				:_srci(srci),
				_dsti(dsti),
				_w(w)
			{}

			bool operator>(const Edge& e)const {
				return _w > e._w;//小根堆判断
			}

		};

		W Kruskal(Self& minTree)
		{
			//minTree为最小生成树,刚开始什么都没有
			size_t n = _vertexs.size();

			//初始化最小生成树
			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}


			//我们每次选边从全部边中选出最小的(保证不构成回路的情况)
			//所以我们可以考虑用小根堆来存入边,这样每次方便找最小的
			priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					if (i < j && _matrix[i][j] != MAX_W)
					{
						//将所有有效值边放进堆中
						minque.push(Edge(i, j, _matrix[i][j]));
					}
				}
			}

			 
			int size = 0;
			W totalW = W();
			UnionFindSet ufs(n); 

			// 选出n-1条边
			while (!minque.empty())
			{
				//取出最小边
				Edge min = minque.top();
				minque.pop();

				if (!ufs.InSet(min._srci, min._dsti))//判断是否成环
				{
				
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] <<":"<<min._w << endl;
					//不成环就将当前边放入最小生成树当中
					    
					minTree._AddEdge(min._srci, min._dsti, min._w);
					//并把这两个顶点放入同一个并查集集合当中
					ufs.Union(min._srci, min._dsti);
					++size;
					totalW += min._w;//权值总和增加
				}
				else
				{
					//cout << "构成环:";
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
				 
			}

			if (size == n - 1)//边数选够说明最小生成树
				//创建成功
			{
				return totalW;
			}
			else
			{
				return W();
			}
		}

三、 Prim算法

1.方法:

Prim算法采用的局部贪心的策略,从当前选入的局部的点中选取最小的边
在这里插入图片描述
在这里插入图片描述

Prim算法的优点在于他不会构成环,他是以顶点为单位进行筛选,X表示已经选入的点,Y中存的未选入的点,X从Y中选顶点加入到X中,然后Y删除这个顶点,(若X中出现两个相同顶点则构成环),但这种情况基本不大可能出现,因为我们每次都是从Y中选,都是X中没有的顶点

2.代码

typedef Graph<V, W, MAX_W, false> Self;

		//建立边的类,保存边的两个顶点下标和权值
		struct Edge {
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci,size_t dsti,W w)
				:_srci(srci),
				_dsti(dsti),
				_w(w)
			{}

			bool operator>(const Edge& e)const {
				return _w > e._w;//小根堆判断
			}

		};
W Prim(Self& minTree, const W& src)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();

			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}

			 

			vector<bool> X(n, false);
			vector<bool> Y(n, true);
			X[srci] = true;
			Y[srci] = false;

			 
			// 从X->Y集合中连接的边里面选出最小的边
			priority_queue<Edge, vector<Edge>, greater<Edge>> minq;

			// 先把srci连接的边添加到小根堆中
			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[srci][i] != MAX_W)
				{
					minq.push(Edge(srci, i, _matrix[srci][i]));
				}
			}

			cout << "Prim开始选边" << endl;
			size_t size = 0;//选出边的数量
			W totalW = W();//权值之和
			while (!minq.empty())
			{
				Edge min = minq.top();
				minq.pop();

				// 最小边的目标点也在X集合,则构成环
				if (X[min._dsti])
				{
					//cout << "构成环:";
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
				else
				{
					//从Y中选出顶点
					minTree._AddEdge(min._srci, min._dsti, min._w);//加入最小生成树
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
					X[min._dsti] = true;
					Y[min._dsti] = false;
					++size;
					totalW += min._w;
					if (size == n - 1)
						break;

					//把新加入顶点相关的边都放入小根堆中
					for (size_t i = 0; i < n; ++i)
					{
						if (_matrix[min._dsti][i] != MAX_W && Y[i])
						{
							minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
						}
					}
				}
			}

			if (size == n - 1)
			{
				return totalW;
			}
			else
			{
				return W();
			}

四、源码

namespace matrix {
	//V为顶点类型,W为边权值类型,MAX_W为权值最大值也就是无效值
	//Direction用来判断是不是有向图,false为无向图
	template<class V,class W,W  MAX_W=INT_MAX,bool Direction=false>
	class Graph {
	public:
		Graph() = default;
		Graph(const V* a, size_t n) {
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; i++) {
				_vertexs.push_back(a[i]);
				_indexMap[a[i]] = i;
				//将顶点存入_vertexs,下标映射存进map
			}

			_matrix.resize(n);
			for (size_t i = 0; i < _matrix.size(); i++) {
				_matrix[i].resize(n, MAX_W);
				//邻接矩阵默认初始值为无效值
			}
		}

		size_t GetVertexIndex(const V& v) {
			//获得对应顶点在数组中的下标
			auto it = _indexMap.find(v);
			if (it != _indexMap.end()) {
				return it->second;
				//有这个顶点返回其下标
			}
			else {
				throw("顶点不存在");
				return -1;
			}
		}

		void _AddEdge(size_t srci, size_t dsti, const W& w) {
			//存入权值
			_matrix[srci][dsti] = w;
			if (Direction == false) {
				_matrix[dsti][srci] = w;
				//无向图要两个方向都存
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w) {
			//添加边与顶点的关系。从src到dst方向的关系
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			//先获取其对应的下标
			_AddEdge(srci, dsti, w);
		}

		void Print() {
			for (size_t i = 0; i < _vertexs.size(); i++) {
				cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
			}//打印顶点集
			cout << endl;


			//打印邻接矩阵
			for (size_t i = 0; i < _matrix.size(); i++) {
				cout << i << " ";
				for (size_t j = 0; j < _matrix[i].size(); j++) {
					if (_matrix[i][j] == MAX_W) {
						printf("%4c", '*');
					}
					else {
						printf("%4d", _matrix[i][j]);
					}
				}
				cout << endl;
			 }
		}

	 
	 


		void BFS(const V& src) {
			size_t srci = GetVertexIndex(src);
			queue<int>q;
			q.push(srci);
			vector<bool>visited(_vertexs.size(), false);
			visited[srci] = true;//标记这个顶点被访问过了
			int levelSize = 1;
			while (!q.empty()) {
				//levelSize为当前层的大小
				for (size_t i = 0; i < levelSize; i++) {
					int front = q.front();
					q.pop();
					cout << front << ":" << _vertexs[front]<<" ";

					for (size_t i = 0; i < _vertexs.size(); i++) {
						if (_matrix[front][i] != MAX_W && visited[i] == false) {
							q.push(i);
							visited[i] = true;//标记这个顶点被访问过了
						}
					}
				}
				levelSize = q.size();//更新当前层的数量
				cout << endl;
			}
			cout << endl;
		}


		void _DFS(size_t srci, vector<bool>& visited) {
			cout << srci << ":" << _vertexs[srci] << endl;
			visited[srci] = true;//标记这个顶点被访问过了
			for (size_t i = 0; i < _vertexs.size(); i++) {
				if (_matrix[srci][i] != MAX_W && visited[i] == false) {
					_DFS(i, visited);
				}
			}
		}

		void DFS(const V& src) {
			size_t srci = GetVertexIndex(src);
			vector<bool>visited(_vertexs.size(), false);

			_DFS(srci, visited);
		}


 

	 
		 
		
		
		 

		typedef Graph<V, W, MAX_W, false> Self;

		//建立边的类,保存边的两个顶点下标和权值
		struct Edge {
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci,size_t dsti,W w)
				:_srci(srci),
				_dsti(dsti),
				_w(w)
			{}

			bool operator>(const Edge& e)const {
				return _w > e._w;//小根堆判断
			}

		};

		W Kruskal(Self& minTree)
		{
			//minTree为最小生成树,刚开始什么都没有
			size_t n = _vertexs.size();

			//初始化最小生成树
			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}


			//我们每次选边从全部边中选出最小的(保证不构成回路的情况)
			//所以我们可以考虑用小根堆来存入边,这样每次方便找最小的
			priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					if (i < j && _matrix[i][j] != MAX_W)
					{
						//将所有有效值边放进堆中
						minque.push(Edge(i, j, _matrix[i][j]));
					}
				}
			}

			 
			int size = 0;
			W totalW = W();
			UnionFindSet ufs(n); 

			// 选出n-1条边
			while (!minque.empty())
			{
				//取出最小边
				Edge min = minque.top();
				minque.pop();

				if (!ufs.InSet(min._srci, min._dsti))//判断是否成环
				{
				
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] <<":"<<min._w << endl;
					//不成环就将当前边放入最小生成树当中
					    
					minTree._AddEdge(min._srci, min._dsti, min._w);
					//并把这两个顶点放入同一个并查集集合当中
					ufs.Union(min._srci, min._dsti);
					++size;
					totalW += min._w;//权值总和增加
				}
				else
				{
					//cout << "构成环:";
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
				 
			}

			if (size == n - 1)//边数选够说明最小生成树
				//创建成功
			{
				return totalW;
			}
			else
			{
				return W();
			}
		}

		W Prim(Self& minTree, const W& src)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();

			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}

			 

			vector<bool> X(n, false);
			vector<bool> Y(n, true);
			X[srci] = true;
			Y[srci] = false;

			 
			// 从X->Y集合中连接的边里面选出最小的边
			priority_queue<Edge, vector<Edge>, greater<Edge>> minq;

			// 先把srci连接的边添加到小根堆中
			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[srci][i] != MAX_W)
				{
					minq.push(Edge(srci, i, _matrix[srci][i]));
				}
			}

			cout << "Prim开始选边" << endl;
			size_t size = 0;//选出边的数量
			W totalW = W();//权值之和
			while (!minq.empty())
			{
				Edge min = minq.top();
				minq.pop();

				// 最小边的目标点也在X集合,则构成环
				if (X[min._dsti])
				{
					//cout << "构成环:";
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
				else
				{
					//从Y中选出顶点
					minTree._AddEdge(min._srci, min._dsti, min._w);//加入最小生成树
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
					X[min._dsti] = true;
					Y[min._dsti] = false;
					++size;
					totalW += min._w;
					if (size == n - 1)
						break;

					//把新加入顶点相关的边都放入小根堆中
					for (size_t i = 0; i < n; ++i)
					{
						if (_matrix[min._dsti][i] != MAX_W && Y[i])
						{
							minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
						}
					}
				}
			}

			if (size == n - 1)
			{
				return totalW;
			}
			else
			{
				return W();
			}
		}

	private:
		vector<V>_vertexs;//顶点集合
		map<V, int>_indexMap;//存顶点与数组下标的映射关系
		vector<vector<W>>_matrix;//邻接矩阵
	};



 }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue前端上传图片到阿里云OSS,超详细上传图片与视频教程

vue前端直传图片与视频到阿里云OSS 1. 简介与日常使用2. 为什么要这么干&#xff1f;是因为我司后端不行吗&#xff1f;&#xff1f;&#xff1f;&#xff08;确实&#xff01;&#xff09;3. vue前端直传的操作4. 如何上传到阿里OSS指定文件夹呢? 1. 简介与日常使用 阿里云…

(CVE-2019-9193)PostgreSQL 高权限命令执行漏洞的复现

漏洞概述 PostgreSQL是一个功能强大对象关系数据库管理系统(ORDBMS)。由于9.3增加一个“COPY TO/FROM PROGRAM”功能。这个功能就是允许数据库的超级用户以及pg_read_server_files组中的任何用户执行操作系统命令。 影响版本 9.3-11.2 环境搭建 1. 本次漏洞环境使用vulhub中…

java: -source 7 中不支持 lambda 表达式 (请使用 -source 8 或更高版本以启用 lambda 表达式)

目录 1、检查项目中 JDK 的设置&#xff1a; 2、检查模块中 JDK 的设置&#xff1a; 3、检查Idea 中的SDK设置 4、检查 IDEA 中 JDK 的设置&#xff08;我出现的问题在这&#xff09;&#xff1a; 今天遇见了一个报错&#xff1a; 问题产生的原因是 JDK 版本太低&#xf…

揭秘2024年最新骨传导耳机排行榜,全面解析骨传导耳机排行榜品牌

随着科技的飞速发展&#xff0c;人们对音质和舒适度的需求也在不断提高。骨传导耳机作为一种独特的耳机类型&#xff0c;近年来逐渐受到了消费者的关注。它通过将声音通过骨骼传导&#xff0c;而不是传统的耳道传递&#xff0c;既能保证音质&#xff0c;又能避免长时间佩戴耳机…

缓存高可用:缓存如何保证高可用?

前面我们提到了缓存集群的负载均衡策略&#xff0c;保证缓存服务的高可用&#xff0c;集群策略是最常用的&#xff0c;本文我们以 Redis 为例&#xff0c;分析一下单点缓存如何扩展到集群&#xff0c;以及集群部署的几种常见模式。 Redis 的主从复制 集群实现依靠副本&#x…

ES-mapping

类似数据库中的表结构定义&#xff0c;主要作用如下 定义Index下的字段名( Field Name) 定义字段的类型&#xff0c;比如数值型、字符串型、布尔型等定义倒排索引相关的配置&#xff0c;比如是否索引、记录 position 等 index_options 用于控制倒排索记录的内容&#xff0c;有如…

人大金仓Kingbase数据库备份和还原

前言 最近在项目开发过程中&#xff0c;使用了国产数据库人大金仓&#xff08;即Kingbase数据库&#xff09;&#xff0c;在使用过过程中需要对数据库进行备份与还原&#xff0c;在此对相关的命令进行简单介绍&#xff0c;以备不时之需。 Linux环境下安装人大金仓可参考此篇文…

猜数字游戏 C语言xdoj490

问题描述 猜数字游戏是令游戏机随机产生一个 100 以内的正整数&#xff0c;用户输入一个数对其进行猜测&#xff0c;需要你编写程序自动对其与随机产生的被猜数进行比较&#xff0c;并提示大了&#xff08;“Too big”&#xff09;&#xff0c;还是小了&#xff08;“Too smal…

C# WPF上位机开发(从demo编写到项目开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 C# WPF编程&#xff0c;特别是控件部分&#xff0c;其实学起来特别快。只是后面多了多线程、锁、数据库、网络这部分稍微复杂一点&#xff0c;不过…

Ubuntu 常用命令之 mkfs 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 mkfs 是在 Linux 和其他 Unix-like 系统中用于创建文件系统的命令。在 Ubuntu 系统中&#xff0c;mkfs 命令也是用于创建文件系统的。mkfs 是一个包装器&#xff0c;它会根据用户指定的文件系统类型调用相应的程序。 mkfs 命令的…

阅读笔记-Minimum margin loss for deep face recognition

Minimum margin loss for deep face recognition 深度人脸识别的最小边缘损失 1、这篇论文要解决什么问题&#xff1f;要验证一个什么科学假设&#xff1f; 以往的损失函数不能解决类不平衡数据集存在的边际偏差问题&#xff0c;即所谓的长尾分布。MS-Celeb-1M和megface都存…

Ubuntu 22.04.3 Server通过修改yaml配置文件方法设置静态IP

目录 1.查看网卡信息 2.修改yaml配置文件 3.应用新的网络配置 4.重新启动网络服务 文章内容 本文介绍Ubuntu 22.04.3 Server系统通过修改yaml配置文件配置静态 ip 的方法。 1.查看网卡信息 使用ifconfig命令查看网卡信息获取网卡名称​ 如果出现Command ifconfig not fo…

LT8711UX,LT8711UXC ,LT8711UXD ,LT8711UXE1,LT8711UXE2的区别,选型的工程师注意了!!!

LT8711UX,LT8711UXC ,LT8711UXD ,LT8711UXE1,LT8711UXE2做为龙迅的重点物料&#xff0c;大家都不陌生。 可是它们不一样的后缀&#xff0c;又有什么不一样的应用区别呢&#xff1f; LT8711***均为DP/Type-C to HDMI系列的芯片。 LT8711UX&#xff0c;是一款带音频的Type-C…

【String str = new String(“hollis“) 创建了几个对象?】

✅典型解析 创建的对象数应该是1个或者2个。 首先要清楚什么是对象? Java是一种面向对象的语言&#xff0c;而Java对象在JVM中的存储也是有一定的结构的&#xff0c;在HotSpot虚机中&#xff0c;存储的形式就是oop-klass model&#xff0c;即ava对象模型。我们在Java代码中&am…

基于iOS平台的车牌识别表情识别项目

基于iOS平台的车牌识别&&表情识别项目 简介 ​ 该项目客户端搭载于iOS平台&#xff0c;服务端搭载于阿里云服务器&#xff0c;主要功能是通过拍照或选取相册图片来进行车牌的识别以及人脸表情识别。本文便是对项目整体流程设计思路和具体实现做一个详细介绍。 整体实…

关于“Python”的核心知识点整理大全37

目录 13.6.2 响应外星人和飞船碰撞 game_stats.py settings.py alien_invasion.py game_functions.py ship.py 注意 13.6.3 有外星人到达屏幕底端 game_functions.py 13.6.4 游戏结束 game_stats.py game_functions.py 13.7 确定应运行游戏的哪些部分 alien_inva…

vue3 + TypeScript使用国际化

vue3 TypeScript使用国际化 本文使用了 Vite 构建工具创建的vue3项目Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块Vite 官方中文文档当然如果你的vue3项目未使用vite,你也可以为你的旧项目提提速&#xff0c;安装vite &#xff0c;安装方法在上一个博客…

【LeetCode:2866. 美丽塔 II | 单调栈 + 前后缀数组】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

AI“百模大战”现状:向垂直、B端谋场景,算力仍是主要制约因素

文章目录 每日一句正能量前言AI&#xff08;人工智能&#xff09;大模型正“飞入”百姓家和行业中。向垂直、B端谋场景算力仍是主要制约因素构建“数据-模型-应用”飞轮后记 每日一句正能量 我们必须在失败中寻找胜利&#xff0c;在绝望中寻求希望。 前言 在当前快速发展的人工…

MACBOOK 通过iterm2连接堡垒机跳转服务器

本公司是通过齐治堡垒机连接远程服务器的环境&#xff0c;因为连接过程中需要自动输入密码和选择主机&#xff0c;所以要使用expect工具&#xff0c;编写expect脚本remote.exp #!/usr/bin/expectif { $argc ! 7 } {send_user "usage: expect $argv0 \[JUMP_HOST\] \[JUM…