棧和隊列是計算機中基本的兩個數據結構,棧可以達到后進先出,隊列可以先進先出。在實際應用上,我們可以使用棧進行逆序遍歷鏈表,非遞歸中序遍歷二叉樹,括號匹配,函數調用等等;可以使用隊列對二叉樹進行層次遍歷,打印機的打印服務,通信中的消息隊列等等。
下面貼幾道關於棧和隊列較常考的筆試/面試題。
鏈表逆序遍歷:思想很簡單,當鏈表節點不為null就進行壓棧,直到為null。
class Node2{ int value; Node2 next; } public void reservePrintList(Node2 root) { if (root == null) { return; } Stack<Node2> stack = new Stack<>(); while (root !=null) { stack.push(root); root = root.next; } while (!stack.isEmpty()) { System.out.println(stack.pop().value); } }
非遞歸中序遍歷二叉樹:中序遍歷二叉樹第一個被遍歷的節點肯定位於二叉樹的最左邊,在未到最左邊之前一直壓棧,到了就出棧。
class Node{ int value; Node left; Node right; } /** * 非遞歸中序遍歷二叉樹,利用棧可以很方便的實現 * @param root */ public void printTree1(Node root) { if (root == null) { return; } Stack<Node> stack = new Stack<>(); while (root != null) { stack.push(root); // 根節點壓棧 if (root.left != null) { // 左子樹不為空,將左子樹壓入棧 stack.push(root.left); }else { // root的左子樹為空,說明root要么是葉子,要么是還有右子樹,不管是葉子還是有右子樹的節點,它都將進行輸出 // 因為中序遍歷的的順序是從左到右 Node node = stack.pop(); System.out.println(node.value); if (node.right != null) { // 存在右子樹,將右子節點壓棧,因為不清楚右子節點是否為葉子,所以是壓棧進行下一輪循環進行處理 stack.push(node.right); } root = stack.peek(); // 取棧頂元素,但不移除,因為不清楚目前棧頂節點是否為葉子 } } }
二叉樹層次遍歷:層次遍歷值對二叉樹從上到下進行一層層的遍歷,每層的遍歷順序為從左到右,借助隊列可以很容易實現
class Node{ int value; Node left; Node right; } /** * 層次遍歷二叉樹(從上到下),借助隊列這一數據結構可以很好的實現 * @param root */ public void printTree2(Node root) { if (root == null) { return; } Queue<Node> queue = new LinkedList<>(); queue.add(root); // 將根節點加入隊列 while (root != null) { Node node = queue.poll(); // 取出隊頭root if (node.left != null) { // 左子節點不為空,入隊 queue.offer(root.left); } if (node.right != null) { // 右子節點不為空 ,入隊 queue.offer(root.right); } System.out.println(node.value); // 打印隊頭元素 root = queue.peek(); // 獲取新的隊頭元素,但不移除,更新為root } }
看師弟師妹們數據機構第三章的總結,都是自行實現棧和隊列這兩個數據結構,我上面貼出的代碼沒有進行棧或隊列滿的判斷,這是因為我偷懶直接使用java類庫,java類庫里的棧或隊列都是在超過初始容量后自動擴容的(最大容量好像int類型最大的整數)。C++也是直接把棧和隊列封裝好可以直接使用,但是在學習的時候建議還是先理解底層原理,自己實現,等底層原理熟練就直接用封裝好的類,還有就是建議師弟師妹們要多去敲代碼,不能說基本理論原理了解了,就不去使用代碼進行實現,理論和代碼實現還是有一定差別的,在代碼實現過程中,可能會出現一些bug,不要怕bug,雖然bug的確很煩,一個引用出錯就找了幾小時(我的慘痛經歷),但是解決bug后下次我們就可以更好的避免同類型的bug了。