栈和队列(期末复习版,铁铁,巨简洁)

栈和队列是两种常用的,重要的数据结构,限定只能在表的端点进行删除插入的线性表。
栈只能删除插入最后一个元素,栈:后进先出。
类似于栈的元素:洗盘子,装电池
队列:就是先进先出,类似于排队,先来的先走,插入的时候从队头插入,删除的时候从队尾删除
栈的定义和特点:
栈是一个特殊的线性表,是限定于仅在一端进行插入和删除操作的线性表。
这种结构称为后进先出的结构,简称LIFO结构
栈其实就是仅在表尾进行插入,删除操作的线性表。
表尾就是栈顶,表头为栈底。
栈一般用stac这个单词,在定义结构体的时候可以参考。
栈一般用一个数组来存储数据,栈顶元素先出栈,弹栈。
栈的结构体里面有栈的长度与栈顶的指针

#define MAXSIZE 50  //定义栈中元素的最大个数
typedef int ElemType;   //ElemType的类型根据实际情况而定,这里假定为int
typedef struct{
    ElemType data[MAXSIZE];
    int top;    //用于栈顶指针
}SqStack;
初始化顺序栈:
void InitStack(SqStack *S){
    S->top = -1;    //初始化栈顶指针
}
判断栈是否为空:
bool StackEmpty(SqStack S){
    if(S.top == -1){    
        return true;    //栈空
    }else{  
        return false;   //不空
    }
}

顺序栈的存储元素为顺序表,用数组来存储数据。指针(top)指示当前栈顶元素的位置。也就是数组此时的索引,并不存储位置
进栈操作:

Status Push(SqStack *S, ElemType e){
    //满栈
    if(S->top == MAXSIZE-1){
        return ERROR;
    }
    S->top++;   //栈顶指针增加一
    S->data[S->top] = e;    //将新插入元素赋值给栈顶空间
    return OK;
}

data是一个数组,用来存储栈中的数据,而int类型的top为记录栈的元素有多少个,记录数组中一共有多少个元素,所以也可以用来记录栈顶元素的指针(也就是栈顶的数据下标)
利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸.
共享栈的结构如下:
/两栈共享空间结构/
#define MAXSIZE 50 //定义栈中元素的最大个数
typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int
/两栈共享空间结构/

typedef struct{
	ElemType data[MAXSIZE];
	int top0;	//栈0栈顶指针
	int top1;	//栈1栈顶指针
}SqDoubleStack;

对于共享栈的堆栈操作,其实就是在普通的堆栈操作上加一个判断stackNumber,有两个栈,如果stackNumber是0,就将栈的元素堆在第一个栈中,如果stackNumber是1,就将栈的元素堆在第二个栈中,代码如下:

Status Push(SqDoubleStack *S, Elemtype e, int stackNumber){
    if(S->top0+1 == S->top1){   //栈满
        return ERROR;
    }
    if(stackNumber == 0){   //栈0有元素进栈
        S->data[++S->top0] = e; //若栈0则先top0+1后给数组元素赋值
    }else if(satckNumber == 1){ //栈1有元素进栈
        S->data[--S->top1] = e; //若栈1则先top1-1后给数组元素赋值
    }
    return OK;
}

需要注意的是,因为两边栈底不一样,所以起始点不一样,一边是从MAXSIZE-1开始,一边是从0开始,所以一边的进栈是++,一边是–,之所以要加一,是因为top0的初始值赋的是-1,如果两栈的指针相遇,则表示栈满。
共享栈的出栈操作:

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqDoubleStack *S, ElemType *e, int stackNumber){
    if(stackNumber == 0){
        if(S->top0 == -1){
            return ERROR;   //说明栈0已经是空栈,溢出
        }
        *e = S->data[S->top0--]; //将栈0的栈顶元素出栈,随后栈顶指针减1
    }else if(stackNumber == 1){
        if(S->top1 == MAXSIZE){
            return ERROR;   //说明栈1是空栈,溢出
        }
        *e = S->data[S->top1++];    //将栈1的栈顶元素出栈,随后栈顶指针加1
    }
    return OK;
}

