关于栈和队列一些例题

有效的括号

给定一个只包括“(”,“(”,‘{’,‘}’,'[',  ']'的字符串s,判断字符串是否有效。

有效的字符串需要满足:

     1.左括号必须用相同类型的右括号闭合。

     2.左括号必须以正确的顺序闭合。

 如上述示例,是对栈的先进后出这个特性的利用,我们只需要让左括号入栈,右括号与上一个数据匹配。我们用两个指针*s和top,*s指针指向下一个需要插入或者需要匹配的数据位置,top指针指向栈顶的位置。然后如果*s指针指向的数据是左括号就放入栈顶,如果是右括号就取出栈顶数据进行匹配,匹配成功就让  ”*s++“,如果匹配失败就直接false。

需要注意的是,有给的数据只有左括号的例子,这时候就要判断一下。代码中的---1。这里使用了一个bool类型的返回值,来接收栈顶数据,这时候如果全部匹配的话栈里应该为空,如果栈不为空,说明有一个左括号没有匹配,就返回false。

还有只有右括号的情况,如代码中的--2,代码中因为是else的情况所以*s指向的数据是右括号的情况,这时如果栈中是空的情况满足了只有一个右括号这个示例,就直接返回false。

typedef char STDataType;
typedef struct stack
{
     STDataType* a;
     int top;
     int capacity;
}ST;

void StackInit(ST* ps)
{
       assert(ps);
       ps->a = (STDataType*)malloc(sizeof(STDatraType)*4);
       
       if (ps->a == NULL)             //判断malloc函数是否使用成功
       {
          printf("realloc fail\n");
          exit(-1);
       }     
       
       ps->top = 0;          //top的初始化
       ps->capacity = 4;      //capacity的初始化
}
void StackPush(ST* ps,STDataType x)
{
       assert(ps);
        
       if(ps->top == ps->capacity)
    {
        STDataType* tmp = (STDataType*)realloc(ps->a,ps->capacity*2sizeof(STDataType));
        if (tmp == NULL)             //判断realloc函数是否使用成功
       {
          printf("realloc fail\n");
          exit(-1);
       } 
        else
        {
          ps->a = tmp;
          ps->capacity *= 2;
        }
 
    ps->a = tmp;                         //对开辟的空间进行分布
    ps->capacity = newCapacity;       
    }

       ps->a[ps->top] = x;             
       ps->top++;
}
void StackDestroy(ST* ps)
{
       assert(ps);
       free(ps->a);
       ps->a = NULL;
       ps->capacity = ps->top = 0;
}
void StackPop(ST* ps)
{
     assert(ps);
     assert(ps->top > 0);       //判断栈是否为空
 
     ps->top--;
}
STDataType StackTop(ST* ps)
{
       assert(ps); 
       assert(ps->top > 0);         //判断这个栈是否为空,为空就报错
     
       return ps-a[ps->top - 1];
}

bool stackEmpty(ST* ps)
{
    assert(ps);
  return ps->top == 0;
}
bool isValid(char* s)
{
    ST st;
    StackInit(&st);
    while(*s);
 {
    if(*s == '(' 
       || *s == '{' 
       || *s == '[')           
     {
          StackPush(&st,*s);      //如果是左括号就把他放入栈里面 -----2
          ++s;
     }
    else
     {     //遇到右括号了,但是,栈里面没有数据,说明前面没有左括号,不匹配,返回false
         if(StackEmpty(&st))
           {
             return false;
           }
         STDataType top = StackTop(&st);    //取栈顶的元素
         StackPop(&st);

         if((*s == '}' && top != '{')
            || (*s == ')' && top != '[')
            || (*s == ']' && top != '['))
         {
            StackDestroy(&st);         //把栈全部释放一下,防止内存泄露
            return false;
         }
         else
         {
              ++s;
         }
        
     }
 }
   bool ret = StackEmpty(&st);    //如果栈不是空,说明栈中还有左括号未出    ------1
                                  //没有匹配,返回是false,如果是空就返回ture
   StackDestroy(&st);
   return ret;
}

用队列实现栈

只是用两个队列实现一个后入先出的栈,并支持普通栈的全部四种操作(push,top,pop,empty)。

实现MyStack类:

void push(int x)   //将元素x压入栈顶
int pop()          //溢出并返回栈顶的元素
int top()         //返回栈顶的元素
boolean empty()   //如果栈是空的,返回true  ;否则返回false

 首先先不考虑功能函数实现的问题,先考虑用两个队列实现栈的方法,栈的规则是后入先出,所有假设把队列的队尾看做是栈的栈顶,那么对于数据的插入队列和栈是一样的,那插入数据就直接插入就行,但是删除数据不同,队列删除数据是在队头一端,栈删除数据是在队列的队尾一端。所以这时候就用到第二个队列了。

假设我们第一个队列里面按照顺序存储了1,2,3,4这四个数据,那么假设我们要按照栈的方式删除一个数据,也就是”4“这个数据,那么我们只需要把栈中的“1”这个数据先保存到另一个栈里面,在保存“2”,这样依次保存,知道保存到“3”,这时原本那个队列里还剩个“4”,在把“4”删除掉,这时因为第一个队列里面只剩下一个“4”,所以删除可以按照队列的方式删除。

届时,我们不需要再把数据传回原本的那个队列,直接使用前面用来转换数据的队列来进行之后的操作。如下图所示,我们执行push插入“4”和“5“的操作。

 核心思路:
1.入数据,往不为空的一个队列插入,保持另一个队列为空

2.出数据,依次出队头的数据,转移另一个队列保存,只剩下最后一个的时候,Pop(删除)掉。

这样就用两个队列实现了栈的“后入先出”的原则。

代码实现“栈”和“栈”的插入操作

因为这个“栈”的插入操作和普通的栈是一样的,经过上面的分析,我们直接用代码来实现“栈”的定义和插入。

typedef struct Queue
{
   QueueNode* head;      //头指针
   QueueNode* tail;      //尾指针
}Queue;


typedef struct {
   Queue q1;
   Queue q2;
}MyStack;

void QueuePush(Queue* pq,QDataType x)
{
    assert(pq);
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));  //创建一片空间为要插入的新的
                                                                 //结点
    newnode->next = NULL;                     
    newnode->data = x;
 
    if(pq->head == NULL)                       //判断这个队列是否为空
   {
       pq->head = pq->tail = newnode;          //让头尾指针都指向队列中唯一的结点
   }
   else
   }
       pq->tail->next = newnode;               //让插入之前的最后一个结点的next指针指向新插入的 
                                               //结点
       pq->tail = newnode;                     //更新尾指针
   } 
}


