重学数据结构之第四章-栈与队列

1、栈的定义

栈(stack)是限定仅在表尾进行插入和删除操作的线性表。

我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈

栈又称为后进先出(Last In First Out),简称LIFO结构。

栈的插入操作(push),叫作进栈,也称压栈入栈。类似子弹入弹夹。

栈的删除操作(pop),叫作出栈,也有的叫作弹栈。类似子弹出弹夹。

来张图认识一下这些概念:

在这里插入图片描述

2、栈的两种存储结构

栈也是线性表,前一章我们介绍了线性表,所以栈依然拥有顺序存储和链式存储两种结构。
每种结构我们需要重点关注其插入和删除操作的算法实现。也是我们说的进栈和出栈操作,一般用push和pop进行表示。

2.1、顺序存储结构

来一张图认识一下顺序存储结构栈:
在这里插入图片描述

2.1.1、进栈

	/**
     * 进栈
     */
    private static ResultEnum push(SqStack stack, String e) {
        if (stack.top == MAXSIZE - 1) {//栈满
            return ResultEnum.ERROR;
        }
        stack.top++;
        stack.data[stack.top] = e;
        return ResultEnum.OK;
    }

2.1.2、出栈

    /**
     * 出栈 将栈顶那个有值的数据元素删除。
     */
    private static ResultEnum pop(SqStack stack) {
        if (stack.top == -1) {//空栈,无元素可删
            return ResultEnum.ERROR;
        }
        stack.data[stack.top] = null;//被删除的元素置空
        stack.top--;
        return ResultEnum.OK;
    }

2.1.3、一个完整案例

java语言实现:

public class TestStack {
    public static void main(String[] args) {

        //第一步:创建栈对象的实例
        SqStack sqStack = buildSqlStack(2);
        if (sqStack == null) {
            System.out.println("创建栈失败");
            return;
        }

        printStackInformation(sqStack);

        //第二步:把e这个数据元素压入栈中(进栈)
        System.out.println("---------插入数据e(进栈)-----------");
        ResultEnum insertResultEnum = push(sqStack, "e");
        if (insertResultEnum == ResultEnum.ERROR) {
            System.out.println("error:栈满溢出");
        } else if (insertResultEnum == ResultEnum.OK) {
            printStackInformation(sqStack);
        }

        //第三步:把栈顶的有值数据从栈中移除(出栈)
        System.out.println("---------删除栈顶有值元素(出栈)-----------");
        ResultEnum deleteResult = pop(sqStack);
        if (deleteResult == ResultEnum.ERROR) {
            System.out.println("error:栈满溢出");
        } else if (deleteResult == ResultEnum.OK) {
            printStackInformation(sqStack);
        }
    }

    private static void printStackInformation(SqStack sqStack) {
        for (int i = sqStack.data.length - 1; i >= 0; i--) {
            System.out.println("data:" + sqStack.data[i]);
        }
        System.out.println("栈顶  top:" + sqStack.top);
    }

    private static int MAXSIZE = 5;//栈的最大可存储空间

    /**
     * 构建一个为顺序存储结构的栈对象的实例
     *
     * @param dataCount
     * @return
     */
    private static SqStack buildSqlStack(int dataCount) {
        if (dataCount == 0) {
            return null;
        }
        String[] data = new String[MAXSIZE];
        for (int i = 0; i < dataCount; i++) {
            data[i] = "a" + i;
        }
        int top = dataCount - 1;
        return new SqStack(data, top);
    }

    /**
     * 进栈
     */
    private static ResultEnum push(SqStack stack, String e) {
        if (stack.top == MAXSIZE - 1) {//栈满
            return ResultEnum.ERROR;
        }
        stack.top++;
        stack.data[stack.top] = e;
        return ResultEnum.OK;
    }

    /**
     * 出栈 将栈顶那个有值的数据元素删除。
     */
    private static ResultEnum pop(SqStack stack) {
        if (stack.top == -1) {//空栈,无元素可删
            return ResultEnum.ERROR;
        }
        stack.data[stack.top] = null;//被删除的元素置空
        stack.top--;
        return ResultEnum.OK;
    }

