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. 入栈
算法步骤
- 创建一个 StackNode 对象,用指针 ptr 指向.
- 将节点数据域制为 data.
- 将新节点插入栈顶.
- 修改栈顶指针.
具体实现如下:
bool Stack::push(T data)
{
StackNode* ptr = new StackNode(data, ptr_);
ptr_ = ptr;
return true;
}
3. 出栈
算法步骤
- 判断栈是否为空,若空返回
false
. - 将栈顶元素赋给 data.
- 临时保存栈顶元素的空间,以备释放
- 修改栈顶指针,指向新的栈顶元素.
- 释放原栈顶元素的指针.
具体实现如下:
bool Stack::pop(T& data)
{
if (ptr_ == nullptr)
{
return false;
}
data = ptr_->data_;
StackNode* ptr = ptr_;
ptr_ = ptr_->next_;
delete ptr;
return true;
}
其中 get_data
和 get_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;
}
结果如下:
这就很爽了,利用泛型使得我们的栈不仅可以存储基本数据类型,还能存储用户自定义类型。嗯,这可能就叫一次编程,终身受益吧!