数据结构-栈,队列

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 函数的返回值。

保证了队列单调递减后,求最大值时只需要直接取双端队列中的第一项即可。

posted @ 2021-04-05 22:40  Morri  阅读(385)  评论(1编辑  收藏  举报