栈与队列详解

1. 栈

        栈,又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

 1.1 栈的顺序存储结构


#include <stdio.h>
#include <malloc.h>
 
#define STACK_MAX_SIZE 10

typedef struct CharStack {
    int top;
    int data[STACK_MAX_SIZE];
} *CharStackPtr;
 

CharStackPtr charStackInit() {
	CharStackPtr resultPtr = (CharStackPtr)malloc(sizeof(CharStack));
	resultPtr->top = -1;
	return resultPtr;
}

void outputStack(CharStackPtr paraStack) {
    for (int i = 0; i <= paraStack->top; i ++) {
        printf("%c ", paraStack->data[i]);
    }
}
 
void push(CharStackPtr paraStackPtr, int paraValue) {
    if (paraStackPtr->top >= STACK_MAX_SIZE - 1) {
        printf("栈已存满");
        return;
    }
	paraStackPtr->top ++;//上移top
    paraStackPtr->data[paraStackPtr->top] = paraValue;//元素赋值
}

char pop(CharStackPtr paraStackPtr) {
    if (paraStackPtr->top < 0) {
        printf("无法删除");
        return '\0';
    }
 
	paraStackPtr->top --;
    return paraStackPtr->data[paraStackPtr->top + 1];
}
void pushPopTest() {

    CharStackPtr tempStack = charStackInit();
	outputStack(tempStack);

	for (char ch = 'a'; ch < 'm'; ch ++) {
		printf("添加%c.\r\n", ch);
		push(tempStack, ch);
		outputStack(tempStack);
	}
	
	for (int i = 0; i < 3; i ++) {
		ch = pop(tempStack);
		printf("删除%c.\r\n", ch);
		outputStack(tempStack);
	}
}
void main() {
	pushPopTest();
}

1.2 栈的链式存储

        栈的链式存储结构是通过线性表的链式存储结构实现的,操作起来和单链表形式差不多。链栈的操作与链表类似,入栈和出栈都在链表的表头进行。在定义链栈的结点时,定义一个存放数据的变量data,一个存放后继指针的变量*next,结点定义如下:

        定义栈的时候,由于栈是在顶部进行元素的插入或删除操作,所以,需要一个栈顶指针top,因为是链栈,还需要一个计数变量count。链栈的结构定义如下。

#include<stdio.h>
#include<stdlib.h>
#include<time.h>//下方获取随机数的time需要的工具箱
#define INFINITY 99999
typedef struct node//数据节点结构体
{
    int val;//数据
    struct node* next;//指针
}pnode;
typedef struct seqstack//头结点结构体
{
    int size;//记录栈的大小
    pnode* top;//指向栈顶元素
}stack;//别名方便使用

 

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define INFINITY 99999
typedef struct node
{
    int val;//数据
    struct node* next;//指针
}pnode;
typedef struct seqstack
{
    int size;//记录栈的大小
    pnode* top;//指向栈顶元素
}stack;
stack*  initstack()//创建栈
{
 
    stack* istack=(stack*)malloc(sizeof(stack));
    if(istack!=NULL)
    {
        istack->top=NULL;
        istack->size=0;
    }
    return istack;
}
int isempty(stack* istack)//判断栈为空
{
    if(istack->top==NULL)
    {
        return 1;//栈为空
    }
    return 0;//栈不为空
}
int seqstack_top(stack* istack)//获取栈顶元素
{
    if(isempty(istack)==0)//
    {
        return istack->top->val;
    }
    return INFINITY;//返回无穷大,不能返回-1,有可能栈的顶端元素就是-1
}
int seqstack_pop(stack* istack)//弹出栈顶元素
{
    if(isempty(istack)==0)//栈不为空
    {
        int account=istack->top->val;
        istack->top=istack->top->next;
        return account;
    }
    return INFINITY;
}
void seqstack_push(stack* istack,int x)//压栈(入栈)
{
   pnode* temp;
   temp=(pnode*)malloc(sizeof(pnode));
   temp->val=x;
   temp->next=istack->top;
   istack->top=temp;
   istack->size++;
   return;
}
void seqstack_destory(stack* istack)//销毁栈
{
    if(isempty(istack)==0)
    {
        free(istack);
    }
}
void seqstack_print(stack* istack)
{
    pnode* temp=istack->top;
    for(int i=1;i<=istack->size;i++)
    {
        printf("%d ",temp->val);
        temp=temp->next;
        if(i%5==0)
        {
            printf("\n");
        }
    }
    printf("\n");
}
int main()
{
    srand((unsigned)time(0));//以时间为种子获得随机数
    stack* istack=initstack();
    printf("请输入初始栈的容量\n");
    int m;
    scanf("%d",&m);
    for(int i=0;i<m;i++)//将随机数压栈
    {
        seqstack_push(istack,rand()%1000);//取余1000是因为我想把获取的随机数控制在1000以内
    }
    //获取栈顶元素
    printf("栈顶元素:%d\n",seqstack_top(istack));
    printf("栈中的元素\n");
    seqstack_print(istack);
    printf("\n请输入压栈的数据\n");
    int n;
    scanf("%d",&n);
    seqstack_push(istack,n);
    printf("输出压栈后的栈中元素\n");
    seqstack_print(istack);
    printf("弹出栈顶的元素后的栈中元素\n");
    seqstack_pop(istack);
    seqstack_print(istack);
    printf("销毁栈表");
    seqstack_destory(istack);
    return 0;
}

