数据结构-栈,队列
0PTA截图
1本周学习总结
1.1栈
栈是一个特定的存储区或寄存器,它的一端是固定的,另一端是浮动的 [1] 。堆这个存储区存入的数据,是一种特殊的数据结构。所有的数据存入或取出,只能在浮动的一端(称栈顶)进行,严格按照“先进后出”的原则存取,位于其中间的元素,必须在其栈上部(后进栈者)诸元素逐个移出后才能取出。在内存储器(随机存储器)中开辟一个区域作为堆栈,叫软件堆栈;用寄存器构成的堆栈,叫硬件堆栈。
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
栈作为一种数据结构,是一种只能在一端进行插入或删除操作的线性表,表中允许插入、删除操作的一端为栈顶。
栈中元素特性:具有线性关系,后进先出
进出栈规则:栈顶出栈,栈底最后出栈;时进时出,元素未完全进栈时,即可出栈。
- 顺序栈
顺序栈四要素
1、栈空:top=-1
2、栈满:top=MaxSize-1
3、进栈e操作:top++;st->data[top]=e
4、退栈操作:e=st->data[top];top--
操作函数
结构体
typedef struct
{
ElemType data[MaxSize];
int top;//栈顶指针
}Stack;
typedef Stack *SqStack;
1、初始化
void InitStack(&s)
{
s=new Stack;
s->top=-1;
}
2、销毁栈
void DestroyStack(SqStack &s)
{
delete s;
}
3、判断栈空
bool StackEmpty(s)
{
return(s->top==-1)
}
4、创建栈
Stack CreateStack(int MaxSize)
{
Stack s=(Stack)malloc(sizeof(struct SNode));
s->Data=(ElementType *)malloc (MaxSize*sizeof(ElementType));
s->top=0;
s->MaxSize = MaxSize;
return s;
}
5、进栈
bool Push (SqStack &s,ElemtType e)
{
if(s->top==MaxSize-1)//顺序栈务必考虑栈满
return false;
s->top++;//顶指针增1
s->Data[s->top]=e;
return true;
}
6、出栈
bool Pop(SqStack &s,ElemType &e)
{
if(s->top==-1)//栈为空,栈下益出
return false';
e=s->Data[s->top];//取栈顶指针元素
s->top--;
return true;
}
链栈
采用链表存储结构称为链栈
- 链栈的结构体
typedef int ElemType;
typedef struct Linknode
{
ElemType data;//数据域
struct Linknode *next;//指针域
}LinkNode, *LiStack;
- 栈的四要素
1、栈空:s->next==NULL;
2、栈满:不考虑
3、进栈e操作:节点插入到头节点后,链表头插法
4、退栈操作:取出头节点之后节点的元素并删除 - 栈的基本运算
1、初始化栈
void InitStack(LiStack &s)
{
s=new LiNode;
s->next=NULL;
}
2、销毁栈
void DestroyStack(LiStack &s)
{
LiStack node;
whilw(s!=NULL)
{
node=s;
s=s->next;
delete node;
}
}
3、判断栈是否为空
bool StackEmpty(LiStack &s)
{
return (s->next==NULL);
}
4、进栈(头插法)
链栈需要用头插法的方式,来入栈,因为如果使用尾插法的话,进栈好进,但是出栈的时候,就有问题了,栈顶指针下不去了。
void Push (LiStack &s)
{
LiStack p;
p=new LiNode;
p->data=e;
p->next=s->next;
s->next=p;
}
5、出栈
bool pop(LiStack &s,ElemType &e)
{
LiStack p;
if(s->next==NULL)
return false;
p=s->next;
e=p->data;
s->next=p->next;
delete p;
return true;
}
6、取栈顶元素
bool GetTop(LiStack s,ElemType &e)
{
if(s->next==NULL)
return false;
e=s->next->data;
return true;
}
1.2栈的应用
- 表达式
本题的测试点有一个比较难想到:第一个数为正数,且带有正号,则不输出;中间的数为正数也带有正号,但输出不带正号,另外本题需要思路清晰,才能完整的考虑到,本题用的循环比较多,且时嵌套循环,会因为思路混乱,导致前面已经考虑到的点,已经写到了,但后面又重复写了
#include <iostream>
#include<stack>
using namespace std;
stack<char>s;
char str[1000];
int main()
{
int i = 0,flag=0;
cin >> str;
for (; str[i] != '\0'; i++)
{
if ((i == 0 && (str[i] == '-'||str[i]=='+')) || (i>=1&&str[i - 1] == '(' &&( str[i] == '-'||str[i]=='+')))//判断负号
{
if(str[i]=='+'&&i==0){
flag = 0;
}
else if(str[i]=='+'){
cout<<" ";flag=0;
}
else if (flag == 0)cout << str[i];
else cout << " " << str[i];
flag = 0;
}
else if (str[i] == '+' || str[i] == '-') {//+或-
while (!(s.empty()||s.top() == '('))
{
cout << " " << s.top();
s.pop();
}
s.push(str[i]);
}
else if (str[i] == '*' || str[i] == '/')
{
while (!s.empty() && (s.top() == '/' || s.top() == '*'))
{
cout << " " << s.top();
s.pop();
}
s.push(str[i]);
}
else if (str[i] == '(')s.push(str[i]);
else if (str[i] == ')') {
while (s.top() != '(')
{
cout << " " << s.top();
s.pop();
}
s.pop();
}
// 3*(-4)
else if ((str[i] >= '0' && str[i] <= '9') || str[i] == '.')
{
if (flag)cout << " ";
while ((str[i] >= '0' && str[i] <= '9') || str[i] == '.')
{
cout << str[i];
i++;
}
i--;
flag = 1;
}
}
while (!s.empty())
{
cout << " " << s.top();
s.pop();
}
return 0;
}
1.3队列
只允许在表的一段进行插入,而在表的另一端进行删除的线性表。
队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量
"假上溢"现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。
队尾(rear)--允许插入的一端
队头(front)--允许删除的一端
队列特点:先进先出
应用:操作系统,销售系统,打印机,手机短信发送
- 顺序队
顺序队四要素(初始时 front=rear=-1)
1、队空:front=rear;
2。队满:rear=MaxSize-1;
3、元素e进队:raer++;data[rear]=e;
4、元素e出队:front++;e=data[front];
结构体
typedef struct
{
ElemType data[MaxSize];
int front,rear;//队首和队尾指针
}Queue;
typedef Queue *SqQueue;
1、初始化
void InitQueue(SqQueue &q)
{
q=new Queue;
q->front=q->rear=-1;
}
2、销毁队列
void DestroyQueue(SqQueue &q)
{
delete q;
}
3、判断队列是否为空
bool QueueEmpty(SqQueue q)
{
return (q->front==q->rear);
}
4、进队列
bool enQueue(SqQueue &q,ElemType e)
{
if(q->rear+1==MaxSize)
return false;
q->rear=q->rear+1;
q->data[q->rear]=e;
return true;
}
5、出队列
bool deQueue(SqQueue &q,ElemType &e)
{
if(q->front==q->rear)
return false;
q->front=q->front+1;
e=q->data[q->front];
return true;
}
- 环形队列
在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列
把存储队列元素的表从逻辑上看成一个环。 - 操作函数
1、初始化
void InitQueue(SqQueue &q)
{
q=new Queue;
q->front=q->rear=0;
}
2、队满:(rear+1)%MaxSize=front;
3、队空:front=rear;
4、入队
bool enQueue(SqQueue &q,ElemType e)
{
if((q->rear+1)%MaxSize==0)
return false;
q->rear=(q->rear+1)%MaxSize;
q->data[q->rear]=e;
return true;
}
5、出队
bool deQueue(SqQueue &q,ElemType &e)
{
if(q->front==q->rear)
return false;
q->front=(q->front+1)%MaxSize;
e=q->data[q->front];
return true;
}
- 队列应用
舞伴问题
刚开始使用的是顺序队列,但一直是答案错误,后面问了月月,本题使用循环队列,但是还是不是很理解为什么要用循环队列才能过测试点
int QueueLen(SqQueue Q)//队列长度
{
return (Q->rear - Q->front);
}
int EnQueue(SqQueue& Q, Person e)//加入队列
{
if (Q->rear == MAXQSIZE - 1)
return false;
Q->data[(++Q->rear)%MAXQSIZE] = e;
return true;
}
int QueueEmpty(SqQueue& Q)//队列是否为空
{
if (Q->front == Q->rear)
return 1;
return 0;
}
int DeQueue(SqQueue& Q, Person& e)//出队列
{
if (Q->front == Q->rear)
return false;
(++Q->front)%MAXQSIZE;
e = Q->data[Q->front];
return true;
}
void DancePartner(Person dancer[], int num) //配对舞伴
{
Person p;
int i;
for (i = 0; i < num; i++)
{
p = dancer[i];
if (p.sex == 'M')
{
EnQueue(Mdancers, p);
}
else
EnQueue(Fdancers, p);
}
while (!QueueEmpty(Fdancers) && !QueueEmpty(Mdancers))
{
DeQueue(Fdancers, p);
cout << p.name << " ";
DeQueue(Mdancers, p);
cout << p.name <<endl;
}
return ;
}
2PTA实验作业
2.1符号配对
**思路及伪代码
定义一个字符类型的栈;和字符串型的leftchar,rightchar分别存放左右括号;令flag=0;
当输入的字符不为空时,利用 stack库里的find函数,若左右括号都存在,flag=1;
如果栈空且有右括号存在,return false;如果配对成功,出栈;否则进栈;
如果flag=1且栈空,return true;
否则如果flag=1,输出栈顶元素,return false;否则 return true;
定义一个char 类型的栈st
string leftchar = "([{";
string rightchar = ")]}";
令 flag = 0;
定义一个字符型变量str并把数组中的符号赋值过来
while(str)
if leftchar.find(str) != -1 || rightchar.find(str) != -1//左括号和右括号都存在
then
flag = 1;
if st.empty() && rightchar.find(str) != -1//栈空,有右括号
then
return false;
else if !st.empty() && rightchar.find(str) != -1&&rightchar.find(str)==leftchar.find(st.top())//配对成功
then
st.pop();//出栈
end if
else
st.push(str);//入栈
end if
str = bracket_str[i++];
end if
if flag && st.empty()
then
return true;
end if
else if flag
then
cout << st.top()<<"\n";
return false;
end else if
else
return true;
- 知识点
用到了stack库里的find(找) empty(判断空) push(进栈) pop(出栈) top(取栈顶元素)函数,使代码更加简洁,提高了效率,利用了栈这一结构存放符号
2.2银行排队
- 思路
利用for循环,将用户的编号输入,并利用%判断奇数或偶数,奇数排到A对队列,偶数排到B队列。
解决输出问题,若A不为空则直接输出,否则输出B。利用while循环,当两个中有不为空的时候输出前面带有空格的数据。
int num;//顾客人数
int temp;//顾客编号
输入顾客人数
queue<int>A, B;
for i = 0 to num-1
输入顾客编号
if (temp % 2)//偶数
then
A.push(temp)//入A栈
end if
else
B.push(temp)//入B栈
令 flag = 0;
if !A.empty()
then
输出A栈栈顶元素
出A栈栈顶元素
i = 1;
end if
else
输出B栈栈顶元素
出B栈栈顶元素
end else
while !A.empty() || !B.empty()
i++;
if i % 2//奇数
then
if !A.empty()
then
输出A栈栈顶元素
出A栈栈顶元素
end if
end if
else
if !A.empty())
then
输出A栈栈顶元素
出A栈栈顶元素 end if
if !B.empty()
then
输出B栈栈顶元素
出B栈栈顶元素
end if
end while
3阅读代码
-
思路:
从队列尾部插入元素时,我们可以提前取出队列中所有比这个元素小的元素,使得队列中只保留对结果有影响的数字。这样的方法等价于要求维持队列单调递减,即要保证每个元素的前面都没有比它小的元素。
-
代码
-
时间复杂度
O(1)(插入,删除,求最大值)
删除操作于求最大值操作显然只需要 O(1) 的时间。
而插入操作虽然看起来有循环,做一个插入操作时最多可能会有 n 次出队操作。但要注意,由于每个数字只会出队一次,因此对于所有的 n 个数字的插入过程,对应的所有出队操作也不会大于 n次。因此将出队的时间均摊到每个插入操作上,时间复杂度为 O(1)。 -
空间复杂度
O(n),需要用队列存储所有插入的元素 -
难点
如何高效实现一个始终递减的队列?
只需要在插入每一个元素 value 时,从队列尾部依次取出比当前元素 value 小的元素,直到遇到一个比当前元素大的元素 value' 即可。 -
优势
从队列尾部取出元素,因此需要使用双端队列来实现。另外我们也需要一个辅助队列来记录所有被插入的值,以确定 pop_front 函数的返回值。
保证了队列单调递减后,求最大值时只需要直接取双端队列中的第一项即可。