栈和队列都属于 线性表,只不过它们的基本操作是线性表操作的子集,他们是操作受限的线性表
一、栈
栈只能在表的一端进行插入与删除操作,称这端为栈顶,另外一段称为栈底。 (像M416弹夹一样)栈元素后进先出(last in first out) 简称LIFO结构。栈分为顺序栈和链栈,链栈操作是线性表的特例,操作易于实现,故此处只讲顺序栈。
一般操作 | 作用 |
---|---|
void initStack(seqStack* s) | 初始化栈 |
bool stackEmpty(seqStack s) | 判栈空 |
bool stackFull(seqStack s) | 判栈满 |
bool stackTop(seqStack & S) | 读栈顶元素 |
void pushStack(seqStack* s, elementType x) | 入栈 |
bool popStack(seqStack & S) | 弹栈 |
void cyclepush(seqStack * s) | 输入循环入栈 |
void printStack(seqStack& S) | 打印栈中元素子函数 |
#include <iostream>
using namespace std;
typedef int elementType;
#define MaxLen 100
typedef struct
{
elementType data[MaxLen];
int top;
}seqStack;
void initStack(seqStack* s)//初始化栈
{
s->top = -1;
}
bool stackEmpty(seqStack s)//判栈空
{
if(s.top == -1)
return true;
else
return false;
}
bool stackFull(seqStack s)//判栈满
{
if (s.top == MaxLen - 1)
return true;
else
return false;
}
bool stackTop(seqStack & S)//读栈顶元素
{
if (stackEmpty(S))
{
cout << endl << "空栈,无栈顶元素" << endl;
return false; //空栈,返回false
}
else
{
cout << endl << "取出的栈顶元素为:" << S.data[S.top] << endl;
return true; //取得栈顶,返回true;取得的值有 x 传递。
}
}
void pushStack(seqStack* s, elementType x)//入栈
{
if (s->top == MaxLen - 1)
cout<<"栈满"<<endl;
else
{
s->top++;
//此处解释了为什么s->top从-1开始,因为存入第一个元素位置应该为0
s->data[s->top] = x;
}
}
bool popStack(seqStack & S) //弹栈
{
elementType x;
if (stackEmpty(S)) //空栈,没元素出栈,返回false
{
cout << "栈空,不能删除!" << endl;
return false;
}
else
{
x = S.data[S.top];
S.top--;
cout << endl << "弹出栈顶元素:" << x << endl;
return true;
}
}
void cyclepush(seqStack * s)//输入循环入栈
{
elementType x;
cout << "请输入入栈元素(回车退出):" << endl;
while (cin.peek() != '\n') //cin.peek()返回值是指针指向的当前字符
{
cin >> x;
pushStack(s, x);
}
}
void printStack(seqStack& S)//打印栈中元素子函数
{
if (stackEmpty(S))
{
cout << "当前为空栈。" << endl;
return;
}
else
{
cout << endl << "当前栈内元素(栈底到栈顶):" << endl;
int i = 0;//为什么从i=0开始?因为s->top=0是最先入栈的
while (i <= S.top)
{
cout << S.data[i] << " ";
i++;
}
}
cout << endl;
}
int main()
{
seqStack S;
initStack(&S);
printStack(S);
cyclepush(&S);
printStack(S);
stackTop(S);
popStack(S);
printStack(S);
return 0;
}
二、队列
- 队列即模仿人排队,队首(head)先得到服务,想要得到服务只能从队尾(tail)开始排队,换言之,只能在一端进行删除,另一端进行插入,符合先进先出原则。同样队列也是操作受限的线性表。(C__zhang关于队列基本操作的描述)
- 队列分为顺序队列和循环队列,如下为普通顺序队列的结构,用数组来存储数据,两个整型变量
head
,tail
分别表示队头和队尾指针,注意head,tail
他们并没有正好指在第一和最后一个元素上,而是其中一个错开,要么head
指在第一个元素前一位,要么tail
指在最后一个元素后一位,这样方便区分队满和队空。
#define MAXLEN 100
typedef struct Queue
{
int data[MAXLEN];
int head;
int tail;
};
思考1:队列存储结构和栈类似,那么为什么要一个头指针呢?
答案1:因为考虑到出队操作需要删除data[0]
的数据,若没有队头指针需要把所有的元素前移一位,非常耗时,所以设置了队头指针,只需将其后移一位即可。
思考2:用数组存储数据的顺序队列有什么缺点?如何解决?
答案2:可定义一个非常大的数组,但是不管出队、入队都是指针后移,这样数组总会有用完的时候,队头前面有可能也还有在许多剩余的空间,这种情况并不是真的没有存储空间了,称为假溢出。由此引出循环队列解决这一问题。
- 循环队列的结构与顺序队列相同,但是把
data[ ]
视为环状结构,如图data[0]
紧接着data[Maxlen-1]
。
![](https://img-blog.csdnimg.cn/20190716105336225.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyODQyNzg2,size_16,color_FFFFFF,t_70)
一般操作 | 作用 |
---|---|
void initQueue(seqQueue* Q) | 初始化 |
bool queueEmpty(seqQueue& Q) | 判队空 |
bool queueFull(seqQueue& Q) | 判队满 |
void queueFront(seqQueue& Q) | 取队头 |
void enQueue(seqQueue* Q, elementType x) | 入队 |
void outQueue(seqQueue* Q) | 出队 |
void cycleenQueue(seqQueue* Q) | 循环输入入队 |
void printQueue(seqQueue* Q) | 打印队列元素 |
- !!! 循环队列如何不会溢出?当
tail
指向数组data[ ]
最后一个单元时,即tail=Maxlen-1
时,进行入队操作:tail++
普通顺序队列一定会溢出,但循环队列会重新指向data[0]
单元
(如果此时data[0]
单元为空仍然可以完成入队操作)
对于front
指针也是一样,当front=Maxlen-1
时,front++
后让其重新指向data[0]
单元 - 那么我们还发现因为循环队列的特性,
front
和tail
可以出现在任何位置,对于front>tail
的情况怎么计算如对位置呢?
可以手工判断,如下
if (tail == Maxlen - 1)
tail = 0;
else
tail++;
或者改为三目运算,如下
tail = (tail + 1 == Maxlen) ? 0 : tail++;
直接使用模运算求得入队位置tail
tail = (tail + 1 == Maxlen) % Maxlen;
- 还有注意在
printQueue
函数中为何我多此一举定义了整型变量elementType n;
?如果直接使用Q->front++
的话当我们在取队头元素时就会错误,因为front
的值已经变了。
其次,注意该函数中的n=Q->front+1
能否改为n=Q->front++
?
答案是不能,因为n=Q->front++
语句会先把Q->front
自增1,但不会立即把值赋给Q
,也就是说下一次使用n
的时候它的值还是原来的值。
#include <iostream>
using namespace std;
typedef int elementType;
#define Maxlen 100
typedef struct sQueue//顺序队列存储结构
{
elementType data[Maxlen];
int front, tail;
}seqQueue;
void initQueue(seqQueue* Q) //参数号可用传值、引用
{
Q->front = 0; //空a队列
Q->tail = 0;
}
bool queueEmpty(seqQueue& Q)
{
if (Q.front == Q.tail)//!!!
{
cout << endl << "当前队列为空!" << endl;
return true; //队空,返回true
}
else
return false; //队不空
}
bool queueFull(seqQueue& Q)
{
if (((Q.tail + 1) % Maxlen) == Q.front)//!!!
{
cout << endl << "队列已满!" << endl;
return true; //队空,返回true
}
else
return false; //不满,返回false
}
void queueFront(seqQueue& Q)//取队头
{
if (queueEmpty(Q))
cout << endl << "队空,不能取队头元素!" << endl;
else
{
cout << endl << "队头元素为:" << Q.data[(Q.front + 1) % Maxlen] << endl;
//front指示的下一个单元才是队头元素
}
}
void enQueue(seqQueue* Q, elementType x)
{
if (queueFull(*Q))
cout << endl << "队列已满,不能完成入队操作!" << endl;
else
{
Q->tail = ((Q->tail) + 1) % Maxlen; //计算入队位置,此处很重要
Q->data[Q->tail] = x; //填入数据x
//2行语句可否并未1行呢?
}
}
void outQueue(seqQueue* Q)
{
if (queueEmpty(*Q))
cout << endl << "空队列,没有元素可供出队!" << endl;
else
{
cout << endl << "出队执行!" << endl;
Q->front = (Q->front + 1) % Maxlen;
}
}
void cycleenQueue(seqQueue* Q)
{
cout << endl << "请输入入队元素(回车结束):" << endl;
elementType x;
while (cin.peek() != '\n') //cin.peek()返回值是指针指向的当前字符
{
cin >> x;
enQueue(Q,x);
}
}
void printQueue(seqQueue* Q)
{
if (queueEmpty(*Q))
cout << endl << "当前为空栈!" << endl;
else
{
elementType n;
n=Q->front+1; //此处改为n=Q->front++;行不行?
cout << endl << "当前队内元素(队头-->队尾):" << endl;
while (n <= Q->tail)
{
cout <<Q->data[n]<< " ";
n++;
}
cout << endl;
}
}
int main()
{
seqQueue s;
initQueue(&s);
queueEmpty(s);
queueFull(s);
cycleenQueue(&s);
printQueue(&s);
queueFront(s);
outQueue(&s);
queueFront(s);
return 0;
}
三、链表
今天先写这点