列表是基于数组或链表实现的更抽象的数据结构。
列表同其他线性表一样,会将元素保存在一个集合内,并支持增改删查的操作。在使用列表时,通常无需考虑他的容量大小。
列表可以基于数组或链表实现:
- 数组:一个固定长度的列表。实现列表时,若元素数量超过容量大小,则需进行扩容操作。
- 链表:遍历性能较低,占用内存更多的列表。但它本身可以灵活地增改删查且无需扩容。
在.NET
中,列表List<T>
是基于数组实现的
扩容操作
基于数组实现的列表均要考虑扩容问题,大多数语言会在列表初始化时预设一个 容量(Capacity) 。在.NET
中,这个预设值是4
。
为什么要扩容
为了使列表的容量可变,数组的容量需要时刻检查。一旦新增的元素数量超过了当前数组的容量,则需要创建一个容量更大的数组,再将原有数组的元素复制进去。
比如说,对于一个容量为4
的数组:
1 | int[] Add(int[] arr, int element) // 一个简单的添加元素实现,与语言实现不同 |
需要扩容多大
每次添加一个元素就要扩容一次当然是极其低效的,即使这可以节省一点内存。
在.NET
中,扩容后长度增加一倍。
1 | // System.Collections.Generic.List<T> Translated Source Code |
常用操作
初始化列表
创建一个空列表:
1 | List<int> bad = new List<int>(); |
从已有的数组创建列表:
1 | int[] s = [1, 1, 4, 5, 1, 4]; |
访问列表元素
由于.NET
列表是基于数组实现的,在访问元素的效率上和数组相当。
1 | int first = bad[0]; // 获取列表的第1个元素 |
添加,插入与删除
若未触发扩容操作,向列表后增加元素的时间复杂度是O(1)
:
1 | // 在列表的尾部添加元素 |
插入和删除元素的时间复杂度均为O(n)
:
1 | bad.Insert(3, 0); // 在 bad 列表索引为 3 的位置插入 0 值 |
遍历
可以通过for
或foreach
关键字来遍历列表的元素,和数组类似:
1 | // 通过 for 循环得到列表的索引,从而获取值 |
排序
.NET
的列表可以通过list.Sort()
方法来让列表元素进行从小到大的排列。
对于更复杂的排序,譬如当元素是对象的时候,通过对象内的某个属性排序,则需要使用LINQ。
1 | var personA = new Person(10, "A"); |