MyStack* myStackCreate()      //我们采用malloc的方式申请一片空间来创建队列。
{
   MyStack* st = (MyStack*)malloc(sizeof(MyStack));
   QueueInit(&st->q1);     //因为在MyStack结构体里的q1和q2优势两个结构体,所以要对st进行解引用
   QueueInit(&st->q2);     

   return st;
}

void myStackPush(MyStack* obj,int x)
{
      if(QueueEmpty(&obj->q1))
       {
          QueuePush(obj->q1,x)
       }
       else
       {
          QueuePusu(&obj->q1.x)
       }
}

int main()
{
   MyStack* st = myStackCreate();
   myStackPush(st,1);
   myStackPush(st,2);
   myStackPush(st,3);
   
   return 0;
{

“栈”的删除操作

 对于“栈”的删除操作,我们在实现的时候要先判断这两个队列哪一个是空哪一个不是空,我们先定义两个指针变量(empty和nonempty)empty指针指向的是没有没有数据的队列,nonempty指针指向的是有数据的队列。对于上述的q1队列和q2队列,我们可以先默认q1队列为空,q2不为空,然后把empty指针指向q1,nonempty指针指向q2。然后再if判断一下如果q1队列不为空,就把nonempty指针指向q1,empty指针指向q2。

当然也可以用if  -- else来实现,我们使用(empty和nonempty)这两个指针用来储存有数据的队列和没有数据的队列,方便下面的实现操作。

当我们判断为空和不为空的队列之后,就要开始实现删除操作了。我们先把有数据队列的第一个数据储存到另一个队列里,然后再把原本有数据队列里的那个数据给删除掉,如此反复,一直操作到要删除的数据的位置就停止储存。然后把要删除的数据之间删除掉即可。


QDataType QueueFront(Qeueu* pq)  //取这个队列中队头的数据
{
   assert(pq);
   assert(!QueueEmpty));       //判断队列尾空去情况,为空就报错
 
 
   return pq->head->data;
}

int QueueSize (Queue*pq)        //取队列中数据个数的函数
{
   assert(pq);
 
   int n = 0;
   QueueNode* cur = pq->head;
   while(cur)
  {
      ++n;
      cur = cur->next;
  }
 
   return n;
}

void QueuePop(Queue* pq)
{
   assert(pq);
   assert(!QueueEmpty(pq));
      
 
   QueueNode* next = pq->head->next;
   free(pq->head);
   pq->head = next;
   
   if(pq->head == NULL)    //判断队列是否被删完
   {
     pq->tail = NULL;      //将tail尾指针置空
   }
}


void QueuePush(Queue* pq,QDataType x)
{
    assert(pq);
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));  //创建一片空间为要插入的新的
                                                                 //结点
    newnode->next = NULL;                     
    newnode->data = x;
 
    if(pq->head == NULL)                       //判断这个队列是否为空
   {
       pq->head = pq->tail = newnode;          //让头尾指针都指向队列中唯一的结点
   }
   else
   }
       pq->tail->next = newnode;               //让插入之前的最后一个结点的next指针指向新插入的 
                                               //结点
       pq->tail = newnode;                     //更新尾指针
   } 
}

