Lists
一、链表的基本概念
- 定义:
链表是一种有限的、有序的数据项序列,称为元素(elements)。
- 表示法: 使用符号表示为 \(<a0, a1, …, an-1>\)。
- 元素位置: 每个元素在链表中都有一个位置。
- 类型:
每个元素可以是任意类型,但所有元素必须是同一类型。
- 长度: 链表的长度是当前存储的元素数量。
- 空链表: 空链表不包含任何元素。
- 头和尾:
链表的开始称为头(head),结束称为尾(tail)。
二、链表的常见操作
- 插入(insert)
- 追加(append)
- 删除(delete/remove)
- 查找(find)
- 判断是否为空(isEmpty)
- 移动到上一个元素(prev)
- 移动到下一个元素(next)
- 获取当前元素的位置(currPos)
- 移动到指定位置(moveToPos)
- 移动到链表开始位置(moveToStart)
- 获取链表长度(length)
三、链表的实现
1. 列表抽象数据类型(ADT)
| 12
 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
 
 | 
 template <typename E>
 class List {
 public:
 
 List() {}
 
 
 virtual ~List() {}
 
 
 
 virtual bool insert(const E& item) = 0;
 
 
 
 virtual bool append(const E& item) = 0;
 
 
 
 
 virtual bool remove(E&) = 0;
 
 
 virtual void clear() = 0;
 
 
 virtual void moveToStart() = 0;
 
 
 virtual void moveToEnd() = 0;
 
 
 virtual void prev() = 0;
 
 
 virtual void next() = 0;
 
 
 virtual int currPos() const = 0;
 
 
 virtual void moveToPos(int pos) = 0;
 
 
 };
 
 
 | 
2. 标准链表实现
2.1 数组实现的链表
- 思想: 预先分配一个大小为 MAX_SIZE的大数组。
- 变量跟踪:
- 当前大小 count
- 当前定位 curr_position
 
插入操作:
- 平均情况下,需要移动一半的元素。
- 最坏情况下,插入位置为 0,需要移动所有 N 项,时间复杂度为 \(O(N)\)。
- 最好情况下,时间复杂度为 \(\Theta(1)\)。
插入代码示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | void insert(const E& it) {Assert(listSize >= maxSize, "Exceed capacity");
 
 for (int i = listSize; i > curr; i--) {
 listArray[i] = listArray[i - 1];
 }
 listArray[curr] = it;
 listSize++;
 }
 
 | 
删除操作:
- 只需移动当前元素之后的元素。
- 时间复杂度为 \(\Theta(1)\)(最好情况下),\(\Theta(n)\)(最坏和平均情况下)。
删除代码示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | E remove() {Assert((curr >= 0) && (curr < listSize), "no element");
 E it = listArray[curr];
 for (int i = curr; i < listSize - 1; i++) {
 listArray[i] = listArray[i + 1];
 }
 listSize--;
 return it;
 }
 
 | 
2.2 指针实现的链表(链表)
- 动态内存分配: 需要时为新的链表元素分配内存。
- 节点结构:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | template <typename E>
 class Link {
 public:
 E element;
 Link *next;
 
 
 Link(const E& elemval, Link* nextval = NULL) {
 element = elemval;
 next = nextval;
 }
 
 
 Link(Link* nextval = NULL) {
 next = nextval;
 }
 };
 
 
 | 
2.3 链表类的实现
| 12
 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
 
 | 
 template <typename E>
 class LList : public List<E> {
 private:
 Link<E>* head;
 Link<E>* tail;
 Link<E>* curr;
 int cnt;
 
 
 void init()
 {
 curr = tail = head = new Link<E>;
 cnt = 0;
 }
 
 
 void removeall()
 {
 while(head != NULL) {
 curr = head;
 head = head->next;
 delete curr;
 }
 }
 };
 
 
 | 
3. 链表的插入与删除
3.1 插入
- 创建一个新的链表节点,存储新元素。
- 设置新节点的 next字段。
- 设置当前节点的 next字段。
插入代码示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | public:
 void insert(const E& it) {
 
 curr->next = new Link<E>(it, curr->next);
 if (tail == curr) tail = curr->next;
 cnt++;
 }
 
 
 | 
