栈、共享栈、链式栈(C++实现)

news2024/11/28 4:36:58

文章目录

  • 前言
  • 1. 栈的顺序存储(顺序栈)
  • 2. 栈的基本操作
    • 🍑 入栈操作
    • 🍑 出栈操作
    • 🍑 获取栈顶元素
    • 🍑 获取栈的长度
    • 🍑 判断是否为空栈
    • 🍑 判断栈是否满了
    • 🍑 打印栈内的元素
    • 🍑 测试函数
  • 3. 共享栈
    • 🍑 代码实现
  • 4. 链式栈
    • 🍑 代码实现
  • 5. 总结


前言

栈是一种线性表。不过它只能在一端进行插入和删除操作,先入栈的数据只能后出来,而后入栈的数据只能先出来。所以栈具有先进后出或者后进先出的特性。通常来说,我们可以把栈理解为一种 受限的线性表

如果我们把栈比成一个类似木桶这样的容器,栈有两端,把允许进行插入和删除操作的一端称为 栈顶(top)也就是桶口,或者称为线性表的表尾,而另一端称为 栈底(bottom)也就是桶底,或者称为线性表的表头。不包含任何数据的栈,叫做空栈(空线性表)。

在这里插入图片描述

整体结构理清之后,我们再说相关的操作。向栈中插入元素,可以叫做 入栈 或进栈,从栈中删除元素,就叫 出栈。除了能够在表尾插入和删除数据外,对于栈这种数据结构,在任何其他位置插入和删除数据都不应该被允许。你只能提供符合这个规定的操作接口,否则实现的就不是栈了。

栈也被称为 后进先出(Last In First Out:LIFO)的线性表,这意味着最后放入到栈里的数据(插入数据)只能最先被从栈中拿出来(删除数据)。

其实,我们生活中满足栈这种后进先出的情形非常多,比如往抽屉里放东西,先放进去的肯定会被堆到最里面,所以只能最后取出,而最后放入的物品往往在最前面或者最上面,所以会被最先取出。

如果用示意图表示用栈存取数据的过程,就会像下图一样:

在这里插入图片描述

在上图中,如果分别将数据 a1、a2、a3、a4、a5 存入栈中,那么在将数据出栈的时候,顺序就应该是 a5、a4、a3、a2、a1(与入栈顺序正好相反)。

栈是 受限的线性表,比如因为只能在栈顶进行元素的插入和删除操作,所以也无法指定插入和删除操作的位置,所以,栈所支持的操作,可以理解为线性表操作的子集,一般包括栈的创建、入栈(增加数据)、出栈(删除数据)、获取栈顶元素(查找数据)、判断栈是否为空或者是否已满等操作。

1. 栈的顺序存储(顺序栈)

所谓顺序栈,就是顺序存储(用一段连续的内存空间依次存储)栈中的数据。

这里有 2 种保存数据的方案:

  • 通过为一维数组 静态 分配内存的方式来保存数据。
  • 通过为一维数组 动态 分配内存的方式来保存数据。

为了顺序栈中数据存满时可以对栈进行扩容,在这里,我会采用第 2 种保存数据的方案来编写顺序栈的实现代码。

此外,为了考虑到元素存取的便利性,将数组下标为 0 的一端作为栈底最合适。

2. 栈的基本操作

先进行类定义、初始化以及释放操作。

#define InitSize 10 //动态数组的初始尺寸
#define IncSize 5 //当动态数组存满数据后每次扩容所能多保存的数据元素数量

template <typename T>
class SeqStack
{
public:
	SeqStack(int length = InitSize); //构造函数,参数可以有默认值
	~SeqStack(); //析构函数

public:
	bool Push(const T& e); //入栈(增加数据)
	bool Pop(T & e);         //出栈(删除数据),也就是删除栈顶数据
	bool GetTop(T& e);  //读取栈顶元素,但该元素并没有出栈而是依旧在栈中

	void DispList(); //输出顺序栈中的所有元素
	int  ListLength();  //获取顺序栈的长度(实际拥有的元素数量)

	bool IsEmpty(); //判断顺序栈是否为空
	bool IsFull(); //判断顺序栈是否已满

private:
	void IncreaseSize(); //当顺序栈存满数据后可以调用此函数为顺序栈扩容

private:
	T* m_data; //存放顺序栈中的元素
	int m_maxsize; //动态数组最大容量
	int m_top;	//栈顶指针(用作数组下标),指向栈顶元素,该值为-1表示空栈
};