    /**
     * 栈的结构定义,可以理解为栈这个对象
     */
    static class SqStack {
        String[] data;
        int top;

        public SqStack(String[] data, int top) {
            this.data = data;
            this.top = top;
        }
    }

    public enum ResultEnum {
        OK, ERROR;
    }
}

案例主要包含三步内容:

第一步:创建栈对象的实例;
第二步:把e这个数据元素压入栈中(进栈);
第三步:把栈顶的有值数据从栈中移除(出栈)。

输出结果

data:null
data:null
data:null
data:a1
data:a0
栈顶  top:1
---------插入数据e(进栈)-----------
data:null
data:null
data:e
data:a1
data:a0
栈顶  top:2
---------删除栈顶有值元素(出栈)-----------
data:null
data:null
data:null
data:a1
data:a0
栈顶  top:1

这种代码,只有动手去写一写,才能更好的理解和消化。

2.2、链式存储结构

来一张图认识一下链式存储结构栈:
在这里插入图片描述

2.1.1、进栈

 private static ResultEnum push(LinkStack linkStack, String e) {
        StackNode stackNode = new StackNode();
        stackNode.data = e;
        stackNode.next = linkStack.top;
        linkStack.top = stackNode;
        linkStack.count++;
        return ResultEnum.OK;
    }

2.1.2、出栈

  private static ResultEnum pop(LinkStack linkStack) {
        if (linkStack.isEmpty()) {
            return ResultEnum.ERROR;
        }
        linkStack.top.data = null;
        linkStack.top = linkStack.top.next;


        linkStack.count--;
        return ResultEnum.OK;
    }```

### 2.1.3、完整案例
java语言实现:

```java
public class TestLinkStack {
    public static void main(String[] args) {

        LinkStack linkStack = new LinkStack();

        push(linkStack, "a0");
        push(linkStack, "a1");
        push(linkStack, "e");

        printStackInformation(linkStack);
        
        pop(linkStack);
        printStackInformation(linkStack);
    }

    private static void printStackInformation(LinkStack linkStack) {
        System.out.println("--------------栈顶到栈底依次输出--------------");
        for (StackNode stackNode = linkStack.top; stackNode != null; stackNode = stackNode.next) {
            System.out.println(stackNode.data);
        }
        System.out.println("栈顶 top:" + linkStack.getTop());
    }
	//入栈
    private static ResultEnum push(LinkStack linkStack, String e) {
        StackNode stackNode = new StackNode();
        stackNode.data = e;
        stackNode.next = linkStack.top;
        linkStack.top = stackNode;
        linkStack.count++;
        return ResultEnum.OK;
    }
	//出栈
    private static ResultEnum pop(LinkStack linkStack) {
        if (linkStack.isEmpty()) {
            return ResultEnum.ERROR;
        }
        linkStack.top.data = null;
        linkStack.top = linkStack.top.next;


        linkStack.count--;
        return ResultEnum.OK;
    }
	//结点对象
    static class StackNode {
        String data;
        StackNode next;
    }
	//链式存储结构的栈对象
    static class LinkStack {
        StackNode top;
        int count;

        public String getTop() {
            return top.data;
        }

        public boolean isEmpty() {
            return top == null;
        }
    }

    enum ResultEnum {
        OK, ERROR;
    }
}

输出结果:

--------------栈顶到栈底依次输出--------------
e
a1
a0
栈顶 top:e
--------------栈顶到栈底依次输出--------------
a1
a0
栈顶 top:a1

3、栈的应用-斐波那契的递归函数

在这里插入图片描述

3.1、迭代法

迭代函数

   private static void useIteration(int count) {
        int[] arr = new int[count];
        arr[0] = 0;
        System.out.println("第0个月:" + arr[0] + " 对兔子");
        arr[1] = 1;
        System.out.println("第1个月:" + arr[1] + " 对兔子");
        for (int j = 2; j < count; j++) {
            arr[j] = arr[j - 1] + arr[j - 2];
            System.out.println("第" + j + "个月:" + arr[j] + " 对兔子");
        }
    }

调用:

public static void main(String[] args) {
	useIteration(7);
}

输出:

0个月:0 对兔子
第1个月:1 对兔子
第2个月:1 对兔子
第3个月:2 对兔子
第4个月:3 对兔子
第5个月:5 对兔子
第6个月:8 对兔子

3.2、递归法

递归函数:

private static int useFbi(int i) {
        if (i < 2) {
            return i == 1 ? 1 : 0;
        }
        return useFbi(i - 1) + useFbi(i - 2);
    }

private static void printFbi(int count) {
        for (int i = 0; i < count; i++) {
            System.out.println("第" + i + "个月:" + useFbi(i) + " 对兔子");
        }
    }

调用:

public static void main(String[] args) {
	printFbi(7);
}

输出:

0个月:0 对兔子
第1个月:1 对兔子
第2个月:1 对兔子
第3个月:2 对兔子
第4个月:3 对兔子
第5个月:5 对兔子
第6个月:8 对兔子

3.3、对比两种实现方法

递归法相较于迭代法的代码,要简洁的多。
递归法的推导过程其实也是栈的操作过程,我们都需要找到最顶上的之后开始依次往回计算。
在这里插入图片描述

4、队列的定义

队列(queue):是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出(First In Frist Out)的线性表,简称FIFO

允许插入的一端称为队尾允许删除的一端称为队头

例如队列(a1,a2,a3,…,an),那么a1就是队头元素,an是队尾元素。这时我们删除时,总是从a1开始,而插入时,在列在an之后。

来一张图认识一下,队列:
在这里插入图片描述

4、队列的两种存储结构

2.1、顺序存储结构

包含一种叫作循环队列,队列的头尾相接的顺序存储结构。
引入两个指针:front指针指向队头元素,rear指针指向队尾元素的下一个位置,rear指针也可以指向剩余的队头位置。

两个计算公式:
如果队列的最大尺寸为QueueSize,那么队列已满的条件是:(rear+1)% QueueSize == front
通用队列计算长度公式=:(rear-front +QueueSize)%QueueSize;不过笔者简单的推导了一下,|rear-front|%QueueSize 也能得出这个队列的长度。

来一张图看看循环队列的模样:
在这里插入图片描述

2.1.1、循环队列出队和入队完整案例

public class TestQueue {
    public static void main(String[] args) {
        SqQueue sqQueue = new SqQueue();

        initQueue(sqQueue, 3);
        System.out.println("-----------入队列后的结果如下-----------");
        sqQueue.enQueue(sqQueue, "e");
        printQueueInformation(sqQueue);

        System.out.println("队列有值数据元素个数(队列长度):"+sqQueue.getLength());

        System.out.println("-----------出队列后的结果如下-----------");
        sqQueue.deQueue(sqQueue);
        printQueueInformation(sqQueue);

        System.out.println("队列有值数据元素个数(队列长度):"+sqQueue.getLength());
    }
    private static void printQueueInformation(SqQueue sqQueue) {
        for (int i = sqQueue.data.length - 1; i >= 0; i--) {
            System.out.println("data:" + sqQueue.data[i]);
        }
        System.out.println("队列  front:" + sqQueue.front+" , rear:"+sqQueue.rear);
    }

    private static void initQueue(SqQueue sqQueue, int dataCount) {
        if (dataCount <= 0) {
            return;
        }
        sqQueue.front = 0;
        sqQueue.rear = dataCount - 1;
        String[] arr = new String[MAXSIZE];
        for (int i = 0; i < dataCount; i++) {
            arr[i] = "a" + i;
        }
        sqQueue.data = arr;
    }


    //队列的最大容量
    private static final int MAXSIZE = 5;

    static class SqQueue {
        String[] data;
        int front;//头指针
        int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置

        int getLength() {
            return (rear - front + MAXSIZE) % MAXSIZE;
        }

        ResultEnum enQueue(SqQueue sqQueue, String e) {
            if ((sqQueue.rear + 1) % MAXSIZE == sqQueue.front) {//队列满
                return ResultEnum.ERROR;
            }
            sqQueue.data[rear] = e;//将元素e赋值给队尾
            sqQueue.rear = (sqQueue.rear + 1) % MAXSIZE;//rear指针指向后移一位
            return ResultEnum.OK;
        }

