实验题1:实现顺序栈的各种基本运算的算法
题目描述
编写一个程序sqstack.cpp,实现顺序栈(假设栈中的元素类型Elemtype为char)的各种基本运算,并在此基础上设计一个程序exp3-1.cpp完成以下功能。
(1)初始化栈s。
(2)判断栈s是否非空。
(3)依次进栈元素a、b、c、d、e。
(4)判断栈s是否非空。
(5)输出出栈序列。
(6)判断栈s是否非空。
(7)释放栈。
sqstack.cpp程序,其中包含如下函数。
· InitStack(SqStack *&s):初始化顺序栈s。
· DestroyStack(SqStack *&s):销毁顺序栈s。
· StackEmpty(SqStack*s):判断顺序栈s是否为空栈。
· Push(SqStack *&s,ElemType e):元素e进顺序栈。
· Pop(SqStack *&s,ElemType &e):元素e出顺序栈。
· GetTop(SqStack *s,ElemType &e):取顺序栈的栈顶元素e。
运行代码
sqstack.cpp
#include<iostream>
using namespace std;
#include<stdio.h>
#include<malloc.h>
#define Maxsize 100
typedef char Elemtype;
typedef struct {
Elemtype data[Maxsize];
int top;
}SqStack;
//初始化顺序栈
void InitStack(SqStack*& s) {
s = (SqStack*)malloc(sizeof(SqStack));
s->top = -1;
}
//销毁顺序栈
void DestoryStack(SqStack*& s) {
free(s);
}
//判断栈是否为空
bool StackEmpty(SqStack* s) {
return(s->top == -1);
}
//进栈
bool Push(SqStack*& s, Elemtype e) {
if (s->top == Maxsize - 1)
return false;
s->top++;
s->data[s->top] = e;
return true;
}
//出栈
bool Pop(SqStack*& s, Elemtype &e) {
if (s->top == -1)
return false;
e = s->data[s->top];
s->top--;
return true;
}
//取得栈顶元素
bool Gettop(SqStack*& s, Elemtype &e) {
if (s->top == -1)
return false;
e = s->data[s->top];
return true;
}
excp3-1.cpp
#include"sqstack.cpp"
int main() {
Elemtype e;
SqStack* s;
cout << "顺序栈的基本运算如下:\n";
cout << " (1)初始化栈s\n";
InitStack(s);
cout << " (2)栈为" << (StackEmpty(s) ? "空" : "非空") << endl;
cout << " (3)进栈元素为a,b,c,d,e\n";
Push(s, 'a');
Push(s, 'b');
Push(s, 'c');
Push(s, 'd');
Push(s, 'e');
cout << " (4)栈为"<<(StackEmpty(s) ? "空" : "非空") << endl;
cout << " (5)出栈序列:";
while (!StackEmpty(s)) {
Pop(s, e);
cout << e << " ";
}
cout << endl;
cout << " (6)栈为"<< (StackEmpty(s) ? "空" : "非空") << endl;
cout << " (7)释放栈\n";
DestoryStack(s);
return 1;
}
代码思路
实现了顺序栈(一种数据结构)的基本操作,包括初始化、进栈、出栈、判断栈是否为空以及获取栈顶元素等功能,并在 main
函数中对这些功能进行了测试。
-
InitStack
函数:作用是初始化顺序栈。通过动态分配内存为顺序栈结构体SqStack
分配空间,并将栈顶指针top
初始化为 -1,表示栈为空。 -
DestoryStack
函数:用于销毁顺序栈。释放动态分配给顺序栈的内存空间。 -
StackEmpty
函数:判断栈是否为空。如果栈顶指针top
为 -1,则说明栈为空,返回true
;否则返回false
。 -
Push
函数:实现进栈操作。- 首先检查栈是否已满,如果栈顶指针等于最大容量减一(
s->top == Maxsize - 1
),则进栈失败,返回false
。 - 如果栈未满,将栈顶指针加一(
s->top++
),并将元素e
存入栈顶位置(s->data[s->top] = e
),然后返回true
。
- 首先检查栈是否已满,如果栈顶指针等于最大容量减一(
-
Pop
函数:执行出栈操作。- 检查栈是否为空,如果栈顶指针为 -1,则出栈失败,返回
false
。 - 如果栈非空,将栈顶元素赋值给变量
e
,然后将栈顶指针减一(s->top--
),并返回true
。
- 检查栈是否为空,如果栈顶指针为 -1,则出栈失败,返回
-
Gettop
函数:取得栈顶元素。- 检查栈是否为空,如果为空则返回
false
。 - 如果栈非空,将栈顶元素赋值给变量
e
,并返回true
。
- 检查栈是否为空,如果为空则返回
实验题2:实现链栈的各种基本运算的算法
题目描述
编写一个程序listack.cpp,实现链栈(假设栈中的元素类型Elemtype为char)的各种基本运算,并在此基础上设计一个程序exp3-2.cpp完成以下功能。
(1)初始化栈s。
(2)判断栈s是否非空。
(3)依次进栈元素a、b、c、d、e。
(4)判断栈s是否非空。
(5)输出出栈序列。
(6)判断栈s是否非空。
(7)释放栈。
listack.cpp程序,其中包含如下函数:
· InitStack(LinkStNode *&s),初始化链找s.
·DestroyStack( LinkStNode *&s): 销毁链栈s.
·StackEmpry(linkStNode s):判断链栈是否为空栈。
·Push(LinkSiNode s &s.ElemType e):元素e进链栈,
· Pop(linkSiNode s &s.ElemType &r): 元素e出链栈。
·Giet TopeLinkStNode &s.ElemType &.e):取链栈的栈顶元素。
运行代码
listack.cpp
#include<iostream>
using namespace std;
#include<stdio.h>
#include<malloc.h>
typedef char Elemtype;
typedef struct Linknode {
Elemtype data;
struct Linknode* next;
}LinkStNode;
//初始化链栈
void InitStack(LinkStNode*& s) {
s = (LinkStNode*)malloc(sizeof(LinkStNode));
s->next = NULL;
}
//销毁链栈
void DestoryStack(LinkStNode*& s) {
LinkStNode* p = s;
while (p != NULL) {
LinkStNode* temp = p;
p = p->next;
free(temp);
}
}
//判断栈是否为空
bool StackEmpty(LinkStNode* s) {
return (s->next == NULL);
}
//进栈
void Push(LinkStNode*& s, Elemtype e) {
LinkStNode* p = (LinkStNode*)malloc(sizeof(LinkStNode));
p->data = e;
p->next = s->next;
s->next = p;
}
//出栈
bool Pop(LinkStNode*& s, Elemtype& e) {
if (s->next == NULL)
return false;
LinkStNode* p = s->next;
e = p->data;
s->next = p->next;
free(p);
return true;
}
//取得栈顶元素
bool Gettop(LinkStNode* s, Elemtype& e) {
if (s->next == NULL)
return false;
e = s->next->data;
return true;
}
excp3-2.cpp
#include"listack.cpp"
int main() {
Elemtype e;
LinkStNode* s;
cout << "链栈的基本运算如下:\n";
cout << " (1)初始化栈s\n";
InitStack(s);
cout << " (2)栈为"<< (StackEmpty(s) ? "空" : "非空") << endl;
cout << " (3)进栈元素为a,b,c,d,e\n";
Push(s, 'a');
Push(s, 'b');
Push(s, 'c');
Push(s, 'd');
Push(s, 'e');
cout << " (4)栈为" << (StackEmpty(s) ? "空" : "非空") << endl;
cout << " (5)出栈序列:";
while (!StackEmpty(s)) {
Pop(s, e);
cout << e << " ";
}
cout << endl;
cout << " (6)栈为" << (StackEmpty(s) ? "空" : "非空") << endl;
cout << " (7)释放栈\n";
DestoryStack(s);
return 1;
}
代码思路
实现了链栈(一种基于链表实现的栈数据结构)的基本操作,包括初始化、进栈、出栈、判断栈是否为空以及获取栈顶元素等功能,并在 main
函数中对这些功能进行了测试。
-
InitStack
函数:作用是初始化链栈。通过动态分配内存为链栈的头节点LinkStNode
分配空间,并将头节点的next
指针初始化为NULL
,表示栈为空。 -
DestoryStack
函数:用于销毁链栈。遍历链表,依次释放每个节点的内存空间,直到链表为空。 -
StackEmpty
函数:判断链栈是否为空。如果头节点的next
指针为NULL
,则说明栈为空,返回true
;否则返回false
。 -
Push
函数:实现进栈操作。- 首先动态分配一个新节点
p
,将待进栈元素e
存入新节点的数据域data
。 - 然后将新节点的
next
指针指向当前栈顶节点(即头节点的下一个节点)。 - 最后将头节点的
next
指针指向新节点,使新节点成为栈顶。
- 首先动态分配一个新节点
-
Pop
函数:执行出栈操作。- 检查栈是否为空,如果头节点的
next
指针为NULL
,则出栈失败,返回false
。 - 如果栈非空,将栈顶节点(头节点的下一个节点)赋值给临时指针
p
,将栈顶节点的数据域data
赋值给变量e
。 - 然后将头节点的
next
指针指向栈顶节点的下一个节点,即新的栈顶。 - 最后释放原来的栈顶节点的内存空间,并返回
true
。
- 检查栈是否为空,如果头节点的
-
Gettop
函数:取得链栈的栈顶元素。- 检查栈是否为空,如果头节点的
next
指针为NULL
,则返回false
。 - 如果栈非空,将栈顶节点(头节点的下一个节点)的数据域
data
赋值给变量e
,并返回true
。
- 检查栈是否为空,如果头节点的
顺序栈和链栈的异同点
相同点
- 逻辑结构:顺序栈和链栈在逻辑上都遵循后进先出(LIFO - Last In First Out)的原则。这意味着最后进入的数据元素将最先被取出,就像一摞盘子,只能从最上面取放盘子一样。
- 基本操作功能:两者都具有基本的操作,如初始化(创建一个空的栈结构)、判断栈是否为空(确定栈中是否有元素)、进栈(将元素压入栈中)、出栈(将栈顶元素弹出)以及获取栈顶元素(查看栈顶元素的值而不弹出它)等操作。这些操作在概念和功能目的上是相同的,只是在具体的实现细节上可能存在差异。
不同点
- 存储结构
- 栈(顺序栈):顺序栈通常使用数组来实现。它在内存中是连续存储的,例如在 C++ 中,可以定义一个数组来存储栈元素,并使用一个变量来表示栈顶指针(指示栈顶元素的位置)。这种存储方式的优点是实现简单、访问速度快,因为数组元素的存储是连续的,可以通过下标直接访问元素。但是,它的缺点是需要预先确定数组的大小,如果栈中元素数量超过数组大小,可能会导致栈溢出;而且在栈的大小变化较大时,可能会造成内存空间的浪费。
- 链栈:链栈是通过链表实现的,每个节点包含数据域和指向下一个节点的指针域。链栈中的节点在内存中可以不连续存储,通过指针将各个节点连接起来形成栈结构。这种存储方式的优点是可以动态地分配内存,不需要预先确定栈的大小,适合处理元素数量不确定或者变化较大的情况。然而,由于需要额外的指针域来存储节点之间的连接关系,并且访问元素需要通过指针遍历链表,所以在空间和时间效率上相对顺序栈可能会稍低一些。
- 空间利用效率
- 栈(顺序栈):顺序栈在初始化时就分配了固定大小的内存空间,如果栈中元素较少,会造成内存空间的浪费;如果元素数量超过预先分配的空间,则会出现栈溢出的情况。
- 链栈:链栈的空间是根据实际元素数量动态分配的,每个节点只占用实际需要的内存空间,不会造成大量的空间浪费,但由于每个节点需要额外的指针域,会占用一定的额外空间。
- 操作的实现细节
- 栈(顺序栈):在进栈操作时,需要先检查栈顶指针是否达到数组的最大下标(栈满情况);出栈操作时,需要检查栈顶指针是否为初始值(栈空情况)。并且在顺序栈中,栈顶指针的操作通常是简单的增减下标操作。
- 链栈:进栈操作需要动态分配新的节点内存,并正确调整指针的指向;出栈操作需要释放节点内存,并重新连接链表指针。在链栈中,操作主要涉及到指针的操作,相对顺序栈来说,指针操作更容易出错,但也更灵活。