从出栈中的MAXSIZE和-1也能够看得出来,共享栈同时共享一个数组,一个从最左端的0开始,一个从最右端的MAXSIZE开始。
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头节点,Lhead指向栈顶元素。

/*栈的链式存储结构*/
/*构造节点*/
typedef struct StackNode{
    ElemType data;
    struct StackNode *next;
}StackNode, *LinkStackPrt;
/*构造链栈*/
typedef struct LinkStack{
    LinkStackPrt top;
    int count;
}LinkStack;
这是构造了一个栈链
/*插入元素e为新的栈顶元素*/
Status Push(LinkStack *S, ElemType e){
    LinkStackPrt p = (LinkStackPrt)malloc(sizeof(StackNode));
    p->data = e;
    p->next = S->top;    //把当前的栈顶元素赋值给新节点的直接后继
    S->top = p; //将新的结点S赋值给栈顶指针
    S->count++;
    return OK;
}

插入一个新元素在栈的栈顶。
P是栈的每一个结点,将p的直接后继赋值给栈顶后,p成为了新的栈顶。最后再将用来计数的count自加一遍就完成了入栈操作。
出栈操作也类似:

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(LinkStack *S, ElemType *e){
    LinkStackPtr p;
    if(StackEmpty(*S)){
        return ERROR;
    }
    *e = S->top->data;
    p = S->top; //将栈顶结点赋值给p
    S->top = S->top->next;  //使得栈顶指针下移一位,指向后一结点
    free(p);    //释放结点p
    S->count--;
    return OK;
}

队列:是只允许在一端进行插入操作,另一端进行删除操作的线性表。也是用数组来存储数据

#define MAXSIZE 50	//定义队列中元素的最大个数
typedef struct{
	ElemType data[MAXSIZE];	//存放队列元素
	int front,rear;
}SqQueue;

定义了一个队尾和一个队头。
队列在初始状态时,队头和队尾元素都指向数组的第一个元素,也就是Q->front == Q->rear == 0
当队空时,也就是队列中没有元素的时候时,Q->front = Q->rear=0。
进队操作,将元素送到队尾,再将队列的长度加一。
要入队时,将要入队的元素用尾指针Q.rear-指向它。
入队:
base[rear]=x;
rear++;
出队时:
x=base[front];
front++;
front和rear都是从0开始的,只不过入队和出队时,自加的元素不同罢了。
队列的溢出问题:
当数组大小为rear=maxqsize时,此时如果再次入队,rear就会加一,可是rear此时已经是最大长度了,这就是假溢出。真溢出就是front=0,rear为MAXQSIZE,无法入队了,这就是真溢出。
解决假溢出的方法是用循环队列。
循环队列:将头尾相接的顺序存储结构称为循环队列。
当rear队尾=MAXQSIZE时,如果向量的开始端空着,又可从头使用空着的空间。
将base[0]接在base[MAXQSIZE-1]之后,当rear+1MAXQSIZE,则令rear=0。
那怎样才能让rear=0呢?使用模运算,其实就是取余运算。
只有MAXSIZE%MAXSIZE时,才会使取余的数为0;
初始时:Q->front = Q->rear=0。
队首指针进1:Q->front = (Q->front + 1) % MAXSIZE。
队尾指针进1:Q->rear = (Q->rear + 1) % MAXSIZE。
队列长度:(Q->rear - Q->front + MAXSIZE) % MAXSIZE。
所以插入的代码应该这样写:
Q.base[Q.rear]=x;
q.rear=(Q.rear+1)%MAXQSIZE;
这样,既保证了数组的长度自加了,也保证了数组达到最大长度以后可以将rear的值变成0。
删除元素时:x=Q.base[Q.front]
Q.front=(Q.front+1)%MAXSIZE
就像转圈排队一样。
那么就会产生一个新的问题,就是当队空和队满的时候,front指针和rear指针都指向同一个位置,那么要怎么判断是队空还是队满呢?
可以设一个标志,来区别队空和队满。
也可以设置一个count变量,来记录元素个数。
最后也可以是少用一个元素空间。
教材给的解决方案是少用一个存储空间,将Q.rear刚开始的时候置空。
那么,队满时,就是(rear+1)%MAXSIZE
front.
队空时还是front==rear
循环队列的定义方法:

typedef int ElemType;   //ElemType的类型根据实际情况而定,这里假定为int
#define MAXSIZE 50  //定义元素的最大个数
/*循环队列的顺序存储结构*/
typedef struct{
    ElemType data[MAXSIZE];
    int front;  //头指针
    int rear;   //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;

还是用数组的形式来存储数据,定义数组的头指针和尾指针
循环队列的初始化:

/*初始化一个空队列Q*/
Status InitQueue(SqQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    return OK;
}

让头指针和尾指针都为0
判断循环队列是否为空;

/*判队空*/
bool isEmpty(SqQueue Q){
    if(Q.rear == Q.front){
        return true;
    }else{
        return false;
    }
}

求循环队列的长度:

/*返回Q的元素个数,也就是队列的当前长度*/
int QueueLength(SqQueue Q){
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

循环队列中的入队操作:

/*若队列未满,则插入元素e为Q新的队尾元素*/
Status EnQueue(SqQueue *Q, ElemType e){
    if((Q->rear + 1) % MAXSIZE == Q->front){
        return ERROR;   //队满
    }
    Q->data[Q->rear] = e;   //将元素e赋值给队尾
    Q->rear = (Q->rear + 1) % MAXSIZE;  //rear指针向后移一位置,若到最后则转到数组头部
    return OK;
}

队列中的入队操作:

/*若队列不空,则删除Q中队头元素,用e返回其值*/
Status DeQueue(SqQueue *Q, ElemType *e){
    if(isEmpty(Q)){
        return REEOR;   //队列空的判断
    }
    *e = Q->data[Q->front]; //将队头元素赋值给e
    Q->front = (Q->front + 1) % MAXSIZE;    //front指针向后移一位置,若到最后则转到数组头部
}

队列的链式存储结构:
队列的链式存储结构表示为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表,只不过它只能尾进头出而已。
链队的存储结构:

/*链式队列结点*/
typedef struct {
	ElemType data;
	struct LinkNode *next;
}LinkNode;//这是对每一个结点的结构,存储了数据,以及next指针
typedef struct{
	LinkNode *front, *rear;	//队列的队头和队尾指针
}LinkQueue;//这是整个链表的结构
链队的初始化:
void InitQueue(LinkQueue *Q){
	Q->front = Q->rear = (LinkNode)malloc(sizeof(LinkNode));	//建立头结点
	Q->front->next = NULL;	//初始为空
}

首先申请内存,将头指针和尾指针都指向整个链表的第一个头结点。
并且将头指针的下一个指针定为NUUL,这样整个链表就只有一个头指针,就初始化成功了
链队列出队
出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,则需将rear指向头结点。
队列只有头指针可以出队,代码如下,我会详细的解释:

Status DeQueue(LinkQueue *Q, Elemtype *e){
	LinkNode p;
	if(Q->front == Q->rear){//如果头指针和尾指针在头一个结点上,就是一个空链表
		return ERROR;
	}
	p = Q->front->next;	//将欲删除的队头结点暂存给p,队头结点并不存储数据。
	*e = p->data;//将欲删除的队头结点的值赋值给e
	Q->front->next = p->next;	//将原队头结点的后继赋值给头结点后继
	//若删除的队头是队尾,则删除后将rear指向头结点
	if(Q->rear == p){	
		Q->rear = Q->front;
	}
	free(p);
	return OK;
}

接下来是双端队列
定义:
双端队列是指允许两端都可以进行入队和出队操作的队列,如下图所示。其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后端,两端都可以入队和出队。
在双端队列进队时,前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列中前端进的元素的后面。在双端队列出队时,无论是前端还是后端出队,先出的元素排列在后出的元素的前面。
就类似于一个中空的管道,前段进的在前后端进的在后面,可以看成一个羽毛球筒。
参考了很多文章,侵删

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值