        ResultEnum deQueue(SqQueue sqQueue) {
            if (sqQueue.front == sqQueue.rear) {//队列为空
                return ResultEnum.ERROR;
            }
            sqQueue.data[front] = "";
            sqQueue.front = (sqQueue.front + 1) % MAXSIZE;

            return ResultEnum.OK;
        }
    }

    enum ResultEnum {
        OK, ERROR;
    }
}

输出结果:

-----------入队列后的结果如下-----------
data:null
data:null
data:e
data:a1
data:a0
队列  front:0 , rear:3
队列有值数据元素个数(队列长度):3
-----------出队列后的结果如下-----------
data:null
data:null
data:e
data:a1
data:
队列  front:1 , rear:3
队列有值数据元素个数(队列长度):2

从输出结果中我们可以看到队列的操作,删除从队头开始,新增从队尾插入。

2.2、链式存储结构

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。

为了操作上更方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端结点,如下图所示:

在这里插入图片描述
空队列时,front和rear都指向头结点,如下图所示:

在这里插入图片描述

2.1.1、出队和入队完整案例

这段代码调试了一定时间,尤其是入队的时候要当心null。

public class TestLinkQueue {
    public static void main(String[] args) {
        LinkQueue linkQueue = new LinkQueue();

        linkQueue.enQueue("a0");
        linkQueue.enQueue("a1");
        linkQueue.enQueue("e");
        printStackInformation(linkQueue);

        linkQueue.deQueue(linkQueue);
        printStackInformation(linkQueue);
    }

    private static void printStackInformation(LinkQueue linkQueue) {
        System.out.println("--------------队列依次输出--------------");
        for (QNode stackNode = linkQueue.front; stackNode != null; stackNode = stackNode.next) {
            System.out.println(stackNode.data);
        }
    }

    static class LinkQueue {
        QNode front = new QNode();     // 头指针
        QNode rear = new QNode();     // 尾指针

        ResultEnum enQueue(String e) {
            QNode qNode = new QNode();
            qNode.data = e;
            qNode.next = null;

            if (rear.next == null) {    // 队列为空
                rear.next = qNode;         // 更新队尾和队首指针
                front.next = qNode;
                return ResultEnum.OK;
            }

            rear.next.next = qNode;//把拥有元素e的新结点qNode赋值给原队尾结点的后继。
            rear.next = qNode;//把当前的qNode设置为队尾结点,rear指向qNode

            return ResultEnum.OK;
        }

        ResultEnum deQueue(LinkQueue linkQueue) {
            QNode qNode;
            if (linkQueue.front == linkQueue.rear) {
                return ResultEnum.ERROR;
            }
            qNode = linkQueue.front.next;//将要删除的队头结点暂存给qNode
            qNode.data = null;
            linkQueue.front.next = qNode.next;//将原队头结点的后继qNode->next赋值给头结点后继

            if (linkQueue.rear == qNode) {//若队头是队尾,则删除后将rear指向头结点
                linkQueue.rear = linkQueue.front;
            }

            return ResultEnum.OK;
        }
    }

    static class QNode {
        String data;
        QNode next;
    }

    enum ResultEnum {
        OK, ERROR;
    }
}

输出结果:

--------------队列依次输出--------------
null
a0
a1
e
--------------队列依次输出--------------
null
a1
e

5、总结

栈和队列它们都是特殊的线性表,只不过对插入和删除操作做了限制。

栈(stack)是限定仅在表尾进行插入和删除操作的线性表。

队列(queue)是只允许在一端进行插入操作,在另一端进行删除操作的线性表。

它们均可以用线性表的顺序存储结构和链式存储结构来实现,但都在着顺序存储的一些弊端,因此它们各自有各自的技巧来解决这个问题。

对于栈来说,如果两个相同的数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数据,这就可以最大化的利用数组空间。

对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得队头和队尾可以在数组中循环变化。解决了移动数据的时间损耗,时间复杂度从O(n)变成了O(1).




原创不易,求个关注。

在这里插入图片描述

微信公众号: 一粒尘埃的漫旅

里面有很多想对大家说的话,就像和朋友聊聊天。
写代码,做设计,聊生活,聊工作,聊职场。
我见到的世界是什么样子的?
搜索关注我吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值