数据结构与算法(C语言版)->栈与队列

栈和队列

1)概述

和顺序表和链表一样,栈也是用来存储逻辑关系为“一对一”数据的线性存储结构

栈对数据存和去的过程有特殊的要求:

  1. 栈只能从表的一端存取数据,另一端是封闭的
  2. 在栈中,无论是存数据还是取数据,都必须遵循先进后出的原则,即最先进栈的元素最后出栈。

简单来说:栈是一种只能从表的一端存取数据且遵循“先进后出”原则的线性存储结构

栈顶:栈的开口端

栈底:栈的封口端

栈顶元素指的是距离栈顶最近的元素,而栈底元素指的是,位于栈最底部的元素

进栈和出栈:

基于栈结构的特点,在实际的应用中通常会对栈执行下面的两种操作:

  1. 向栈中添加元素,此过程被称为“进栈”(入栈或压栈)
  2. 从栈中提取出指定元素,此过程中被称为出栈(或弹栈)

栈的具体实现:

栈是一种特殊的线性存储结构,因此栈的具体实现有以下两种方式:

  1. 顺序栈:采用顺序存储结构,可以模拟栈存储数据的特点,从而实现栈存储结构
  2. 莲栈:采用链式存储结构实现栈

2)顺序栈

顺序栈,也就是用顺序表来实现栈存储结构(就是说用数组来实现)。

如果仔细观察顺序表和栈结构就会发现,他们存储数据的方式高度相似,只不过栈对数据的存取过程有特殊的限制,而顺序表没有。

在使用顺序表模拟栈存储结构常用的实现思路,就是在顺序表中设定一个实时指向栈顶元素的**变量(**top),top的初值为-1,表示栈中没有存储任何数据元素,也就是说栈是空栈,一旦数据元素进栈,top就 +1,如果元素出栈 top 就-1

理解:

top代表栈顶元素的索引值

元素“入栈”

最初的时候栈是空栈,即数组是空的,top的初始值为-1。

向栈中添加元素1,则top = 0然后依次存储

typedef struct SortStack{
    int* array;
    int top ;
}*sortStack;



int push(sortStack stack,int elem){
    stack->array[++(stack->top)]=elem;
    return stack->top;
}

分析:

从代码中定义了一个SortStack的数据结构,这个数据结构中一个属性用于存储一个数组,另一个用于存储top指针。

push方法用于向数组中添加一个数据。

这样的行为称之为“入栈”

元素“出栈”

top变量其实对模拟数据入栈的操作没有什么实际的帮助,但是它是为了实现数据出栈而准备的。

出栈时需要把数据一个个的扔出栈,直到取到对应位置的元素,而这里的对应的位置索引就是通过top来完成指向的。

/**
 * 弹出栈的最后一个元素
 * @param array
 * @return
 */
int pop(sortStack stack){
    if (stack->top == -1){
        INFO_LOG("This stack is a empty stack");
        return -1;
    }
    int tempData = stack->array[stack->top];
    printf("The last elem is : %d",tempData);
    stack->top--;
    return tempData;
}

分析:

由于top总是指向栈顶元素,所以每次都会把栈定的元素弹出来。

如果top的值为-1则说明这个栈是一个空栈

3)链栈

链栈就是用链表实现栈存储结构

顺序栈是将顺序表的一端作为栈底,另一端作为栈顶,而链栈也是这样的,通常我们将链表的头部作为栈顶,尾部作为栈底。

对于这个也很显而易见,对于栈来说,我们无法随机存取,这和链表的顺序存取结构也是十分契合的。

这里以头为顶更便于栈的理解。

元素“入栈”

linkStackNode push(linkStackNode head,int element){
    linkStackNode newNode = (linkStackNode)malloc(sizeof (struct LinkStackNode));
    //让新结点的next指向head
    newNode->next = head;
    newNode->data = element;
    head = newNode;
    //把新结点作为head返回
    return head;
}

元素“出栈”

linkStackNode pop(linkStackNode head){
    if (head){
        //保证栈内是有元素的
        linkStackNode temp = head;
        //然后把栈顶元素进行更新
        head = head->next;
        printf("出栈元素 :%d",temp->data);
        if (head){
            printf("新栈顶元素 %d\n",head->data);
        } else{
            printf("栈已空");
        }
        free(temp);
    } else{
        printf("栈内已无元素");
        return head;
    }
    return head;
}

出栈的思想也很简单,就是把栈顶元素弹出,然后更新栈顶元素即可。

注意整个过程中避免栈内无数据还要做数据出栈的错误操作

4)用栈结构求表达式的值

如何用栈结构求一个表达式的值?

所谓表达式,就是由变量、常量以及运算符组合而成的式子,常用的运算符有(!,^,+,-,*,/,())这几种

用栈结构去求一个表达式的值,已经设计好了全新的表示表达式方法,称为后缀表达式或者逆波兰表达式。和普通表达式不同,后缀表达式习惯把运算符写在它的运算项之后,而且整个表达式的过程中不用() 表明运算的优先级关系

比如说:表达式-> !3 + 4*2/(1-5)^2

就可以转换为后缀表达式:

3 !  4 2 * 1 5 - 2 ^ / +

虽然后缀表达式相比于普通的表达式来说,能轻松的借助栈存储结构求得值,但是却完全舍弃了表达式该有的可读性。

具体求值的过程:当用户给定一个后缀表达式时,按照从左到右的顺序依次扫描表达式中的各个运算项和运算符

  1. 遇到运算项时,直接入栈
  2. 遇到运算符时,将位于栈顶的运算项出栈,对于单目运算符去一个运算项,对于双目运算符去两个运算项,第一个取出来的运算向作为右运算项,另一个作为左运算项,求得表达式的值后将其入栈
  3. 直到栈中存在一个运算项为止,此运算项即为整个表达式的值

5)队列

队列和栈一样也是一种对数据的存取由严格要求的“线性存储结构”

和栈结构不同的是,队列的两端都开口,,要求数据只能从一端进,从另一端出

进数据的一端叫做队尾,出数据的一段叫做队头,数据元素进队列的过程叫做入队,出队列的过程叫做出队。

队列中数据的进出要求遵循,先进先出的原则,最先进队列的数据元素最先出队列,最后进队列的元素,最后出队列

区分:

对于栈来说是先进后出,而对于队列来说是先进先出。栈是一端开口,队列是两端开口

根据存储结构的实现可分为:

  1. 顺序队列:在顺序表的基础上实现的队列结构
  2. 链队列:在链表的基础上实现的队列结构

两者的区别仅是顺序表和链表的去呗,即在实际的物理空间中,如果数据几种存储就是用的顺序队列,如果数据分散存储就是用的链队列。

6)顺序队列

顺序队列使用的是数组,因此需要提前申请一块足够大的内存空间初始化顺序队列,初次之外,满足顺序队列中数据从队尾进,队头出且先进先出的要求,需要定义两个指针(top 和 rear) 分别指向队列的队头元素和队尾元素。

当数据元素进队列时,对应的操作应该是将其存储在rear指向的数组的位置,然后rear + 1,当需要出队列的时候,就需要做 top +1 的操作

方法一:

#include <stdio.h>
#define DEBUG_LOG
#define INFO
#include "log.h"
#include "malloc.h"

typedef struct SortQueue{
    int* array;
    int top;
    int rear;
}*sortQueue;

/**
 * 初始化具有阿巴个元素的队列
 * @param n
 * @return
 */
sortQueue init(){
    sortQueue queue = (sortQueue)malloc(sizeof (struct SortQueue));
    int array[] = {1,2,3,4};
    queue->array = array;
    queue->rear = 4;
    queue->top = 0;
}

/**
 * 进队列的方法
 * @param queue
 * @return
 * 把data放到queue 里面
 */
sortQueue inQueue(sortQueue queue,int data){
    int rearIndex = queue->rear;
    queue->array[rearIndex] = data;
    queue->rear += 1;

    return queue;
}

/**
 * 出队列的方法
 * @param queue 
 * @return 
 */
sortQueue outQueue(sortQueue queue){
    int topIndex = queue->top;
    int rearIndex = queue->rear;
    if (topIndex == rearIndex){
        INFO_LOG("There is not element!Can't out queue");
    } else{
        printf("The top element is >>>%d",queue->array[queue->top]);
    }
    queue->top+=1;
    return queue;
}

这种方法穿在一个问题:

