Java集合类(链表,栈,队列实战)

Java集合类(链表,栈,队列实战)


本节将通过几个实例来深入理解链表,栈和队列的实际应用,包含以下内容:

  • 括号匹配问题

  • Josephus问题

  • 检查链表是否包含环

  • 用两个栈实现队列

  • 自定义阻塞式链表队列

  1. 括号匹配问题
    括号匹配问题是指对于给定的一个字符串,检查里面的括号是否成对出现即是否匹配,成对的括号包括:(),【】,{}。
    例如:字符串“()【】{}”里的括号是成对的,“a((【{c}】))b”也是成对的,但“(【)】”不是成对的。
    要求写一程序,输入为字符串,输出为true或者false,表示括号匹配或者不匹配。
    思路:对给定的字符串逐一检查,如果出现(,【,{,则将该字符压入一栈。如果出现)则从栈中弹出一字符,如果是(,则继续,否则返回false。同理,若出现}则检查弹出的字符是否是{,若出现】则检查弹出的字符是否是【。若不是任何的括号字符,则continue。遍历完所有的字符后,如果栈为空,那么原字符串括号匹配,否则不匹配。
    该问题充分利用了栈的先进后出原理(FILO),源码如下,
    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<Character> 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
    
  2. 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<String> 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
    
  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<Object> objs = new MyLink<>();
    		System.out.println("Link size: " + objs.getSize());
    		System.out.println("Contains cycle? " + objs.isContainsCycle());
    	}
    
    	private static class MyLink<E> {
    		// Link size
    		private int size;
    		// Root node
    		private Node<E> root;
    		// Last node
    		private Node<E> last;
    		// Default link size
    		private static final int DEFAULT_LINK_SIZE = 100;
    
    		public MyLink() {
    			this(DEFAULT_LINK_SIZE);
    		}
    
    		public MyLink(int size) {
    			if (size <= 0) {
    				size = DEFAULT_LINK_SIZE;
    			}
    			this.size = size;
    			// Build link
    			root = new Node<>();
    			Node<E> current = root;
    			for (int i = 0; i < size - 1; i++) {
    				current.next = new Node<>();
    				current = current.next;
    			}
    			last = current;
    
    			// Case 1: last node's next points to root
    			last.next = root;
    
    			// Case 2: last node's next points to a middle node
    			// last.next = root.next.next.next;
    		}
    
    		// Check if link contains a cycle, for example:
    		//
    		//       * * *
    		//      *     *
    		//     *      *
    		//    * * * * 
    		//   *
    		//  *
    		// *
    		//
    		public boolean isContainsCycle() {
    			Node<E> p1 = root;
    			Node<E> p2 = root;
    			for (int i = 0; i < size; i++) {
    				if (p1.next == null || p2.next == null) {
    					return false;
    				}
    				// p1 goes one step, but p2 goes two steps
    				p1 = p1.next;
    				p2 = p2.next;
    				if (p2.next == null) {
    					return false;
    				}
    				p2 = p2.next;
    				// Cycle exists when p2 meets p1
    				if (p1 == p2) {
    					return true;
    				}
    			}
    			return false;
    		}
    
    		public int getSize() {
    			return size;
    		}
    
    		private static class Node<E> {
    			private E e;
    			private Node<E> next;
    		}
    	}
    }
    

    输出:

    Link size: 100
    Contains cycle? true
    
  4. 用两个栈实现队列
    给定两个栈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<String> 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<E> {
    		// Stack capacity
    		private int stackCapacity;
    		// s1 is used to offer elements
    		private Stack<E> s1;
    		// s2 is used to poll elements
    		private Stack<E> 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<E>();
    			s2 = new Stack<E>();
    		}
    
    		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<E> from, Stack<E> to) {
    			to.clear();
    			while (!from.isEmpty()) {
    				to.push(from.pop());
    			}
    			from.clear();
    		}
    	}
    }
    

    输出:

    obj1
    obj2
    obj3
    obj4
    obj5
    obj6
    
  5. 自定义阻塞式链表队列
    要求实现阻塞式链表队列,类似于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<String> queue = new MyLinkedBlockingQueue<String>();
    		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<E> {
    		// Queue head and last reference
    		private Node<E> head;
    		private Node<E> 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<E>();
    					last = head;
    				} else {
    					last.next = new Node<E>();
    					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<E> {
    			private E element;
    			private Node<E> 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
    

猜你喜欢

转载自blog.csdn.net/funnyrand/article/details/81453256