【数据结构与算法】背包、队列和栈
前言
集合类数据类型的实现
1 栈
1.1 定容栈
/** * 定容栈的实现 * */ public class FixedCapacityStackOfStrings { private String[] a; private int N = 0; public FixedCapacityStackOfStrings(int capacity) { a = new String[capacity]; } public int size() { return N; } public boolean isEmpty() { return N == 0; } public void push(String item) { a[N++] = item; } public String pop() { return a[--N]; } // 测试 public static void main(String[] args) { String str = "to be or not to - be - - that - - - is"; String expect = "to be not that or be "; String result = ""; FixedCapacityStackOfStrings s = new FixedCapacityStackOfStrings(100); for(String item : str.split(" ")) { if(!"-".equals(item)) { s.push(item); } else if(!s.isEmpty()) { result += s.pop() + " "; } } System.out.println(expect.equals(result)); } }
1.2 泛型定容栈
/** * 泛型定容栈 */ public class FixedCapacityStack<Item> { private Item[] a; private int N = 0; public FixedCapacityStack(int capacity) { // 不能使用泛型数组 a = (Item[]) new Object[capacity]; } public boolean isEmpty() { return N == 0; } public Item pop() { return a[--N]; } public void push(Item item) { a[N++] = item; } public int size() { return N; } // 测试 public static void main(String[] args) { String str = "to be or not to - be - - that - - - is"; String expect = "to be not that or be "; String result = ""; FixedCapacityStack<String> s = new FixedCapacityStack<>(100); for(String item : str.split(" ")) { if(!"-".equals(item)) { s.push(item); } else if(!s.isEmpty()) { result += s.pop() + " "; } } System.out.println(expect.equals(result)); } }
需要注意的是,上面代码中泛型定容栈的初始化使用的是:
a = (Item[]) new Object[capacity];
而不是:
a = new Item[capacity];
这是由于创建泛型数组在Java中是不允许的
1.3 调整数组的大小:实现Stack API动态改变数组大小
选择用数组表示栈内容意味着用例必须预先估计栈的最大容量。在Java中,数组一旦被声明长度就不能够再进行更改了,选择大容量的用例在栈为空时会造成内存的极大浪费。同时在push的时候还需要考虑栈满的情况,综合上面两点,书中提供了一种解决方法:即使用一个大小不同的数组对栈进行转移:
public void resize(int max) { Item[] temp = (Item[]) new Object[max]; for(int i=0;i<N;i++) { temp[i] = a[i]; } a = temp; }
如此一来,就可以在push操作之前和pop操作之后判断栈的容量,以动态改变栈数组的大小。
public void push(Item item) { if(N == a.length) resize(a.length * 2); a[N++] = item; } public Item pop() { Item item = a[--N]; // 防止对象游离 a[N] = null; // N=0不必再进行长度缩减 if(N > 0 && N <= a.length/4) resize(a.length / 2); return item; }
这里解决了栈满的判定问题,但是栈空pop时还需要进行判定
对象游离:
Java的垃圾回收策略是回收所有无法被访问到的对象的内存;但是像栈被pop出的元素,它是永远不可能被访问到的了,但是仍然存在于内存,但是Java的垃圾回收器没有办法知道这一点,因此需要通过引用覆盖的方式告知:
a[N] = null
1.4 实现Stack API的迭代
Java集合类型数据类型的基本操作之一就是能够通过for-each语句进行遍历元素,原因是因为Collection类实现了Iterable接口:
public interface Collection<E> extends Iterable<E>
Iterable接口维护了一个Iterator迭代器接口,接口方法用于定义迭代的规则,迭代器接口方法包括:
public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
因此要实现Stack API的迭代,需要API implement Iterable<Item>和添加它的方法iterator:
@Override public Iterator iterator() { return new ReverseArrayIterator(); }
ReverseArrayIterator实现了Iterator接口,定义了迭代需要的hashNext和next接口方法:
private class ReverseArrayIterator implements Iterator<Item> { private int i = N; @Override public boolean hasNext() { return i > 0; } @Override public Item next() { return a[--i]; } @Override public void remove() { } }
ReverseArrayIterator为定义在Stack的内部类
1.5 综合:一个能够动态调整数组大小并且能够进行迭代的Stack API
package Algorithms.One; import java.util.Collection; import java.util.Iterator; public class IterableStack<Item> implements Iterable<Item>{ private Item[] a; //默认初始化为0 private int N; public void resize(int max) { Item[] temp = (Item[]) new Object[max]; for(int i=0;i<N;i++) { temp[i] = a[i]; } a = temp; } public IterableStack(int capacity) { a = (Item[]) new Object[capacity]; } public void push(Item item) { if(N == a.length) resize(a.length * 2); a[N++] = item; } public Item pop() { Item item = a[--N]; // 防止对象游离 a[N] = null; // N=0不必再进行长度缩减 if(N > 0 && N <= a.length/4) resize(a.length / 2); return item; } public boolean isEmpty() { return N == 0; } @Override public Iterator iterator() { return new ReverseArrayIterator(); } private class ReverseArrayIterator implements Iterator<Item> { private int i = N; @Override public boolean hasNext() { return i > 0; } @Override public Item next() { return a[--i]; } @Override public void remove() { } } public static void main(String[] args) { String str = "to be or not to - be - - that - - - is"; String expect = "to be not that or be "; String result = ""; IterableStack<String> s = new IterableStack<>(100); for(String item : str.split(" ")) { if(!"-".equals(item)) { s.push(item); } else if(!s.isEmpty()) { result += s.pop() + " "; } } System.out.println(expect.equals(result)); Iterator iterator = s.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); } for (String str2 : s) { System.out.println(str2); } } }
2 链表
链表是一种递归的线性数据结构,它或为空(null),或者是指向一个节点(node)的引用,该节点包含一个泛型的元素和一个指向另一条链表的引用。
public class LinkedList<Item> { private class Node { Item item; Node next; } } private Node first;
在需要使用Node类的类中将其定义为私有的内部类,因为它不是为用例准备的(书中这句话我的理解是它的数据类型随用例类LinkedList变化而不是自己能够决定,每个用例都可以这么写,不是单独为用每个例准备的)
2.1 在表头插入节点
/** * 在表头插入节点 * @param item */ private void addOnHead(Item item) { Node temp = new Node(); temp.item = item; temp.next = first.next; first = temp; }
2.2 从表头删除节点
/** * 在表头删除节点 * @return */ private boolean deleteAtHead() { // 没有节点删除失败 if(first == null) return false; // 有一个节点,将链表置空 if(first.next == null) first = null; // 两个以上节点的情况 else first.next = first.next.next; return true; }
2.3 在表尾插入节点
/** * 尾插法 * @param item */ public void insertOnTail(Item item) { Node temp = new Node(); temp.item = item; Node tail = first; if(tail==null) { tail = temp; } while(tail.next!=null) { tail = tail.next; } tail.next = temp; }
2.5 链栈LinkedStack的实现
链栈即为使用链表实现的栈,栈的顶部即为表头,实例变量first指向栈顶,这样当使用push的时候将元素添加到表头,pop的时候将表头元素删除即可,使用一个变量N计数元素个数,并且实现了Iterable使得链栈可以使用for-each进行遍历,链表的使用完美达到了栈的最优设计目标:
① 可以存储任意数据类型的数据
② 所需的空间总是和集合大小成正比,且大小不被限制(硬件允许的条件下)
③ 操作所需时间和集合大小无关
④ 可以进行for-each遍历
public class LinkedStack<Item> implements Iterable<Item> { private class Node { Item item; Node next; } private Node first; private int N; public void push(Item item) { Node oldFirst = first; first = new Node(); first.item = item; first.next = oldFirst; N++; } public Item pop() { if(first == null) return null; Item item = first.item; first = first.next; N--; return item; } public int size() { return N; } public boolean isEmpty() { return N == 0; } @Override public Iterator<Item> iterator() { return new LinkedStackIterator(); } private class LinkedStackIterator implements Iterator<Item>{ private int i = N; @Override public boolean hasNext() { return i > 0; } @Override public Item next() { Node temp = first; for(int j=0;j<i-1;j++) temp = temp.next; i--; return temp.item; } } // 测试 public static void main(String[] args) { LinkedStack<Integer> stack = new LinkedStack<>(); for (int i=0;i<10;i++) stack.push(i); System.out.println(stack.pop()); for(int val : stack) { System.out.print(val); } } }
2.6 链式队列LinkedQueue的实现
将队列表示为一条最早插入到最近插入的链表,实例变量first指向队列的队首,tail指向队尾,入队enQueue将添加元素添加到表尾,出队deQueue操作将元素添加到表首。
package Algorithms.One; import java.util.Iterator; public class LinkedQueue<Item> implements Iterable<Item> { private class Node { Item item; Node next; } private int N; private Node first; private Node tail; // 表尾入队 public void enQueue(Item item) { Node oldTail = tail; tail = new Node(); tail.item = item; if(N == 0) first = tail; else oldTail.next = tail; N++; } // 表首出队 public Item deQueue() { if(first == null) return null; Item item = first.item; first = first.next; if(isEmpty()) tail=null; N--; return item; } public boolean isEmpty() { return N == 0; } public int size() { return N; } private void printAll() { Node temp = first; while(first!=null) { System.out.println(first.item); first = first.next; } } @Override public Iterator<Item> iterator() { return new LinkedQueueIterator(); } private class LinkedQueueIterator implements Iterator<Item> { private int i = 0; @Override public boolean hasNext() { return i<N; } @Override public Item next() { Node temp = first; for(int j=0;j<i;j++) temp = temp.next; i++; return temp.item; } } public static void main(String[] args) { LinkedQueue<Integer> queue = new LinkedQueue<>(); for(int i=0;i<10;i++) queue.enQueue(i); for(int var:queue) { System.out.print(var); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步