当数据全部出队后,top和rear的重合指向了a[4]而不是a[0],也就是说,整个顺序队列在数据的不断进队出队的过程中,在顺序表中的位置不断的后移

影响:

  1. 顺序队列之前的数组存储空间将无法再被使用,造成空间浪费
  2. 如果顺序表申请的空间不够大,就容易造成溢出,产生溢出错误

所以就有了方法二

方法二:

将顺序表打造成一个环状

代码修改:

/**
 * 进队列的方法
 * @param queue
 * @return
 * 把data放到queue 里面
 */
sortQueue inQueue(sortQueue queue,int data){
    /*
     * 新增判断语句,如果再增加的一个元素的下一个元素大于max,则从a[0]重新开始存储
     * 如果rear +1 和 front 重合,则表明数组已满
     *
     */
    if (((queue->rear)+1)%sizeMax == queue->top){
        /**
         * 这里就表示空间已满,rear队尾指针和头指针重合
         */
        printf("The space if full");
        return queue;
    }
    int rearIndex = queue->rear;
    queue->array[rearIndex % sizeMax] = data;
    queue->rear = (rearIndex % sizeMax) + 1;

    return queue;
}

/**
 * 出队列的方法
 * @param queue
 * @return
 */
sortQueue outQueue(sortQueue queue){
    int topIndex = queue->top;
    int rearIndex = queue->rear;
    //如果front == rear 表示队列为空
    if (topIndex == rearIndex){
        INFO_LOG("There is not element!Can't out queue");
    } else{
        printf("The top element is >>>%d",queue->array[queue->top]);
    }
    queue->top = (queue->top+1)%sizeMax;
    return queue;
}

通过取余的方式来实现最后可以接到表头,这样其实是一种很巧妙的方式实现顺序队列的头尾相接,且不用再本身的物理结构上进行修改,因为对于这个顺序队列还是线性的,但是在逻辑上已经是环状的了。

注意:

  1. 当队列为空的时候,队列的头指针会等于队列的尾指针
  2. 当队列满员的时候,队列的头指针会等于队列的尾指针

7)链式队列

链式队列,通过链表实现队列的存储结构

通过链表实现队列的方式和顺序队列是一样的,都需要借助一个top指针和一个rear指针,一个指向队列头,一个指向队列尾。

#include <stdio.h>
#include"log.h"
#include <malloc.h>

/*
 *定义链式队列的结点
 */
typedef struct LinkQueueNode{
	struct LinkQueueNode * next;
	int data;
}*linkQueueNode;

/*
 * 向队列中插入数据应该向队尾插入
 * 对于链式队列来说就不存在会有空间满了的情况了
 * 传入链表的最后一个结点,直接把结点添加最后即可
 */
linkQueueNode inQueue(linkQueueNode rear,int newData){
	linkQueueNode newNode = (linkQueueNode)malloc(sizeof(struct LinkQueueNode));
	newNode->next = NULL;
	newNode->data = newData;
	rear->next = newNode;
	rear = newNode;
	return newNode;
}

/*
 * 向队列中退出一个元素,将top指针所指向的首元素出队
 *	出队之后要根据对列的长度保证 top 指针和 rear 指针指向正确	
 *  如果队列出队后的长度大于等于1,则rear 指针不用管
 * 但是如果队列出队后的长度为0,这个时候就意味着rear指针的之前指向已经出队的node 这个时候就需要更新指针的指向了 
 */
linkQueueNode outQueue(linkQueueNode top,linkQueueNode rear){
	if (top == NULL || top->next == NULL)
	{
		printf("There is a empty queue");
	}

	linkQueueNode outNode = top;
	printf("出栈元素是>>>>%d\n",outNode->data);
	top = top->next;
	/*
	 * 如果尾指针指向一开始的首元素,且队列的长度为1,那么让他指向新的空结点
	 */
	if (rear == outNode)
	{
		rear = top;
	}
	free(outNode);
}

在出队元素的时候一定要提前判断队列中是否还有元素,要提示用户无法做出出队操作,保证程序的健壮性

8)小结

首先:虽然栈和队列存储结构不同,但是两者都是线性结构

线性结构:用于存储逻辑关系为”一对一“数据的存储结构,比如说顺序存储结构和链式存储结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值