数据结构-栈容器的实现

2021/11/23 更新 将用于生成栈节点的StackNode类改为Stack类的内嵌类,不仅使组织结构更合理,还减少了函数调用,有效减少了代码量.

栈的实现

栈是一种重要的数据结构,是一种线性结构,具有后进先出的特点.
因为线性表有 2 种实现方式,故栈也有 2 种实现方式,即顺序存储结构和链式存储结构.
本篇将以链式存储结构给出一种实现方式.

链栈的存储结构

和单链表的存储结构类似,链栈的存储映像也包括数据域指针域两个部分.存储结构表示如下:

class StackNode
{
	// 这里不妨先用 T 来表示数据类型,后面将看到它的作用
	T data_;
	StackNode* next_;
};

为了方便管理一组栈节点不妨定义个 Stack 类如下:

class Stack
{
private:
	StackNode<T>* ptr_;
	// 这里的 T 表示它所指向的栈节点数据域是 T 类型的
};

栈的主要操作

1. 初始化

初始化的任务就是构造一个空栈,与链表不同的是,链栈没有必要设置头节点,故只需将栈顶指针制空即可.可利用 Stack 类构造函数实现,如下:

	Stack()
	{
		ptr_ = nullptr;
	}

2. 入栈

算法步骤
  1. 创建一个 StackNode 对象,用指针 ptr 指向.
  2. 将节点数据域制为 data.
  3. 将新节点插入栈顶.
  4. 修改栈顶指针.

具体实现如下:

	bool Stack::push(T data)
	{
		StackNode* ptr = new StackNode(data, ptr_);
		ptr_ = ptr;
		return true;
	}

3. 出栈

算法步骤
  1. 判断栈是否为空,若空返回 false.
  2. 将栈顶元素赋给 data.
  3. 临时保存栈顶元素的空间,以备释放
  4. 修改栈顶指针,指向新的栈顶元素.
  5. 释放原栈顶元素的指针.

具体实现如下:

	bool Stack::pop(T& data)
	{
		if (ptr_ == nullptr)
		{
			return false;
		}
		data = ptr_->data_;
		StackNode* ptr = ptr_;
		ptr_ = ptr_->next_;
		delete ptr;
		return true;
	}

其中 get_dataget_next 分别为返回当前节点的数据元素和指针的函数.

4. 取栈顶元素

当栈非空时,返回当前栈顶元素的值,具体实现如下:

	T Stack::get_top()
	{
		if (ptr_ != nullptr)
			return ptr_->data_;
	}

当然除了这些还可以定义其它接口,比如说,求栈长、清空栈等.这里就不一一赘述了.

其实到这里为止栈的数据结构基本已经实现完了,但任有改进的余地.
比方说,如果要同时使用存储 int型数据的栈和 double型数据的栈.上述代码就显得无能为力了. 你当然可以单独定义一个DoubleStack类来存储 double型数据.但为每种类型都单独定义相同功能的类的话是不现实的(因为还有用户自定义类型), 应用范围有限.那有没有一种方法可以定义一次对各种类型都适用呢? 有! C++ 的泛型.

栈容器的实现

其实在这里我们只要稍微修改我们的代码,就可以让我们的栈存储任意类型的数据了(这才叫容器嘛).基本思想一样,整合如下:

#pragma once

template <typename T>
// 用于创建一个栈
class Stack
{
public:
	// 在栈顶插入元素
	bool push(T);
	// 弹出栈顶元素,并保存
	bool pop(T&);
	// 弹出栈顶元素
	bool pop();
	// 取栈顶元素
	T get_top();
	// 清空栈
	void clear();
	// 求栈长
	size_t length();
	// 判断栈是否为空
	bool is_empty();

	Stack()
	{
		ptr_ = nullptr;
		length_ = 0;
	}
	
	~Stack()
	{
		clear();
	}

private:
	template <typename T>
	// 用于生成栈节点
	class StackNode
	{
	public:
		StackNode() : data_(), next_() {}

