数据结构_栈和队列

数据结构_栈和队列

前言:此类笔记仅用于个人复习,内容主要在于记录和体现个人理解

栈的概念

栈(Stack),一种特殊的线性表

线性:线状结构(一对一的关系)

PNG图像

与之相对的是非线性结构(一对多的关系或者说是多对一;以下是其中一种,树)

PNG图像 2

特殊在必须按照如下原则存储数据:
只允许在固定的一端进行插入和删除元素

就是"后进先出"的原则(最后进来的最先出去)(Last In First Out,简称LIFO原则)

类似于一个有底的容器,依次将元素放进去,想要拿出来的话当然只能从最上面开始拿

不过也不一定非得都放进去再拿出来,可以拿一个放一个

可以参考手枪弹夹

可以都上满膛再打出去

也可以随便,上几个再打出去一个或几个

也可以参考便利贴

截屏2022-08-03 19.51.25 截屏2022-08-03 19.51.56

总之就是这个样子的

PNG图像
1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出 
栈的顺序是(C)。
A 12345ABCDE
B EDCBA54321
C ABCDE12345
D 54321EDCBA

2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是(C)
A 1,4,3,2
B 2,3,4,1
C 3,1,4,2
D 3,4,1,2

因为在入栈过程中可能有数据出栈,因此元素的出栈顺序会有多种情况,但一定满足LIFO原则

在函数的栈帧里面讲到过一种”栈“,它是虚拟进程地址空间划分的一个区域,用来函数调用时建立栈帧

它的遵循的原则跟数据结构里的“栈”一样,都是“后进先出”

但这两个“栈”并没有什么特殊联系,属于两个学科

栈的实现

栈的实现可以用两种,一种是数组,一种是链表,链表可单可双,都可以

如果是用数组的话,就是数组栈:用数组的头当作栈底,尾当作栈顶,尾插尾删就行

如果用链表的话,就是链式栈:

如果是双向循环链表,也是尾插尾删

如果是单链表的话,就让头当作栈顶,尾当作栈底,头插头删

因为单链表找尾很麻烦,但是头结点好找

PNG图像

一般来说认为数组栈要比链式栈好一点

因为虽然数组栈可能会有空间浪费(扩容),但由于物理存储上是连续的,cpu高速缓存命中率更高

数组栈的实现

top作为数组的权重,用来表示栈顶元素的位置

有两种表示的方法:

  1. top指向栈顶元素,一开始top=-1,有一个元素时top为0指向数组第一个元素,栈顶元素是数组的第一个元素;top+1的值等于元素个数
PNG图像
  1. top指向栈顶元素的后一个元素,一开始top=0指向数组第一个元素,有一个元素时top=1指向第二个元素也就是栈顶元素的后一个元素;top的值等于元素个数

    PNG图像 2
  • 学了链表之后,实现栈和队列如同砍瓜切菜!!*

下面是用top指向栈顶元素实现的动态数组栈

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int STDataType;//之前一直没有明说,为什么要用别名,因为这是为了修改数据方便,如果数据是char,在这一改就都改了
//栈的结构
typedef struct Stack
{
    STDataType *a;//用链表来存数据
    int top;//记录栈顶元素的位置
    int capacity;//栈容量
}ST;
//栈的初始化
void StackInit(ST* ps);
//栈的销毁
void StackDestory(ST* ps);
//数据入栈/压栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//判断栈是不是空,是的话返回0,不是的话返回非0
bool StackEmpty(ST* ps);
//返回元素个数
int StackSize(ST* ps);
//返回栈顶元素
STDataType StackTop(ST* ps);

不需要额外的扩容函数,因为只有压栈的时候需要扩容,用不着复用

一定要考虑边界情况:栈为空

栈的初始化

void StackInit(ST* ps)
{  
    ps->a = NULL;
    ps->top = -1;
    ps->capacity = 0;
}

栈的销毁

void StackDestory(ST* ps)
{
    assert(ps);

    free(ps->a);//ps->a 是malloc出来的,所以必须要free
    ps->a = NULL;
    ps->top = ps->capacity = 0;//数组置空了top跟capacity跟着清零
    //ps不用指向空,它只是main函数里的局部变量,ps只是一个结构体指针,并不是栈的本体,本体是数组
    //更何况传的不是二级指针
}