bool QueueEmpty(Queue* pq)
{
   assert(pq);
   return pq->head == NULL;
}

int myStackPop(MyStack* obj)
{
     Queue* emptyQ = &obj->q1;
     Queue* nonempty = &obj->q2;
     if(!QueueEmpty(&obj->q1))
     {
         empty = &obj->q2;
         nonemptyQ = &obj->q1;
     }

    //利用循环来把有数据队列里前面的数据存储到另一个空队列里
     while(QueueSize(nonemptyQ) > 1)      //当队列里面只有一个数据的时候就停止循环
     {
          QueuePush(emptyQ,QueueFront(nonemptyQ));  //先从队头把一个数据用另一个空队列储存
          QueuePop(nonemptyQ);                      //再从队列的队头删除刚刚传入的那个数据
     }
  int top = QueueFront(nonemptyQ);  //因为题目要求返回这个删除数据的值,所以在删除之前储存一下
  QueuePop(nonemptyQ);      //删除需要删除的那个数据
 
  return top;
}

取“栈”顶的数据

对于取数据就不会用到第二个队列了,虽然按照我们之前对这个“栈”的定义,取“栈“顶的数据相当于就是取队列的队尾,我们只需要找到这个有数据的队列,然后然后利用这个队列的尾指针来寻找这个”栈“顶数据即可。

bool QueueEmpty(Queue* pq)
{
   assert(pq);
   return pq->head == NULL;
}



QueueType QueueBack(Queue* pq)    //取队列队尾的数据
{
   assert(pq);
   assert(!QueueEmpty);        //判断队列为空的情况
 
   return pq->tail->data;
}

