本节将通过几个实例来深入理解链表,栈和队列的实际应用,包含以下内容:
package com.my.study.algorithm.stackAndQueue;
import java.util.EmptyStackException;
import java.util.Stack;
// This class demonstrates how to use stack to check if a string has matched brackets.
public class BracketMatcher {
public static void main(String[] args) {
String str = "()";
System.out.println(str + " : " + checkBracket(str));
str = "()[]";
System.out.println(str + " : " + checkBracket(str));
str = "a(b)c[d]e{f}g";
System.out.println(str + " : " + checkBracket(str));
str = "(2+1)[5-8]{7*9}({55+98})([abc]def)g({sf}{ass})";
System.out.println(str + " : " + checkBracket(str));
str = "(()[]{})";
System.out.println(str + " : " + checkBracket(str));
str = "{(){}}";
System.out.println(str + " : " + checkBracket(str));
str = "()[{}]";
System.out.println(str + " : " + checkBracket(str));
str = "([)]{}";
System.out.println(str + " : " + checkBracket(str));
str = "(()[]{}";
System.out.println(str + " : " + checkBracket(str));
str = "()[]{}}";
System.out.println(str + " : " + checkBracket(str));
str = "()[]]{}";
System.out.println(str + " : " + checkBracket(str));
str = "()]{}";
System.out.println(str + " : " + checkBracket(str));
}
/**
* Check if string has matched brackets
*
* @param str
* give string
* @return true: string value has matched brackets, false: not have
*/
public static boolean checkBracket(String str) {
if (str == null || str.isEmpty()) {
return false;
}
Stack stack = new Stack<>();
try {
for (char c : str.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
continue;
}
if (c == ')') {
if ('(' == stack.pop()) {
continue;
} else {
break;
}
}
if (c == ']') {
if ('[' == stack.pop()) {
continue;
} else {
break;
}
}
if (c == '}') {
if ('{' == stack.pop()) {
continue;
} else {
break;
}
}
}
} catch (EmptyStackException e) { // This exception will occur when stack is empty but stack.pop is invoked.
return false;
}
// Only when stack is empty, the given string has matched brackets.
if (stack.isEmpty()) {
return true;
}
return false;
}
}
输出:
() : true
()[] : true
a(b)c[d]e{f}g : true
(2+1)[5-8]{7*9}({55+98})([abc]def)g({sf}{ass}) : true
(()[]{}) : true
{(){}} : true
()[{}] : true
([)]{} : false
(()[]{} : false
()[]{}} : false
()[]]{} : false
()]{} : false
Josephus问题
Josephus问题是下面的游戏:N个人编号从1到N,围坐成一个圆圈。从1号开始传递一个热土豆,。经过M次传递后拿着热土豆的人被清除离座,围坐的圆圈紧缩,由坐在被清除的人后面的人拿起热土豆继续进行游戏。最后剩下的人取胜。因此,如果M=1和N=5,则游戏人依序被清除,5号游戏人获胜。如果M=2和N=5,那么被清除的人的顺序是2,4,1,5,3号人获胜。
该问题有多种解法,可通过循环链表和队列的数据结构来实现,这里用队列实现。
思路:先将N个人依次入队,再逐一出队和重新入队并开始计数,每次计数到M的时候那个人就被踢出,并重新开始计数和出队入队,直到队列中只有一个人,此人获胜。
该问题利用了队列的先进先出原理(FIFO),源码如下:
package com.my.study.algorithm.stackAndQueue;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* This class demonstrates how to use a queue to resolve Josephus cycle problem.
*
*/
public class JosephusCycle {
public static void main(String[] args) {
josephus(5, 2);
}
private static void josephus(int persons, int k) {
if (persons <= 0 || k <= 0) {
throw new IllegalArgumentException("Parameters is incorrect: " + persons + ", k:" + k);
}
Queue queue = new ConcurrentLinkedQueue<>();
// Initialize queue
for (int i = 0; i < persons; i++) {
queue.offer("Person_" + (i + 1));
}
while (!queue.isEmpty()) {
// Skip k -1 persons
for (int i = 0; i < k - 1; i++) {
queue.offer(queue.poll());
}
// Check the number k person
String name = queue.poll();
if (queue.isEmpty()) {
System.out.println("Winner: " + name);
} else {
System.out.println("Eliminate: " + name);
}
}
}
}
输出:
Eliminate: Person_2
Eliminate: Person_4
Eliminate: Person_1
Eliminate: Person_5
Winner: Person_3
检查链表是否包含环
该问题检查给定的链表是否包含环,循环链表的last节点的next指向head节点,其包含环,是一种情况,第二种情况是last节点的next指向head的next.next...next,即部分包含环。
思路:给定两个指针p1和p2,初始情况都指向head节点,依次往后移动,p1每次移动一个单位,p2每次移动两个单位,如果在某个时间点p2与p1又重新指向了某一个相同的节点,那么原链表存在环,如果任何时候(遍历完链表所有节点)p2与p1都无法重新指向同一个节点,那么原链表不存在环。因为p2的移动速度比p1快,一般情况下p2都在p1前面,二者不可能重新相遇,除非存在环。
该问题考查了循环链表的特性,源码如下:
package com.my.study.algorithm.stackAndQueue;
/**
* This class demonstrates how to check if a link contains a cycle.
*/
public class CheckLinkCycle {
public static void main(String[] args) {
MyLink
输出:
Link size: 100
Contains cycle? true
用两个栈实现队列
给定两个栈S1和S2,以及栈方法pop和push,要求实现一队列Queue的两个方法,offer,poll。
思路:栈的原理是FILO,队列的原理是FIFO,两个栈,一个用于入队,一个用于出队。当负责入队的那个栈满了,且出队的栈是空的,那么将入队的栈元素依次倒入出队的栈。如果出队的栈空了,且入队的栈不为空,那么将入队的栈元素依次倒入出队的栈。依次重复,即可用两个栈实现一个队列。注意,整个队列的容量(capacity)并不一定等于两个栈的容量和,因为可能出现入队的栈满了,但出队的栈不为空的情况。所以,队列的容量应该介于一个栈容量到两个栈容量之间。
该问题考查了栈和队列概念以及灵活运用,源码如下:
package com.my.study.algorithm.stackAndQueue;
import java.util.Stack;
/**
* This class demonstrates how to use two stacks to simulate a queue.
*/
public class TwoStacksQueue {
public static void main(String[] args) {
MyQueue queue = new MyQueue<>(3);
queue.offer("obj1");
queue.offer("obj2");
queue.offer("obj3");
queue.offer("obj4");
queue.offer("obj5");
queue.offer("obj6");
for (int i = 0; i < 6; i++) {
String obj = queue.poll();
System.out.println(obj);
}
}
static class MyQueue {
// Stack capacity
private int stackCapacity;
// s1 is used to offer elements
private Stack s1;
// s2 is used to poll elements
private Stack s2;
// Default stack size
private static final int DEFAULT_STACK_SIZE = 5;
public MyQueue() {
this(DEFAULT_STACK_SIZE);
}
public MyQueue(int stackCapacity) {
if (stackCapacity <= 0) {
stackCapacity = DEFAULT_STACK_SIZE;
}
this.stackCapacity = stackCapacity;
s1 = new Stack();
s2 = new Stack();
}
public E poll() {
if (s2.isEmpty()) {
if (!s1.isEmpty()) {
transfer(s1, s2);
} else {
throw new RuntimeException("Queue is empty, cannot poll element.");
}
}
return s2.pop();
}
public void offer(E e) {
if (s1.size() >= stackCapacity) {
if (s2.isEmpty()) {
transfer(s1, s2);
} else {
throw new RuntimeException("Queue is full, cannot offer element.");
}
}
s1.push(e);
}
public int size() {
return s1.size() + s2.size();
}
// Transfer all elements in stack "from" to stack "to"
private void transfer(Stack from, Stack to) {
to.clear();
while (!from.isEmpty()) {
to.push(from.pop());
}
from.clear();
}
}
}
输出:
obj1
obj2
obj3
obj4
obj5
obj6
自定义阻塞式链表队列
要求实现阻塞式链表队列,类似于JDK自带的类: LinkedBlockingQueue。
思路:阻塞式队列是生产者消费者模型的一个典型应用,一个或多个生产者负责生产产品,并将产品放入队列,一个或多个消费者负责消费产品,从队列中取出产品。如果队列满了,所有的生产者都将被阻塞,直到某个消费者消费了一个产品。如果队列为空,所有的消费者都将被阻塞,直到某个生产者生产了一个产品。为了防止并发问题,需要对临界资源(队列)加锁,每个生产者和消费者之间都互斥,为了实现阻塞,需要对生产者和消费者做同步处理。
该问题考查了并发环境下的同步与互斥,以及链表和队列的灵活运用,源码如下:
package com.my.study.algorithm.stackAndQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* This class demonstrates the mechanism of LinkedBlockingQueue.
*
*/
public class TestLinkedBlockingQueue {
public static void main(String[] args) {
MyLinkedBlockingQueue queue = new MyLinkedBlockingQueue();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
String val = "str" + i;
queue.offer(val);
System.out.println("Offer:" + val);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
String str = queue.poll();
System.out.println("Poll:" + str);
}
}).start();
}
private static class MyLinkedBlockingQueue {
// Queue head and last reference
private Node head;
private Node last;
private int capacity;
private int size;
// ReentrantLock, used to synchronize offer and poll threads
private ReentrantLock lock = new ReentrantLock();
// Condition notFull, means this queue should not be full, if queue is full,
// then cannot offer element to queue,
// notFull will be await, until a thread which has condition's lock permissions
// invokes signalAll or signal method.
private Condition notFull = lock.newCondition();
// The similar to notFull
private Condition notEmpty = lock.newCondition();
private static final int DEFAULT_CAPACITY = 10;
public MyLinkedBlockingQueue() {
this(DEFAULT_CAPACITY);
}
public MyLinkedBlockingQueue(int capacity) {
if (capacity <= 0) {
capacity = DEFAULT_CAPACITY;
}
this.capacity = capacity;
}
/**
* Insert element to queue, if queue if full, thread will be blocked until a
* consumer thread invokes poll method.
*
* @param e
* element
*/
public void offer(E element) {
// Try to get lock permission
lock.lock();
try {
// Generally we need to use "while" instead of "if" here, but "if" is OK and
// better than "while" here because we have two conditions to control consumer
// and producer threads.
if (size >= capacity) {
try {
notFull.await();
} catch (InterruptedException e) {
throw new RuntimeException(e.getMessage());
}
}
// Initialize the first node, head and last should be null
if (head == null) {
head = new Node();
last = head;
} else {
last.next = new Node();
last = last.next;
}
last.element = element;
size++;
// Notify consumers
notEmpty.signalAll();
} finally {
// Need to unlock in finally block to make sure unlock successfully.
lock.unlock();
}
}
/**
* Retrieves and removes the head of this queue, if queue is empty, thread will
* be blocked until a producer thread to invoke method offer.
*
* @return element
*/
public E poll() {
lock.lock();
try {
if (size <= 0) {
try {
notEmpty.await();
} catch (InterruptedException e) {
throw new RuntimeException(e.getMessage());
}
}
E element = head.element;
head = head.next;
size--;
// If there are only one element before polling, need to set last reference to
// null.
if (size == 0) {
last = null;
}
// Notify producers
notFull.signalAll();
return element;
} finally {
lock.unlock();
}
}
/**
* Get queue size.
*
* @return queue size
*/
public int getSize() {
return size;
}
private static class Node {
private E element;
private Node next;
}
}
}
输出:
Poll:str0
Offer:str0
Offer:str1
Offer:str2
Offer:str3
Offer:str4
Offer:str5
Offer:str6
Poll:str1
Offer:str7
Poll:str2
Offer:str8
Poll:str3
Offer:str9
Poll:str4
Poll:str5
Poll:str6
Poll:str7
Poll:str8
Poll:str9
Poll:str10
Offer:str10
Offer:str11
Offer:str12
Offer:str13
Offer:str14
Offer:str15
Offer:str16
Offer:str17
Offer:str18
Offer:str19
Poll:str11
Poll:str12
Poll:str13
Poll:str14
Poll:str15
Poll:str16
Poll:str17
Poll:str18
Poll:str19