栈
栈是一种“先进后出”的数据结构
用数组实现栈:
接口IStack
:
public interface IStack {
/**
* 元素入栈(压栈)
*
* @param o
* @return
*/
boolean push(Object o) throws Exception;
/**
* 元素出栈(弹栈)
*
* @return
*/
Object pop() throws Exception;
/**
* 栈是否为空栈
*
* @return
*/
boolean isEmpty();
/**
* 栈是否满了
*
* @return
*/
boolean isFull();
/**
* 获取栈顶元素
*
* @return
*/
Object getPeek() throws Exception;
/**
* 获取元素在栈中的位置
*
* @param obj
* @return
*/
int getIndex(Object obj);
/**
* 获取栈的实际长度
*
* @return
*/
int getSize();
/**
* 获取栈的容量
*
* @return
*/
int getCapacity();
/**
* 打印栈
*/
void print();
}
实现类StackImpl
:
public class StackImpl implements IStack {
public static void main(String[] args) throws Exception {}
//用数组来模拟栈
private Object[] stack;
//栈的容量
private int capacity;
//栈顶指针
private int top = -1;
public StackImpl() {
this(10);
}
public StackImpl(int initCapacity) {
int capacity = initCapacity > 0 ? initCapacity : 10;
this.capacity = capacity;
stack = new Object[capacity];
}
@Override
public boolean push(Object o) throws Exception {
if (isFull()) {
throw new Exception("栈已满,新元素 " + o + " 无法入栈!");
}
stack[++top] = o;
return true;
}
@Override
public Object pop() throws Exception {
if (isEmpty()) {
throw new Exception("栈为空,没有元素可以出栈!");
}
Object o = stack[top];
stack[top--] = null;
return o;
}
@Override
public boolean isEmpty() {
return top == -1;
}
@Override
public boolean isFull() {
return top == capacity - 1;
}
@Override
public Object getPeek() throws Exception {
if (isEmpty()) {
throw new Exception("栈为空,无法获取栈顶元素!");
}
return stack[top];
}
@Override
public int getIndex(Object obj) {
for (int i = 0; i <= top; i++) {
if (stack[i] == obj) {
return i + 1;
}
}
return -1;
}
@Override
public int getSize() {
return top + 1;
}
@Override
public int getCapacity() {
return capacity;
}
@Override
public void print() {
if (top == -1) {
System.out.println("[]");
} else {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i <= top; i++) {
stringBuilder.append(i == 0 ? "[" : "")
.append(stack[i] != null ? stack[i] : "")
.append(i != top ? ", " : "]");
System.out.print((stack[i] != null ? stack[i] : "") + (i != top ? ", " : "]"));
}
System.out.println(stringBuilder);
}
}
}
队列
-
队列是一种“先进先出”的数据结构
-
一般队列在初始时会有一个头指针和一个尾指针,他们都指向队列(数组)的头部(队首),当有元素入队时,尾指针后移;当有元素出队时,头指针后移。
顺序队列
顺序队列容易产生一个问题,即当队列满了时,此时头指针和尾指针都指向队尾,即便此时有元素出队给队列“腾出位置”,但是出队操作并不会改变尾指针的位置,所以即便此时所有元素都出了队,尾指针仍然在队尾的位置,新的元素仍然不能入队,这种问题又叫“假溢出”
用数组实现队列:
队列接口IQueue
:
public interface IQueue {
/**
* 元素入队
*
* @param o
* @return
*/
boolean push(Object o) throws Exception;
/**
* 元素出队
*
* @return
* @throws Exception
*/
Object pop() throws Exception;
/**
* 队列是否为空
*
* @return
*/
boolean isEmpty();
/**
* 队列是否已满
*
* @return
*/
boolean isFull();
/**
* 获取队首元素
*
* @return
* @throws Exception
*/
Object getPeek() throws Exception;
/**
* 获取元素在队列中的位置
*
* @param obj
* @return
*/
int getIndex(Object obj);
/**
* 获取队列的实际长度
*
* @return
*/
int getSize();
/**
* 获取队列的容量
*
* @return
*/
int getCapacity();
/**
* 打印队列
*/
void print();
}
队列接口实现类顺序队列SequentialQueueImpl
:
public class SequentialQueueImpl implements IQueue {
public static void main(String[] args) throws Exception {}
//用数组来模拟队列
private Object[] queue;
//队首指针
private int front = 0;
//队尾指针
private int rear = 0;
//队列的容量
private int capacity;
public SequentialQueueImpl() {
this(10);
}
public SequentialQueueImpl(int initCapacity) {
this.capacity = initCapacity;
queue = new Object[capacity];
}
@Override
public boolean push(Object o) throws Exception {
if (isFull()) {
throw new Exception("队列已满,新元素 " + o + " 无法入队!");
}
queue[rear++] = o;
return true;
}
@Override
public Object pop() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,没有元素可以出队!");
}
Object o = queue[front];
queue[front++] = null;
return o;
}
@Override
public boolean isEmpty() {
return front == rear && (front == 0 || (queue[front - 1] == null));
}
@Override
public boolean isFull() {
return rear == capacity;
}
@Override
public Object getPeek() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,无法获取队首元素!");
}
return queue[front];
}
@Override
public int getIndex(Object obj) {
for (int i = 0; i <= capacity; i++) {
if (queue[i] == obj) {
return i;
}
}
return -1;
}
@Override
public int getSize() {
return rear - front;
}
@Override
public int getCapacity() {
return this.capacity;
}
@Override
public void print() {
if (getSize() == 0) {
System.out.println("[]");
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = front; i < rear; i++) {
stringBuilder.append(queue[i]).append(i != rear - 1 ? ", " : "]");
}
System.out.println(stringBuilder);
}
}
}
上面这种顺序队列的“假溢出”现象:
SequentialQueueImpl sequentialQueue = new SequentialQueueImpl();
for (int i = 0; i < 10; i++) {
sequentialQueue.push(i);
}
for (int i = 0; i < 10; i++) {
sequentialQueue.pop();
}
sequentialQueue.print();
sequentialQueue.push(999);
“假溢出”现象的解决方案:
循环队列
循环队列通过将队列的首和尾连接起来,只要队列不是真正的满了,就不会溢出:
那么怎么判断循环队列是否满/空了呢?
循环队列的判满/判空的三种方法
法一:
- 使用一个默认值为0的flag标签,每当有元素入队时,flag=1;每当有元素出队时,flag=0
- 在front==rear的前提下,如果flag为0,该循环队列为空队列;否则如果flag为1,该循环队列则为满队列
flag标记的循环队列的数组实现:
public class CircleQueueWithFlagImpl implements IQueue {
public static void main(String[] args) throws Exception {}
//用数组来模拟队列
private Object[] queue;
//队首指针
int front = 0;
//队尾指针
int rear = 0;
//队列的容量
int capacity;
//判满/空标签
int flag = 0;
public CircleQueueWithFlagImpl() {
this(10);
}
public CircleQueueWithFlagImpl(int initCapacity) {
this.capacity = initCapacity;
queue = new Object[capacity];
}
@Override
public boolean push(Object o) throws Exception {
if (isFull()) {
throw new Exception("队列已满,新元素 " + o + " 无法入队!");
}
queue[rear++ % capacity] = o;
flag = 1;
return true;
}
@Override
public Object pop() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,没有元素可以出队!");
}
Object o = queue[front % capacity];
queue[front++ % capacity] = null;
flag = 0;
return o;
}
@Override
public boolean isEmpty() {
return front == rear && flag == 0;
}
@Override
public boolean isFull() {
return rear - front == capacity && flag == 1;
}
@Override
public Object getPeek() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,无法获取队首元素!");
}
return queue[front];
}
@Override
public int getIndex(Object obj) {
for (int i = 0; i <= capacity; i++) {
if (queue[i] == obj) {
return i;
}
}
return -1;
}
@Override
public int getSize() {
return rear - front;
}
@Override
public int getCapacity() {
return this.capacity;
}
@Override
public void print() {
if (getSize() == 0) {
System.out.println("[]");
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = front; i < rear; i++) {
stringBuilder.append(queue[i % capacity]).append(i != rear - 1 ? ", " : "]");
}
System.out.println(stringBuilder);
}
}
}
此时就不会碰到顺序队列可能会碰到的“假溢出”问题了:
CircleQueueWithFlagImpl circleQueueWithFlag = new CircleQueueWithFlagImpl();
for (int i = 0; i < 10; i++) {
circleQueueWithFlag.push(i);
}
circleQueueWithFlag.print();
for (int i = 0; i < 10; i++) {
circleQueueWithFlag.pop();
}
circleQueueWithFlag.print();
circleQueueWithFlag.push(999);
circleQueueWithFlag.print();
法二:
- 使用一个计数器count,每当有元素入队时,count+1;每当有元素出队时,count-1
- 如果count==0,该循环队列为空队列;否则如果count=capacity,该循环队列则为满队列
public class CircleQueueWithCountImpl implements IQueue {
public static void main(String[] args) throws Exception {}
//用数组来模拟队列
private Object[] queue;
//队首指针
private int front = 0;
//队尾指针
private int rear = 0;
//队列的容量
private int capacity;
//队列计数器
private int count = 0;
public CircleQueueWithCountImpl() {
this(10);
}
public CircleQueueWithCountImpl(int initCapacity) {
this.capacity = initCapacity;
queue = new Object[capacity];
}
@Override
public boolean push(Object o) throws Exception {
if (isFull()) {
throw new Exception("队列已满,新元素 " + o + " 无法入队!");
}
queue[rear++ % capacity] = o;
count++;
return true;
}
@Override
public Object pop() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,没有元素可以出队!");
}
Object o = queue[front % capacity];
queue[front++ % capacity] = null;
count--;
return o;
}
@Override
public boolean isEmpty() {
return count == 0;
}
@Override
public boolean isFull() {
return count == capacity;
}
@Override
public Object getPeek() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,无法获取队首元素!");
}
return queue[front];
}
@Override
public int getIndex(Object obj) {
for (int i = 0; i <= capacity; i++) {
if (queue[i] == obj) {
return i;
}
}
return -1;
}
@Override
public int getSize() {
return rear - front;
}
@Override
public int getCapacity() {
return this.capacity;
}
@Override
public void print() {
if (getSize() == 0) {
System.out.println("[]");
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = front; i < rear; i++) {
stringBuilder.append(queue[i % capacity]).append(i != rear - 1 ? ", " : "]");
}
System.out.println(stringBuilder);
}
}
}
法二同时也可以解决“假溢出”问题:
CircleQueueWithCountImpl circleQueueWithCount = new CircleQueueWithCountImpl();
for (int i = 0; i < 10; i++) {
circleQueueWithCount.push(i);
}
circleQueueWithCount.print();
for (int i = 0; i < 10; i++) {
circleQueueWithCount.pop();
}
circleQueueWithCount.print();
circleQueueWithCount.push(999);
circleQueueWithCount.print();
法一和法二的原理是类似的
法三:
- 牺牲一个元素的位置用来判定
- 如果front=rear,则该队列为空队列;否则如果(rear + 1) % capacity = front,则该队列为满队列
第三种方法和前两种方法的不同之处在于,第三种方法舍弃了一个元素的位置用来判定,但是没有使用额外的变量(flag、count之类的)来进行判定
public class CircleQueueWithSacrificeImpl implements IQueue {
public static void main(String[] args) throws Exception {}
//用数组来模拟队列
private Object[] queue;
//队首指针
private int front = 0;
//队尾指针
private int rear = 0;
//队列的容量
private int capacity;
public CircleQueueWithSacrificeImpl() {
this(10);
}
public CircleQueueWithSacrificeImpl(int initCapacity) {
this.capacity = initCapacity;
queue = new Object[capacity];
}
@Override
public boolean push(Object o) throws Exception {
if (isFull()) {
throw new Exception("队列已满,新元素 " + o + " 无法入队!");
}
queue[rear++ % capacity] = o;
return true;
}
@Override
public Object pop() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,没有元素可以出队!");
}
Object o = queue[front % capacity];
queue[front++ % capacity] = null;
return o;
}
@Override
public boolean isEmpty() {
return front == rear;
}
@Override
public boolean isFull() {
return (rear + 1) % capacity == front;
}
@Override
public Object getPeek() throws Exception {
if (isEmpty()) {
throw new Exception("队列为空,无法获取队首元素!");
}
return queue[front];
}
@Override
public int getIndex(Object obj) {
for (int i = 0; i <= capacity; i++) {
if (queue[i] == obj) {
return i;
}
}
return -1;
}
@Override
public int getSize() {
return rear - front;
}
@Override
public int getCapacity() {
return this.capacity;
}
@Override
public void print() {
System.out.println(front+" "+rear);
if (getSize() == 0) {
System.out.println("[]");
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = front; i < rear; i++) {
stringBuilder.append(queue[i % capacity]).append(i != rear - 1 ? ", " : "]");
}
System.out.println(stringBuilder);
}
}
}