手把手教你实现栈以及C#中Stack源码分析
定义
栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作。
它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈),这个比喻并非十分恰当,比如拿盘子的时候只是习惯从上面开始拿,也可以从中间拿,而栈的话是只能操作最上面的元素,这样比喻只是为了便于了解。

刚开始接触栈可能会有些疑问,我们已经有数组和链表了,为什么还要栈这个操作受限制的数据结构呢?数组和链表虽然灵活,但是操作起来也更容易出错,而栈因为操作受限,在特定场景中使用还是有优势的。
当某个数据集合只涉及在一端插入和删除数据,并且满足先进后出的特性时,我们就应该首选“栈”这种数据结构。
栈的实现
栈的实现方式有两种,一种是基于数组实现的顺序栈,另一种是基于链表实现的链式栈。它的主要操作也就两个,即入栈和出栈,难度并不大。
先了解一下入栈(Push)和出栈(Pop),如下图


顺序栈
基于数组实现,就面临着数组大小固定、扩容成本大的问题,下面是使用C#实现出栈和入栈简单功能代码。
// 基于数组实现的顺序栈
public class ArrayStack
{
private string[] items; // 数组
private int count; // 栈中元素个数
private int n; //栈的大小
// 初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n)
{
this.items = new string[n];
this.n = n;
this.count = 0;
}
// 入栈操作
public bool Push(string item)
{
// 数组空间不够了,直接返回false,入栈失败。
if (count == n) return false;
// 将item放到下标为count的位置,并且count加一
items[count] = item;
++count;
return true;
}
// 出栈操作
public string Pop()
{
// 栈为空,则直接返回null
if (count == 0) return null;
// 返回下标为count-1的数组元素,并且栈中元素个数count减一
string tmp = items[count - 1];
--count;
return tmp;
}
}
上面代码有一些很明显的缺点,比如存储的数据类型固定为string(C#中使用泛型可以很好的解决),大小固定...这只是简单的功能演示,后面分析C#中Stack源码时这些问题都会被化解。
出栈和入栈的时间复杂度是多少呢?这个很好计算,因为出栈和入栈都只涉及栈顶的元素,所以是O(1)。
空间复杂度呢?还是O(1),因为这里只额外使用了count和n两个临时变量。
♂ 空间复杂度是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。例子中大小为n的数组是无法省略的,也就是说这n个空间是必须的,对复杂度不了解的可以点击查看一文搞定算法复杂度分析。
链式栈
话不多说,上代码
// 链表实现栈
public class LinkStack<T>
{
//栈顶指示器
public Node<T> Top { get; set; }
//栈中结点的个数
public int NCount { get; set; }
//初始化
public LinkStack()
{
Top = null;
NCount = 0;
}
//获取栈的长度
public int GetLength()
{
return NCount;
}
//判断栈是否为空
public bool IsEmpty()
{
if ((Top == null) && (0 == NCount))
{
return true;
}
return false;
}
//入栈
public void Push(T item)
{
Node<T> p = new Node<T>(item);
if (Top == null)
{
Top = p;
}
else
{
p.Next = Top;
Top = p;
}
NCount++;
}
//出栈
public T Pop()
{
if (IsEmpty())
{
return default(T);
}
Node<T> p = Top;
Top = Top.Next;
--NCount;
return p.Data;
}
}
//结点定义
public class Node<T>
{
public T Data;
public Node<T> Next;
public Node(T item)
{
Data = item;
}
}
时间复杂度和空间复杂度均为O(1).
C#中Stack源码分析
前面我们已经知道了顺序栈和链式栈的优缺点,那么C#语言中自带的Stack是基于什么实现的呢?
答案是顺序栈。Stack是一个泛型类,里面定义了一个泛型数组用以存储数据
private T[] _array;
既然是一个顺序栈,为什么在使用的过程中什么不需要初始化数组大小,也不用担心扩容问题呢?
当我们实例化Stack的时候,会调用它的构造函数,初始化数组大小为0.
public Stack()
{
_array = _emptyArray;
_size = 0;
_version = 0;
}
向数组中添加元素时,会检测数组是否还有空闲容量,如果超出数组大小,将进行扩容
public void Push(T item)
{
if (_size == _array.Length)
{
T[] array = new T[(_array.Length == 0) ? 4 : (2 * _array.Length)];
Array.Copy(_array, 0, array, 0, _size);
_array = array;
}
_array[_size++] = item;
_version++;
}
正是因为C#帮我们封装好了,所以我们使用起来才感觉如此的方便。
Push()函数的时间复杂度是多少呢?当栈中有空闲空间时,可以直接添加,它的时间复杂度是O(1)。但当内存不够需要扩容时,需要重新申请内存,进行数据搬移,所以时间复杂度就变成了O(n),其平均时间复杂度也为O(1).
总结

手把手教你实现栈以及C#中Stack源码分析的更多相关文章
- 【原】Spark中Client源码分析(二)
继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...
- 【原】Spark中Master源码分析(二)
继续上一篇的内容.上一篇的内容为: Spark中Master源码分析(一) http://www.cnblogs.com/yourarebest/p/5312965.html 4.receive方法, ...
- 【原】 Spark中Worker源码分析(二)
继续前一篇的内容.前一篇内容为: Spark中Worker源码分析(一)http://www.cnblogs.com/yourarebest/p/5300202.html 4.receive方法, r ...
- php中foreach源码分析(编译原理)
php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...
- Spark中决策树源码分析
1.Example 使用Spark MLlib中决策树分类器API,训练出一个决策树模型,使用Python开发. """ Decision Tree Classifica ...
- Java中ArrayList源码分析
一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...
- Java中HashMap源码分析
一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...
- 深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)
events模块对外提供了一个 EventEmitter 对象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模块events中的类,用于对NodeJS中 ...
- Netty中FastThreadLocal源码分析
Netty中使用FastThreadLocal替代JDK中的ThreadLocal[JAVA]ThreadLocal源码分析,其用法和ThreadLocal 一样,只不过从名字FastThreadLo ...
随机推荐
- Python--数据存储
新建测试文档测试.txt内容如下: 张三:今天天气真好. 李四:是的,真的太好了. 张三:阳光明媚. 李四:鸟语花香. 王五:装逼现场:请带好安全帽 张三:难得好天气,今天就不偷懒了. 李四:能把乞讨 ...
- Shell-02-数据类型
shell数据类型 shell常用的数据类型有 字符串.整数型.数组 字符串 字符串是shell编程中最常用最有用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号 建议使用双引号,因为双 ...
- Golang语言系列-13-常用内置包
常用内置包 net/http包 http请求和响应 http服务端 main.go文件 package main import ( "fmt" "io/ioutil&qu ...
- msfvenom简介
写此文是因为网上资料杂乱,不方便查阅,辣眼睛 测试免杀的时候刚好用到这个功能,顺便写一下(0202年靠msfvenom生成的纯原生payload可以宣告死亡了,如果有查不出来的杀软可以退群了,这也叫杀 ...
- nvcatmysql安装注册流程以及远程登陆配置步骤
前言:网络上下载工具良莠不齐,找到合适的比较困难.因为nvcat回收了网络上的大部分注册码,这个nvcatformysql下载到可以破解的费了点时间,最后经过配置成功远程登陆到mysql,在此记录一下 ...
- Pikachu-CSRF模块
一.概述 Cross-site request forgery 简称为"CSRF",在CSRF的攻击场景中攻击者会伪造一个请求(这个请求一般是一个链接),然后欺骗目标用户进行点击, ...
- NOIP 模拟 10 考试总结
T1 一道很妙的题,打暴力分也很多,但是考试的时候忘开 long long 了. 题解 T2 一道挺水的题,不过...(打挂了) 题解 T3 此题甚妙,转化真多,不过对思维是一个非常大的扩展 题解 考 ...
- reduce使用技巧
一.使用reduce同时执行map(循环)和filter(过滤) 例如,将数组中的项的值加倍,然后只选择那些大于50的项 const numbers = [10, 20, 30, 40]; const ...
- Json 文件 : 出现 Expected value at 1:0 问题的解决
只要找一个json在线解析,验证你的json文件格式的正确性,错误可以忽略. 如要消除红叉,关闭Json Validation即可,如下操作:
- mysql基础操作(四):简单多表查询
-- 创建部门 (主表) create table dept( id int primary key auto_increment, name varchar(20) ); -- 创建员工 (副表) ...