追加节点
| 12
 3
 4
 5
 6
 
 | void append(const E& it) {
 tail = tail->next = new Link<E>(it, NULL);
 cnt++;
 }
 
 
 | 
3.2 删除
- 删除节点只需重新定向指针。
- 记得回收已删除节点占用的空间。
删除代码示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | E remove() {
 E it = curr->next->element;
 Link<E>* ltemp = curr->next;
 if (tail == curr->next)
 tail = curr;
 curr->next = curr->next->next;
 delete ltemp;
 cnt--;
 return it;
 }
 
 
 | 
3.3 当前指针移动操作
向前移动
| 12
 3
 4
 5
 6
 7
 
 | void next() {
 if (curr != tail) {
 curr = curr->next;
 }
 }
 
 
 | 
向后移动
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | void prev() {
 if (curr == head) return;
 Link<E>* temp = head;
 
 while (temp->next != curr) temp = temp->next;
 curr = temp;
 }
 
 
 | 
四、链表与数组的空间效率比较
空间复杂度比较
在使用不同的数据结构实现列表时,内存占用是一个重要考虑因素。以下是数组实现与链表实现的空间复杂度比较:
数组实现的空间复杂度
假设列表中当前元素的数量为 $ n $,指针的大小为 $ P
$,数据元素的大小为 $ E $,则使用数组实现时,总大小计算如下:
$ = (n + 1) P + n E $
- $ n + 1 $ 是为了存储 $ n $
个元素的指针以及一个额外的指针来跟踪列表的末尾。
- $ n E $ 是存储 $ n $ 个数据元素所需的空间。
链表实现的空间复杂度
对于链表实现,总大小为:
$ = (2n + 2) P + n E $
- $ 2n $
是因为每个节点有两个指针(当前节点指针和下一个节点指针)。
- 额外的两个 $ P $ 是为头指针和尾指针所保留的空间。
- $ n E $ 是存储 $ n $ 个数据元素所需的空间。
示例
假设 $ n = 1000 \(,\) P = 4 \(,\) E = 4 $:
完整代码实现
数组实现
| 12
 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
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 
 | #include <iostream>#include <cassert>
 
 template <typename E>
 class ArrayList {
 private:
 E* listArray;
 int listSize;
 int maxSize;
 int curr;
 
 public:
 
 ArrayList(int size = 100) {
 maxSize = size;
 listSize = 0;
 listArray = new E[maxSize];
 curr = 0;
 }
 
 
 ~ArrayList() {
 delete[] listArray;
 }
 
 
 void insert(const E& it) {
 assert(listSize < maxSize && "Exceed capacity");
 for (int i = listSize; i > curr; i--) {
 listArray[i] = listArray[i - 1];
 }
 listArray[curr] = it;
 listSize++;
 }
 
 
 void append(const E& it) {
 assert(listSize < maxSize && "Exceed capacity");
 listArray[listSize++] = it;
 }
 
 
 E remove() {
 assert((curr >= 0) && (curr < listSize) && "No element to remove");
 E it = listArray[curr];
 for (int i = curr; i < listSize - 1; i++) {
 listArray[i] = listArray[i + 1];
 }
 listSize--;
 return it;
 }
 
 
 int find(const E& it) const {
 for (int i = 0; i < listSize; i++) {
 if (listArray[i] == it) return i;
 }
 return -1;
 }
 
 
 bool isEmpty() const {
 return listSize == 0;
 }
 
 
 int currPos() const {
 return curr;
 }
 
 
 void moveToPos(int pos) {
 assert(pos >= 0 && pos < listSize);
 curr = pos;
 }
 
 
 void moveToStart() {
 curr = 0;
 }
 
 
 void moveToEnd() {
 curr = listSize;
 }
 
 
 int length() const {
 return listSize;
 }
 
 
 void print() const {
 for (int i = 0; i < listSize; i++) {
 std::cout << listArray[i] << " ";
 }
 std::cout << std::endl;
 }
 };
 
 int main() {
 ArrayList<int> myList;
 
 myList.append(10);
 myList.append(20);
 myList.insert(15);
 
 std::cout << "List after insertions: ";
 myList.print();
 
 myList.remove();
 std::cout << "List after removal: ";
 myList.print();
 
 std::cout << "Current position: " << myList.currPos() << std::endl;
 std::cout << "Length of list: " << myList.length() << std::endl;
 
 return 0;
 }
 
 
 | 
