数据结构初阶--栈和队列(讲解+类模板实现)
栈
栈的概念和结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)加粗样式的原则。
入栈:从栈顶放入数据的操作。
出栈:从栈顶取出元素的操作。
栈的实现
栈用链表和顺序表两种数据结构都可以实现,我们要确定选择哪一种更优,我们来分析一下。
链表栈:如果选择单链表的话,我们应该选择头当栈顶,尾当栈底,不然的话,每次存取数据都要遍历一遍链表。所以选双链表会比较好一点。
数组栈:访问栈顶的时间复杂度为O(1),相比链表栈比较优。
所以下面我们用顺序表来实现栈的这种数据结构。
结构如下:初始化栈的大小为5
#define InitSize 5
template <class DateType>
class Stack
{
public:
private:
DateType* data;//栈空间指针
int size;//栈容量
int top;//栈顶,栈中元素个数
};
栈的接口
栈要实现的接口有以下几个:
Stack();//初始化栈,初始化的大小是5
Stack(const Stack& stack);//拷贝构造函数
Stack& operator=(const Stack& stack);//等号运算符的重载
~Stack();//销毁栈
bool ifFull();//判断栈是否满了
bool isEmpty();//判断栈是否为空
void Push(const DateType& val);//入栈
bool Pop(DateType &item);//出栈,并获取出栈元素
void ExpandStack();//扩容
void PrintStack();//打印
栈的初始化
初始化栈就是把结构体中的成员都初始化一下,方便后续的扩容等操作。
具体实现如下:
//初始化栈,初始化的大小是5
Stack()
{
data = new DateType[InitSize];
if (data == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
size = InitSize;
top = 0;
}
拷贝构造
//拷贝构造函数
Stack(const Stack& stack)
{
this->data = new DateType[stack.size];
if (data == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
for (int i = 0; i < stack.size; i++)
{
this->data[i] = stack.data[i];
}
this->size = stack.size;
this->top = stack.top;
}
判断栈满
//判断栈是否满了
bool ifFull()
{
if (top == size)
{
return true;
}
return false;
}
扩容
//扩容
void ExpandStack()
{
this->size = this->size == 0 ? 4 : 2 * this->size;
DateType* tmp = new DateType[this->size];
if (tmp == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
for (int i = 0; i < top; i++)
{
tmp[i] = data[i];
}
delete[] data;
data = tmp;
}
判断栈空
//判断栈是否为空
bool isEmpty()
{
if (top == 0)
{
return true;
}
return false;
}
入栈
压栈就是在栈顶插入元素,其中是肯定要考虑到扩容的问题,当this->top == this->size时,就要考虑到扩容了,扩容也是像之前顺序表那样每次扩一倍,这样可以一定程度地减少扩容次数,但同时是会带来一定的空间消耗的。
//入栈
void Push(const DateType& val)
{
if (ifFull())
{
ExpandStack();
}
data[top++] = val;
}
出栈
出栈就是在栈顶pop掉一个元素,也就是top-1指向的位置,只需要将top进行一个减1的操作即可。
与此同时,我们要确保此次栈不为空,所以要进行判断栈空的操作,防止程序崩溃。
//出栈,并获取出栈元素
bool Pop(DateType &item)
{
if (isEmpty())
{
cout << "栈为空,无法出栈" << endl;
return false;
}
item = data[--top];
return true;
}
赋值运算符重载
运算符重载的注意事项在前面的博客我已经介绍过了,尤其是赋值运算符,感兴趣的小伙伴可以去看看,这里要注意几点
- 返回的是*this,对象本身
- 不要自己给自己赋值
- 要防止内存泄漏
- 防止浅拷贝的发生
//等号运算符的重载
Stack& operator=(const Stack& stack)
{
//防止自赋值
if (this == &stack)
{
return *this;
}
//防止内存泄漏
if (data != NULL)
{
delete[] data;
}
//防止浅拷贝
this->data = new DateType[stack.size];
if (data == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
for (int i = 0; i < stack.top; i++)
{
this->data[i] = stack.data[i];
}
this->size = stack.size;
this->top = stack.top;
return *this;
}
打印
//打印
void PrintStack()
{
for (int i = 0; i < top; i++)
{
cout << data[i] << " ";
}
cout << endl;
}
整体代码以及测试
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入头文件
#include<string>//C++中的字符串
using namespace std; //标准命名空间
#define InitSize 5
/*
栈,利用顺序表实现
*/
template <class DateType>
class Stack
{
public:
//初始化栈,初始化的大小是5
Stack()
{
data = new DateType[InitSize];
if (data == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
size = InitSize;
top = 0;
}
//拷贝构造函数
Stack(const Stack& stack)
{
this->data = new DateType[stack.size];
if (data == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
for (int i = 0; i < stack.size; i++)
{
this->data[i] = stack.data[i];
}
this->size = stack.size;
this->top = stack.top;
}
//等号运算符的重载
Stack& operator=(const Stack& stack)
{
//防止自赋值
if (this == &stack)
{
return *this;
}
//防止内存泄漏
if (data != NULL)
{
delete[] data;
}
//防止浅拷贝
this->data = new DateType[stack.size];
if (data == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
for (int i = 0; i < stack.top; i++)
{
this->data[i] = stack.data[i];
}
this->size = stack.size;
this->top = stack.top;
return *this;
}
//销毁栈
~Stack()
{
if (data != NULL)
{
delete[] data;
data = NULL;
}
}
//判断栈是否满了
bool ifFull()
{
if (top == size)
{
return true;
}
return false;
}
//判断栈是否为空
bool isEmpty()
{
if (top == 0)
{
return true;
}
return false;
}
//入栈
void Push(const DateType& val)
{
if (ifFull())
{
ExpandStack();
}
data[top++] = val;
}
//出栈,并获取出栈元素
bool Pop(DateType &item)
{
if (isEmpty())
{
cout << "栈为空,无法出栈" << endl;
return false;
}
item = data[--top];
return true;
}
//扩容
void ExpandStack()
{
this->size = this->size == 0 ? 4 : 2 * this->size;
DateType* tmp = new DateType[this->size];
if (tmp == NULL)
{
cout << "内存分配失败" << endl;
exit(-1);
}
for (int i = 0; i < top; i++)
{
tmp[i] = data[i];
}
delete[] data;
data = tmp;
}
//打印
void PrintStack()
{
for (int i = 0; i < top; i++)
{
cout << data[i] << " ";
}
cout << endl;
}
private:
DateType* data;//栈空间指针
int size;//栈容量
int top;//栈顶,栈中元素个数
};
int main()
{
Stack<int> stack;
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack.Push(4);
stack.Push(5);
stack.PrintStack();
cout << "-------------------------" << endl;
int b = 0;
stack.Pop(b);
cout << b << endl;
stack.Pop(b);
cout << b << endl;
stack.PrintStack();
cout << "-------------------------" << endl;
Stack<int> stack2(stack);
stack2.PrintStack();
cout << "-------------------------" << endl;
Stack<int> stack3;
stack3 = stack2;
stack3.PrintStack();
cout << "-------------------------" << endl;
system("pause");
return EXIT_SUCCESS;
}
队列
队列的概念和结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。
队列的结构,我们选取单链表来实现,秩序进行头删和为插的不足即可。如果选数组,那么每一次删头我们都要挪动一遍数据,这种方式不优,所以我们还是选取用单链表来实现。
定义的结构如下:
template<class DateType>
//链队的结点类型
struct Node
{
DateType data;
Node<DateType> *next;
Node(Node<DateType>* p = NULL)
{
next = p;
}
//构造函数
Node(DateType val, Node<DateType>* p = NULL)
{
data = val;
next = p;
}
};
template <class DateType>
class Queue
{
public:
private:
//声明,也是定义,只不过定义的是指针类型,保存的应该是地址,未初始化
//队头指针
Node<DateType>* front;
//队尾指针
Node<DateType>* rear;
};
队列的实现
队列的接口
Queue();//构造函数,初始化队列
~Queue();//析构函数
bool QueuePush(const DateType& val);//队尾入队
bool QueuePop(DateType& val);//对头出队列
bool getFront(DateType& val) const;//获取对头元素的值
bool getRear(DateType& val);//获取队尾元素的值
void MakeEmpty();//将队列清空
bool isEmpty() const;//判断队列是否为NULL
int getSize() const;//返回队列元素的个数
void PrintQueue();//输出队列元素
队列的初始化
初始化很简单,只要将头指针和尾指针都置空。
//构造函数
Queue()
{
front = NULL;
rear = NULL;
}
判断队列是否为空
//判断队列是否为NULL
bool isEmpty() const
{
if (front == NULL)
{
return true;
}
else
{
return false;
}
}
入队
出队就是进行单链表尾删的操作,要考虑链表为空时不能进行删除,还要注意的是只有一个节点进行删除是要改变尾指针的指向。
//队尾入队列
bool QueuePush(const DateType& val)
{
if (front == NULL)
{
//如果队列为空,直接用指针开辟结点
front = rear = new Node<DateType>(val);
if (front == NULL)
{
cout << "内存分配失败" << endl;
return false;
}
}
else
{
Node<DateType>* p = new Node<DateType>(val);
rear->next = p;
if (rear->next == NULL)
{
cout << "内存分配失败" << endl;
return false;
}
//更新尾结点
rear = rear->next;
}
return true;
}
出队
出队就是进行单链表尾删的操作,要考虑链表为空时不能进行删除,还要注意的是只有一个节点进行删除是要改变尾指针的指向。
bool QueuePop(DateType& val)
{
//如果队列为空,不允许出列
if (isEmpty())
{
return false;
}
else
{
Node<DateType>* p = front;
val = front->data;
front = front->next;
delete p;
return true;
}
}
获取队头元素和队尾元素
首先要确保链表不为空,对头就是返回头节点的大小,队尾就是返回尾节点的大小。
具体实现如下:
//获取对头元素的值
bool getFront(DateType& val) const
{
if (isEmpty())
{
return false;
}
val = front->data;
return true;
}
//获取队尾元素的值
bool getRear(DateType& val) {
if (isEmpty())
{
return false;
}
val = rear->data;
return true;
}
获取队列元素个数
遍历一遍链表,同时进行计数操作,最后返回计数结果即可。
//返回队列元素的个数
int getSize() const
{
//函数返回队列元素的个数
Node<DateType>* p = front;
int count = 0;
while (p != NULL)
{
++count;
p = p->next;
}
return count;
}
队列的销毁
为了防止内存泄漏,动态内存申请的空间一定要我们自己手动释放,养成一个良好的习惯。所以要将链表的空间逐个释放。
//将队列清空
void MakeEmpty()
{
//置空队列,释放链表中所有的结点
Node<DateType>* current;
if (front != NULL)
{
current = front;
front = front->next;
delete current;
}
}
打印队列
//输出队列元素
void PrintQueue()
{
Node<DateType>* p = front;
while (p != NULL)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
整体代码以及测试
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入头文件
#include<string>//C++中的字符串
using namespace std; //标准命名空间
/*
队列,单链表实现
*/
template<class DateType>
//链队的结点类型
struct Node
{
DateType data;
Node<DateType> *next;
Node(Node<DateType>* p = NULL)
{
next = p;
}
//构造函数
Node(DateType val, Node<DateType>* p = NULL)
{
data = val;
next = p;
}
};
template <class DateType>
class Queue
{
public:
//构造函数
Queue()
{
front = NULL;
rear = NULL;
}
//析构函数
~Queue()
{
MakeEmpty();
}
//队尾入队列
bool QueuePush(const DateType& val)
{
if (front == NULL)
{
//如果队列为空,直接用指针开辟结点
front = rear = new Node<DateType>(val);
if (front == NULL)
{
cout << "内存分配失败" << endl;
return false;
}
}
else
{
Node<DateType>* p = new Node<DateType>(val);
rear->next = p;
if (rear->next == NULL)
{
cout << "内存分配失败" << endl;
return false;
}
//更新尾结点
rear = rear->next;
}
return true;
}
//对头出队列
bool QueuePop(DateType& val)
{
//如果队列为空,不允许出列
if (isEmpty())
{
return false;
}
else
{
Node<DateType>* p = front;
val = front->data;
front = front->next;
delete p;
return true;
}
}
//获取对头元素的值
bool getFront(DateType& val) const
{
if (isEmpty())
{
return false;
}
val = front->data;
return true;
}
//获取队尾元素的值
bool getRear(DateType& val) {
if (isEmpty())
{
return false;
}
val = rear->data;
return true;
}
//将队列清空
void MakeEmpty()
{
//置空队列,释放链表中所有的结点
Node<DateType>* current;
if (front != NULL)
{
current = front;
front = front->next;
delete current;
}
}
//判断队列是否为NULL
bool isEmpty() const
{
if (front == NULL)
{
return true;
}
else
{
return false;
}
}
//返回队列元素的个数
int getSize() const
{
//函数返回队列元素的个数
Node<DateType>* p = front;
int count = 0;
while (p != NULL)
{
++count;
p = p->next;
}
return count;
}
//输出队列元素
void PrintQueue()
{
Node<DateType>* p = front;
while (p != NULL)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
private:
//声明,也是定义,只不过定义的是指针类型,保存的应该是地址,未初始化
//队头指针
Node<DateType>* front;
//队尾指针
Node<DateType>* rear;
};
int main()
{
Queue<int> que;
que.QueuePush(1);
que.QueuePush(2);
que.QueuePush(3);
que.QueuePush(4);
que.PrintQueue();
cout << "----------------------" << endl;
int b = 0;
que.QueuePop(b);
cout << b << endl;
que.QueuePop(b);
cout << b << endl;
que.PrintQueue();
cout << "----------------------" << endl;
int c = 0;
que.getFront(c);
cout << c << endl;
que.PrintQueue();
cout << que.getSize() << endl;
cout << "----------------------" << endl;
system("pause");
return EXIT_SUCCESS;
}