【数据结构】栈(代码篇)

文章目录

    • 栈的常用操作
    • 基于链表实现栈
    • 基于数组实现栈
    • 两种实现方法对比
    • 栈的应用

本篇文章仅仅展示用C++实现栈
如若想深入了解栈,请移步数据结构专栏中寻找[栈]的文章(用Java写的)

栈的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
// hello 算法 第五章 栈和队列
#include
#include
#include

using namespace std;

// 栈的常用操作
void Stack()
{
	// 初始化栈
	stack<int> stack;

	// 元素入栈
	stack.push(1);
	stack.push(3);
	stack.push(2);
	stack.push(5);
	stack.push(6);
	stack.push(4);

	// 访问栈顶元素
	int top = stack.top();

	// 元素出栈
	stack.pop(); // 无返回值

	// 获取栈的长度
	int size = stack.size();

	// 判断是否为空
	bool empty = stack.empty();

}


基于链表实现栈

#define _CRT_SECURE_NO_WARNINGS 1
// hello 算法 第五章 栈和队列
#include
#include
#include

using namespace std;
// 栈的实现
// 1. 基于链表的实现
class ListNode {
	public:
		int value;
		ListNode* next;

		ListNode(int val) {
			value = val;
			next = nullptr;
		}
};

class LinkedListStack {
	private:
		ListNode* stackTop; // 将头节点作为栈顶
		int stackSize; // 栈的长度


	public:
		LinkedListStack() {
			stackTop = nullptr;
			stackSize = 0;
		}


		~LinkedListStack() {
			// 遍历链表删除节点,释放内存
			freeMemoryLinkedList(stackTop);
		}

		void freeMemoryLinkedList(ListNode* node) {
			if (node == nullptr) {
				return;
			}

			freeMemoryLinkedList(node->next);
			delete node;
		}

		// 获取栈的长度
		int size()
		{
			return stackSize;
		}

		// 判断栈是否为空
		bool isEmpty()
		{
			return size() == 0;
		}

		// 入栈
		void push(int num)
		{
			ListNode* node = new ListNode(num);
			node->next = stackTop;
			stackTop = node;
			stackSize++;
		}

		// 出栈
		void pop()
		{
			int num = top();
			ListNode* tmp = stackTop;
			stackTop = stackTop->next;
			// 释放内存
			delete tmp;
			stackSize--;

		}

		// 访问栈顶元素
		int top()
		{
			if (isEmpty())
				throw out_of_range("栈为空");
			return stackTop->value;
		}

		// 将 List 转化为 Array 并返回
		vector<int> toVector()
		{
			ListNode* node = stackTop;
			vector<int> res(size());
			for (int i = res.size() - 1; i >= 0; i--)
			{
				res[i] = node->value;
				node = node->next;
			}
			return res;
		}

};

基于数组实现栈

#define _CRT_SECURE_NO_WARNINGS 1
// hello 算法 第五章 栈和队列
#include
#include
#include

using namespace std;

// 2. 基于数组实现栈
class ArrayStack {
	private:
		vector<int> stack;
		
	public:
		// 获取栈的长度
		int size()
		{
			return stack.size();
		}

		// 判断是否为空
		bool isEmpty()
		{
			return stack.size() == 0;
		}

		// 入栈
		void push(int num)
		{
			stack.push_back(num);
		}

		// 出栈
		void pop()
		{
			int oldTop = top();
			stack.pop_back();
		}

		// 访问栈顶元素
		int top()
		{
			if (isEmpty())
				throw out_of_range("栈为空");
			return stack.back();
		}

		// 返回 Vector
		vector<int> toVector()
		{
			return stack;
		}
};


两种实现方法对比

支持操作

两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。

时间效率

在基于数组的实现中,入栈和出栈操作都在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为 O(n)

在基于链表的实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。

综上所述,当入栈与出栈操作的元素是基本数据类型时,例如 intdouble ,我们可以得出以下结论。

  • 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。
  • 基于链表实现的栈可以提供更加稳定的效率表现。

空间效率

在初始化列表时,系统会为列表分配“初始容量”,该容量可能超出实际需求;并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容的,扩容后的容量也可能超出实际需求。因此,基于数组实现的栈可能造成一定的空间浪费

然而,由于链表节点需要额外存储指针,因此链表节点占用的空间相对较大

综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。

栈的应用

  • 浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
  • 程序内存管理。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。

你可能感兴趣的:(数据结构与算法,数据结构,c++,算法)