链表实现
| 12
 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
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 
 | #include <iostream>#include <cassert>
 
 
 template <typename E>
 class List {
 public:
 virtual ~List() {}
 
 virtual bool insert(const E& item) = 0;
 virtual bool append(const E& item) = 0;
 virtual bool remove(E&) = 0;
 virtual void clear() = 0;
 virtual void moveToStart() = 0;
 virtual void moveToEnd() = 0;
 virtual void prev() = 0;
 virtual void next() = 0;
 virtual int currPos() const = 0;
 virtual void moveToPos(int pos) = 0;
 virtual int length() const = 0;
 };
 
 
 template <typename E>
 class Link {
 public:
 E element;
 Link* next;
 
 Link(const E& elemval, Link* nextval = nullptr)
 : element(elemval), next(nextval) {}
 
 Link(Link* nextval = nullptr)
 : next(nextval) {}
 };
 
 
 template <typename E>
 class LList : public List<E> {
 private:
 Link<E>* head;
 Link<E>* tail;
 Link<E>* curr;
 int cnt;
 
 void init() {
 curr = tail = head = new Link<E>();
 cnt = 0;
 }
 
 void removeAll() {
 while (head != nullptr) {
 curr = head;
 head = head->next;
 delete curr;
 }
 }
 
 public:
 LList() { init(); }
 
 ~LList() { removeAll(); delete head; }
 
 
 bool insert(const E& it) override {
 curr->next = new Link<E>(it, curr->next);
 if (tail == curr) tail = curr->next;
 cnt++;
 return true;
 }
 
 
 bool append(const E& it) override {
 tail = tail->next = new Link<E>(it, nullptr);
 cnt++;
 return true;
 }
 
 
 bool remove(E& it) override {
 if (curr->next == nullptr) return false;
 it = curr->next->element;
 Link<E>* ltemp = curr->next;
 if (tail == curr->next)
 tail = curr;
 curr->next = curr->next->next;
 delete ltemp;
 cnt--;
 return true;
 }
 
 
 void clear() override {
 removeAll();
 init();
 }
 
 
 void moveToStart() override {
 curr = head;
 }
 
 
 void moveToEnd() override {
 curr = tail;
 }
 
 
 void prev() override {
 if (curr == head) return;
 Link<E>* temp = head;
 while (temp->next != curr)
 temp = temp->next;
 curr = temp;
 }
 
 
 void next() override {
 if (curr != tail)
 curr = curr->next;
 }
 
 
 int currPos() const override {
 Link<E>* temp = head;
 int pos = 0;
 while (temp != curr) {
 temp = temp->next;
 pos++;
 }
 return pos;
 }
 
 
 void moveToPos(int pos) override {
 if (pos < 0 || pos > cnt) return;
 curr = head;
 for (int i = 0; i < pos; i++)
 curr = curr->next;
 }
 
 
 int length() const override {
 return cnt;
 }
 
 
 void printList() {
 Link<E>* temp = head->next;
 while (temp != nullptr) {
 std::cout << temp->element << " ";
 temp = temp->next;
 }
 std::cout << std::endl;
 }
 };
 
 
 int main() {
 LList<int> myList;
 
 myList.append(1);
 myList.append(2);
 myList.append(3);
 myList.insert(0);
 
 std::cout << "List after insertions: ";
 myList.printList();
 
 int removedItem;
 myList.remove(removedItem);
 std::cout << "Removed item: " << removedItem << std::endl;
 
 std::cout << "List after removal: ";
 myList.printList();
 
 return 0;
 }
 
 |