Lists
一、链表的基本概念
- 定义:
链表是一种有限的、有序的数据项序列,称为元素(elements)。
- 表示法: 使用符号表示为 \(<a0, a1, …, an-1>\)。
- 元素位置: 每个元素在链表中都有一个位置。
- 类型:
每个元素可以是任意类型,但所有元素必须是同一类型。
- 长度: 链表的长度是当前存储的元素数量。
- 空链表: 空链表不包含任何元素。
- 头和尾:
链表的开始称为头(head),结束称为尾(tail)。
二、链表的常见操作
- 插入(insert)
- 追加(append)
- 删除(delete/remove)
- 查找(find)
- 判断是否为空(isEmpty)
- 移动到上一个元素(prev)
- 移动到下一个元素(next)
- 获取当前元素的位置(currPos)
- 移动到指定位置(moveToPos)
- 移动到链表开始位置(moveToStart)
- 获取链表长度(length)
三、链表的实现
1. 列表抽象数据类型(ADT)
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
|
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)\)。
插入代码示例:
1 2 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)\)(最坏和平均情况下)。
删除代码示例:
1 2 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 指针实现的链表(链表)
- 动态内存分配: 需要时为新的链表元素分配内存。
- 节点结构:
1 2 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 链表类的实现
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
|
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
字段。
插入代码示例:
1 2 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++; }
|
追加节点
1 2 3 4 5 6
| void append(const E& it) { tail = tail->next = new Link<E>(it, NULL); cnt++; }
|
3.2 删除
- 删除节点只需重新定向指针。
- 记得回收已删除节点占用的空间。
删除代码示例:
1 2 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 当前指针移动操作
向前移动
1 2 3 4 5 6 7
| void next() { if (curr != tail) { curr = curr->next; } }
|
向后移动
1 2 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 $:
完整代码实现
数组实现
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 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; }
|
链表实现
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 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; }
|