赞
踩
(1)栈:栈实际上是一种线性表,它只允许在固定的一段进行插入或者删除元素,在进行数据插入或者删除的一段称之为栈顶,剩下的一端称之为栈顶。其遵循的原则是后进先出。
(2)栈的核心操作:三大核心操作,入栈,出栈,取栈顶元素
(3)对于栈的形象理解:子弹的弹夹我们一定见过,子弹在被压入的时候就相当于是一个个元素,而弹夹就相当于是栈。先被压入的子弹是最后被打出的,先压入的元素是最后出来的,也就是后进先出。
(1)队列:首先队列也是一种特殊的线性表,它允许在一端进行插入数据,在另一端进行删除数据的。队列里边有队首,队尾,队首元素。其遵循的原则是先进先出。
(2)队列的核心操作:三大核心操作分别是入队列,出队列,取队首元素。
(3)对于队列的形象理解:火车穿越隧道,火车的头相当于是队列的首,火车的尾相当于是队列的尾部。火车在穿越隧道的时候,头部先进入隧道头部也先出隧道,尾部后进入尾部后出隧道。队列也就是先入的元素先出队列,后进入的元素后出队列。
(1)栈和队列的出入方式不同:栈是后进先出、队列是先进先出。
(2)栈和队列在具体实现的时候操作的位置不同:因为栈是后进先出,它在一段进行操作;而队列是先进先出,实现的时候在两端进行。在Java标准库中实现队列时是按照链表实现的。
上边我们提到过:栈和队列都是一种典型的线性表,都是基于线性表(顺序表和链表)来实现的,那么我们研究栈和队列的目的何在?因为在栈和队列定义后,只有那三种操作,而那三种操作都是最常用的,它支持的操作越少,我们在使用的时候关心的点也就越少,用起来就越不容易出错。在计算机中“少即是多”,少意味着功能比较少、比较呆板。多意味着功能很多,用的时候要操的心就越多,就越容易出错。综上:栈和队列存在的意义就是减少线性表的基本操作,提取常用操作,让人们使用起来更方便,更不容易出错。
栈的操作是在一端进行的,所以我们选择在顺序表的尾部进行操作:入栈用尾插来实现、出栈用尾删来实现、取栈顶元素就是取尾部元素。
注意:我们在用顺序表来实现栈的时候选取的是在顺序表的尾部来进行的,但这并不是说在顺序表的头部就不能实现。只是在头部实现的时候,不管是头插还是头删都要进行元素的搬运,时间复杂度太高,所以不选取。
下边是基于顺序表来实现的栈:
- public class MyStackForArrayList {
- // 用顺序表来实现栈
- // 栈的特点:后进先出,所以在顺序表中入栈用尾插,出栈用尾删,取栈顶元素用[]操作
- // 创建数组用来表示顺序表,栈的容量是100,要是不够后边可以进行扩容
- private int[] data = new int[100];
- // 记录顺序表的当前值
- private int size = 0;
-
- // 一,栈的实现:顺序表实现
- // 1.入栈,就是顺序表中的尾插,头插也能实现,但是要进行搬运,效率太低
- public void push(int val) {
- // 特殊情况的考虑,这里也可以进行扩容
- if (size >= data.length) {
- return;
- }
- // 一般情况的处理
- data[size] = val;
- size++;// 入栈要让size++
- }
-
- // 2.出栈,是有返回值的,用Integer来接收,它可以返回null。
- public Integer pop() {
- // 特殊情况
- if (size == 0) {
- return null;
- }
- // 一般情况
- int ret = data[size-1];// 保存那个出栈的元素,后边进行返回
- size--;// 出栈要让size--
- return ret;
- }
-
- // 3.取栈顶元素
- public Integer peek() {
- // 特殊情况
- if (size == 0) {
- return null;
- }
- return data[size - 1];
- }
- }
-
用链表来实现栈:在用链表实现栈的时候,我们操作的一段选取头端。用头插表示入栈,用头删来表示出栈,取栈顶元素就是取链表的head的值。
注意:选取在链表的头部实现并不是说在链表的尾部不能进行,而是在尾部进行操作的时候,因为每一次那个tail都会进行更新,所以要用一个引用来记录tail的位置,这样就占据了一块内存空间,因此不选它。
下边是基于链表实现的栈:
- class Node {
- int val;
- Node next;
- public Node(int val) {
- this.val = val;
- }
- }
-
- // 链表实现栈,头插表示入栈,头删表示出栈,取栈顶元素。链表头插,头删还是为了减少内存的开销
- public class MyStackForLinkedList {
- // 先搞一个链表
- private Node head = null;
- // 1.入栈,头插,不需要返回要插入的值
- public void push(int val) {
- // 将要插入的元素放在链表里边
- Node newNode = new Node(val);
- // 特殊情况的处理,空链表
- if (head == null) {// 链表本来是空的
- head = newNode;
- return;
- }
- // 处理一般情况
- newNode.next = head;
- head = head.next;
- }
-
- // 2.出栈,就是头删,出栈要返回一个值
- public Integer pop() {// 用Integer来为了可以返回那个null
- // 特殊情况处理,链表是空
- if (head == null) {
- return null;
- }
- // 链表之中只有一个元素,删除就没有元素了
- if (head.next == null) {
- int ret = head.val;
- head = null;
- return ret;
- }
- // 一般情况的处理
- int ret = head.val;
- head = head.next;
- return ret;
- }
-
- // 3.取栈顶元素
- public Integer peek() {
- // 特殊情况:要是链表是空的,没得取,返回null
- if (head == null) {
- return null;
- }
- return head.val;
- }
- }
队列的操作在两端进行,所以我们选择在链表的尾部进行插入表示入队列,头删来表示出队列,用 head.val 操作来表示取队首元素。
注意:这里的头删和尾插的顺序是可以进行交换的,头和尾只是选的两个引用罢了。咋样选取都是一样的。
下边是基于链表来实现的队列:
- // 基于链表来实现队列
- // 因为队列是先进先出的,所以用尾插表示入队列。头删表示出队列,.操作表示取队列元素
-
- public class MyQueueForLinkedList {
- // 弄一个Node内部类,要带上static,为了和外部的实例无关
- static class Node {
- int val;
- Node next;
-
- public Node(int val) {
- this.val = val;
- }
- }
- // 先创建一个链表,并记录其头节点和尾节点,方便后边的进行尾插
- Node newHead = null;
- Node newTail = null;
-
- // 1.入队列,就是尾插,为了和Java库中的队列保持一致,用boolean返回值
- public boolean offer(int val) {
- // 将要插入的值放在Node 节点里面
- Node newNode = new Node(val);
- // 情况特殊的处理
- if (newHead == null) {
- // 头节点和尾节点都是要插入的值
- newHead = newNode;
- newTail = newNode;
- return true;
- }
- // 一般情况的处理
- newTail.next = newNode;
- newTail = newTail.next;
- return true;
- }
-
- // 2.出队列,就是头删,注意头删也是要返回那个要删除的值
- public Integer poll() {
- // 特殊情况的处理,链表为空没得删
- if (newHead == null) {
- return null;
- }
- // 链表只要一个元素
- if (newHead.next == null) {
- int ret = newHead.val;
- // 新头节点就没有了,就是null
- newHead = null;
- return ret;
- }
- // 处理一般情况的处理
- int ret = newHead.val;
- newHead = newHead.next;
- return ret;
- }
- // 3.取队列首元素
- public Integer peek() {
- // 特殊情况的处理
- // 链表为空,没得取
- if (newHead == null) {
- return null;
- }
- // 一般情况的处理
- return newHead.val;
- }
- }
(1)基本思想:创建两个引用head和tail表示队列的头部和尾部,开始的时候head和tail都表示null,此时head == tail表示的是队列是空的 。
(2)出现的问题:每入一次队列给tail进行++,要是tail已经满了的时候就让tail == head,此时head和tail也是相等的,所以就不能区别队列到底是空的还是满的。
(3)解决方法:
方法1:是空上让出一个内存空间,不让tail和head重合。这种方法浪费了一块内存空间,直接让那个空间是空的,也就是用这种方法后:head == tail表示空, tail == head - 1表示队列是满的。看起来非常不好看。
方法2:创建一个变量size,时刻记录队列里边元素的多少,没进行一次入队操作进行size++,每进行一次出队操作让size–,最后用size的值来区别队列是空的还是满的。
下边是基于顺序表实现的队列:
- public class MyQueueForArrayList {
- // 用顺序表来实现队列
- // 基本思路:让head = 0,tail = 0;队列的长度是[head,tail),包含head不包含tail。
- // 入队:tail++,入完判断tail的值,要是 == data.length,让tail从0又开始
- // 出队列:让head++
- // 这样操作当head == tail时,有两种结果:1,是队列是空的时候 2,是队列是满的时候
- // 所以用size来记录一下顺序表的具体的大小,根据size来判断队列是否为满。
- // 创建数组
- public int[] data = new int[100];
- public int head = 0;
- private int tail = 0;
- // 这个用来判断队列到底是空的还是满的
- private int size = 0;
-
- // 1.入队操作,tail++,然后判断size的值
- public boolean offer(int val) {
- // 特殊情况的处理,先判断size的值的大小,每一次都是以size来判断队列是否是空的
- if (size == data.length) {// 队列已经满了
- return false;
- }
- // 一般情况的处理
- data[tail] = val;
- // 完成插入之后,判断一下tail的值的
- if (tail == data.length) {// 要是让tail从0开始
- tail = 0;
- }
- // 否则更新size的值
- size++;
- return true;
- }
-
- // 2.出队列,核心操作,头删,head--
- public Integer poll() {
- // 特殊情况的处理
- // 队列为空,没得取
- if (size == 0) {
- return null;
- }
- // 一般情况的处理
- int ret = data[head];
- head++;
- // 每一次要判断head的值是否到达了末尾
- // 要是到达了末尾,让head也是0
- if (head == data.length) {
- head = 0;
- }
- size--;
- return ret;
- }
-
- // 3.取队首元素
- public Integer peek() {
- // 特殊情况的处理
- if (size == 0) {// 为空,没得取
- return null;
- }
- return data[head];
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。