2.队列

2.1 队列的顺序存储

2.1.1 队列的顺序存储存在的缺陷

        假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即为对头。
所谓的入队,就是在队尾追加一个元素,不需要移动任何元素,所以时间复杂度为O(1).

        队列的出队是在对头,即下标为0的位置,也就意味着,队列中的所有位置都得向前移动,以保证下标为0的位置,即对头不为空。此时时间复杂度为O(n)。

        可是有时候想想,为什么出队列时一定要全部移动呢?如果不限制队列的元素一定要存储在数组的前n个单元,出队的性能就会大大增加。也就是说,队头不需要一定在下标为0的位置。

 

        但如果不全部移动的话,又会引入新的问题?那就是数组的 “假溢出”

        为了避免当只有一个元素时,对头和队尾重合使得处理变得麻烦,所以引入两个指针,front指针指向对头元素,rear指针指向队尾元素的下一个元素。这样当front等于rear时,不是队列中有一个元素,而是表示空队列。

        假设数组的长度为5,空队列及初始状态如左图所示,front与rear指针都指向下标为0的位置。当队列中有4个元素时,front指针不变,rear指针指向下标为4的位置。

        此时出队两个元素,则front指针指向下标为2的位置,rear不变。再入队一个元素,front指针不变,此时rear指针移动到数组之外,这就产生了 “假溢出” 现象。

        为了解决这种假溢出的现象。我们引入了循环队列

2、循环队列的定义

        我们把队列的头尾相接的顺序存储结构称之为循环队列。

        为了解决假溢出的现象,就是当队列满了,我们再从头开始存数据,这是时候头尾已经相连了,就形成了循环队列。

        继续刚才的例子,将rear指针指向下标为0的位置,就不会导致rear指针指向不明的问题。

        接着入队两个元素,会发现rear指针与front重合了

        此时问题又来了,刚才说了,当rear等于front时,表示是空队列,现在当队列满时,rear也等于front。那么如何判断队列到底是空的还是满的了?

我们有两个判断方法:

        办法一是设置标志变量flag,当front = rear ,且flag =0 为队列空,当front =rear ,且flag =1 时队列满。

        办法二是当队列为空时,条件是front = rear ,当队列满时,我们修改其条件,保留一个元素的空间,也是就是说,当队列满时,数组里面还有一个空闲的单元。

        例如下图就表示了队列已经满了

        就不能再插入元素了。

        由于队列是循环队列,rear可能比front大,也可能比front小,所以假设队列的最大尺寸为QueueSize, 队列满的判断条件改为(rear + 1)%QueueSize = front. 队列的长度为(rear - front + QueueSize)% QueueSize.

        循环队列的引入解决了数据移动的时间损耗,使得本来插入和删除是O(n)的时间复杂度变成了O(1).

typedef struct{
    
    int data[MAXSIZE];   //容量
    int front;          //前驱下标
    int rear;           //后继下标

}SqQueue;

/*初始化队列*/
void InitQueue(SqQueue* Q){
    Q->front=0;
    Q->real=0;   //前驱和后继都指向0
}


/*入队操作*/
int EnQueue(SqQueue* Q,int e){
    if(FullQueue(*Q))
        return 0;
    Q->data[Q->rear]=e;
    Q->rear = (Q->rear+1)%MAXSIZE;  //将rear指针向后移一位
    /*若到最后则转到数组头部*/
}

/*出队操作*/
int DeQueue(SqQueue* Q){
    int e;
    if(Q->front ==Q->rear){
        return 0;
    }
    e = Q->data[Q->front];
    Q->front = (Q->front+1)%MAXSIZE;
    return e;
}

2.2 队列的链式存储

// 链式队列结点
typedef struct LinkNode
{
	int data;
	struct linkNode *next;

}LinkNode;
 
// 链式队列 
typedef struct 
{    
	//队列的队头和队尾指针
	LinkNode *front,*rear;
}LinkQueue;

 

 

//初始化队列(带头结点)
void InitQueue(LinkQueue &Q)
{
	Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));
    Q.front->next = NULL;	
}