int myStackTop(MyStack* obj)
{
       if(!QueueEmpty(&obj->q1)         //判断(找出)不为空的队列
         {
           return QueueBack(&obj->q1);     //取出队尾的数据
         }
       else
         {
           return QueueBack(&obj->q2);
         }
}

判断“栈”为空的函数

 我们之前在“栈”的删除操作里实现的判断队列为空的函数,而判断这个“栈”为不为空,其实就是看这个两个队列是否都为空,都为空这个“栈”就不是空,反之亦然。

bool QueueEmpty(Queue* pq)   
{
   assert(pq);
   return pq->head == NULL;
}


bool mtStackEmpty(MyStack* obj)  
{
    return QueueEmpty(&obj->q1)  && QueueEmpty(&obj->q2);
}

“栈”的整个删除

 对于我们定义的“栈”的结构,是两个队列所组成的,我们有一个MyStack*类型的st指针指向这个结构,而这个结构里面有两个指针q1和q2,这两个指针分别指向两个队列,这两个队列有一个是NULL的,有一个是可能是有数据的,那么我们不能直接free(st),不能直接把这个“栈”的指针free掉,要先把里面的q1和q2指向的空间释放掉再去释放st指针。(防止出现野指针)

void QueueDestroy(Queue* pq)     //对于整个队列的删除函数
{
   assert(pq);
   QueueNode* cur = pq->head;
   while(cur != NULL)     //如果while循环的条件使用(cur != pq->tail)会导致有一个数据删不完
  {
    QueueNode* next = cur->next;    //先存储下一个结点位置
    free(cur);                      //在释放这个结点
    cur = next;                     //指向下一个结点
  }
  pq->next = pq->tail = NULL;
}


void myStackFree(MyStack* st)
{
   QueueDestroy(&obj->q1);   //先把q1和q2指向空间删除
   QueueEestroy(&obj->q2);
   free(obj);
}

用栈实现队列     

 请你只使用两个栈实现先入先出的队列,这个“队列”应当支持一般队列支持的所有操作

(push      pop      peek      empty)

实现MyQueue类:

void push(int x)  //将元素x推到队列的末尾
int pop()    //从队列的开头移除并返回元素
int peek()    //返回队列开头的元素
boolean empty()    //如果队列为空,返回true ; 否则返回false

对于这个“队列”的实现,我们用两个栈(pushST和popST)来实现,首先我们来考虑插入数据的情况,我们假设把栈pushST的栈顶看做是“队列”的队尾,那么对于这个“队列”的插入操作就非常的简单了,直接插入就行。我们主要来看看删除操作,我们使用第二个栈来实现删除操作,当我们要删除数据的时候,就把栈pushST里的数据按照栈的规则传入到栈里面popST,当然传入到popST里面也是按栈的规则传入的,我们发现,数据的顺序就行了逆序。

我们之前在用两个队列实现栈的时候,我们在两个队列互相传数据的时候,数据的顺序是不会改变的。而两个栈在互相传数据的时候,数据的顺序发生了改变。

当我们把数据都传好之后,这时原本在push底端的数据就跑到了头上,我们就直接把栈popST的栈顶数据删除就行。这样就完成了对这个“队列”的数据进行删除的操作。

我们发现,我们用两个栈实现的队列,这个队列的插入操作和删除操作是互不影响的。而且我们定义的pushST插入栈,和popST删除栈,这两个栈也是不影响的。一个只管插入数据,一个只管删除数据,这用方便了我们对这些操作的实现。

这个“队列”的初始化及其定义

Typedef int STDataType;        
Typedef struct Stack          //栈的结构体定义
{
     STDataType* a;
     int top;         //栈顶的位置
     int capacity;    //容量空间
}ST;
 
void StackInit(ST* ps)        //栈的初始化函数
{
       assert(ps);
       ps->a = NULL;
       ps->top = 0;          //top的初始化
       ps->capacity = 0;      //capacity的初始化
}

typedef struct 
{
   ST pushST;
   ST popST;
}MyQueue;

MyQueue* myQueueCreate()      //创建两个栈
{
     MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));   //开辟栈的空间
     StackInit(&q->pushST);           //栈pushST的初始化
     StackInit(&q->popST);            //栈popST的初始化

     return q;
}

 判断这个队列为不为空的函数

 我们判断这个队列为不为空就要看这两个栈是否同时为空,同时为空及时曾队列为空

bool AstackEmpty(ST* ps)
{
    assert(ps);
  return ps->top == 0;
}