为什么malloc的空间最后要free

数据压栈

void StackPush(ST* ps, STDataType x)
{
    assert(ps);
    if((ps->top+1) == ps->capacity)
    {
        int newCapacity = ps->capacity == 0 ? 4 : (ps->capacity)*2;
        ps->a = (STDataType *)realloc(ps->a , newCapacity * sizeof(STDataType));//这里要注意扩容的大小的计算,是新空间元素个数乘以单个数据类型大小

        ps->capacity = newCapacity;//别忘了更新数组大小

        if(ps->a == NULL)//为了安全起见最好判断一下空间申请是否成功
        {
            printf("realloc fail\n\n");
            exit(-1);
        }
    }

    ps->a[(ps->top)+1] = x;
    ps->top++;//栈顶后移
}

出栈

void StackPop(ST* ps)
{
    assert(ps);
    if(ps->top>-1)
    //也可以更暴力一点,assert(ps->top>-1)
    --ps->top;
}

判断栈是不是空,是的话返回 非0( true ),不是的话返回 0( false )

bool StackEmpty(ST* ps)
{
    return ps->top == -1;//栈空了 = 等式成立 = 返回值为真(ture,非0)
}

bool 类型,返回值可以是非0(真),0(假),也可以是true(真)和false(假),在意义上两种是一样的

返回元素个数

int StackSize(ST* ps)
{   
    assert(ps);
    return ps->top+1;
}

返回栈顶元素

STDataType StackTop(ST* ps)
{
    assert(ps);
    return ps->a[ps->top];
}
队列
队列的概念

队列(Queue),一种只允许在一端插入数据,在另一端删除数据的特殊线性表。

像自来水管,从一端进水,另一端出水

遵循的原则是“先进先出”(最先进来的元素最先出去)(First In First Out,简称FIFO)

进行 插入 操作的位置称为队尾,进行 删除 操作的位置称为队头

image-20220806110908085

因此出队列的情况要比出栈更简单,只有一种情况,那就是排在前面的一定会先出来,不论入队列过程中是否有数据出队列

队列的实现

使用 单链表 来实现

根据队列的需求,只需要进行 尾插 和 头删

至于为什么不是 头插 和 尾删,以及为什么不用数组

基于队列的原则,必须头删 或 头插 ,头部的处理,无论是头插还是头删,数组都比较麻烦,而链表效率高。

数组实现尾插尾删很简单,只要记住尾的位置就行,因此用数组栈。

单链表尾插比较简单,虽然要找到尾,需要先遍历一次链表,不过可以用一个尾指针来保存尾结点的位置

单链表尾删比较麻烦,必须要找尾,找尾必须遍历链表,每次尾删都要遍历一次,因为即使记录了尾结点的位置,删除了也会找不到,就算记录了尾结点上一个节点的位置,最终还是会删除掉,所以无论如何最后到要遍历找尾。

因此单链表+头删尾插 容易实现

\

定义两个结构体

一个是队列的结点,包含结点数据和next指针

一个是队列,包含队列的头指针和尾指针,也可以增加一个整型size来方便记录数据个数

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int QDataType;

typedef struct Qnode
{
QDataType data;
struct Qnode *next;
}Qnode;//队列结点

typedef struct Queue
{
Qnode *head;
Qnode *tail;
int size;
}Queue;

//队列初始化
void QueueInit(Queue *pq);//pq的意思是phead of queue(大概
//销毁队列
void QueueDestory(Queue *pq);
//入队列
void QueuePush(Queue *pq , QDataType x);
//出队列
void QueuePop(Queue *pq);
//判断队列是否为空
bool QueueEmpty(Queue *pq);
//获取元素个数
size_t QueueSize(Queue *pq);
//返回队头元素
QDataType QueueFront(Queue *pq);
//返回队尾元素
QDataType QueueBack(Queue *pq);

一定要考虑边界情况:队列为空

队列初始化