//通过构造函数对顺序栈进行初始化
template <typename T>
SeqStack<T>::SeqStack(int length)
{
	m_data = new T[length];  //为一维数组动态分配内存
	m_maxsize = length;      //顺序栈最多可以存储m_maxsize个数据元素
	m_top = -1;              //空栈
}

//通过析构函数对顺序栈进行资源释放
template <typename T>
SeqStack<T>::~SeqStack()
{
	delete[] m_data;
}

在主函数中,加入代码创建一个初始大小为 10 的顺序栈对象。

int main()
{
	SeqStack<int> st(10);
	return 0;
} 

创建好以后,那么顺序栈看起来就会是下图的样子,此时是一个空栈:

在这里插入图片描述

🍑 入栈操作

入栈,增加数据,通常时间复杂度为 O ( 1 ) O(1) O(1),但一旦需要扩容,时间复杂度就会变成 O ( n ) O(n) O(n)

代码实现

template <typename T>
bool SeqStack<T>::Push(const T& e)
{
	if (IsFull() == true)
	{
		IncreaseSize(); //扩容
	}

	m_data[++m_top] = e; //存放元素e,栈顶指针向后走
	return true;
}

当顺序栈存满数据后可以调用此函数为顺序栈扩容,时间复杂度为 O ( n ) O(n) O(n)

template<class T>
void SeqStack<T>::IncreaseSize()
{
	T* p = m_data;
	m_data = new T[m_maxsize + IncSize]; //重新为顺序栈分配更大的内存空间	
	for (int i = 0; i <= m_top; i++)
	{
		m_data[i] = p[i];                //将数据复制到新区域
	}
	m_maxsize = m_maxsize + IncSize;     //顺序栈最大长度增加IncSize
	delete[] p;                          //释放原来的内存空间
}

🍑 出栈操作

出栈,删除数据,也就是删除栈顶数据,时间复杂度为 O ( 1 ) O(1) O(1)

template <typename T>
bool SeqStack<T>::Pop(T& e)
{
	if (IsEmpty() == true)
	{
		cout << "当前顺序栈为空,不能进行出栈操作!" << endl;
		return false;
	}

	e = m_data[m_top--];  //栈顶元素值返回到e中。
	
	return true;
}

🍑 获取栈顶元素

读取栈顶元素,但该元素并没有出栈而是依旧在栈顶中,因此 m_top 值不会发生改变,时间复杂度为 O ( 1 ) O(1) O(1)

代码实现

template <typename T>
bool SeqStack<T>::GetTop(T& e)
{
	if (IsEmpty() == true)
	{
		cout << "当前顺序栈为空,不能读取栈顶元素!" << endl;
		return false;
	}

	e = m_data[m_top];  //栈顶元素返回到e中。
	return true;
}

🍑 获取栈的长度

获取顺序栈的长度(实际拥有的元素数量),时间复杂度为 O ( 1 ) O(1) O(1)

代码实现

template<class T>
int SeqStack<T>::ListLength()
{
	return m_top + 1;
}

🍑 判断是否为空栈

判断顺序栈是否为空,时间复杂度为 O ( 1 ) O(1) O(1)

代码实现

template<class T>
bool SeqStack<T>::IsEmpty()
{
	if (m_top == -1)
	{
		return true;
	}
	return false;
}

🍑 判断栈是否满了

判断顺序栈是否已满,时间复杂度为 O ( 1 ) O(1) O(1)

代码实现

template<class T>
bool SeqStack<T>::IsFull()
{
	if (m_top >= m_maxsize - 1)
	{
		return true;
	}
	return false;
}

🍑 打印栈内的元素

输出顺序栈中的所有元素,时间复杂度为 O ( n ) O(n) O(n)

代码实现

template<class T>
void SeqStack<T>::DispList()
{
	//按照从栈顶到栈底的顺序来显示数据
	for (int i = m_top; i >= 0; --i)
	{
		cout << m_data[i] << " ";  //每个数据之间以空格分隔
	}
	cout << endl; //换行
}

🍑 测试函数

在主函数中,增加测试代码。

代码实现

int main()
{
	SeqStack<int> st(10);
	
	//入栈 
	st.Push(10); 
	st.Push(20); 
	st.Push(30); 
	st.Push(40); 
	st.Push(50); 
	
	//打印
	st.DispList(); 
	
	//获取栈顶元素
	int elem = 0;
	st.GetTop(elem);
	cout << elem << endl; 
	
	//出栈 
	st.Pop(elem);
	st.Pop(elem);
	
	//打印
	st.DispList(); 
	
	return 0;
} 

运行结果如下:

在这里插入图片描述

在有的实现栈的代码中,会让 m_top 的初始值等于 0(指向 0 的位置),那么判断栈是否为空的代码(IsEmpty 函数)也就是判断 m_top 是否等于 0,而判断栈满(IsFull 函数)的条件也应该变成 if (m_top >= m_maxsize)

这种实现方式,实际就是让 m_top 代表下一个可以放入栈中的元素的下标,当数据入栈(Push 函数)时,代码行 m_top++ 和代码行 m_data[m_top] = e 的执行就需要互换顺序,而当数据出栈(Pop 函数)时,代码行 e = m_data[m_top] 和代码行 m_top-- 的执行也需要互换顺序。

3. 共享栈

所谓共享栈,就是两个顺序栈共享存储空间。为什么会提出这个概念呢?

之前我们提到的顺序栈,一个比较大的缺点是保存数据的空间初始尺寸不好确定,如果太大,就会浪费空间,如果太小,那么存满数据后再入栈新数据就需要扩容,而扩容就又需要开辟一整块更大的新区域并将原有数据复制到新区域,操作起来比较耗费性能。

不过,我们可以设想一下。假设有两个相同数据类型的顺序栈,如果分别为他们开辟了保存数据的空间,那是不是就可能出现,第一个栈的数据已经存满了而另一个栈中还有很多存储空间的情形呢?那么,如果开辟出来一块保存数据的空间后,让这两个栈同时使用,也就是共享这块空间,是不是也许就能达到最大限度利用这块空间、减少浪费的目的呢?这就是共享栈的含义。

下面直接给出共享栈的实现代码。

🍑 代码实现

代码实现

//共享栈
template <typename T> //T代表数组中元素的类型
class ShareStack
{
public:
	ShareStack(int length = InitSize)    //构造函数,参数可以有默认值
	{
		m_data = new T[length];  //为一维数组动态分配内存
		m_maxsize = length;      //共享栈最多可以存储m_maxsize个数据元素
		m_top1 = -1;             //顺序栈1的栈顶指针为-1,表示空栈
		m_top2 = length;         //顺序栈2的栈顶指针为length,表示空栈
	}
	~ShareStack()                        //析构函数
	{
		delete[] m_data;
	}

public:
	bool IsFull()                        //判断共享栈是否已满
	{
		if (m_top1 + 1 == m_top2)
		{
			return true;
		}
		return false;
	}

	bool Push(int stackNum, const T& e) //入栈(增加数据),参数stackNum用于标识栈1还是栈2
	{
		if (IsFull() == true)
		{
			//共享栈满了,你也可以自行增加代码来支持动态增加共享栈的容量,这里简单处理,直接返回false
			cout << "共享栈已满,不能再进行入栈操作了!" << endl;
			return false;
		}
		if (stackNum == 1)
		{
			//要入的是顺序栈1
			m_top1++;           //栈顶指针向后走
			m_data[m_top1] = e;
		}
		else
		{
			//要入的是顺序栈2
			m_top2--;
			m_data[m_top2] = e;
		}
		return true;
	}

	bool Pop(int stackNum, T& e)  //出栈(删除数据),也就是删除栈顶数据
	{
		if (stackNum == 1)
		{
			//要从顺序栈1出栈
			if (m_top1 == -1)
			{
				cout << "当前顺序栈1为空,不能进行出栈操作!" << endl;
				return false;
			}
			e = m_data[m_top1];  //栈顶元素值返回到e中
			m_top1--;
		}
		else
		{
			//要从顺序栈2出栈
			if (m_top2 == m_maxsize)
			{
				cout << "当前顺序栈2为空,不能进行出栈操作!" << endl;
				return false;
			}
			e = m_data[m_top2];
			m_top2++;
		}
		return true;
	}

private:
	T* m_data; //存放共享栈中的元素
	int m_maxsize; //动态数组最大容量
	int m_top1; //顺序栈1的栈顶指针
	int m_top2; //顺序栈2的栈顶指针    
};

从代码中可以看到,既然是两个顺序栈共享同一块内存空间,那么就需要引入两个栈顶指针(m_top1m_top2)来分别标识这两个顺序栈的栈顶位置。顺序栈 1 的栈底位置在最下面,而顺序栈 2 的栈底位置在最上面。

