栈和队列是计算机中基本的两个数据结构,栈可以达到后进先出,队列可以先进先出。在实际应用上,我们可以使用栈进行逆序遍历链表,非递归中序遍历二叉树,括号匹配,函数调用等等;可以使用队列对二叉树进行层次遍历,打印机的打印服务,通信中的消息队列等等。
下面贴几道关于栈和队列较常考的笔试/面试题。
链表逆序遍历:思想很简单,当链表节点不为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了。