三、栈
还记得当初第一次学习编程的时候还是8051单片机中的汇编语言,现在还记得很清楚,当初遇到的一个简单的数据结构就是——栈,对应的汇编语言中的命令是push
和pop
。这个结构在生活中是有很多类似的例子的,比如水杯、碗等。该结构的特点如下:
- 最大特点是先进后出、后进先出;
- 使用一个指针标识栈中元素的位置——栈顶,该值指的是在栈中最上面元素的位置;
针对栈的基本操作主要有:
- 入栈
- 出栈
- 是否为空
具体的实现类如下:
public class Stack<T> {
private int top = 0;
private Object[] items;
public Stack(){
items = new Object[10];
}
public Stack(int initSize){
items = new Object[initSize];
}
public synchronized T push(T item){
if(top > items.length){
throw new IllegalStateException("elements appear in this stack are full");
}
items[top++] = item;
return item;
}
public synchronized T pop(){
if(top <= 0){
throw new IllegalStateException("no element appear in this stack");
}
T item = (T)items[top-1];
items[top-1] = null;
top--;
return item;
}
public boolean isEmpty(){
return top == 0;
}
public int size(){
return top;
}
}
测试类如下:
com.demo.category.Stack<String> stack = new com.demo.category.Stack<>();
stack.push("test1");
stack.push("test2");
stack.push("test3");
stack.push("test4");
stack.push("test5");
System.out.println("count: " + stack.size());
String src1 = stack.pop();
String src2 = stack.pop();
String src3 = stack.pop();
System.out.println("count: " + stack.size());
System.out.println("src1: " + src1);
System.out.println("src2: " + src2);
System.out.println("src3: " + src3);
在针对栈的操作中要注意以下问题:
- 会有数量的限制,导致越界;如果想要使用的栈是无限容量的,就要随时保证数组的容量比实际的元素要多,因此会涉及到数组的扩容问题,当然如果你不是通过数组来实现栈的,也可以使用链表来实现,这样就不必要考虑容量的问题;
- 多线程环境下操作的互斥;
四、队列
队列这种数据结构在生活中也是有各种各样的类似的例子,比如隧道里,先进入的车肯定是先出来的,这种特点和栈正好是相反的:
- 先进先出(FIFO),后进后出;
所以在队列中所有的元素都是有序的,一切行为的产生都是按照顺序来的,但是也有例外,比如为了区分优先级而设计出来的优先队列。而在队列中又可以细分成单向队列和双向队列。
4.1 单向队列
在单向队列中是队列中最简单的一种,它的特点如下:
- 入队:只能从队尾添加元素,也就说不能插队;
- 出队:只能从队头删除元素,也就说只有轮到你的时候才能离开,否则就老老实实待着;
单向队列如下:
public class Queue<T> {
private Object[] items;
private int tail = 0;
private int head = 0;
private int size;
public Queue(int initSize){
items = new Object[initSize];
}
public boolean add(T item){
if(size == items.length){
throw new IllegalStateException("no space for the new element");
}
items[tail] = item;
tail++;
size++;
return true;
}
public T remove(){
if(size == 0){
throw new IllegalStateException("no element in this queue");
}
T item = (T)items[head];
items[head] = null;
head++;
size--;
return item;
}
public int size(){
return size;
}
}
在上述的例子中,队列是通过数组来实现的,示意图如下:
4.2 双向队列
双向队列则对于元素的出队和入队限制很松:
- 入队:即可以在队头,也可以在队尾;
- 出队:队头可以出队,队尾也可以出队;
该双向队列可以直接继承自单向队列,代码如下:
public class Biqueue<T> extends Queue<T> {
public Biqueue() {
super.items = new Object[10];
}
public Biqueue(int initSize) {
super.items = new Object[initSize];
}
public boolean addTail(T item) {
return super.add(item);
}
public T removeHead() {
return super.remove();
}
// 队头入队
public boolean addHead(T item) {
if (size == items.length) {
throw new IllegalStateException("no space for the new element");
}
if (head == 0) {
if (size > 0) {
// 插队是要付出代价的,即所有后面的元素都要跟着变化
for (int i = size - 1; i >= 0; i--) {
items[i + 1] = items[i];
}
}
items[head] = item;
tail++;
} else if (head > 0) {
items[head - 1] = item;
head--;
}
size++;
return true;
}
// 队尾出队
public T removeTail() {
if (size == 0) {
throw new IllegalStateException("no element in this queue");
}
T item = (T) items[tail - 1];
items[tail - 1] = null;
tail--;
size--;
return item;
}
}
上述所有的操作均没有考虑多线程的环境,如果要在多线程的环境下操作需要考虑互斥。
针对数据结构的操作,之前一直以为知道数据结构的特性即可,感觉实现起来应该不难,但是真正地去实现一个的时候,才知道要考虑的问题很多!