如题,还有为啥是栈和堆,而不是队列或者其他什么数据结构?这就是本文要说的。
一、栈与堆简介
在回答上述问题前,先要明确什么是“栈”,什么又是“堆”。所谓栈就是一种一维的“线性表”,而“堆”则是二维的完全二叉树。不难看出栈相比于堆是一种更简单的数据结构,这也是其被用于内存模型的原因之一,够简单,所以其创建成本就很低,进而内存的使用性能就可以提升上去。而堆作为一种完全二叉树,同时具有“顺序结构”的特点,这就让其在处理大量数据的排序时拥有更好的性能。
图片
二、栈相比队列的优势
前面说栈结构简单,难免不让人想到队列,相比较起来,队列结构似乎更简单一点,就算没有更简单也绝不比栈复杂,那凭什么不用队列而用栈呢?
队列无法取代栈用作内存模型就是因为它太简单了,“先进先出”的数据结构,就只能先进先出。而栈虽然常被描述为“先进后出”型数据结构,但实际却并非看上去那么简单,而是可以让多个元素在入栈顺序相同的情况下,根据需求产生各种不同的出栈顺序。
图片
比如有a、b、c三个元素依次进栈,问出栈顺序是怎样的?依据先进后出的原则,很多人应该马上会想到c、b、a的出栈顺序,其实还可以仍旧是a、b、c的顺序出栈,和入栈顺序是一样的。这是不是违背了“先进后出”原则呢?其实没有。
认为a、b、c的进栈顺序和出栈顺序一样是违背了“先进后出”原则是因为把三个元素捆绑在一起去看了,但是分别去看就会发现并没有违背先进后出。我们可以让a进栈之后立刻出栈,然后b进栈再出栈,最后让c进栈然后出栈,基于此种操作,你就会发现a、b、c的进栈顺序和出栈顺序一样,同时每个元素单独来看仍旧是“先进后出”的。
想得简单一点,栈就是一个口袋,往里面放东西和取东西的顺序自由度是很高的,这就让栈在拥有与队列差不多的简单结构易于被创建的同时,拥有更强大的可操作性。而队列则像是一个垂直悬空放置且没有封堵出口的管道,第一个进去的必然第一个出来,没有其他的可能性,这种过于简单的结构缺乏可操作性,所以落选内存模型的数据结构设计。
三、堆的优势
如果只是管理非常小的内存,栈已经是个足够好的数据结构了,这里说的“小”指的是1M到2M这种规模,而这种规模显然与实际不符,实际的主流内存已经达到8G以上了,那剩下的那些内存如何管理的呢?它们则主要依靠堆结构管理(是主要不是全部)。前面说了堆是一种完全二叉树,也就是除了最后一层外其他层全是满结点的二叉树,而且更重要的是它还是“顺序结构”的。
说堆是一种“顺序结构”的完全二叉树,意思就是说,假如用“中序遍历”对其进行从根到叶子的遍历,将会得到“整体有序”的队列。再说得通俗一点就是,我们知道一棵大的二叉树往往是由若干小的二叉树组合而成的,然后在这样一种结构中,我们会看到里面所有的父结点的值一定都是大于其左、右子结点的,这被称为大根堆。还有一种情况是所有父结点都是小于其左、右子结点的,这被称为小根堆。
图片
这时我们会看到堆不光因为其完全二叉树的结构像一个金字塔,其每层的值也是如金字塔一样,从底部开始一层比一层大,直到根节点是最大值,即大根堆。或者从底层开始一层比一层小,直到根节点是最小值,即小根堆。
因为堆的这种顺序结构,导致其在处理庞大的数据排序时具有更好的性能,“堆排序”本身就是一种典型的处理大量数据的选择排序算法。而高效的排序则意味着可以更快地基于有序队列进行高效的查找,进而从整体上提升查找的效率。这就弥补了栈结构在大规模使用,管理过大内存后的性能短板。
四、栈与堆的取长补短
但是堆作为一种相对复杂的数据结构,其创建过程明显比栈复杂,这导致它在处理少量的简单类型数据时,其性能反而不如栈,这里的短板则由栈来弥补。于是内存模型就同时设计了栈与堆,二者配合使用取长补短。