void QueueInit(Queue *pq)//pq的意思是phead of queue(大概
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}

队列销毁

void QueueDestory(Queue *pq)
{
assert(pq);

while(pq->head)
{
    Qnode *next = pq->head->next;
    free(pq->head);
    pq->head = next;
}
pq->size = 0;
pq->tail = NULL;
}

只有malloc的空间才需要销毁

入队列

void QueuePush(Queue *pq , QDataType x)
{
  assert(pq);

  Qnode *newndoe =(Qnode *)malloc(sizeof(Qnode));
  assert(newndoe);

  newndoe->data = x;
  newndoe->next = NULL;

  if(pq->head == NULL)//队列之存在两种情况,空或者不空,空的话头尾同时为空,不空的话头尾都不为空
  //如果头为空或者尾为空(或者同时为空,这样的话下面就不用断言了),说明队列是空的
  {
      assert(pq->tail == NULL);//确保头尾同时为空
      pq->head = pq->tail = newndoe;
  }
  else
  {
      pq->tail->next  = newndoe;
      pq->tail = newndoe;
  }
  pq->size++;
}

一定要考虑边界情况

出队列

void QueuePop(Queue *pq)
{
    assert(pq);
    assert(pq->head);//防止空队列

    Qnode *newhead = pq->head->next;
    if(newhead == NULL)//如果队列只有一个元素的话
    {
        free(pq->head);
        pq->head = pq->tail = NULL;//出队列之后队列就为空了
    }
    else
    {
        free(pq->head);
        pq->head = newhead;
    }

    pq->size--;
}

判断队列是否为空

bool QueueEmpty(Queue* pq)
{
	assert(pq);

	//return pq->head == NULL && pq->tail == NULL;
	return pq->head == NULL;
}

获取元素个数

size_t QueueSize(Queue *pq)
{
    assert(pq);
    return pq->size;
}

返回队头元素

QDataType QueueFront(Queue *pq)
{
    assert(pq);
    assert(pq->head);//防止队列为空
    return pq->head->data;
}

返回队尾元素

QDataType QueueBack(Queue *pq)
{
    assert(pq);
    assert(pq->tail);//防止队列为空
    return pq->tail->data;
}
栈和队列面试题
笔试题

有效括号(括号匹配问题)

iShot_2022-08-04_10.10.51

可以看到“正确的顺序”就是在右括号之前,越靠后的左括号越先闭合

就是“LIFO"原则

思路:

用栈来解决,只有字符是左括号,就让它入栈,如果字符是右括号,那么就让栈顶的左括号出栈

这样就保证了在右括号出现的时候,是最后面的左括号跟右括号进行匹配

PNG图像

注意:

  1. 如果左括号的数量多于右括号,比如s里只有一个左括号,那么在遍历完字符串之后栈不为空,就返回false
  2. 如果右括号数量多于左括号,比如s里只有一个右括号,那么在栈为空的时候字符串元素是右括号,返回false

如何进入遍历字符串s的循化:

题目中传的参是char*,是s的首元素地址指针,s++就指向下一个元素,最后在指向完最后一个元素之后就会越界

字符串的细节 其实只要一个字符串赋过值(包括空字符串“”),就一定有一个字符’/0‘在里面,这是很基础的东西,但是往往会忘记,因为我们一般认为超过字符串的长度以后的索引都会越界,其实越界会取到‘/0’,取出来的是一个空字符‘’

所以只要s的元素也就是*s不是空字符就说明字符串没结束

因为c还不能用库,所以栈的实现需要自己造轮子,把上面写的栈的实现的代码先粘过去就行

然后完成题目

STDataType StackTop(ST* ps)
{
    assert(ps);
    return ps->a[ps->top];
}