void myQueueEmpty(MyQueue* obj)
{
   return StackEmpty(&obj->pushST && StackEmpty(&obj->popST);
}

插入数据操作

void StackPush(ST* ps,STDataType x)   //在栈的栈顶插入数据
{
       assert(ps);
        
       if(ps->top == ps->capacity)
    {
        int newCapacity = ps->zapacity == ? 4 : ps->capacity * 2;    //判断这个栈是否为空
        STDataType* tmp = realloc(ps->a,sizeof(STDataType)*newCapacity);
        if (tmp == NULL)             //判断realloc函数是否使用成功
       {
          printf("realloc fail\n")
       } 
 
    ps->a = tmp;                         //对开辟的空间进行分布
    ps->capacity = newCapacity;       
    }
 
       ps->a[ps->top] = x;             
       ps->top++;
}

void myQueuePush(MyQueue* obj,int x)
{  
    StackPush(&obj->pushST,x);    
}

删除数据操作

 对于出数据,当我们的popST栈里有数据,就直接出数据就行;如果popST栈里没有数据,就把pushST栈里的数据传入到popST栈里,然后再在popST栈里实现出数据的操作。

void StackPop(ST* ps)     //取栈顶的数据
{
     assert(ps);
     assert(ps->top > 0);       //判断栈是否为空
 
     ps->top--;
}

STDataType StackTop(ST* ps)      //栈的栈顶数据进行删除
{
       assert(ps); 
       assert(ps->top > 0);         //判断这个栈是否为空,为空就报错
     
       return ps-a[ps->top - 1];
}
 

bool AstackEmpty(ST* ps)        //判断栈为空的函数
{
    assert(ps);
  return ps->top == 0;
}

int myQueuePop(MyQueue* obj)
{

   //如果popST栈中没有数据,就把pushST栈中的数据导入到popST里,这样就满足了先入先出的原则
   if(StackEmpty(&obj->popST))         //判断popST栈是否为空
    {    //如果是空 就把pushST栈里面的数据导入到popST栈里面
        while (StackEmpty(&obj->pushST)
      {
        StackPush(&obj->popST,StackTop(&obj->pushST));
        StackPop(&obj->pushST);
      }
    }
   
   int front = StackTop(&obj->popST);
   StackPop(&obj->popST);
   
   return front:
}

 取队列开头的元素

我们取队列开头的数据,要分两种情况,第一种是popST里面有数据,那就直接使用之前实现的StackTop()这个函数来寻找popST栈的栈顶的数据就行。第二种是popST里面没有数据,没有数据就把pushST里面的数据传入到popST栈里面,然后再用StackTop()函数就行查找返回就行。

STDataType StackTop(ST* ps)
{
       assert(ps); 
       assert(ps->top > 0);         //判断这个栈是否为空,为空就报错
     
       return ps-a[ps->top - 1];
}
void StackPush(ST* ps,STDataType x)
{
       assert(ps);
        
       if(ps->top == ps->capacity)
    {
        int newCapacity = ps->zapacity == ? 4 : ps->capacity * 2;    //判断这个栈是否为空
        STDataType* tmp = realloc(ps->a,sizeof(STDataType)*newCapacity);
        if (tmp == NULL)             //判断realloc函数是否使用成功
       {
          printf("realloc fail\n")
       } 
 
    ps->a = tmp;                         //对开辟的空间进行分布
    ps->capacity = newCapacity;       
    }
 
       ps->a[ps->top] = x;             
       ps->top++;
}

void StackPop(ST* ps)
{
     assert(ps);
     assert(ps->top > 0);       //判断栈是否为空
 
     ps->top--;
}

if(StackEmpty(&obj->popST))         //判断popST栈是否为空
    {    //如果是空 就把pushST栈里面的数据导入到popST栈里面
        while (StackEmpty(&obj->pushST)
      {
        StackPush(&obj->popST,StackTop(&obj->pushST));
        StackPop(&obj->pushST);
      }
    }

  return StackTop(&obj_>pushST);

 整个“队列”的删除

 对于这个指向队列的指针指向的空间,这个空间里面有两个指针,pushST和popST,这两个指针分别指向了pushST栈和popST栈,我们在删除整个队列的时候,不能直接把指向这个队列的指针的空间free掉,这个空间只是存储了指向pushST栈和popST栈的指针,如果直接free这些指针,那这两个指针指向的空间还没有free掉,造成了内存泄漏。

所以我们要先把这两个栈的空间free 掉在free 这个指向队列的“大指针”。

void StackDestroy(ST* ps)     //将整个栈释放掉
{
       assert(ps);
       free(ps->a);
       ps->a = NULL;
       ps->capacity = ps->top = 0;
}

void myQueueFree(MyQueue* obj)
{
      StackDestroy(&obj->pushST);    //先把两个栈释放掉
      StackDestroy(&obj->popST);
      
      free(obj);      //最后释放这个指针结构体
}

 设计循环队列

 设计你的循环队列实现。循环队列是一种特殊的线性数据结构,其操作表现基于FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。他也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通的队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我能使用这些空间取存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k)    //构造器,设置队列长度为k
Front      //从队首获取元素。如果队列为空,返回-1
Rear       //获取队尾的元素。如果队列为空,返回1
enQueue(value)     //向循环队列插入一个元素。如果成功插入则返回真

 

在实现之前,我们需要注意的是:

1.符合先进先出

2.空间大小固定

这里有两种实现方式,一种是用数组来实现,一种使用链表来实现。用链表来实现的话,其实我们之前咋学习单链表和双链表的时候学习过类似的结构,就是循环双链表 ,它在双链表的基础上,在第一个结点的prev指针(每个结点指向上一个结点的指针)是指向这个链表的最后一个结点的,而与之对应的最后一个结点的next指针(每个结点指向下一个结点的指针),是指向第一个结点的。由这样的链接方式,我们可以理解为,他们首位相连了。

所以,在用链表实现队列的时候,用这个方法就可以实现循环队列。

循环队列实现:

因为这个队列的大小是K,也就是说这个队列的空间要在一开始就开辟好,之后不能改变。
我们定义两个指针(tail和front),咋插入数据的时候,tail指向下一个结点,在下一个结点的位置插入数据就行。而front指针使用来删除数据的,front指针是一直指向这个队列的第一个有数据的结点的,我们先把front指针指向的结点的数据删除,然后再把front指向它删除数据结点的下一个结点。如此反复就用链表实现了循环队列。

 

需要注意的是,当tail指针和front指针指向同一个位置的时候,就表示这个队列为空。

数组实现:

对于数组的实现,也是用两个指针(tail和front),同样是插入数据时先把tail指向下一个结点,然后插入数据,咋删除数据的时候,先把front指针指向位置的数据删除然后在把front指针指向下一个结点。

 当tail指针指向数组最后一个元素的下一个位置的时候,就把tail指针指向第一个元素。

 

注:
循环队列,无论使用数据实现还是使用链表实现,都要多开一个空间,也就意味着,要是一个存k个数据的循环队列,要开k+1空间。否则无法实现判空和判满。我们上面也说了,当front == tail的时候,是用来判定队列是否为空的,如果我们不多开一个空间的话,就有一种情况,如下所示:

 这时候也是front = tail的情况,但是这个时候队列是满的状态,而不是空的状态。

数组和队列的判空条件都是 front和tail指向一个位置,但是判满的条件不太一样:

数组:

(tail + 1)%( k + 1) == front;

链式结构:

tail -> next == front;

 因为链式的最后一个结点有直接指向第一个结点的next指针,所以这里判空很方便。

数组的代码实现:

实现流程之前已经说过了,这个还需要注意的是一种特殊情况,如下所示:

 

 这个情况下,tail指向结点的下一个结点是空的,他的下一个结点应该是front指向的结点,所以在代码实现中,这个情况要把tail指向第一个结点。

typedef struct {
   int * a;
   int front;
   int tail;
   int k;
}MyCircularQueue;

MyCircularQueue* myCircularqueueCreate(int k)
{
   MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCiecularQueue));
   cq->a = (int*)malloc(sizeof(int)*(k+1));
   cq->front = q->tail = 0;
   cq->k = k;
 
   return q;
}