//初始化队列(不带头结点)
void InitQueue(LinkQueue &Q)
{
	// 初始时 front、rear 都指向NULL
	Q.front = NULL;
	Q.rear = NULL;
}

        入队

 

 

 

//入队(带头结点)
void EnQueue(LinkQueue &Q,int x)
{

	LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    Q.rear->next = s; //新结点插入到rear之后
    Q.rear = s;       // 修改表尾指针
}

 

//入队 (不带头节点)
void EnQueue(LinkQueue &Q,int x)
{
	LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
          //空队列中插入第一个元素
    if(Q.front == NULL){
    	Q.front = s;
    	Q.rear = s;  //第一个元素入队时需要特别处理
    }else{
    	Q.rear->next = s;
    	Q.rear = s;
    }
}

        出队

 

//出队 (带头结点)
bool DeQueue(LinkQueue &Q,int &x)
{
	if(Q.front == Q.rear){
		return false;
	}
	LinkNode *p = Q.front->next;
	x = p->data;
	Q.front->next = p->next;
	if(Q.rear == p){  //z最后一个结点出队
		Q.rear = Q.front
	}
    free(p);
    return true;
}

//出队(不带头结点)
bool DeQueue(LinkQueue &Q,int &x)
{
	if(Q.front==NULL){
		return false;
	}
	LinkNode *p = Q.front;
	x = p->data;
	Q.front = p->next;
	if(Q.rear == p){   //最后一个结点出队
		Q.front = NULL;
		Q.rear = NULL;
	}
	free(p);
	return true;
}


        判空

//判断队列是否为空(带头结点)
bool IsEmpty(LinkQueue Q)
{
	if(Q.front == Q.rear){
		return true;
	}else{
		return false;
	}
}

//判断队列是否为空(不带头结点)
bool IsEmpty(LinkQueue Q)
{
	if(Q.front == NULL){
		return true
	}else{
		return false;
	}
}

3.应用

3.1 栈的应用

3.1.1 递归

3.1.2 逆波兰

括号匹配:


bool bracketMatching(char* paraString, int paraLength) {
    CharStackPtr tempStack = charStackInit();
	push(tempStack, '#');
	char tempChar, tempPopedChar;
	for (int i = 0; i < paraLength; i++) {
		tempChar = paraString
		switch (tempChar) {
		case '(':
		case '[':
		case '{':
			push(tempStack, tempChar);
			break;
		case ')':
			tempPopedChar = pop(tempStack);
			if (tempPopedChar != '(') {
				return false;
			}
			break;
		case ']':
			tempPopedChar = pop(tempStack);
			if (tempPopedChar != '[') {
				return false;
			}
			break;
		case '}':
			tempPopedChar = pop(tempStack);
			if (tempPopedChar != '{') {
				return false;
			}
			break;
		default:
			
			break;
		}
	}
	tempPopedChar = pop(tempStack);
	if (tempPopedChar != '#') {
		return false;
	}
	return true;
}

 中序表达式转换成后序表达式
什么是中缀表达式?
例如a+b,运算符在两个操作数的中间。这是我们从小学开始学习数学就一直使用的表达式形式。

什么是后缀表达式?
例如a b + ,运算符在两个操作数的后面。后缀表达式虽然看起来奇怪,不利于人阅读,但利于计算机处理。

转换为后缀表达式的好处是:
1、去除原来表达式中的括号,因为括号只指示运算顺序,不是实际参与计算的元素。
2、使得运算顺序有规律可寻,计算机能编写出代码完成计算。

逆波兰算法的核心思想是将普通的中缀表达式转换为后缀表达式。

逆波兰算法的核心步骤就2个:
1、将中缀表达式转换为后缀表达式,例如输入的原始表达式是 3*(5+7) ,转换得到 3 5 7 + *
2、根据后缀表达式,按照特定的计算规则得到最终计算结果

