【1】栈的定义
- 栈是仅仅限定在表尾进行插入和删除操作的线性表;运行插入和删除元素的一端叫栈顶,另外一端叫栈底,栈元素遵循先进先出的规则。栈的插入操作叫进栈,删除操作叫出栈,如下图所示:
- 顺序栈:我们知道栈也是一种特殊的线性表,那么顺序栈就是顺序线性表的一种简化,其Java代码实现如下:
-
/** * Created by shifeifei on 2017/6/11. */ public class MyStack<T> { protected Object[] elementData; public MyStack() { elementData = new Object[]{}; } //入栈 public T push(T e) { int len = capacity(); elementData[len - 1] = e; return e; } //出栈 public T pop() { int len = size(); if (len == 0) { throw new RuntimeException("空栈"); } //删除栈顶元素 int index = len - 1; if (index < 0) { throw new RuntimeException("空栈"); } T e = (T) elementData[index]; Object[] newElements = new Object[index]; for (int i = 0; i < index; i++) { newElements[i] = elementData[i]; } elementData = newElements; return e; } public int size() { return elementData.length; } private int capacity() { elementData = Arrays.copyOf(elementData, elementData.length + 1); return elementData.length; } public void print() { for (Object o : elementData) { System.out.print(o + "\n"); } } public static void main(String[] args) { MyStack<String> stack = new MyStack<String>(); stack.push("a"); stack.push("b"); stack.push("c"); stack.push("d"); stack.push("f"); stack.print(); System.out.println("--------"); stack.pop(); stack.print(); stack.pop(); System.out.println("--------"); stack.print(); } }
- 链式栈:无非是使用单链表来实现栈,栈只允许在栈顶进行删除和插入操作,那么为了操作方便,我们肯定会把栈顶放链表的头部,对于链栈来说,不存在栈满的情况,除非物理内存不够用了。
- 链栈的定义
-
/** * Created by shifeifei on 2017/6/11. */ public class MyLinkedStack<T> { private Node<T> top; private int size; public void MyLinkedStack() { } private static class Node<T> { private T element; private Node<T> next; Node(T element, Node<T> next) { this.element = element; this.next = next; } } }
- 链栈的入栈操作
- 如图所示,只需要将top向栈顶移动即可;Java代码如下所示:
-
//入栈 public void push(T e) { top = new Node<T>(e, top); size++; }
- 链栈的出栈栈操作
-
//出栈 public void pop() { if (size == 0) { throw new RuntimeException("空栈"); } Node old = top; top = top.next; //释放引用 old.next = null; size--; }
- 测试
-
public String print() { StringBuffer sb = new StringBuffer(); Node temp = top; while (null != temp) { sb.append(temp.element).append("\t"); temp = temp.next; } return sb.toString(); } public static void main(String[] args) { MyLinkedStack<String> linkedStack = new MyLinkedStack<String>(); linkedStack.push("a"); linkedStack.push("b"); linkedStack.push("c"); linkedStack.push("d"); System.out.println(linkedStack.print()); System.out.println("---------------"); linkedStack.pop(); System.out.println(linkedStack.print()); }
- 队 :只允许在一段进行插入操作,在另一端进行删除操作的线性表,是一种先进先出的线性表,运行插入的一段称之为队尾,运行删除的一段称为队头。
- 顺序存储的队列:假如有n个元素的顺序队列,那我们需要建立一个长度大于n数组,并不所有元素存储在数组的前n个单元里,数组下标为0的是队头,入队操作就是在队尾添加一个元素,此时的时间复杂度为O(1);
- 入队操作:
出队操作:
出队操作是在队头进行,所以删除队头元素后,队列中的所有元素都要向前移动,意味着时间复杂度是O(n)。
- 循环队列:把这种头尾相接的顺序存储的队列称之为循环队列。
- 我们再想想,出队的时候,队列元素可以不往前移动吗?也就是说队头元素不一定是下标为0的元素,如下图所示:
- 为了避免队列只有一个元素的时,队头和队尾重合,我们可以让front指向队头元素,rear指向队尾元素的下一个位置,当front等于rear时,就表示是空队列。现在假设长度为5的数组,初始状态入下图左图所示,front和rear均指向0的位置,然后插入4个元素,rear指向下标为4的元素位置,如下右图所示:
- 现在假设前两个元素出栈,则如下图所示:
- 我们发现,rear明显已经指向队尾之外的地方,如果再在队尾添加元素,可以回出现数组越界的情况,但是实际上我们的队列还有空间的,那就是下标是0和1的地方;我们想想可不可以让rear指向0的位置呢?答案是肯定的,这样的话就可以继续向队列中插入元素了,假如此时我们再以此插入两个元素,情况如下图所示:
- 当再插入两个元素之后,到底如何判断队列是空还是满呢?现在做如下规定:当front == rear时,为空队列;那么要讨论的就是如何判断队满的情况?当队列满时,可以让front和rear之间差一个元素,即队列数组中有一个空闲单元,如下图所示:
- 假设队列的最大长度是size,由图4-12-8可以推出队列满的条件是:(rear+1)%size ==front;而计算队列的当前长度公式为:(rear-front+size)%size;同时也可以得到队头的位置:(front-1)%size;
- 循环队列的顺序存储结构
- 顺序存储结构的定义
-
/** * Created by shifeifei on 2017/6/15. * 队列的线性实现 */ public class MyQueue<T> { //存储队列元素 T[] elements; //指向队头 private int head; //指向队尾 private int tail; public MyQueue() { elements = (T[]) new Object[5]; tail = head = 0; } }
- 队尾添加元素
-
/** * 入队,队尾添加元素 * * @param e */ public void addLast(T e) { if (e == null) throw new RuntimeException("插入元素不能为空"); elements[tail] = e; //指向队列尾部 tail = (tail + 1) % elements.length; //判断队列是否满了,满了则动态扩容 if (tail == head) allocateSpace(); }
- 队头删除元素
-
/** * 出队,队头删除元素 * * @param e */ public Object removeFirst(T e) { if (e == null) throw new RuntimeException("插入元素不能为空"); Object temp = elements[head]; elements[head] = null; //向后移动队头下标 head = (head + 1) % elements.length; return temp; }
- 其它方法
-
/** * 动态扩容 */ public void allocateSpace() { int p = head; int size = elements.length; //队列数组右侧的元素个数 int r = size - p; int newCapacity = size << 1;// size值的二进制数的最后一位上加个0 if (newCapacity < 0) throw new RuntimeException("队列扩容出错"); Object[] a = new Object[newCapacity]; //复制head下标右侧元素:即从elements数组的head开始r长度元素复制到a数组0位置开始 System.arraycopy(elements, p, a, 0, r); //复制head下标左侧元素:即从elements数组的0开始p长度元素复制到a数组r位置开始 System.arraycopy(elements, 0, a, r, p); elements = (T[]) a; head = 0; tail = size; //指向队尾 } /** * 队列长度 * * @return */ public int size() { return (tail - head + elements.length) % elements.length; } public static void main(String[] args) { MyQueue<String> myQueue = new MyQueue<String>(); myQueue.addLast("a"); myQueue.addLast("b"); myQueue.addLast("c"); myQueue.addLast("d"); myQueue.addLast("f"); myQueue.addLast("g"); myQueue.addLast("h"); myQueue.addLast("i"); System.out.println(Arrays.toString(myQueue.elements) + ",size=" + myQueue.size()); System.out.println(myQueue.removeFirst("a")); System.out.println(Arrays.toString(myQueue.elements) + ",size=" + myQueue.size()); }
- 队列的链式存储结构:其实它就是线性单链表,只不过是要去尾进头出而已;我们可以将头指针指向链队列的头结点,而队尾指针指向终端结点;如下图所示: