写在前面:
栈(Stack):是一种只能在一端,进行插入和删除操作的特殊线性表,分为顺序栈和链式栈。
堆(Heap):堆是一种数组对象,他可以被视为一颗完全二叉树结构,所以,堆又称为二叉堆。
队列(Queue):一般分为顺序队列,链式队列和循环队列。
(一)栈
由于栈的特点是:只能在一段进行插入和删除操作。
特点:先进后出结构(LIFO:Last In First Out),进行插入和删除操作的这一端成为栈顶(top),相对的另一端为栈底(boottom)。
压栈、入栈:向栈顶添加一个元素,使其成为栈顶元素
出栈、退栈:从栈顶移除一个元素,使相邻元素成为新的栈顶元素。
1.顺序栈:使用数组结构实现一个栈
特点:栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。由于是由数组实现的,所以在存储上是连续的一块空间。
缺点:
1.在编译时期,就会分配固定的大小,会引发由于数组大小用光而导致的溢出问题
2.系统将内存分配给数组后,其他任务就不能再引用这块空间了。
程序实现:
package stack;
public class MyStackDemo1<E> {
private int INITIAL_SIZE = 10;// 默认初始化容量
private Object[] myStack;
private int size = 0;// 当前元素个数
private int capacity;
public MyStackDemo1() {
// 以默认大小初始化栈
myStack = new Object[INITIAL_SIZE];
capacity = INITIAL_SIZE;
}
public MyStackDemo1(E e) {
this();
// 以默认大小初始化栈
myStack[0] = e;
size++;
}
public void expand() {
Object[] newStack = new Object[capacity * 2];
for (int i = 0; i < size; i++) {
newStack[i] = myStack[i];
}
myStack = newStack;
}
/**
* 判断是否非空,栈为空则返回1,非空返回1
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 压栈(先判断是否需要扩容)
*
* @param e
*/
public void push(E e) {
if (size + 1 > capacity) {
expand(); // 调用扩容方法
}
myStack[size++] = e;
}
/**
* 返回栈顶元素
*
* @return
*/
public E peek() {
if (isEmpty()) {
return null;
} else {
return (E) myStack[size - 1];
}
}
/**
* 出栈
*
* @return
*/
public E pop() {
E e = peek(); // 调用peek()返回栈顶元素
myStack[--size] = null;
return e;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer(size * 2 + 1);
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(myStack[i] + ",");
}
sb.append("]");
return sb.toString();
}
}
2.链式栈:使用链表结构实现一个栈
特点:由一个单链表结构,使用栈顶的top节点就可以操作整个栈结构。
程序实现:
package stack;
/**
* 链表实现栈结构
*
* @author McGRADY
*
* @param <E>
*/
public class MyStackDemo2<E> {
private Node<E> top = null;
private int size = 0;
class Node<E> {
E ele;
Node<E> next;
Node(E ele) {
this.ele = ele;
}
}
public int length() {
return size;
}
public boolean isEmpty() {
return top == null;
}
/**
* 压栈
*
* @param ele
*/
public void push(E ele) {
Node<E> node = new Node<E>(ele);
node.next = top;
top = node;
size++;
}
/**
* 出栈
*
* @return
*/
public E pop() {
if (isEmpty()) {
return null;
} else {
E data = top.ele;
top = top.next;
size--;
return data;
}
}
/**
* 返回栈顶元素
*
* @return
*/
public E peek() {
if (isEmpty()) {
return null;
}
return top.ele;
}
/**
* 重写toString(测试用)
*/
@Override
public String toString() {
Node node = this.top;
StringBuffer sb = new StringBuffer(size * 2 + 1);
sb.append("[");
while (node.next != null) {
sb.append(node.ele + ",");
node = node.next;
}
sb.append(node.ele + "]");
return sb.toString();
}
}
(二)队列
特点:是一种先进先出的线性表(FIFO:First In First Out),只允许在表的一端插入,另一端进行删除元素。允许插入的一端为队尾(rear),允许删除的一端为队头(front)。
1.循环队列:
数组实现,用一组地址连续的存储单元依次存放从队列头到队列尾的元素。此外,front指向队头元素,rear指向队尾元素。
实现过程:
初始化新建一个空队列的时候,front=rear=0,每当插入一个元素,队尾指针rear+1,每当删除一个元素,队头指针rear+1。所以,在一个非空队列中,fron始终指向队列的头元素,而rear始终指向队列的队尾元素的下一个位置。
注意:需要注意的一点是,由于是循环队列,且rear指向尾元素的下一个位置,所以front=rear=0不能区分,队列处于空还是处于满的状态,所以,我们的解决办法是,为队列留出一个存储位置,不存储其他元素,这样当front=rear=0时,可以判断队列为空。
程序实现:
package queue;
/**
* 循环队列 (先进先出) 注意:要留出一个空位置不存储其他元素,仅用于判断是否队列已满
*
* @author McGRADY
*
*/
public class MyQueueDemo1 {
private Object[] MyQueue;
private int front; // 队头,队列非空则指向队头元素
private int rear; // 队尾,队列非空则指向队尾元素
public MyQueueDemo1(int MAXSIZE) {
front = rear = 0;
MyQueue = new Object[MAXSIZE];
}
public void clear() {
front = rear = 0;
}
public boolean isEmpty() {
return front == rear;
}
/**
* 判断队列是否已经满了
*
* @return
*/
public boolean isFull() {
// 留出一个空位置不存储其他元素,仅用于判断是否队列已满
return (rear + 1) % MyQueue.length == front;
}
/**
* 队列中元素个数
*
* @return
*/
public int length() {
return (rear - front + MyQueue.length) % MyQueue.length;
}
/**
* 读取队列首元素
*
* @return
*/
public Object peek() {
if (isEmpty()) {
return null;
} else {
return MyQueue[front];
}
}
/**
* 入队
*
* @param e
* @throws Exception
*/
public void offer(Object e) throws Exception {
if (isFull()) {
throw new RuntimeException("队列已满");
}
MyQueue[rear] = e;
rear = (rear + 1) % MyQueue.length;
}
/**
* 出队
*
* @return
*/
public Object poll() {
if (isEmpty()) {
return null;
}
Object e = MyQueue[front];
front = (front + 1) % MyQueue.length;
return e;
}
/**
* 重写toString,用于测试
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[");
for (int i = front; i != rear; i = (i + 1) % MyQueue.length) {
sb.append(MyQueue[i] + ",");
}
sb.append("]");
return sb.toString();
}
}
2.链队列
特点:用链表表示的队列。队列为空时,front和rear都指向null。在rear添加节点元素,在front删除节点元素。
优点:链队列不像循环队列那样首尾相接,链队列没有长度限制。
程序实现
package queue;
public class MyQueueDemo2 {
private Node front; // 链表的第一个节点
private Node rear; // 链表的最后一个节点
class Node {
private Node next; // 下一个节点对象
private Object ele; // 当前节点存储的数据
public Node(Object ele) {
this.ele = ele;
}
}
public MyQueueDemo2() {
front = rear = null;
}
public void clear() {
front = rear = null;
}
public boolean isEmpty() {
return front == null;
}
/**
* 当前队列的元素个数
*
* @return
*/
public int length() {
int size = 0;
Node node = this.front;
while (node != rear) {
size++;
node = node.next;
}
size++;
return size;
}
/**
* 入队
*
* @param ele
* @throws Exception
*/
public void offer(Object ele) throws Exception {
Node node = new Node(ele);
if (front != null) {
rear.next = node;
rear = node;
} else {
front = rear = node;
}
}
/**
* 出队
*
* @return
*/
public Object poll() {
if (front != null) {
Node node = front;
front = front.next;
return node.ele;
} else {
return null;
}
}
/**
* 重写toString方法 (用于测试)
*/
@Override
public String toString() {
if (front == null) {
return "[]";
}
Node current = this.front;
StringBuffer sb = new StringBuffer(this.length());
sb.append("[");
while (rear != current) {
sb.append(current.ele + ",");
current = current.next;
}
sb.append(rear.ele + "]");
return sb.toString();
}
}
(三)