同时,注意阅读判断共享栈是否已满的代码(IsFull)以及入栈和出栈(Push、Pop)的代码。如果对顺序栈 1 进行入栈操作,则 m_top1 要递增,数据要从下向上存储。如果对顺序栈 2 进行入栈操作,则 m_top2 要递减,数据从上向下存储。

这样的话,从逻辑上看,实现的是两个栈,但这两个栈又是共享着同一块物理内存的,从而提高内存利用率。如下图所示:

在这里插入图片描述

4. 链式栈

链式栈,就是链式存储方式来实现的栈。我们知道单链表的插入操作 ListInsert 方法,其第一个参数用于指定元素要插入的位置,如果把该参数值设置为 1,就是链式栈的入栈操作。对于单链表的删除操作 ListDelete 方法,其参数用于指定要删除的元素位置,如果把该参数值也设置为 1,就是链式栈的出栈操作。

可以发现,链式栈其实就是一个单链表,只不过人为的规定只能在单链表的第一个位置进行插入(入栈)和删除(出栈)操作,即链表头这一端是栈顶。

链式栈的实现代码和单链表的实现代码非常类似,可以把链式栈理解成受限的单链表。但对于链式栈来讲,考虑到只在链表头位置插入数据,所以链式栈一般不需要带头节点。

🍑 代码实现

代码实现

//链式栈中每个节点的定义
template <typename T> //T代表数据元素的类型
struct StackNode
{
	T data;  //数据域,存放数据元素
	StackNode<T>* next;  //指针域,指向下一个同类型(和本节点类型相同)节点
};

//链式栈的定义
template <typename T>
class LinkStack
{
public:
	LinkStack();  //构造函数
	~LinkStack(); //析构函数

public:
	bool Push(const T& e); //入栈元素e
	bool Pop(T& e);        //出栈(删除数据),也就是删除栈顶数据
	bool GetTop(T& e);     //读取栈顶元素,但该元素并没有出栈而是依旧在栈中

	void DispList(); //输出链式栈中的所有元素
	int  ListLength(); //获取链式栈的长度
	bool Empty(); //判断链式栈是否为空

private:
	StackNode<T>* m_top; //栈顶指针
	int m_length; //链式栈当前长度
};


//通过构造函数对链式栈进行初始化
template <typename T>
LinkStack<T>::LinkStack()
{
	m_top = nullptr;
	m_length = 0;
}


//通过析构函数对链式栈进行资源释放
template <typename T>
LinkStack<T>::~LinkStack()
{
	T tmpnousevalue = { 0 };
	while (Pop(tmpnousevalue) == true) {} //把栈顶元素删光,while循环也就退出了,此时也就是空栈了
}


//入栈元素e,时间复杂度为O(1)
template <typename T>
bool LinkStack<T>::Push(const T& e)
{
	StackNode<T>* node = new StackNode<T>;
	node->data = e;
	node->next = m_top;
	m_top = node;
	m_length++;
	return true;
}


//出栈(删除数据),也就是删除栈顶数据,时间复杂度为O(1)
template <typename T>
bool LinkStack<T>::Pop(T& e)
{
	if (Empty() == true) //链式栈为空
		return false;

	StackNode<T>* p_willdel = m_top;
	m_top = m_top->next;
	m_length--;
	e = p_willdel->data;
	delete p_willdel;
	return true;
}


//读取栈顶元素,但该元素并没有出栈而是依旧在栈中
template <typename T>
bool LinkStack<T>::GetTop(T& e)
{
	if (Empty() == true) //链式栈为空
		return false;

	e = m_top->data;
	return true;
}


//输出链式栈中的所有元素,时间复杂度为O(n)
template<class T>
void LinkStack<T>::DispList()
{
	if (Empty() == true) //链式栈为空
		return;

	StackNode<T>* p = m_top;
	while (p != nullptr)
	{
		cout << p->data << " ";  //每个数据之间以空格分隔
		p = p->next;
	}
	cout << endl; //换行
}


//获取链式栈的长度,时间复杂度为O(1)
template<class T>
int  LinkStack<T>::ListLength()
{
	return m_length;
}


//判断链式栈是否为空,时间复杂度为O(1)
template<class T>
bool LinkStack<T>::Empty()
{
	if (m_top == nullptr) //链式栈为空
	{
		return true;
	}
	return false;
}

与顺序栈相比,链式栈没有长度限制,不存在内存空间的浪费问题。但对于数据的入栈和出栈这些需要对数据进行定位的操作,顺序栈更加方便,而链式栈中的每个数据节点都需要额外的指针域以指向下一个数据节点,这会略微降低数据的存储效率,当然也会多占用一些内存。

所以,如果要存储的数据数量无法提前预估,一般考虑使用链式栈,而如果数据的数量比较固定,可以考虑使用顺序栈。

5. 总结

顺序栈可以看成是功能受限的数组,或把链式栈看成是功能受限的单链表,都是没有问题的。为什么创造出功能受限的栈来呢?你可以理解为,因为功能受限,所以使用起来也更加简单,错用误用的概率比数组、单链表等更低。

栈有很多应用,比如:在函数调用期间需要用栈来保存临时的参数信息、函数内局部变量信息、函数调用返回地址信息等。网上也有很多小例子演示栈的简单应用,比如利用栈来进行括号匹配的检验,利用栈来计算表达式结果等。以及用栈来实现诸如树的非递归遍历、记录节点路径信息等操作。

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

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

相关文章

『VUE2 - 车牌号软键盘』

一、需求描述 在 VUE2 Vant2 的项目中&#xff0c;实现 车牌号软键盘 的功能。 二、引入全局组件 引入封装好的 vant-number-plate 组件&#xff0c;整个文件夹放在 src/components 里面&#xff1a; git clone https://gitee.com/csheng-gitee/vant-number-plate.git三、具…

IMYAI-人工智能聊天绘画机器人4.0 - 终身学习者的超级生产力工具!

什么是ChatGPT&#xff1f;ChatGPT能帮我干什么&#xff1f; 在开始之前&#xff0c;我们首先需要知道ChatGPT是什么。 ChatGPT 全名Chat Generative Pre-trained Transformer (聊天生成性预训练转换模型) 说简单点&#xff0c;ChatGPT就是一个AI聊天机器人&#xff0c;背后是一…

Redis(08)| 线程模型

一、redis 的线程模型 redis 内部使用文件事件处理器 file event handler&#xff0c;它是单线程的&#xff0c;所以redis才叫做单线程模型。它采用IO多路复用机制同时监听多个 socket&#xff0c;将产生事件的 socket 压入内存队列中&#xff0c;事件分派器根据 socket 上的事…

Vue3.3指北(四)

Vue3.3指北 1、WebPack - VueCLI1.1、WebPack安装VueCli1.2、vue create 创建项目1.3、项目目录结构介绍 2、ViteVue32.1、认识create-vue2.2、使用create-vue创建项目2.3、项目目录剖析2.4、ESlint代码规范及手动修复2.5、通过eslint插件来实现自动修正 3、VueRouter43.1、单页…

SpringCloud 微服务全栈体系(七)

第九章 Docker 一、什么是 Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署&#xff0c;环境不一定一致…

基于SpringBoot的个人博客系统

基于SpringBootVue的个人博客系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 系统公告 博客详情 后台发文 摘要 基于Spring Boot的个人博客系统是一种…

实用搜索小技巧——站在巨人的肩膀上看世界

文章目录 1. 关于搜索效率2. 谷歌搜索语法2.1 “” 限定关键词2.2 intitle 限定标题2.3 限定关键词限定标题2.4 allintitle 标题多个关键词2.5 intext 限定内容关键词2.6 inurl 限定网址关键词2.7 site 限定网址来源2.8 imagesize 限定图片尺寸2.9 filetype 限定文件格式 3. in…

8.自定义组件布局和详解Context上下文

pages/index.vue layout布局运行在服务端 1、在项目的目录下新建layout文件夹&#xff0c;并新建一个blog.vue布局文件 2、在页面中的layout函数里&#xff0c;返回刚才新建布局文件的名字blog就可以使用了 export default {...layout (context) {console.log(context)retu…

探索Vue 3和Vue 2的区别

目录 响应式系统 性能优化 Composition API TypeScript支持 总结 Vue.js是一款流行的JavaScript框架&#xff0c;用于构建用户界面。Vue 3是Vue.js的最新版本&#xff0c;相较于Vue 2引入了许多重大变化和改进。在本文中&#xff0c;我们将探索Vue 3和Vue 2之间的区别。 …

【中国知名企业高管团队】系列50:荣耀手机

今天为您介绍中国手机的一个“新势力”——荣耀。说是“新”是因为他单独运作的时间最短&#xff0c;说他“势力”是因为他的崛起自带光环&#xff0c;市场拓展和发展让OPPO、VIVO和小米都感到巨大的压力&#xff0c;可以说中国市场的大部分份额都被苹果、华为、OPPO、VIVO、小…

图解java.util.concurrent并发包源码系列——深入理解ConcurrentHashMap并发容器,看完薪水涨一千

图解java.util.concurrent并发包源码系列——深入理解ConcurrentHashMap并发容器 HashMap简单介绍HashMap在并发场景下的问题HashMap在并发场景下的替代方案ConcurrentHashMap如何在线程安全的前提下提升并发度1.71.8 JDK1.7的ConcurrentHashMap源码JDK1.8的ConcurrentHashMap源…

2016年上半年上午易错题(软件设计师考试)

以下媒体文件格式中&#xff0c;&#xff08; 12 &#xff09;是视频文件格式。 A &#xff0e; WAV B &#xff0e; BMP C &#xff0e; MP3 D&#xff0e;MOV 以下软件产品中&#xff0c;属于图像编辑处理工具的软件是&#xff08; 13 &#xff09;。 A &#xff0e; Po…

Flask 路由机制分析之一

一、前言 《Flask Run运行机制剖析》这篇我们讲了应用启动的内部机制&#xff0c;启动后就开始监听Http请求了&#xff0c;请求过来如何跳到对应的函数执行&#xff0c;这就是路由机制。我们沿用上一篇例子&#xff0c;来探究一下app.route("/")内部干了些什么事。 …

2017年上半年上午易错题(软件设计师考试)

CPU 执行算术运算或者逻辑运算时&#xff0c;常将源操作数和结果暂存在&#xff08; &#xff09;中。 A &#xff0e; 程序计数器 (PC) B. 累加器 (AC) C. 指令寄存器 (IR) D. 地址寄存器 (AR) 某系统由下图所示的冗余部件构成。若每个部件的千小时可靠度都为 R &…

HiQPdf Library for .NET - HTML to PDF Crack

HiQPdf Library for .NET - HTML 到 PDF 转换器 .NET Core&#xff0c;用于 .NET 的 HiQPdf HTML 到 PDF 转换器 &#xff1a;HiQPdf HTML to PDF Library for .NET C# 和 HTML to PDF .NET Core 为您提供了一个现代、快速、灵活且强大的工具&#xff0c;只需几行代码即可创建复…

Python 算法高级篇:堆排序的优化与应用

Python 算法高级篇&#xff1a;堆排序的优化与应用 引言 1. 什么是堆&#xff1f;2. 堆的性质3. 堆排序的基本原理4. 堆排序的 Python 实现5. 堆排序的性能和优化6. 堆排序的实际应用7. 总结 引言 堆排序是一种高效的排序算法&#xff0c;它基于数据结构中的堆这一概念。堆排序…

给VSCode插上一双AI的翅膀

文章目录 前言一、安装DevChat1.1、访问地址1.2、注册1.3、在VSCode里安装DevChat插件1.3.1、未安装状态1.3.2、已安装状态 二、设置Access Key2.1. 点击左下角管理&#xff08;“齿轮”图标&#xff09;—命令面板&#xff08;Command Palette&#xff09;&#xff0c;如下图2…

独家揭秘|小米14魔改存储芯片多出8GB空间背后的秘诀

在昨天发表的文章下面&#xff0c;有粉丝朋友要求“评价下小米256GB多8GB的技术”。小编也是好奇&#xff0c;本文就让我们一起来看看这个“高科技”背后的秘密。&#xff08;提前声明&#xff1a;本文内容仅代表个人观点&#xff0c;如果不当之处&#xff0c;小米公司不要投诉…

09.K8S高可用-堆叠etcd拓扑测试说明

堆叠 etcd 拓扑 1、部署架构如图 2、原理说明 核心组件高可用模式高可用实现方式apiserver主备keepalived + haproxycontroller-manager主备leader electionscheduler主备leader electionetcd集群kubeadm**「vip」**由外部负载均衡器提供一个vip,流量负载到keepalived master…

vue 内置指令-v-pre/v-memo

一、v-pre 使用了该指令的元素和子元素会被编译忽略&#xff0c;也就是不进行编译&#xff0c;其中包含的所有vue模版语法都会原样显示&#xff0c;作用加快vue的编译 例子&#xff1a; <p v-pre>{{不会被编译}}<span v-text"msg"></span></p&…