判断队列是空和队列是满的两个的函数

判空:

我们之前说过了,当两个指针指向一个位置的时候,这个栈就为空了。

bool myCircularQueueIsEmpty(MyCircularQueue* obj)    
{ 
     return obj->front == obj->tail;                //判断队列是否为空
}

判满:

对于数组的判满前面也说过了,满足  (tail + 1)%( k + 1) == front;  就认为这个队列已经满了。

这里还需要注意的是一种特殊情况,如下所示:

 

 这个情况下,tail指向结点的下一个结点是空的,他的下一个结点应该是front指向的结点,所以在代码实现中,这个情况要把tail指向第一个结点。

bool myCircularQueueIsFull(MyCircularQueue* obj)    
{
     return (obj->tail+1) % (obj->k+1) == obj->front;      //判断这个栈是否满了
}

插入数据

 对于插入数据,有插入成功的情况和插入失败的情况,对于插入操作,就是当这个队列满了的时候这个插入操作就失败了。

而插入数据也是有特殊情况的:

 这种情况是tail需要在第一个结点进行插入数据的操作,当我们在这个tail指向的位置插入数据之后,tail指向的是下一个NUll的位置,我们这个时候只要让tail %= k+1就行,而这个式子不需要加if判断,因为当这个tail在中间,图中所示位置的后一个位置(NULL)的时候,我们让tail %= k+1不影响tail的值。实现图如下所示:

bool myCircularQueueIsFull(MyCircularQueue* obj)    
{
     return (obj->tail+1) % (obj->k+1) == obj->front;      //判断这个栈是否满了
}