中缀表达式转换为后缀表达式:
1、你需要设定一个栈SOP,和一个线性表 L 。SOP用于临时存储运算符和左括号分界符( ,L用于存储后缀表达式。
2、遍历原始表达式中的每一个表达式元素:
(1)如果是操作数,则直接追加到 L中。只有 运算符 或者 分界符( 才可以存放到 栈SOP中
(2)如果是分界符
    Ⅰ 如果是左括号 ( , 则 直接压入SOP,等待下一个最近的 右括号 与之配对。
    Ⅱ 如果是右括号 ) ,则说明有一对括号已经配对(在表达式输入无误的情况下)。
不将它压栈,丢弃它,然后从SOP中出栈,得到元素e,将e依次追加到L里。一直循环,直到出栈元素e 是 左括号 ( ,同样丢弃他。
(3)如果是运算符(用op1表示)
    Ⅰ 如果SOP栈顶元素(用op2表示) 不是运算符,则二者没有可比性,则直接将此运算符op1压栈。 例如栈顶是左括号 ( ,或者栈为空。
    Ⅱ 如果SOP栈顶元素(用op2表示) 是运算符 ,则比较op1和 op2的优先级。如果op1 > op2 ,则直接将此运算符op1压栈。
如果不满足op1 > op2,则将op2出栈,并追加到L,再试图将op1压栈,如果如果依然不满足 op1>新的栈顶op2,继续将新的op2弹出追加到L ,直到op1可以压入栈中为止。
也就是说,如果在SOP栈中,有2个相邻的元素都是运算符,则他们必须满足:下层运算符的优先级一定小于上层元素的优先级,才能相邻。
3、最后,如果SOP中还有元素,则依次弹出追加到L后,就得到了后缀表达式。

逆波兰式的计算:
1、从左到右扫描表达式,如果当前字符为数字,则入栈。
2、如果当前字符为运算符,则将栈顶两个元素出栈,作相应运算,结果再入栈。
3、最后当表达式扫描完后,栈里的就是计算结果了。

下图是逆波兰式的转换和逆波兰式的计算。
1 + 2 * ( 3 + 4 ) - 5

 

在这里插入图片描述
在这里插入图片描述

3.1.3 共享栈

        利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。

        共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。

 

        栈空条件:栈0空为top0==-1;栈1空为top==MaxSize。
        栈满条件:top0==top1-1。
        元素x进栈操作:进栈=操作为top0++;data[top0]=x;进栈1操作为top1–;data[top1]=x。
        出栈操作:出栈0操作为x=data[top0];top0–;出栈1操作为x=data[top1];top1++。
        在上述设置中,data数组表示共享栈的存储空间,top0和top1分别为两个栈的栈顶指针,这样该共享栈通过data、top0和top1来标识,也可以将它们设计为一个结构体类型:

#include<stdio.h>
#define MaxSize 100
typedef int ElemType;
typedef struct
{
	ElemType data[MaxSize];
	int top0, top1;
}StackType;

void InitStack(StackType& s)
{
	s.top0 = -1;
	s.top1 = MaxSize;
}

bool EmptyStack(StackType s, int i)
{
	if (i == 0) return s.top0 == -1;
	else return s.top1 == MaxSize;
}

bool Push(StackType& s, int i, ElemType e)
{
	if (i < 0 || i>1)
	{
		printf("栈号输入不对!");
		return false;
	}
	if (s.top0 + 0 == s.top1) return false; //栈满
	else
	{
		if (i == 0) s.data[++s.top0] = e;
		else s.data[--s.top1] = e;
	}
	return true;
}

bool Pop(StackType& s, int i, ElemType& e)
{
	if (i < 0 || i>1)
	{
		printf("栈号输入不对!");
		return false;
	}
	if (i == 0)
	{
		if (s.top0 == -1) return false; //栈s1空
		else e = s.data[s.top0--];
	}
	if (i == 1)
	{
		if (s.top1 == MaxSize) return false; //栈s2空
		else e = s.data[s.top1++];
	}
	return true;
}

void Input(StackType& s)
{
	int symbol = 1, stacknum;
	ElemType num;
	while (symbol)
	{
		printf("输入0(0号栈)或1(1号栈),或3(退出):\n");
		scanf("%d", &num);
		if (num == 3) break;
		else if (num < 0 || num>1)
		{
			printf("输入错误!\n");
			break;
		}
		if (num == 0 || num == 1)
		{
			printf("请输入元素值:\n");
			scanf("%d", &stacknum);
			if (Push(s, num, stacknum) == 0)
			{
				printf("栈已满,无法添加!\n");
				break;
			}
		}
	}
}

bool DispStack(StackType s, int stacknum)
{
	int i;
	if (s.top0 == -1 && s.top1 == MaxSize)
	{
		printf("栈为空\n");
		return false;
	}
	if (stacknum == 0)
	{
		printf("栈0中的元素为:");
		for (i = 0; i <= s.top0; i++)
			printf("%d ", s.data[i]);
		printf("\n");
		printf("栈顶元素为:%d\n", s.data[s.top0]);
	}
	else if (stacknum == 1)
	{
		printf("栈1中的元素为:");
		for (i = MaxSize - 1; i >= s.top1; i--)
			printf("%d ", s.data[i]);
		printf("\n");
		printf("栈顶元素为:%d\n", s.data[s.top1]);
	}
}

int main()
{
	StackType s;
	InitStack(s);
	Input(s);
	DispStack(s, 0);
	DispStack(s, 1);
	printf("共享栈中当前有:%d个元素", MaxSize - (s.top1 - s.top0) + 1);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林家小院

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

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

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

打赏作者

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

抵扣说明:

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

余额充值