数据结构与算法(六)-背包、栈和队列
前言:许多基础数据类型都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加、删除或是访问集合中的对象。而且有很多高级数据结构都是以这样的结构为基石创造出来的,在本文中,我们将了解学习三种这样的数据类型,分别是背包(Bag)、栈(Stack)和队列(Queue)
一、学习感悟
- 首先先对该结构的场景操作进行API化;
- 然后再对数据类型的值的所有可能的表示方法以及各种操作的实现;
- 总结特点和比较;
二、API
2.1、背包(Bag)
2.1.1 背包API
public class Bag<Item> implements Iterable<Item> Bag() 创建一个空背包 void add(Item item) 添加一个元素 boolean isEmpty() 背包是否为空 int size() 背包中的元素数量
2.1.2 背包实现
public class Bag<T> implements Iterable<T> { private Node<T> first; private Integer size; Bag() { first = new Node<>(); first.next = null; size = 0; } //由于Bag类型不需要考虑元素的相对顺序,所以这里我们可以使用头插法来进行插入,提高效率 public void add(T t) { Node<T> newNode = new Node<>(); newNode.t = t; newNode.next = first.next; first.next = newNode; size++; } public Boolean isEmpty() { return size < 1; } public Integer size() { return size; } class Node<T> { T t; Node<T> next; } @Override public Iterator<T> iterator() { return new ListIterator(); } class ListIterator implements Iterator<T> { private Node<T> current = first.next; @Override public boolean hasNext() { return current!=null; } @Override public T next() { T t = current.t; current = current.next; return t; } } public static void main(String[] args) { Bag<Integer> bag = new Bag<>(); for (int i = 1; i <= 100; i++) { bag.add(i); } double sum = 0; Iterator<Integer> iterator = bag.iterator(); while (iterator.hasNext()) { sum = sum + iterator.next(); } System.out.println("和:"+sum); double size = bag.size(); String format = new DecimalFormat("0.00").format(sum / size); System.out.println("平均值:"+format); } }
Bag.java
核心代码为add(),使用了头插法::
//由于Bag类型不需要考虑元素的相对顺序,所以这里我们可以使用头插法来进行插入,提高效率 public void add(T t) { Node<T> newNode = new Node<>(); newNode.t = t; newNode.next = first.next; first.next = newNode; size++; }
2.1.3 总结
2.2、栈(Stack)
- 写入数据(堆积)操作称作入栈(PUSH);
- 读取数据操作称作出栈(POP);
2.2.1 栈API
public class Stack<Item> implements Iterable<Item> Stack() 创建一个空栈 void push(Item item) 添加一个元素 Item pop() 删除最近添加的元素 boolean isEmpty() 栈是否为空 int size() 栈中的元素数量
2.2.2 栈实现
public class Stack<T> implements Iterable<T> { private Node<T> head; private Integer size; Stack() { head = new Node<>(); head.next = null; size = 0; } //头插法 public void push(T t) { Node<T> first = head.next; head.next = new Node<>(); head.next.t = t; head.next.next = first; size++; } //取的时候从最上面开始取,也就是最近插入的元素 public T pop() { Node<T> first = head.next; head.next = first.next; size--; return first.t; } public Boolean isEmpty() { return size < 1; } public Integer size() { return size; } class Node<T> { T t; Node<T> next; } @Override public Iterator<T> iterator() { return new ListIterator<T>(); } class ListIterator<T> implements Iterator<T> { private Node<T> current = (Node<T>) head.next; @Override public boolean hasNext() { return current!=null; } @Override public T next() { T t = current.t; current = current.next; return t; } } public static void main(String[] args) { Stack<Integer> stack = new Stack<>(); for (int i = 0; i < 10; i++) { stack.push(i); System.out.println("push --> "+i); } Iterator<Integer> iterator = stack.iterator(); while (iterator.hasNext()) { System.out.println("pop --> "+iterator.next()); } } }
Stack.java
//头插法 public void push(T t) { Node<T> first = head.next; head.next = new Node<>(); head.next.t = t; head.next.next = first; size++; } //取的时候从最上面开始取,也就是最近插入的元素 public T pop() { Node<T> first = head.next; head.next = first.next; size--; return first.t; }
2.2.3 总结
- 先进后出;
- 具有记忆功能,栈的特点是先进栈的后出栈,后进栈的先出栈,所以你对一个栈进行出栈操作,出来的元素肯定是你最后存入栈中的元素,所以栈有记忆功能;
- 对栈的插入与删除操作中,不需要改变栈底指针;
- 栈可以使用顺序存储也可以使用链式存储,栈也是线性表,因此线性表的存储结构对栈也适用线性表可以链式存储;
2.3、队列(Queue)
2.3.1 队列API
public class Queue<Item> implements Iterable<Item> Queue() 创建一个空队列 void enqueue(Item item) 添加一个元素 Item dequeue() 删除最近添加的元素 boolean isEmpty() 队列是否为空 int size() 队列中的元素数量
2.3.2 队列实现
public class Queue<T> implements Iterable<T> { private Node<T> head; private Node<T> tail; private Integer size; Queue() { head = new Node<>(); tail = null; head.next = tail; tail = head; size = 0; } //从队列的尾部插入数据 public void enqueue(T t) { Node<T> oldNode = tail; tail = new Node<>(); tail.t = t; tail.next = null; if (isEmpty()) head.next = tail; else oldNode.next = tail; size++; } //从队列的头部取数据 public T dequeue() { Node<T> first = head.next; head.next = first.next; return first.t; } public Boolean isEmpty() { return size < 1; } public Integer size() { return size; } class Node<T> { T t; Node<T> next; } @Override public Iterator<T> iterator() { return new ListIterator(); } class ListIterator implements Iterator<T> { private Node<T> current = head.next; @Override public boolean hasNext() { return current!=null; } @Override public T next() { T t = current.t; current = current.next; return t; } } public static void main(String[] args) { Queue<Integer> queue = new Queue<>(); for (int i = 0; i < 10; i++) { queue.enqueue(i); System.out.println("enqueue --> "+i); } Iterator<Integer> iterator = queue.iterator(); while (iterator.hasNext()) { System.out.println("dequeue --> "+iterator.next()); } } }
Queue.java
//从队列的尾部插入数据 public void enqueue(T t) { Node<T> oldNode = tail; tail = new Node<>(); tail.t = t; tail.next = null; if (isEmpty()) head.next = tail; else oldNode.next = tail; size++; } //从队列的头部取数据 public T dequeue() { Node<T> first = head.next; head.next = first.next; return first.t; }
2.3.3 总结
- 先进先出;
- 特殊的线性结构;
- 关注于元素的顺序,公平性;
三、背包、栈和队列的比较
本系列参考书籍:
《写给大家看的算法书》
《图灵程序设计丛书 算法 第4版》