数据结构与算法第五节:栈的使用
特定的数据结构是对特定场景的抽象。
栈是一种“操作受限”的线性表,只允许在一端插入和删除数据; 先进后出
当某个数据集合只涉及在一端的插入和删除数据, 并且满足后进先出, 先进后出的特性,我们就应该首选“栈”这种数据结构.
栈既可以数组来实现(顺序栈), 也可以用链表来实现(链式栈)。
- 基于数组实现的栈
// 基于数组实现的顺序栈
public class ArrayStack {
private String[] items; // 数组
private int count; // 栈中元素个数
private int n; //栈的大小
// 初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n) {
this.items = new String[n];
this.n = n;
this.count = 0;
}
// 入栈操作
public boolean push(String item) {
// 数组空间不够了,直接返回false,入栈失败。
if (count == n) return false;
// 将item放到下标为count的位置,并且count加一
items[count] = item;
++count;
return true;
}
// 出栈操作
public String pop() {
// 栈为空,则直接返回null
if (count == 0) return null;
// 返回下标为count-1的数组元素,并且栈中元素个数count减一
String tmp = items[count-1];
--count;
return tmp;
}
}
-
栈的几种应用方向
1.支持动态扩容的顺序栈
2.栈在函数调用中的应用
3.栈在表达式求值中的应用
4.栈在括号匹配中的应用
数据结构&&算法第六节: 队列的使用
队列跟栈一样,也是一种操作受限的线性表数据结构。先进先出
队列在尾部插入数据,在头部删除数据。
常用的几个特殊队列: “循环队列”, “阻塞队列”, “并发队列”;
与栈类似: 用数组实现
的队列叫做顺序队列
, 用链表实现
的队列叫做链式队列
;
相对于栈的一个栈顶指针
, 队列需要两个指针: 一个是head指针,指向队头(); 一个是tail指针,指向队尾。队头指向的位置有数据,队尾指向的位置没有数据!
- 基于数组的队列实现(JAVA),可参考
// 用数组实现的队列
public class ArrayQueue {
// 数组:items,数组大小:n
private String[] items;
private int n = 0;
// head表示队头下标,tail表示队尾下标
private int head = 0;
private int tail = 0;
// 申请一个大小为capacity的数组
public ArrayQueue(int capacity) {
items = new String[capacity];
n = capacity;
}
// 入队
public boolean enqueue(String item) {
// 如果tail == n 表示队列已经满了
if (tail == n) return false;
items[tail] = item;
++tail;
return true;
}
// 出队
public String dequeue() {
// 如果head == tail 表示队列为空
if (head == tail) return null;
// 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了
String ret = items[head];
++head;
return ret;
}
}
当不停的入队和出队时,head和tail会持续往后移动,当tail移动到最右边时,即使数组中还有空闲空间,也无法继续往队列中添加数据了。
所以需要改进入队操作: 当移动最右边时,对数据进行整体向左边搬移
// 入队操作,将item放入队尾
public boolean enqueue(String item) {
// tail == n表示队列末尾没有空间了
if (tail == n) {
// tail ==n && head==0,表示整个队列都占满了
if (head == 0) return false;
// 数据搬移
for (int i = head; i < tail; ++i) {
items[i-head] = items[i];
}
// 搬移完之后重新更新head和tail
tail -= head;
head = 0;
}
items[tail] = item;
++tail;
return true;
}
- 基于链表的队列实现
同样需要两个指针:head 指针和 tail 指针。它们分别指向链表的第一个结点和最后一个结点, 它的出队与入队比较简单:
入队: tail->next = new_node; tail = tail->next;
出队: head = head->next;
循环队列: (是否可以避免数组的数据搬移而改进的一种方法)
关键之处:
确定好队空和队满的条件:
队空: head == tail;
队满: (tail+1)%n = head;
循环队列的入队与出队
public class CircularQueue {
// 数组:items,数组大小:n
private String[] items;
private int n = 0;
// head表示队头下标,tail表示队尾下标
private int head = 0;
private int tail = 0;
// 申请一个大小为capacity的数组
public CircularQueue(int capacity) {
items = new String[capacity];
n = capacity;
}
// 入队
public boolean enqueue(String item) {
// 队列满了
if ((tail + 1) % n == head) return false;
items[tail] = item;
tail = (tail + 1) % n;
return true;
}
// 出队
public String dequeue() {
// 如果head == tail 表示队列为空
if (head == tail) return null;
String ret = items[head];
head = (head + 1) % n;
return ret;
}
}