		StackNode(T data) : StackNode(data, nullptr) {}

		StackNode(T data, StackNode* next) : data_(data), next_(next) {}
		
		// 栈节点的数据域
		T data_;
		// 指向下一个栈节点的指针域
		StackNode* next_;
	};
	
	// 栈的头指针,可标志一个栈
	StackNode<T>* ptr_;
	// 栈的长度
	size_t length_;
};

template <typename T>
bool Stack<T>::push(T data)
{
	StackNode<T>* ptr = new StackNode<T>(data, ptr_);
	ptr_ = ptr;
	++length_;
	return true;
}

template <typename T>
bool Stack<T>::pop(T& data)
{
	if (ptr_ == nullptr)
	{
		return false;
	}
	data = ptr_->data_;
	StackNode<T>* ptr = ptr_;
	ptr_ = ptr_->next_;
	delete ptr;
	--length_;
	return true;
}

template <typename T>
bool Stack<T>::pop()
{
	if (ptr_ == nullptr)
	{
		return false;
	}
	StackNode<T>* ptr = ptr_;
	ptr_ = ptr_->next_;
	delete ptr;
	--length_;
	return true;
}

template <typename T>
inline T Stack<T>::get_top()
{
	return ptr_->data_;
}

template <typename T>
void Stack<T>::clear()
{
	while (ptr_)
	{
		StackNode<T>* ptr = ptr_;
		ptr_ = ptr_->next_;
		delete ptr;
	}
	length_ = 0;
	ptr_ = nullptr;
}

template <typename T>
inline size_t Stack<T>::length()
{
	return length_;
}

template <typename T>
bool Stack<T>::is_empty()
{
	if (ptr_)
	{
		return false;
	}
	return true;
}

测试

可写出测试代码来测试代码是否正确,如下:

#include <iostream>
#include "linkstack.h" // 这里存放我们编写的栈容器

using namespace std;

int main(void)
{
	Stack<double> double_stack;
	Stack<int> int_stack;
	for (size_t i = 1; i < 10; i++)
	{
		double_stack.push(i * 2.5);
		int_stack.push(i);
	}
	cout << "double 栈的长度:" << double_stack.length() << endl;
	cout << "int 栈的长度:" << int_stack.length() << endl;
	cout << "弹出 double 栈前5个元素\n";
	for (size_t i = 1; i < 5; i++)
	{
		double data;
		double_stack.pop(data);
		cout << data << " ";
	}
	cout << "\ndouble 栈:我的长度变了。变成:" << double_stack.length() << endl;
	cout << "int 栈:我的长度没变。还是:" << int_stack.length() << endl;
	return 0;
}

运行结果如下:
测试结果
接下来测试是否可以正确存放用户自定义类型,可定义一个平面点集如下:
注:这里不要缺省默认构造函数

class CPoint
{
private:
	int x_;
	int y_;
public:
	CPoint(): CPoint(0, 0) {}
	CPoint(int x, int y): x_(x), y_(y) {}
	
	friend std::ostream& operator<<(std::ostream& out, CPoint point)
	{
		out << "(" << point.x_ << "," << point.y_ << ")";
		return out;
	}
};

测试代码如下:

#include <iostream>
#include "linkstack.h" // 这里存放我们编写的栈容器

using namespace std;

int main(void)
{
	Stack<CPoint> stack_point;
	for (size_t i = 1; i < 10; i++)
	{
		CPoint point(i, i * i);
		stack_point.push(point);
	}
	cout << "现在栈的长度为:" << stack_point.length() << endl;
	cout << "弹出所有的元素:" << endl;
	while (!stack_point.is_empty())
	{
		CPoint point;
		stack_point.pop(point);
		::std::cout <<point << " ";
	}
	cout << "\n现在栈的长度为:" << stack_point.length() << endl;
	system("pause");
	return 0;
}

结果如下:
测试结果2
这就很爽了,利用泛型使得我们的栈不仅可以存储基本数据类型,还能存储用户自定义类型。嗯,这可能就叫一次编程,终身受益吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值