列表(list)是一个抽象的数据结构概念,表示元素的有序集合,支持元素访问、修改、添加、删除和遍历等操作,无需使用者考虑容量限制的问题。列表可以基于链表或数组实现。
- 链表天然可以看作一个列表,其支持元素增删改查操作,并且可以灵活动态扩容。
- 数组也支持元素增删改查,但由于其长度不可变,因此只能看作一个具有长度限制的列表。
由于数组不具备扩展长度的能力,因此可以使用动态数组来实现列表。它继承了数组的各项优点,并且可以在程序运行过程中进行动态扩容。
许多编程语言中的标准库提供的列表是基于动态数组实现的,例如Python中的list
、Java中的ArrayList
、C++中的vector
和C#中的List
等。通常,将列表和动态数组视为等同的概念。
1. 列表常用操作
1.1 初始化列表
列表的初始化可以采用“无初始值”和“有初始值”这两种初始化方法:
1 2 3 4 5
|
vector<int> nums1;
vector<int> nums = { 1, 3, 2, 5, 4 };
|
1.2 访问元素
列表本质上是数组,因此可以在$O(1)$时间内访问和更新元素。
1 2 3 4 5
| int num = nums[1];
nums[1] = 0;
|
1.3 插入与删除元素
在列表末尾添加元素的时间复杂度为$O(1)$,但插入和删除元素的效率仍与数组相同,时间复杂度为$O(n)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| nums.clear();
nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4);
nums.insert(nums.begin() + 3, 6);
nums.erase(nums.begin() + 3);
|
1.4 遍历列表
与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。
1 2 3 4 5 6 7 8 9 10 11
| int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; }
count = 0; for (int num : nums) { count += num; }
|
1.5 拼接列表
给定一个新列表nums1
,我们可以将其拼接到原列表的尾部。
1 2 3 4
| vector<int> nums1 = { 6, 8, 7, 10, 9 };
nums.insert(nums.end(), nums1.begin(), nums1.end());
|
1.6 排序列表
1 2
| sort(nums.begin(), nums.end());
|
2. 简单列表的实现
由于Python和C++都内置了列表,直接使用内置的列表list
和vector
即可,因此下面仅提供C实现列表的方法。
列表的设计需要考虑下面三个因素:
- 初始容量:选取一个合理的数组初始容量。下面的示例中选择10作为初始容量。
- 数量记录:声明一个变量
size
,用于记录列表当前元素数量,并随着元素插入和删除实时更新。根据此变量,我们可以定位列表尾部,以及判断是否需要扩容。
- 扩容机制:若插入元素时列表容量已满,则需要进行扩容。先根据扩容倍数创建一个更大的数组,再将当前数组的所有元素依次移动至新数组。在下面的示例中,规定每次将数组扩容至之前的2倍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| typedef struct { int *arr; int capacity; int size; int extendRatio; } MyList;
MyList *newMyList() { MyList *nums = malloc(sizeof(MyList)); nums->capacity = 10; nums->arr = malloc(sizeof(int) * nums->capacity); nums->size = 0; nums->extendRatio = 2; return nums; }
void delMyList(MyList *nums) { free(nums->arr); free(nums); }
int size(MyList *nums) { return nums->size; }
int capacity(MyList *nums) { return nums->capacity; }
int get(MyList *nums, int index) { assert(index >= 0 && index < nums->size); return nums->arr[index]; }
void set(MyList *nums, int index, int num) { assert(index >= 0 && index < nums->size); nums->arr[index] = num; }
void add(MyList *nums, int num) { if (size(nums) == capacity(nums)) { extendCapacity(nums); } nums->arr[size(nums)] = num; nums->size++; }
void insert(MyList *nums, int index, int num) { assert(index >= 0 && index < size(nums)); if (size(nums) == capacity(nums)) { extendCapacity(nums); } for (int i = size(nums); i > index; --i) { nums->arr[i] = nums->arr[i - 1]; } nums->arr[index] = num; nums->size++; }
int removeItem(MyList *nums, int index) { assert(index >= 0 && index < size(nums)); int num = nums->arr[index]; for (int i = index; i < size(nums) - 1; i++) { nums->arr[i] = nums->arr[i + 1]; } nums->size--; return num; }
void extendCapacity(MyList *nums) { int newCapacity = capacity(nums) * nums->extendRatio; int *extend = (int *)malloc(sizeof(int) * newCapacity); int *temp = nums->arr;
for (int i = 0; i < size(nums); i++) extend[i] = nums->arr[i];
free(temp);
nums->arr = extend; nums->capacity = newCapacity; }
int *toArray(MyList *nums) { return nums->arr; }
|
参考
[1] GitHub 开源项目《hello 算法》
[2] 程杰.大话数据结构【溢彩加强版】[M].清华大学出版社,2020.