bool isValid(char * s)
{
    ST st;
    StackInit(&st);

    while(*s)
    {
        if(*s == '(' || *s == '{' ||*s == '[')//是左括号,入栈
        {
            StackPush(&st , *s);
            s++;
        } 
        else//右括号,让栈顶元素出栈
        {
            if(StackEmpty(&st))//先判断栈是不是空
            {
                return false;
            }

            char top = StackTop(&st);//获取栈顶元素
            StackPop(&st);//出栈

            if(*s == ')' && top != '('|| *s == ']' && top != '['|| *s == '}' && top != '{')//匹配判断
            {
                return false;
            }
            else
            {
                s++;
            }   
        }
    }//如果顺利循环完说明存右括号都匹配上了
    bool ret = StackEmpty(&st);//看看栈是不是空,如果不是说明左括号有多余的左括号

    StackDestory(&st);//销毁栈,防止内存泄漏

    return ret;//这里很妙,用bool类型作为bool函数的返回值,省得分情况讨论返回真还是假
}

用队列实现栈

image-20220809002450521

就是用两个队列来实现栈的特点,后进先出

具体实现方法就是,保持一个队列是空的,一个不是空的

入数据的话就让数据进到非空队列,出数据的话,把非空队列的最后一个元素以外的元素全都转移到空队列,然后再把原非空队列中的这最后一个数据出队列

img

题解

实现队列首先需要先自己造轮子,直接粘贴上Queue的实现代码就行

构造MyStack结构体,定义构造函数

typedef struct 
{
Queue Qa;
Queue Qb;

} MyStack;


MyStack* myStackCreate() 
{   

MyStack *obj = (MyStack *)malloc(sizeof(MyStack));
assert(obj);

QueueInit(&obj->Qa);
QueueInit(&obj->Qb);

return obj;
}
  1. 为什么myStackCreate函数里面用malloc构造obj而不是直接声明一个obj?

因为函数内构造的是局部变量,出了函数作用域就会被销毁,必须用malloc在堆上申请空间

  1. 为什么MyStack里面用的是Queue结构体而不是结构体指针?

这个栈里面包含着两个队列,就是单纯的需要包含两个队列,用指针干啥。。。。。

就像Queue的定义里面用的是Qnode * 那是单纯的需要链表指针,一个指向头,一个指尾

就算是用结构体指针,那结构体Queue本体在哪儿?还是要malloc出来(在MyStackCreat的时候),要不队列没法用啊,有指针没实体

img

而函数在传参的时侯传栈、队列什么的结构体指针那是为了修改栈、队列的结构体成员

将数据x压入栈顶

void myStackPush(MyStack* obj, int x) 
{
    assert(obj);

    if(QueueEmpty(&obj->Qb))//把数据放到非空的队列里
    QueuePush(&obj->Qa , x);//注意这里,queuepush函数中有malloc结点的操作,因此最后一定要free掉
    else
    QueuePush(&obj->Qb , x);
}

移除并返回栈顶元素(指的是移除之前的栈顶元素)

int myStackPop(MyStack* obj) 
{
 assert(obj);

 Queue *emptyQ = &obj->Qa;//先假定a是空的,b不是空的
 Queue *nonEmptyQ = &obj->Qb;

 if(!QueueEmpty(&obj->Qa))//要是假设错了换过来就行了
 {
     emptyQ = &obj->Qb;
     nonEmptyQ = &obj->Qa;
 }

 while(QueueSize(nonEmptyQ) >1 )
 {
     QueuePush(emptyQ , QueueFront(nonEmptyQ));
     QueuePop(nonEmptyQ);
 }
 int top = QueueFront(nonEmptyQ);//注意要先nonEmpty的尾元素再free掉nonEmpty
 QueuePop(nonEmptyQ);

 return top;
}
  1. 这里用了一个简单的方法分辨非空队列:先假定一个是空的,另一个不是空的,如果错了的话那就反过来

这样后面不用每个判断都写一遍

  1. 判断非空队列是不是只剩一个元素

一开始我写的是判断队列的head == tail ,写起来有点麻烦

杭哥的方法是直接调用现成QueueSize函数

这里体现了函数复用的简便性,还是杭哥的好

返回栈顶元素

int myStackTop(MyStack* obj) 
{
    assert(obj);

    if(QueueEmpty(&obj->Qa))//还是调用前面的函数,简单
    return QueueBack(&obj->Qb);

    else
    return QueueBack(&obj->Qa);

}

判空函数和free函数

