数据结构与算法面试之——一文搞定栈结构 1.什么是栈? 实际上我们每天在使用浏览器的时候的前进和后退能力本身就是依赖于栈结构实现,那么什么是栈呢?后进者先出,先进者后出,这就是典型的“栈”结构。从栈的操作特性来看,是一种“操作受限”的线性表,只允许在端插入和删除数据。 事实上,从功能上来说,数组或链表确实可以替代栈,;但是特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出,先进者后出的操作特性时,我们应该首选栈这种数据结构,即:特定的数据结构是对特定场景的抽象。 举例说明:栈这个数据结构是对计算机组成原理里面函数调用这个特定场景的抽象,当发生函数调用时,call指令的下一条指令的地址入栈,当函数调用完成后,出栈,把call指令的下一条指令的地址取出存入PC寄存器,根据PC寄存器存入的指令地址,找到该条指令,存入指令寄存器,共CPU解析执行。 又比如:浏览器的前进和后退能力;以及树的广度遍历迭代时,如果采用的是非递归的算法,也是需要使用栈的能力。 2 如何实现栈 正如前文所说,实际上真正底层的数据结构只有两种:数组和链表,因此,栈本身也是通过数组和链表实现,只不过,使用数组实现的栈称为顺序栈,使用链表实现的栈称为链式栈。 在使用数组实现栈的时候,控制只在数组的尾部进行插入删除等操作,来避免数据的迁移带来的复杂度递增,但是这里也需要注意,由于数组的大小是固定的,因此,在实际使用中需要注意数组的扩容;在使用链表实现链式栈的时候,控制之在链表的头部进行插入删除操作,避免遍历带来的复杂度增加。 2.1 指定数组大小的栈的实现 既然是指定数组大小,那么只要是底层维护一个指定长度的数组即可,不需要考虑扩容的问题。 从上面的代码也能够快速看出,实际上在入栈和出栈过程中,只需要一两个临时的变量存储空间即可,而且只涉及到个别操作的操作,因此时间复杂度和空间复杂度均为O(1);再次强调下,空间复杂度指的是:除了原本的数据存储空间外,算法运行还需要额外的存储空间。 2.2 支持动态扩容的顺序栈 既然是顺序栈还要支持动态扩容,所以简单的实现逻辑就是:如果当数组空间不足时,我们重新申请一块更大的内存,将原有数组的数据进行拷贝迁移。 对于出栈操作来说,我们不会涉及内存的重新申请和数据的搬移,所以出栈的时间复杂度仍然是 O(1)。但是,对于入栈操作来说,情况就不一样了。当栈中有空闲空间时,入栈操作的时间复杂度为 O(1)。但当空间不够时,就需要重新申请内存和数据搬移,所以最坏时间复杂度就变成了 O(n)。 但是对于均摊复杂度的分析就有些复杂,分析如下:如果当前栈大小为 K,并且已满,当再有新的数据要入栈时,就需要重新申请 2 倍大小的内存,并且做 K 个数据的搬移操作,然后再入栈。但是,接下来的 K-1 次入栈操作,我们都不需要再重新申请内存和搬移数据,所以这 K-1 次入栈操作都只需要一个 simple-push 操作就可以完成。为了让你更加直观地理解这个过程,我画了一张图。 


2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/25756.html