bool myCircularQueue(MyCircularQueue* obj ,int value)
{
     if(myCircularQueueIsFull(obj))
        return false;
     

     obj->a[obj->tail] = value;
     ++obj->tail;
     obj->tail %= (obj->k+1);

     return true;
}

删除数据操作

 删除操作也同样有与插入操作类似的特殊情况:

 对于上处的情况,当front在删除数据之后,指向了下一个位置,而下一个位置为空,那么他应该指向数据值为“6”的那个位置,我们同样是用front %= k+1的形式来实现front的换位。

bool myCircularQueueIsEmpty(MyCircularQueue* obj)    
{ 
     return obj->front == obj->tail;                //判断队列是否为空
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
     if(myCircularQueueIsEmpty(obj))
        return false;

     ++obj->front;
     obj->front %= (obj->k+1);
     return true;
}

取队列队头的数据

 题目规定这个函数如果队列为空返回-1,我们取队头的数据也就是取front指针指向的数据。

bool myCircularQueueIsEmpty(MyCircularQueue* obj)    
{ 
     return obj->front == obj->tail;                //判断队列是否为空
}

int myCircularQueueFront(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
      return -1;


    return obj->a[obj->front];
}

取队列队尾的数据

 取队尾数据就是取tail的前一个结点的数据,但是有一个特殊情况,就是当tail指向第一个结点的时候,也就是当tail在最后一个结点插完数据,返回到第一个结点的时候,如下图所示:

 这种情况下tail的前一个是数组-1的位置,这就访问错误了。这个时候我们访问的是最后一个结点的数据,也就是数组下标为k的位置。我们可以直接粗暴的if判断这个问题,如下所示:

if(obj->tail == 0)
   return obj->a[obj->k];
else
   return obj->a[obj->tail-1];

 还可以利用“%”来实现:

int i = (obj->tail+obj->k) % (obj->k+1);
  return obj->a[i];

我们拿这两种情况来理解一些这段代码。

第一种情况,tail = 2,这时候i = (2 + 4)% 5 = 1,而下标为1的数据就是tail之前的那个数据,

第二种情况,tail = 0,这时候i = (0 + 4) % 5 = 4,而下标为4的数据就是对应的最后的那个数据。

 下面实现取队尾的数据的代码:

int myCirlarQueueRear(MyCircularQueue* obj)
{
       if(myCircularQueueIsQmpty(obj))    //判断队列是否为空
          return -1;
 
      int i = (obj->tail+obj->k) % (obj->k+1);
      return obj->a[i];
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
引用: 单调栈和单调队列是一种常用的优化思想,可以通过维护一个有序的数据结构,排除不可能的决策,提高算法的效率。其中,单调栈适用于解决求解左边第一个比自己大(小)的点等问题,而单调队列适用于维护左区间最值等问题。通过这两种思想,可以优化动态规划等算法的时间复杂度。 引用: 在求解最大子序和问题时,可以使用单调队列优化。假设当前位置为i,我们可以维护一个单调递减的队列,其中存放的是前i个位置的子序和。每次加入新的元素时,可以通过比较队列中的元素和当前元素的大小,若当前元素更大,则弹出队列中的元素,直到队列为空或者队尾元素大于当前元素。然后将当前元素加入队列。最终,队列中的最大值即为最大子序和。 引用: 单调队列优化动态规划是通过将可转移状态存入一个单调队列中,来进行状态转移的一种方法。这个可转移状态要满足连续且单调的特性。具体原理是,如果一个选手比你小,但是比你强,那么你就可以退役了。在动态规划中,可以先使用朴素的dp方法找到一种方法,再根据单调队列优化。 例题1:求左边第1个比自己大的点 问题分析:将可转移给第i个点的对象存入栈中。假设i可以从j,k转移而来,其中j < k。栈中的元素是一个单调递减的栈。每次加入第i个点,都会依次将比ai小的弹出,最后栈顶元素即为答案。 例题2:维护左区间最值 问题分析:通过维护一个单调递减的队列,存储左区间的最值。在每次更新元素时,比较队列中的元素和当前元素的大小,若当前元素更大,则弹出队列中的元素,直到队列为空或者队尾元素大于当前元素。然后将当前元素加入队列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chihiro1122

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值