bool myStackEmpty(MyStack* obj) 
{
    assert(obj);

    return QueueEmpty(&obj->Qa) && QueueEmpty(&obj->Qb);
}
void myStackFree(MyStack* obj) 
{
    assert(obj);

    QueueDestory(&obj->Qa);//队列的结点是malloc出来的,因此要free掉,而且要注意free的顺序,先是队列的结点,再是obj。队列只保存了结点指针,没有保存结点,所以free掉obj并不会free掉结点,反而会找不到结点的地址
    QueueDestory(&obj->Qb);
    free(obj);
    obj = NULL;
}

用栈实现队列

image-20220809121341382

队列的特点是先进先出(FIFO),对于栈而言,最先进来的元素在最里面(栈底)

设置两个栈,一个专门用来入数据pushST,一个专门用来出数据popST

入队列直接进到pushST里面

一旦popST空了,就把pushST的数据都导过来,就保证了之前pushST里的最里面的元素(栈底,先进来的)到了popST里就变成了最外面的(栈顶,后进来的),就符合先进先出的原则了

img img

题解

还是要自己造轮子实现栈,直接先粘贴上Stack的代码

构造MyQueue结构体,定义构造函数

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


MyQueue* myQueueCreate() 
{
    MyQueue *obj = (MyQueue *)malloc(sizeof(MyQueue));
    assert(obj);
    
    StackInit(&obj->pushST);
    StackInit(&obj->popST);
    
    return obj;
}

元素x入队列

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

出队列,并返回被移除的元素

int myQueuePop(MyQueue* obj) 
{
    assert(obj);

    if(StackEmpty(&obj->popST))//凡是涉及队头元素的,都要判断一下popST是不是空,是空就把pushST移过去
    {   
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST , StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }

    int top = StackTop(&obj->popST);//记录被移除的队头元素
    StackPop(&obj->popST);
    return top;//返回被移除的队头元素
}

返回队头元素

int myQueuePeek(MyQueue* obj) 
{
    assert(obj);

    if(StackEmpty(&obj->popST))//凡是涉及队头元素的,都要判断一下popST是不是空,是空就把pushST移过去
    {   
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST , StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }

    return StackTop(&obj->popST);
}

判空函数和free函数

bool myQueueEmpty(MyQueue* obj) 
{
    assert(obj);

    return StackEmpty(&obj->popST) && StackEmpty(&obj->pushST);
}

void myQueueFree(MyQueue* obj) 
{
    assert(obj);

    StackDestory(&obj->pushST);
    StackDestory(&obj->popST);
    free(obj);
}

MyQueue设计符合**“解耦设计”** 解耦_百度百科 解耦设计 知乎 解耦设计 CSDN

低耦合,高内聚

MyQueue的实现用到了栈的接口,但是栈内部的实现跟MyQueue这个队列的没有关联,不管栈是数组栈还是链式栈,只要是后进先出的栈,能实现MyQueue的先进先出,栈和MyQueue的底层关联度是很低的。如果栈变成了链式栈,MyQueue照样能实现

循环队列

简单来说就是环状队列,全程空间都是既定的,可以存储的数据个数是有限的,但是同一空间是可以循环利用的

入队和出队无非就是头尾指针的移动,不涉及空间的创建和销毁

这种队列既可以使用数组实现,也可以使用链表实现

数组的物理存储空间是连续的,CPU高速缓存命中率更高,更优一些些

以下使用数组实现的

在head和tail之间的空间的数据才位于队列里面,其外的空间虽然存储了数据但是并不属于队列,只是在“缓冲区域”

通过控制head和tail的位置就可以操控队列

  1. 队列什么时候为空,什么时候为满

让head指向队列第一个元素,tail指向最后一个元素的下一个空间

PNG图像 3

如果head和tail相等,说明队列是空的

PNG图像

但是如果队列满了的话,head和tail也是相等的

PNG图像 2

所以“存4开5”,如果规定循环队列的容量是4的话,那就开5个存储空间,只存4个数据,当4个空间满了的时候

tail指向第5个,如果tail的下一个是head的话,说明满了

PNG图像 4
  1. 数组在逻辑上是成环的,但是物理上并不成环,如何让数组循环起来:

头尾指针移动的时候需要判断是不是走到了数组最后一位(单链表不用,直接成环了)

如果到了最后一位,再往后走,就要到第一位

Eg:假设进行出队列,出队列完头指针要后移,此时要进行判断:

if(obj->head == k)

obj->head == 0;

else

obj->head +=obj->head;

或者用第二种方法,不用进行判断

其实数组的指针就是权重

让head和tail取模就行

如果要求可以存储的总数据个数k=5,因为要开6个空间,k=5的权重是第6个空间,因此head和tail取k +1的模就可以

如果head要后移,那就obj->head = (obj->head+1)%(obj->k+1)

PNG图像

截屏2022-08-08 00.26.49

typedef struct 
{       
    int *a;//队列数组
    int head;//队列头权重
    int tail;//队列尾权重
    int k;//记录总的存储空间大小

} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k)//构造器,设置队列长度为 k 
{
    MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));//开队列结构体
    assert(obj);
    obj->a = (int *)malloc(sizeof(int) * (k+1));//开队列数组
    assert(obj->a);//防止malloc失败,不过其实因为这是题目,没必要进行malloc
    obj->head = obj->tail = 0;//初始化
    obj->k = k;//记录总存储空间大小
    return obj;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //向循环队列插入一个元素。如果成功插入则返回真。
{
    assert(obj);//其实没有必要断言,因为这是题目,题目不会给队列空指针
    if((obj->tail +1) % (obj->k +1) == obj->head)//判断是不是满了
    return false;
    else
    {
        obj->a[obj->tail] = value;//要注意,tail是指向队列最后一个元素的后一个,因此尾插的话要给tail而不是tail+1
        obj->tail = (obj->tail +1)%(obj->k +1);
        return true;
    } 
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)//从循环队列中删除一个元素。如果成功删除则返回真。
{
    if(obj->head == obj->tail)//判断是不是空
    return false;
    else
    {
        obj->head = (obj->head +1)%(obj->k +1);
        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj) //从队首获取元素。如果队列为空,返回 -1
{
    if(obj->head == obj->tail)
    return -1;
    else
    {
        return obj->a[obj->head];
    }
}

int myCircularQueueRear(MyCircularQueue* obj) //获取队尾元素。如果队列为空,返回 -1
{
    if(obj->head == obj->tail)
    return -1;
    else
    {
        if(obj->tail == 0)//如果tail此时是在数组头的话,那说明队尾元素是数组最后一个元素
        return obj->a[obj->k];
        else
        return obj->a[obj->tail - 1];
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) //检查循环队列是否为空。
{
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) //检查循环队列是否已满。
{
    return obj->head == (obj->tail +1)%(obj->k +1);
}

void myCircularQueueFree(MyCircularQueue* obj) //销毁队列
{
    free(obj->a);//题目中一般只管free,不用最后指向空,不怕野指针
    free(obj);
}
选择题

1、2题见上

image-20220808190139909

解析:

3和4题不用讲

第五题:

“长度是N,实际最多存储N-1个数据”

(在循环队列的面试题中,实际存储k个数据,长度是k+1)

思路一

假设N是5,那么队列的有效长度最大就是4,也就是队列有效长度在0~4之间

就是%N(当然%N还是权重的范围,以实现循环,也就是%(k+1))

img

只有数据%N之后才能保证有效长度在0~4之间,因此找%N的选项就行

可以选几个值进行验证一下

Eg:如果front = 0 , rear = 4 (注意rear指向的是队列最后一个元素的下一个)

那么此时队列的实际有效长度是4

按照B的式子,rear - front = 4 , rear - front + N = 4 + 5 = 9

9%N = 9%5 = 4

长度为4 ,正确,和实际有效长度一样

思路二

rear - front,无论rear和front哪个大,算的结果是不是负数,rear - front都表示rear领先front几个单位,如果结果是负数,说明rear领先了front一圈,跑到了front的后面,所以结果要加上N,就算rear比front大,相减的结果是正数,加上N之后,在%N就可以了,这样就把多的N模走了

结束

That’s all, thanks for reading!💐

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值