鹤发童心 發表於 2021-9-21 11:11:00

C#中List是链表吗?为什么可以通过下标访问

<p class="md-end-block md-heading"><span class="md-tab"><span class="md-plain">使用C#的同学对List应该并不陌生,我们不需要初始化它的大小,并且可以方便的使用Add和Remove方法执行添加和删除操作,但却可以使用下标来访问它的数据,它是我们常说的链表吗?</span></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded"><span> &nbsp; &nbsp; <span class="cm-variable">List<span class="cm-operator">&lt;<span class="cm-variable-3">int<span class="cm-operator">&gt; <span class="cm-variable">ls <span class="cm-operator">= <span class="cm-keyword">new <span class="cm-variable">List<span class="cm-operator">&lt;<span class="cm-variable-3">int<span class="cm-operator">&gt;();<br><span> &nbsp; &nbsp; <span class="cm-variable">ls.<span class="cm-variable">Add(<span class="cm-number">1);<br><span> &nbsp; &nbsp; <span class="cm-variable">Console.<span class="cm-variable">WriteLine(<span class="cm-variable">ls[<span class="cm-number">0]);<span class="cm-tab"><span class="cm-tab">    <span class="cm-comment">//输出 1 </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">先简单回顾一下链表的概念。</span></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">什么是链表</span></h2>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-pair-s"><strong>链表是一种线性表数据结构,在内存中它可以是非连续的,通过在每个结点中使用指针存储下一个结点的地址来实现逻辑顺序</strong><span class="md-plain">。一个结点由两部分组成:一个是存储数据元素的<span class="md-pair-s "><strong>数据域</strong><span class="md-plain">,另一个是存储下一个结点地址的<span class="md-pair-s "><strong>指针域</strong><span class="md-plain">。</span></span></span></span></span></span></span></p>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">链表由很多种类,常见的有:单链表、双链表和循环链表,它们实现的原理差别不大,相对于单链表只是多添加了一些特定的功能,所以今天主要讲解最简单、最常用的单链表。</span></span></p>
<h3 class="md-end-block md-heading"><span class="md-plain">单链表添加、删除结点</span></h3>
<p class="md-end-block md-p md-focus"><span class="md-tab"> <span class="md-plain md-expand">由于链表是通过指针来指向下一个结点,所以添加和删除操作需要改变指针的指向即可。并且它们的<span class="md-pair-s"><strong>时间复杂度都是O(1)</strong><span class="md-plain">.</span></span></span></span></p>
<p><img src="https://img2020.cnblogs.com/blog/1544400/202109/1544400-20210921110700155-966075839.jpg"></p>
<p id="1632193619717">&nbsp;</p>
<h3 class="md-end-block md-heading"><span class="md-plain">单链表查找指定结点</span></h3>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">数组可以通过下标和寻址公式使用O(1)的时间复杂度来访问指定结点,但是由于链表结点在内存中可以是非连续的,无法通过寻址公式计算对应的内存地址,所以要查找一个结点就只能依次遍历,<span class="md-pair-s"><strong>时间复杂度为O(n)</strong><span class="md-plain">.</span></span></span></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">C#中的List</span></h2>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">既然链表不能通过下标访问,那上面例子中ls为什么会输出1呢?</span></span></p>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">查看源码,首先从它的Add方法开始,在vs中点击f12进入,发现跳转到<span class="md-pair-s"><code>List</code><span class="md-plain">类内部的<span class="md-pair-s"><code>SynchronizedList</code><span class="md-plain">类中,Add函数定义如下</span></span></span></span></span></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded"><span> &nbsp; &nbsp;<span class="cm-keyword">public <span class="cm-keyword">void <span class="cm-variable">Add(<span class="cm-variable">T <span class="cm-variable">item)<br><span> &nbsp;{<br><span> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">lock (<span class="cm-variable">_root)<br><span> &nbsp; &nbsp; &nbsp;{<br><span> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">_list.<span class="cm-variable">Add(<span class="cm-variable">item);<br><span> &nbsp; &nbsp; &nbsp;}<br><span> &nbsp;}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">目前还没有看出什么问题,继续查看 <span class="md-pair-s"><code>_list.Add</code><span class="md-plain">方法</span></span></span></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded"><span><span class="cm-tab">    <span class="cm-keyword">public <span class="cm-keyword">void <span class="cm-variable">Add(<span class="cm-variable">T <span class="cm-variable">item)<br><span> &nbsp;{<br><span> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if (<span class="cm-variable">_size <span class="cm-operator">== <span class="cm-variable">_items.<span class="cm-variable">Length)<br><span> &nbsp; &nbsp; &nbsp;{<br><span> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">//确保不超出容量,否则会执行扩容操作<br><span> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">EnsureCapacity(<span class="cm-variable">_size <span class="cm-operator">+ <span class="cm-number">1);<br><span> &nbsp; &nbsp; &nbsp;}<br><span><span>​<br><span> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">_items[<span class="cm-variable">_size<span class="cm-operator">++] <span class="cm-operator">= <span class="cm-variable">item;<br><span> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">_version<span class="cm-operator">++;<br><span> &nbsp;}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-pair-s"><code>_items = item</code><span class="md-plain">这句看起来是不是很眼熟,不就是向数组中添加一个元素嘛。为了严谨,我们再查看_items是什么</span></span></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded"><span><span class="cm-keyword">    private <span class="cm-keyword">const <span class="cm-variable-3">int <span class="cm-variable">_defaultCapacity <span class="cm-operator">= <span class="cm-number">4;<br><span><span class="cm-keyword">    private <span class="cm-variable">T[] <span class="cm-variable">_items;<br><span><span class="cm-keyword">    private <span class="cm-variable-3">int <span class="cm-variable">_size;<br><span><span class="cm-keyword">    private <span class="cm-variable-3">int <span class="cm-variable">_version;</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">果不其然,<span class="md-pair-s"><code>_items</code><span class="md-plain">就是一个泛型数组,并且还有<span class="md-pair-s"><code>_size</code><span class="md-plain">、<span class="md-pair-s"><code>_version</code><span class="md-plain">等其他字段</span></span></span></span></span></span></span></span></p>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">原来<span class="md-pair-s"><strong>List其实并不是链表,在内存中它也是使用数组来进行存储的</strong><span class="md-plain">,只是对添加、删除等操作进行了封装,并且使其能够“自动扩容”。</span></span></span></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">LinkedList链表</span></h2>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">这才是C#中真正的链表,不过它不是最简单的单链表,而是一个双链表。单链表只有一个指针结点指向它的下一个元素,而双链表中每个结点有两个指针结点,一个指向它的下一个元素,另一个指向它的上一个元素。</span></span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/1544400/202109/1544400-20210921110714414-977447448.jpg"></p>
<p id="1632193633954">&nbsp;</p>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">下面是LinkedListNode的部分源码,可以看到它包含一个next指针和一个prev指针。</span></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded"><span><span class="cm-tab">    <span class="cm-keyword">internal <span class="cm-variable">LinkedList<span class="cm-operator">&lt;<span class="cm-variable">T<span class="cm-operator">&gt; <span class="cm-variable">list;<br><span><span class="cm-tab">    <span class="cm-keyword">internal <span class="cm-variable">LinkedListNode<span class="cm-operator">&lt;<span class="cm-variable">T<span class="cm-operator">&gt; <span class="cm-variable">next;<br><span><span class="cm-tab">    <span class="cm-keyword">internal <span class="cm-variable">LinkedListNode<span class="cm-operator">&lt;<span class="cm-variable">T<span class="cm-operator">&gt; <span class="cm-variable">prev;</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">具体怎么使用,这里就不具体讲解了,看官方文档也比较容易理解。</span></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">链表和数组的区别</span></h2>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">ok,现在我们对数组和链表都有了一定程度上的了解,那能不能归纳出它们之间有哪些区别呢?对数组有不清楚的地方,可以查看文章<span class="md-meta-i-cmd-link"><span class="md-plain">为什么数组从0开始编号</span><span class="md-plain">。</span></span></span></span></p>
<ol class="ol-list" start="">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">从逻辑结构上看,它们都是属于线性表这个数据结构</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s"><strong>从内存上来看,数组是顺序存储,占用一块连续的内存,大小固定,扩容成本大;链表是链式存储,也就是非连续的,并且因为它多了一个指针,所以不存在大小限制,天然支持动态扩容,但占用的内存也更大。</strong></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">访问操作:数组可以支持“随机访问”,O(1)的时间复杂度;链表需要按顺序逐个访问,O(n)的时间复杂度.</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">添加和删除操作:数组的时间复杂度为O(n); 链表的时间复杂度为O(1)</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">操作系统内存管理方面,借助CPU的缓存机制,可以预读数组的内容,所以访问效率更高,而链表则不行。 </span></p>
</li>
</ol>
<h2 class="md-end-block md-heading"><span class="md-plain">总结</span></h2>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/1544400/202109/1544400-20210921110730456-1727546661.png"></p>
<p id="1632193649993">&nbsp;</p>
<p><span class="md-image md-img-loaded" data-src="003%20%E9%93%BE%E8%A1%A8.assets/image-20210920143935864.png">&nbsp;</span></p><br><br>
来源:https://www.cnblogs.com/forever-Ys/p/15316202.html
頁: [1]
查看完整版本: C#中List是链表吗?为什么可以通过下标访问