数据结构与算法实现
1, 顺序表(SeqList)
顺序表是线性表的顺序存储结构,它是用一组地址连续的存储单元依次存储线性表中的各个元素。
特点:
随机访问: 可以在O(1)时间内通过下标直接访问任意元素
存储密度高: 数据元素之间不需要额外空间存储逻辑关系
插入和删除操作需要移动大量元素: 时间复杂度为O(n)
需要预分配内存空间: 可能存在空间浪费或溢出问题
时间复杂度分析:
按位查找(getElem):O(1)
按值查找(locateElem):平均O(n)
插入操作(insertElem):平均O(n),最坏O(n)
删除操作(deleteElem):平均O(n),最坏O(n)
追加操作(appendElem):O(1)
顺序表扩容机制:
通常顺序表满时,会创建一个更大的数组(如原来的1.5倍或2倍),并将原数据复制到新数组中,然后释放旧数组。这是动态数组(如C++ STL vector)的核心实现机制。
应用场景:
需要频繁随机访问元素的场景
存储密度要求高的场景
元素数量变化不大或可以预估的场景
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 const int MAX_SIZE = 100 ;template <typename ElemType>class SeqList {private : ElemType* elem; int length; int max_size; public : SeqList (int size = MAX_SIZE) { if (size <= 0 ) { throw invalid_argument ("Size must be positive" ); } max_size = size; elem = new ElemType[max_size]; length = 0 ; } SeqList (const SeqList& other) { max_size = other.max_size; length = other.length; elem = new ElemType[max_size]; for (int i = 0 ; i < length; i++) { elem[i] = other.elem[i]; } } SeqList& operator =(const SeqList& other) { if (this == &other) { return *this ; } delete [] elem; max_size = other.max_size; length = other.length; elem = new ElemType[max_size]; for (int i = 0 ; i < length; i++) { elem[i] = other.elem[i]; } return *this ; } SeqList (SeqList&& other) noexcept { max_size = other.max_size; length = other.length; elem = other.elem; other.elem = nullptr ; other.length = 0 ; other.max_size = 0 ; } SeqList& operator =(SeqList&& other) noexcept { if (this != &other) { delete [] elem; max_size = other.max_size; length = other.length; elem = other.elem; other.elem = nullptr ; other.length = 0 ; other.max_size = 0 ; } return *this ; } ~SeqList () { delete [] elem; elem = nullptr ; } int getLength () const { return length; } ElemType getElem (int i) const { if (i < 1 || i > length) { throw out_of_range ("Index out of range (getElem)" ); } return elem[i - 1 ]; } ElemType& operator [](int i) { if (i < 1 || i > length) { throw out_of_range ("Index out of range (operator[])" ); } return elem[i - 1 ]; } int locateElem (ElemType e) const { for (int i = 0 ; i < length; i++) { if (elem[i] == e) { return i + 1 ; } } return 0 ; } template <typename Compare> int locateElem (ElemType e, Compare comp) const { for (int i = 0 ; i < length; i++) { if (comp (elem[i], e)) { return i + 1 ; } } return 0 ; } void insertElem (int i, ElemType e) { if (i < 1 || i > length + 1 ) { throw out_of_range ("Index out of range (insertElem)" ); } if (isFull ()) { resize (max_size * 3 / 2 ); } for (int j = length; j >= i; j--) { elem[j] = elem[j - 1 ]; } elem[i - 1 ] = e; length++; } void resize (int new_size) { if (new_size <= max_size) { return ; } ElemType* new_elem = new ElemType[new_size]; for (int i = 0 ; i < length; i++) { new_elem[i] = elem[i]; } delete [] elem; elem = new_elem; max_size = new_size; } void appendElem (ElemType e) { if (isFull ()) { resize (max_size * 3 / 2 ); } elem[length++] = e; } void deleteElem (int i) { if (i < 1 || i > length) { throw out_of_range ("Index out of range (deleteElem)" ); } for (int j = i; j < length; j++) { elem[j - 1 ] = elem[j]; } length--; if (length < max_size / 4 && max_size > MAX_SIZE) { resize (max_size / 2 ); } } void clear () { length = 0 ; } bool isEmpty () const { return length == 0 ; } bool isFull () const { return length == max_size; } int getCapacity () const { return max_size; } void printList () const { if (isEmpty ()) { cout << "SeqList is empty" << endl; return ; } cout << "SeqList elements: " ; for (int i = 0 ; i < length; i++) { cout << elem[i] << " " ; } cout << endl; } void merge (const SeqList& other) { int new_length = length + other.length; if (new_length > max_size) { resize (max (new_length, max_size * 3 / 2 )); } for (int i = 0 ; i < other.length; i++) { elem[length++] = other.elem[i]; } } };
2, 单链表(Linked List)
单链表是一种常见的线性表链式存储结构,它通过指针将分散存储的结点连接起来,形成一个线性序列。
单链表的特点:
动态存储: 不需要预分配固定大小的内存,可根据需要动态增减结点
非连续存储: 物理存储上不连续,数据元素之间通过指针链接
插入删除效率高: 在已知位置的情况下,插入和删除操作时间复杂度为O(1)
随机访问效率低: 访问第i个元素需要从表头开始遍历,时间复杂度为O(n)
存储开销大: 每个元素需要额外空间存储指针
单链表的基本结构:
头结点: 通常不存储数据,指向第一个数据结点,方便操作统一
数据结点: 包含数据域和指针域(指向下一个结点)
尾结点: 指向nullptr(C++)或NULL的最后一个结点
时间复杂度分析:
按位查找(getElem):O(n)
按值查找(locateElem):平均O(n)
插入操作(insertElem):已知位置时O(1),未知位置时O(n)
删除操作(deleteElem):已知位置时O(1),未知位置时O(n)
追加操作(appendElem):O(n)
单链表vs顺序表对比:
空间分配: 链表动态分配,顺序表静态或动态数组
内存利用: 链表不连续但有额外指针开销,顺序表连续存储利用率高
操作效率: 链表插入删除快,顺序表随机访问快
应用场景:
需要频繁插入删除操作的场景(如频繁修改的链表)
数据量变化较大且无法预估的场景
不需要频繁随机访问的场景
实现其他数据结构(如栈、队列、图的邻接表等)
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 template <typename ElemType>class LinkList {private : struct LNode { ElemType data; LNode* next; LNode (ElemType val = ElemType (), LNode* nxt = nullptr ) : data (val), next (nxt) {} }; LNode* head; int length; public : LinkList () { head = new LNode (); length = 0 ; } LinkList (const LinkList& other) { head = new LNode (); length = 0 ; LNode* pOther = other.head->next; LNode* pCurrent = head; while (pOther != nullptr ) { pCurrent->next = new LNode (pOther->data); pCurrent = pCurrent->next; pOther = pOther->next; length++; } } LinkList& operator =(const LinkList& other) { if (this == &other) { return *this ; } clear (); LNode* pOther = other.head->next; LNode* pCurrent = head; while (pOther != nullptr ) { pCurrent->next = new LNode (pOther->data); pCurrent = pCurrent->next; pOther = pOther->next; length++; } return *this ; } LinkList (LinkList&& other) noexcept { head = other.head; length = other.length; other.head = new LNode (); other.length = 0 ; } LinkList& operator =(LinkList&& other) noexcept { if (this != &other) { clear (); delete head; head = other.head; length = other.length; other.head = new LNode (); other.length = 0 ; } return *this ; } ~LinkList () { clear (); delete head; head = nullptr ; } int getLength () const { return length; } ElemType getElem (int i) const { if (i < 1 || i > length) { throw out_of_range ("LinkList::getElem: 索引越界,合法范围[1, " + to_string (length) + "]" ); } LNode* p = head->next; for (int j = 1 ; j < i; j++) { p = p->next; } return p->data; } LNode* getElemNode (int i) const { if (i < 0 || i > length) { return nullptr ; } LNode* p = head; for (int j = 0 ; j < i; j++) { p = p->next; } return p; } int locateElem (const ElemType& e) const { LNode* p = head->next; int pos = 1 ; while (p != nullptr ) { if (p->data == e) { return pos; } p = p->next; pos++; } return 0 ; } template <typename Compare> int locateElem (const ElemType& e, Compare comp) const { LNode* p = head->next; int pos = 1 ; while (p != nullptr ) { if (comp (p->data, e)) { return pos; } p = p->next; pos++; } return 0 ; } void insertElem (int i, const ElemType& e) { if (i < 1 || i > length + 1 ) { throw out_of_range ("LinkList::insertElem: 插入位置越界,合法范围[1, " + to_string (length + 1 ) + "]" ); } LNode* p = getElemNode (i - 1 ); LNode* newNode = new LNode (e, p->next); p->next = newNode; length++; } void appendElem (const ElemType& e) { insertElem (length + 1 , e); } void prependElem (const ElemType& e) { insertElem (1 , e); } ElemType deleteElem (int i) { if (i < 1 || i > length) { throw out_of_range ("LinkList::deleteElem: 删除位置越界,合法范围[1, " + to_string (length) + "]" ); } LNode* p = getElemNode (i - 1 ); LNode* temp = p->next; ElemType deletedVal = temp->data; p->next = temp->next; delete temp; length--; return deletedVal; } bool isEmpty () const { return length == 0 ; } void clear () { while (!isEmpty ()) { deleteElem (1 ); } } void mergeSorted (const LinkList& other) { if (other.isEmpty ()) { return ; } LinkList tempList; LNode* p1 = head->next; LNode* p2 = other.head->next; LNode* p3 = tempList.head; while (p1 != nullptr && p2 != nullptr ) { if (p1->data <= p2->data) { p3->next = new LNode (p1->data); p1 = p1->next; } else { p3->next = new LNode (p2->data); p2 = p2->next; } p3 = p3->next; tempList.length++; } while (p1 != nullptr ) { p3->next = new LNode (p1->data); p1 = p1->next; p3 = p3->next; tempList.length++; } while (p2 != nullptr ) { p3->next = new LNode (p2->data); p2 = p2->next; p3 = p3->next; tempList.length++; } clear (); head->next = tempList.head->next; length = tempList.length; tempList.head->next = nullptr ; tempList.length = 0 ; } void createListHead (const ElemType arr[], int n) { if (n < 0 ) { throw invalid_argument ("LinkList::createListHead: 数组长度不能为负数" ); } clear (); for (int i = 0 ; i < n; i++) { insertElem (1 , arr[i]); } } void createListTail (const ElemType arr[], int n) { if (n < 0 ) { throw invalid_argument ("LinkList::createListTail: 数组长度不能为负数" ); } clear (); for (int i = 0 ; i < n; i++) { appendElem (arr[i]); } } void reverse () { if (length <= 1 ) { return ; } LNode* prev = nullptr ; LNode* curr = head->next; LNode* next = nullptr ; while (curr != nullptr ) { next = curr->next; curr->next = prev; prev = curr; curr = next; } head->next = prev; } void printList () const { if (isEmpty ()) { cout << "LinkList is empty" << endl; return ; } LNode* p = head->next; cout << "LinkList: " ; while (p != nullptr ) { cout << p->data << " -> " ; p = p->next; } cout << "nullptr (length: " << length << ")" << endl; } ElemType& at (int i) { if (i < 1 || i > length) { throw out_of_range ("LinkList::at: 索引越界,合法范围[1, " + to_string (length) + "]" ); } LNode* p = head->next; for (int j = 1 ; j < i; j++) { p = p->next; } return p->data; } ElemType back () const { if (isEmpty ()) { throw underflow_error ("LinkList::back: 链表为空" ); } return getElem (length); } ElemType front () const { if (isEmpty ()) { throw underflow_error ("LinkList::front: 链表为空" ); } return getElem (1 ); } };
3, 双向循环链表类(DuLinkList)
双向循环链表是在双向链表基础上进一步发展而来的数据结构,它既允许向前遍历,也允许向后遍历,并且首尾相连形成一个环形结构。
双向循环链表的特点:
双向遍历能力: 每个结点同时拥有前驱指针和后继指针,可以双向遍历
循环连接: 尾结点的后继指向头结点,头结点的前驱指向尾结点,形成闭环
头结点哨兵: 使用头结点作为哨兵,简化边界条件处理
插入删除高效: 在已知位置的情况下,插入删除操作只需O(1)时间,且无需额外查找前驱
额外空间开销: 每个结点需要额外存储一个前驱指针,空间开销较大
双向循环链表的基本结构:
头结点: 不存储数据,是链表的入口点,head->prior指向尾结点,head->next指向第一个数据结点
数据结点: 包含数据域、前驱指针和后继指针
尾结点: 最后一个数据结点,其next指向头结点,prior指向倒数第二个结点
时间复杂度分析:
按位查找(getElem):O(n)
按值查找(locateElem):平均O(n)
插入操作(insertElem):已知位置时O(1),未知位置时O(n)
删除操作(deleteElem):已知位置时O(1),未知位置时O(n)
追加操作:O(1)(利用循环特性可以直接访问尾结点)
双向遍历:从任意位置可向前或向后遍历,O(n)
双向循环链表vs单链表对比:
遍历方向: 单链表只能单向遍历,双向链表可双向遍历
操作效率: 双向链表在某些操作(如逆向遍历、已知结点删除)上更高效
空间开销: 双向链表需要额外存储前驱指针,空间开销更大
复杂度: 双向链表实现逻辑稍复杂,但使用更灵活
应用场景:
需要频繁双向遍历的场景
需要在任意位置进行高效插入删除操作的场景
需要实现循环队列、双向队列等数据结构
文本编辑器中的撤销/重做操作
操作系统中的进程调度表
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 template <typename ElemType>class DuLinkList {private : struct DulNode { ElemType data; DulNode* prior; DulNode* next; DulNode (ElemType val = ElemType (), DulNode* pre = nullptr , DulNode* nxt = nullptr ) : data (val), prior (pre), next (nxt) {} }; DulNode* head; int length; public : DuLinkList () { head = new DulNode (); head->prior = head; head->next = head; length = 0 ; } DuLinkList (const DuLinkList& other) { head = new DulNode (); head->prior = head; head->next = head; length = 0 ; DulNode* pOther = other.head->next; DulNode* pCurrent = head; while (pOther != other.head) { pCurrent->next = new DulNode (pOther->data, pCurrent, head); pCurrent = pCurrent->next; pOther = pOther->next; length++; } head->prior = pCurrent; } DuLinkList& operator =(const DuLinkList& other) { if (this == &other) { return *this ; } clear (); DulNode* pOther = other.head->next; DulNode* pCurrent = head; while (pOther != other.head) { pCurrent->next = new DulNode (pOther->data, pCurrent, head); pCurrent = pCurrent->next; pOther = pOther->next; length++; } head->prior = pCurrent; return *this ; } DuLinkList (DuLinkList&& other) noexcept { head = other.head; length = other.length; other.head = new DulNode (); other.head->prior = other.head; other.head->next = other.head; other.length = 0 ; } DuLinkList& operator =(DuLinkList&& other) noexcept { if (this != &other) { clear (); delete head; head = other.head; length = other.length; other.head = new DulNode (); other.head->prior = other.head; other.head->next = other.head; other.length = 0 ; } return *this ; } ~DuLinkList () { clear (); delete head; head = nullptr ; } DulNode* getElemNode (int i) const { if (i < 0 || i > length) { return nullptr ; } if (i <= length / 2 ) { DulNode* p = head->next; for (int j = 1 ; j < i; j++) { p = p->next; } return p; } else { DulNode* p = head->prior; for (int j = length; j > i; j--) { p = p->prior; } return p; } } int getLength () const { return length; } ElemType getElem (int i) const { if (i < 1 || i > length) { throw out_of_range ("DuLinkList::getElem: 索引越界,合法范围[1, " + to_string (length) + "]" ); } DulNode* p = getElemNode (i); return p->data; } ElemType& at (int i) { if (i < 1 || i > length) { throw out_of_range ("DuLinkList::at: 索引越界,合法范围[1, " + to_string (length) + "]" ); } DulNode* p = getElemNode (i); return p->data; } ElemType front () const { if (isEmpty ()) { throw underflow_error ("DuLinkList::front: 链表为空" ); } return head->next->data; } ElemType back () const { if (isEmpty ()) { throw underflow_error ("DuLinkList::back: 链表为空" ); } return head->prior->data; } int locateElem (const ElemType& e) const { if (isEmpty ()) return 0 ; DulNode* p = head->next; int pos = 1 ; while (p != head) { if (p->data == e) { return pos; } p = p->next; pos++; } return 0 ; } template <typename Compare> int locateElem (const ElemType& e, Compare comp) const { if (isEmpty ()) return 0 ; DulNode* p = head->next; int pos = 1 ; while (p != head) { if (comp (p->data, e)) { return pos; } p = p->next; pos++; } return 0 ; } void insertElem (int i, const ElemType& e) { if (i < 1 || i > length + 1 ) { throw out_of_range ("DuLinkList::insertElem: 插入位置越界,合法范围[1, " + to_string (length + 1 ) + "]" ); } DulNode* p = (i == 1 ) ? head : getElemNode (i - 1 ); DulNode* newNode = new DulNode (e, p, p->next); p->next->prior = newNode; p->next = newNode; length++; } void appendElem (const ElemType& e) { DulNode* tail = head->prior; DulNode* newNode = new DulNode (e, tail, head); tail->next = newNode; head->prior = newNode; length++; } void prependElem (const ElemType& e) { insertElem (1 , e); } ElemType deleteElem (int i) { if (i < 1 || i > length) { throw out_of_range ("DuLinkList::deleteElem: 删除位置越界,合法范围[1, " + to_string (length) + "]" ); } DulNode* p = getElemNode (i); ElemType deletedVal = p->data; p->prior->next = p->next; p->next->prior = p->prior; delete p; length--; return deletedVal; } void clear () { while (!isEmpty ()) { deleteElem (1 ); } } void reverse () { if (length <= 1 ) { return ; } DulNode* p = head; do { DulNode* temp = p->next; p->next = p->prior; p->prior = temp; p = p->next; } while (p != head); } bool isEmpty () const { return length == 0 ; } void printList () const { if (isEmpty ()) { cout << "DuLinkList is empty" << endl; return ; } DulNode* p = head->next; cout << "DuLinkList: " ; while (p != head) { cout << p->data << " <-> " ; p = p->next; } cout << "head (length: " << length << ")" << endl; } void printListReverse () const { if (isEmpty ()) { cout << "DuLinkList is empty" << endl; return ; } DulNode* p = head->prior; cout << "DuLinkList (reversed): " ; while (p != head) { cout << p->data << " <-> " ; p = p->prior; } cout << "head (length: " << length << ")" << endl; } void createFromArray (const ElemType arr[], int n) { if (n < 0 ) { throw invalid_argument ("DuLinkList::createFromArray: 数组长度不能为负数" ); } clear (); for (int i = 0 ; i < n; i++) { appendElem (arr[i]); } } };
4, 栈的实现(顺序存储和链式存储)
栈(Stack)是一种遵循**后进先出(LIFO, Last In First Out)**原则的线性数据结构,它只允许在一端(称为栈顶)进行插入和删除操作。
栈的基本特点:
操作受限: 只允许在栈顶进行插入(入栈/压栈)和删除(出栈/弹栈)操作
LIFO特性: 最后进入栈的元素最先被取出
线性结构: 元素之间存在一对一的线性关系
抽象数据类型: 定义了一组操作接口,与具体实现无关
栈的主要操作:
初始化(InitStack): 创建一个空栈
入栈(Push): 在栈顶插入新元素
出栈(Pop): 移除并返回栈顶元素
取栈顶元素(GetTop/Peek): 返回栈顶元素但不移除
判空(IsEmpty): 判断栈是否为空
判满(IsFull): 判断顺序栈是否已满
获取长度(GetLength): 获取栈中元素个数
栈的应用场景:
函数调用的管理
表达式求值
括号匹配验证
算法的回溯实现
内存管理
浏览器的前进/后退功能
顺序栈(SeqStack):
顺序栈是使用数组实现的栈,具有以下特点:
优点: 实现简单,存储密度高,入栈出栈操作时间复杂度为O(1)
缺点: 静态空间有限,可能发生栈溢出;动态扩容时需要额外时间开销
适用场景: 预先知道栈最大容量,且操作频繁的场景
顺序栈的基本结构:
数组:用于存储栈元素
栈顶指针:指示当前栈顶位置,初始值为-1表示空栈
最大容量:栈的最大存储元素数量
顺序栈的时间复杂度:
入栈(push):O(1)
出栈(pop):O(1)
取栈顶元素(getTop):O(1)
判空/判满(isEmpty/isFull):O(1)
获取长度(getLength):O(1)
链栈(LinkStack):
链栈是使用单链表实现的栈,具有以下特点:
优点: 动态分配空间,不存在栈满问题;不需要预先确定大小
缺点: 每个元素需要额外空间存储指针,存储密度较低;操作时需要动态内存管理
适用场景: 无法预估栈大小,需要灵活扩展的场景
链栈的基本结构:
单链表节点:包含数据域和指针域
栈顶指针:指向链表头节点,作为栈的唯一访问点
链表长度:维护栈中元素数量
链栈的时间复杂度:
入栈(push):O(1)
出栈(pop):O(1)
取栈顶元素(getTop):O(1)
判空(isEmpty):O(1)
获取长度(getLength):O(1)
顺序栈与链栈对比:
空间效率: 顺序栈存储密度高,链栈每个元素需要额外指针空间
时间效率: 两者基本操作均为O(1)时间复杂度,但链栈需要动态内存分配和释放
实现复杂度: 顺序栈实现简单,链栈稍复杂
空间限制: 顺序栈有容量限制,链栈无此限制
内存管理: 顺序栈内存连续,链栈内存分散
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 template <typename ElemType>class SeqStack {private : static const int MAX_STACK_SIZE = 100 ; ElemType stack_array[MAX_STACK_SIZE]; int top; public : SeqStack () : top (-1 ) {} SeqStack (const SeqStack& other) : top (other.top) { for (int i = 0 ; i <= top; i++) { stack_array[i] = other.stack_array[i]; } } SeqStack& operator =(const SeqStack& other) { if (this != &other) { top = other.top; for (int i = 0 ; i <= top; i++) { stack_array[i] = other.stack_array[i]; } } return *this ; } SeqStack (SeqStack&& other) noexcept : top (other.top) { for (int i = 0 ; i <= top; i++) { stack_array[i] = std::move (other.stack_array[i]); } other.top = -1 ; } SeqStack& operator =(SeqStack&& other) noexcept { if (this != &other) { top = other.top; for (int i = 0 ; i <= top; i++) { stack_array[i] = std::move (other.stack_array[i]); } other.top = -1 ; } return *this ; } bool isEmpty () const { return top == -1 ; } bool isFull () const { return top == MAX_STACK_SIZE - 1 ; } void push (ElemType e) { if (isFull ()) { throw overflow_error ("SeqStack::push: 栈已满,无法压栈" ); } stack_array[++top] = e; } ElemType pop () { if (isEmpty ()) { throw underflow_error ("SeqStack::pop: 栈为空,无法弹栈" ); } return stack_array[top--]; } ElemType getTop () const { if (isEmpty ()) { throw underflow_error ("SeqStack::getTop: 栈为空,无法获取栈顶元素" ); } return stack_array[top]; } int getLength () const { return top + 1 ; } void clear () { top = -1 ; } void printStack () const { if (isEmpty ()) { cout << "SeqStack is empty" << endl; return ; } cout << "SeqStack: [" ; for (int i = 0 ; i <= top; i++) { cout << stack_array[i]; if (i < top) { cout << ", " ; } } cout << "] (top: " << stack_array[top] << ")" << endl; } }; template <typename ElemType>class LinkStack {private : struct StackNode { ElemType data; StackNode* next; StackNode (ElemType val = 0 , StackNode* nxt = nullptr ) : data (val), next (nxt) {} }; StackNode* top; int length; public : LinkStack () : top (nullptr ), length (0 ) {} LinkStack (const LinkStack& other) : top (nullptr ), length (0 ) { if (!other.isEmpty ()) { StackNode* pOther = other.top; StackNode* tempStack = nullptr ; while (pOther != nullptr ) { tempStack = new StackNode (pOther->data, tempStack); pOther = pOther->next; } StackNode* pTemp = tempStack; StackNode** pDest = ⊤ while (pTemp != nullptr ) { *pDest = new StackNode (pTemp->data); pDest = &((*pDest)->next); pTemp = pTemp->next; length++; } while (tempStack != nullptr ) { StackNode* temp = tempStack; tempStack = tempStack->next; delete temp; } } } LinkStack& operator =(const LinkStack& other) { if (this != &other) { while (top != nullptr ) { StackNode* temp = top; top = top->next; delete temp; } length = 0 ; if (!other.isEmpty ()) { StackNode* pOther = other.top; StackNode* tempStack = nullptr ; while (pOther != nullptr ) { tempStack = new StackNode (pOther->data, tempStack); pOther = pOther->next; } StackNode* pTemp = tempStack; StackNode** pDest = ⊤ while (pTemp != nullptr ) { *pDest = new StackNode (pTemp->data); pDest = &((*pDest)->next); pTemp = pTemp->next; length++; } while (tempStack != nullptr ) { StackNode* temp = tempStack; tempStack = tempStack->next; delete temp; } } } return *this ; } LinkStack (LinkStack&& other) noexcept : top (other.top), length (other.length) { other.top = nullptr ; other.length = 0 ; } LinkStack& operator =(LinkStack&& other) noexcept { if (this != &other) { while (top != nullptr ) { StackNode* temp = top; top = top->next; delete temp; } top = other.top; length = other.length; other.top = nullptr ; other.length = 0 ; } return *this ; } ~LinkStack () { while (top != nullptr ) { StackNode* temp = top; top = top->next; delete temp; } } bool isEmpty () const { return top == nullptr ; } void push (ElemType e) { top = new StackNode (e, top); length++; } ElemType pop () { if (isEmpty ()) { throw underflow_error ("LinkStack::pop: 栈为空,无法弹栈" ); } ElemType poppedVal = top->data; StackNode* temp = top; top = top->next; delete temp; length--; return poppedVal; } ElemType getTop () const { if (isEmpty ()) { throw underflow_error ("LinkStack::getTop: 栈为空,无法获取栈顶元素" ); } return top->data; } int getLength () const { return length; } void clear () { while (top != nullptr ) { StackNode* temp = top; top = top->next; delete temp; } length = 0 ; } void printStack () const { if (isEmpty ()) { cout << "LinkStack is empty" << endl; return ; } cout << "LinkStack: [" ; StackNode* p = top; vector<ElemType> elements; while (p != nullptr ) { elements.push_back (p->data); p = p->next; } for (auto it = elements.rbegin (); it != elements.rend (); ++it) { cout << *it; if (next (it) != elements.rend ()) { cout << ", " ; } } cout << "] (top: " << top->data << ")" << endl; } };
5, 队列的实现(顺序存储和链式存储)
链式队列(LinkQueue)
链式队列是队列的链式存储实现,通过单链表结构存储队列元素,避免了顺序队列的容量限制问题。
工作原理:
使用带头结点的单链表实现
队首指针(front)指向头结点
队尾指针(rear)指向队尾元素
入队操作:在队尾(rear)后添加新结点
出队操作:删除队首(front->next)结点
队列为空的判断条件:front == rear
时间复杂度分析:
入队(enQueue):O(1)
出队(deQueue):O(1)
获取队首元素(getHead):O(1)
判断队列空(isEmpty):O(1)
获取队列长度(getLength):O(1)
优缺点:
优点 :动态分配空间,不需要预先确定容量,不会出现假溢出问题
缺点 :每个元素需要额外的空间存储指针,空间利用率相对较低
应用场景:
元素数量不确定的队列应用
频繁进行入队出队操作的场景
需要避免空间浪费的场合
使用示例:
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 template <typename ElemType>class CircularQueue {private : static const int MAX_QUEUE_SIZE = 100 ; ElemType queue_array[MAX_QUEUE_SIZE]; int front; int rear; public : CircularQueue () : front (0 ), rear (0 ) {} bool isEmpty () const { return front == rear; } bool isFull () const { return (rear + 1 ) % MAX_QUEUE_SIZE == front; } void enQueue (ElemType e) { if (isFull ()) { throw overflow_error ("CircularQueue::enQueue: 队列已满,无法入队" ); } rear = (rear + 1 ) % MAX_QUEUE_SIZE; queue_array[rear] = e; } ElemType deQueue () { if (isEmpty ()) { throw underflow_error ("CircularQueue::deQueue: 队列为空,无法出队" ); } ElemType dequeuedVal = queue_array[(front + 1 )%MAX_QUEUE_SIZE]; front = (front + 1 ) % MAX_QUEUE_SIZE; return dequeuedVal; } ElemType getHead () const { if (isEmpty ()) { throw underflow_error ("CircularQueue::getHead: 队列为空,无法获取队首元素" ); } return queue_array[(front + 1 ) % MAX_QUEUE_SIZE]; } int getLength () const { return (rear - front + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE; } }; template <typename ElemType>class LinkQueue {private : struct QNode { ElemType data; QNode* next; QNode (ElemType val = 0 , QNode* nxt = nullptr ) : data (val), next (nxt) {} }; QNode* front; QNode* rear; int length; public : LinkQueue () { front = rear = new QNode (); front->next = nullptr ; length = 0 ; } ~LinkQueue () { while (front != nullptr ) { QNode* temp = front; front = front->next; delete temp; } } bool isEmpty () const { return front == rear; } void enQueue (ElemType e) { rear->next = new QNode (e); rear = rear->next; length++; } ElemType deQueue () { if (isEmpty ()) { throw underflow_error ("LinkQueue::deQueue: 队列为空,无法出队" ); } ElemType dequeuedVal = front->next->data; QNode* temp = front->next; front->next = temp->next; if (rear == temp) { rear = front; } delete temp; length--; return dequeuedVal; } ElemType getHead () const { if (isEmpty ()) { throw underflow_error ("LinkQueue::getHead: 队列为空,无法获取队首元素" ); } return front->next->data; } int getLength () const { return length; } };
6, 字符串类(String)
1. 定长字符串(FixedString)核心理论
(1)数据结构本质
采用静态字符数组 作为底层存储载体,属于顺序存储结构 的典型实现,串的字符元素在内存中连续存放,物理地址与逻辑顺序完全一致。
(2)核心特性
存储容量编译期固定 ,由预设的最大串长常量限制,运行过程中无法扩容;
实际串长与底层数组空间相互独立,通过成员变量单独记录有效字符长度,避免遍历数组判断长度的性能损耗;
内存分配在栈区 完成,生命周期由作用域控制,无需手动管理内存,访问效率高但空间灵活性差。
(3)核心操作理论支撑
赋值操作:需严格校验源字符串长度,避免超出数组最大容量导致内存越界 ,本质是字符数组的逐字符拷贝;
串连接操作:核心约束为「原串长度 + 待拼接串长度 ≤ 最大串长」,本质是在原串有效字符末尾追加新字符,同步更新实际长度;
子串提取:核心是区间合法性校验 (起始位置非负、长度非负、区间不超原串边界),本质是从指定起始地址开始的定长字符拷贝,需手动补充字符串结束符保证语法合规;
BF 模式匹配:属于暴力匹配算法 ,核心思想是主串从起始位置开始,与模式串逐字符比对,匹配失败则主串回溯、模式串复位,最坏时间复杂度 O(n \times m)(n 为主串长度,m 为模式串长度),优点是逻辑简单、实现成本低,缺点是存在大量无效回溯,效率较低。
2. 堆字符串(HeapString)核心理论
(1)数据结构本质
采用动态字符指针 作为底层存储载体,仍属于顺序存储结构 ,字符元素连续存放,区别在于存储载体的内存分配方式不同。
(2)核心特性
存储容量运行期动态调整 ,可根据串的实际长度按需分配内存,完全适配数据规模,无空间浪费;
内存分配在堆区 完成,生命周期由程序员通过构造/析构函数控制,需手动完成内存申请与释放,空间灵活性极强,访问效率与定长串一致;
存在内存泄漏风险 ,必须通过析构函数释放堆区内存,同时遵循「赋值/拼接操作先释放旧内存、再分配新内存」的原则,避免内存碎片。
(3)核心操作理论支撑
赋值操作:核心是动态内存重分配 ,先释放当前指针指向的旧堆内存,再根据源字符串长度申请新内存,最后完成字符拷贝,保证内存空间与有效字符完全匹配;
串连接操作:本质是「扩容 + 拼接」两步走,申请「原串长度 + 待拼接串长度 + 1」的新内存(预留结束符空间),先拷贝原串、再追加拼接串,释放旧内存后完成指针重定向,同步更新实际长度;
子串提取:与定长串逻辑一致,区别在于子串的底层内存为堆区动态分配,需单独管理子串对象的内存生命周期;
KMP 模式匹配:核心是消除主串回溯 ,通过预处理模式串生成 next 数组,匹配失败时仅复位模式串、主串持续向后遍历,时间复杂度优化至 O(n + m)(最优/最坏复杂度一致),效率远高于 BF 算法;
next 数组核心原理:next[i] 表示模式串第 i 个位置之前的子串中,最长相等前后缀 的长度,其作用是匹配失败时,指导模式串快速回退至最优比对位置,避免无效的字符重复比对。
(4)定长串与堆串核心对比
空间维度:定长串空间固定、易浪费/溢出,堆串按需分配、空间利用率100%;
内存维度:定长串栈区存储、无需手动管理,堆串堆区存储、需严格把控内存释放;
适用场景:定长串适用于字符长度已知且固定的场景,堆串适用于字符长度不确定、需动态扩容的场景。
3. 字符串通用核心理论(两类串共性)
串的逻辑定义 :由零个或多个字符组成的有限序列,空串的有效长度为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 const int MAX_STRLEN = 100 ; class FixedString {private : char str[MAX_STRLEN]; int length; public : FixedString () : length (0 ) { str[0 ] = '\0' ; } bool isEmpty () const { return length == 0 ; } int getLength () const { return length; } int strAssign (const char * chars) { if (strlen (chars) >= MAX_STRLEN) { throw invalid_argument ("FixedString::strAssign: 输入字符串长度超过最大限制" ); } strcpy (str, chars); length = strlen (chars); return length; } void strConcat (const FixedString& s) { if (length + s.getLength () >= MAX_STRLEN) { throw overflow_error ("FixedString::strConcat: 连接后字符串长度超过最大限制" ); } strcat (str, s.str); length += s.getLength (); } FixedString subString (int pos, int len) const { if (pos < 0 || pos >= length || len < 0 || pos + len > length) { throw invalid_argument ("FixedString::subString: 子串位置或长度无效" ); } FixedString sub; strncpy (sub.str, str + pos, len); sub.length = len; sub.str[len] = '\0' ; return sub; } int index (const FixedString& pattern) const { if (pattern.isEmpty ()) { throw invalid_argument ("FixedString::index: 模式串为空" ); } for (int i = 0 ; i <= length - pattern.length; ++i) { if (strncmp (str + i, pattern.str, pattern.length) == 0 ) { return i; } } return -1 ; } void clear () { length = 0 ; str[0 ] = '\0' ; } }; class HeapString {private : char * ch; int length; public : HeapString () : ch (nullptr ), length (0 ) {} ~HeapString () { if (ch != nullptr ) { delete [] ch; ch = nullptr ; } } bool isEmpty () const { return length == 0 ; } int getLength () const { return length; } char * getCh () const { return ch; } void strAssign (const char * chars) { if (ch != nullptr ) { delete [] ch; } length = strlen (chars); ch = new char [length + 1 ]; strcpy (ch, chars); } void strConcat (const HeapString& s) { if (ch == nullptr ) { ch = new char [length + 1 ]; strcpy (ch, s.getCh ()); } else { char * temp = new char [length + s.length + 1 ]; strcpy (temp, ch); strcat (temp, s.getCh ()); delete [] ch; ch = temp; } length += s.length; } HeapString subString (int pos, int len) const { if (pos < 0 || pos >= length || len < 0 || pos + len > length) { throw invalid_argument ("HeapString::subString: 子串位置或长度无效" ); } HeapString sub; sub.ch = new char [len + 1 ]; strncpy (sub.ch, ch + pos, len); sub.ch[len] = '\0' ; sub.length = len; return sub; } int indexKMP (const HeapString& pattern) const { vector<int > next (pattern.getLength(),0 ) ; char *p = pattern.getCh (); int j=0 ; for (int i=1 ;i<pattern.getLength ();++i) { while (j>0 && p[i]!=p[j]) { j=next[j-1 ]; } if (p[i]==p[j]) { ++j; } next[i]=j; } for (int i=0 ,j=0 ;i<length;++i) { while (j>0 && ch[i]!=p[j]) { j=next[j-1 ]; } if (ch[i]==p[j]) { ++j; } if (j==pattern.getLength ()) { return i-j+1 ; } } return -1 ; } void makeNext (const HeapString& pattern, int * next) const { if (next == nullptr ) return ; char * p = pattern.getCh (); int j = 0 ; next[0 ] = 0 ; for (int i = 1 ; i < pattern.getLength (); ++i) { while (j > 0 && p[i] != p[j]) { j = next[j - 1 ]; } if (p[i] == p[j]) { ++j; } next[i] = j; } } };
7, 二维数组类(TwoDArray)
1. 二维数组的存储本质(核心理论)
二维数组的物理存储形态唯一 :计算机底层不存在真正的「二维内存空间」,所有二维数组最终都需映射为一维连续内存 存储,本类的核心就是实现「二维逻辑结构 → 一维物理结构」的映射转换,属于顺序存储结构 在多维数据中的扩展应用。
2. 两种核心存储映射规则(数组寻址理论)
(1)行优先存储(Row-Major Order)
核心规则
按行序为主、列序为辅 的顺序存放元素,即先完整存放第 0 行的所有列元素,再存放第 1 行的所有列元素,以此类推,是最常用的存储方式(C/C++、Java 等语言默认采用)。
寻址公式(核心)
对于 rows 行 cols 列的二维数组,逻辑下标 (i,j) 映射到一维数组的物理下标为
物理下标 = i * cols + j
理论特点
同一行的元素在一维数组中连续存放,行内元素访问的局部性好,缓存命中率高;
行索引 i 决定元素所在的「行块起始位置」,列索引 j 决定行块内的偏移量。
(2)列优先存储(Column-Major Order)
核心规则
按列序为主、行序为辅 的顺序存放元素,即先完整存放第 0 列的所有行元素,再存放第 1 列的所有行元素,以此类推(Fortran 语言默认采用)。
寻址公式(核心)
对于 rows 行 cols 列的二维数组,逻辑下标 (i,j) 映射到一维数组的物理下标为
物理下标 = j * rows + i
理论特点
同一列的元素在一维数组中连续存放,适合以列为单位的批量数据操作;
列索引 j 决定元素所在的「列块起始位置」,行索引 i 决定列块内的偏移量。
3. 模板化二维数组核心特性
(1)数据类型无关性
基于模板编程思想 实现,底层存储与数据元素类型解耦,可适配任意基础类型(int、float、char)与自定义类型,满足通用化数据存储需求,遵循「一次定义、多类型复用」原则。
(2)内存管理理论
底层通过一维动态数组 实现,堆区内存分配大小为 rows \times cols \times \text{单个元素字节数},内存连续且可按需调整行列规模;
析构函数统一释放堆区内存,避免内存泄漏,内存生命周期由对象实例控制,支持运行期动态指定行列数。
4. 核心操作理论约束
元素访问(取值/赋值):核心前提是下标合法性校验 ,行索引 i 需满足 0 ≤ i < rows,列索引 j 需满足 0 ≤ j < cols,否则触发越界错误,本质是通过寻址公式完成「二维逻辑下标 → 一维物理下标」的转换,再执行内存读写;
存储顺序不可变性:行优先/列优先的存储规则在对象构造时确定,运行过程中不可修改,保证数组存储结构的一致性;
空间效率特性:无冗余存储,底层一维数组的空间大小与二维数组的元素总数完全匹配,空间利用率为 100%。
5. 二维数组顺序存储的通用优缺点
优点
元素物理连续,支持随机访问 ,通过寻址公式可在 O(1) 时间内定位任意位置元素;
内存管理简单,仅需维护一个一维动态数组的指针,无额外空间开销;
缓存友好性强,连续的内存访问可充分利用 CPU 缓存,提升数据读写效率。
缺点
行列规模确定后,动态扩容成本较高(需重新分配更大内存并完成元素拷贝);
仅适用于规整的二维数据 ,对稀疏矩阵等非规整多维数据,存在大量空间浪费。
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 template <typename ElemType>class TwoDArray {private : ElemType* data; int rows; int cols; bool rowMajor; public : TwoDArray (int r, int c, bool rowMajor = true ) { data = new ElemType[r * c]; rows = r; cols = c; this ->rowMajor = rowMajor; } ~TwoDArray () { delete [] data; } ElemType getElem (int i, int j) const { if (i < 0 || i >= rows || j < 0 || j >= cols) { throw invalid_argument ("TwoDArray::getElem: 索引超出范围" ); } ElemType e; if (rowMajor) { e = data[i * cols + j]; } else { e = data[j * rows + i]; } return e; } void setElem (int i, int j, ElemType e) { if (i < 0 || i >= rows || j < 0 || j >= cols) { throw invalid_argument ("TwoDArray::setElem: 索引超出范围" ); } if (rowMajor) { data[i * cols + j] = e; } else { data[j * rows + i] = e; } } int getRows () const { return rows; } int getCols () const { return cols; } };
8, 稀疏矩阵类(SparseMatrix)
(1)存储结构
三元组数组 :存储非零元素的行、列、值三元组,按行优先顺序排序,支持快速随机访问。
行/列指针数组 :记录每一行/列的第一个非零元素在三元组数组中的索引,支持快速定位行/列的非零元素范围。
(2)核心操作
插入/更新非零元素 :根据三元组数组中的排序规则,插入或更新指定位置的元素,保持三元组数组的有序性。
矩阵转置 :通过行/列指针数组和三元组数组,实现稀疏矩阵的转置操作,时间复杂度为 O(tu)。
矩阵乘法 :通过行/列指针数组和三元组数组,实现稀疏矩阵与另一个稀疏矩阵的乘法操作,时间复杂度为 O(tu * v)。
(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 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 #define MAX_TRIPLE 1000 template <typename ElemType>class SparseMatrix {private : struct Triple { int i; int j; ElemType e; bool operator <(const Triple& other) const { return (i < other.i) || (i == other.i && j < other.j); } }; Triple data[MAX_TRIPLE]; int mu; int nu; int tu; void sortTriples () { if (tu <= 0 ) return ; std::sort (data, data + tu, [](const Triple& a, const Triple& b) { return a < b; }); } public : SparseMatrix () : mu (0 ), nu (0 ), tu (0 ) {} void initMatrix (int m, int n) { if (m <= 0 || n <= 0 ) { std::cerr << "矩阵行列数必须为正!" << std::endl; return ; } mu = m; nu = n; tu = 0 ; } void insertElem (int i, int j, ElemType e) { if (i < 1 || i > mu || j < 1 || j > nu) { std::cerr << "行/列越界!i=" << i << ", j=" << j << " (max: " << mu << "," << nu << ")" << std::endl; return ; } int k = find (i, j); if (k != -1 ) { data[k].e = e; } else { if (tu >= MAX_TRIPLE) { std::cerr << "非零元素个数超出最大容量!" << std::endl; return ; } data[tu].i = i; data[tu].j = j; data[tu].e = e; tu++; sortTriples (); } } void transpose (SparseMatrix& res) { res.initMatrix (nu, mu); res.tu = tu; for (int i = 0 ; i < tu; ++i) { res.data[i].i = data[i].j; res.data[i].j = data[i].i; res.data[i].e = data[i].e; } res.sortTriples (); } void fastTranspose (SparseMatrix& res) { if (tu == 0 || mu == 0 || nu == 0 ) { res.initMatrix (nu, mu); res.tu = 0 ; return ; } res.initMatrix (nu, mu); res.tu = tu; std::vector<int > num (nu + 1 , 0 ) ; for (int i = 0 ; i < tu; ++i) { int col = data[i].j; num[col]++; } std::vector<int > cpot (nu + 1 , 0 ) ; for (int j = 1 ; j <= nu; ++j) { cpot[j] = cpot[j - 1 ] + num[j - 1 ]; } for (int i = 0 ; i < tu; ++i) { int col = data[i].j; int pos = cpot[col]; res.data[pos].i = data[i].j; res.data[pos].j = data[i].i; res.data[pos].e = data[i].e; cpot[col]++; } } ElemType getElem (int i, int j) const { if (i < 1 || i > mu || j < 1 || j > nu) { std::cerr << "行/列越界!" << std::endl; return ElemType (); } int k = find (i, j); return (k == -1 ) ? ElemType () : data[k].e; } void printMatrix () const { std::cout << "稀疏矩阵(" << mu << "行×" << nu << "列,非零元素数:" << tu << "):" << std::endl; for (int i = 1 ; i <= mu; ++i) { for (int j = 1 ; j <= nu; ++j) { ElemType val = getElem (i, j); std::cout << val << "\t" ; } std::cout << std::endl; } } void printTriples () const { std::cout << "三元组列表(行, 列, 值):" << std::endl; for (int i = 0 ; i < tu; ++i) { std::cout << "(" << data[i].i << ", " << data[i].j << ", " << data[i].e << ")" << std::endl; } } private : int find (int i, int j) const { int low = 0 , high = tu - 1 ; while (low <= high) { int mid = (low + high) / 2 ; if (data[mid].i == i && data[mid].j == j) { return mid; } else if (data[mid].i < i || (data[mid].i == i && data[mid].j < j)) { low = mid + 1 ; } else { high = mid - 1 ; } } return -1 ; } };
9, 广义表(Generalized List)
(1), 广义表定义
广义表是一种递归定义的数据结构,用于表示层次结构的数据。它由一个表头和一个表尾组成,表头可以是任意数据类型,表尾必须是另一个广义表。广义表可以表示复杂的层次结构,如树、图等。
(2), 广义表表示
广义表通常用括号表示,表头和表尾之间用逗号分隔。例如,(a, (b, c), d) 表示一个广义表,其中 a 是表头,(b, c) 是表尾,d 是另一个表头。
(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 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 class GeneralizedList {private : struct GLNode { int tag; union { char atom; vector<GLNode*>* sub; }; GLNode (char c) : tag (0 ), atom (c) {} GLNode () : tag (1 ), sub (new vector <GLNode*>()) {} ~GLNode () { if (tag==1 ) delete sub; } }; GLNode* head = nullptr ; public : ~GeneralizedList (){ clear (head); } void create (string s) { int i=0 ; clear (head); head = parse (s, i); } void print () { print (head);cout<<endl; } int length () { return head && head->tag==1 ? head->sub->size () : 1 ; } int depth () { return depth (head); } private : void clear (GLNode* p) { if (!p) return ; if (p->tag==1 ){ for (auto x:*p->sub) clear (x); } delete p; } GLNode* parse (string& s, int & i) { if (s[i]=='(' ){ i++; GLNode* p=new GLNode (); while (s[i]!=')' ){ p->sub->push_back (parse (s,i)); if (s[i]==',' ) i++; } i++; return p; }else { return new GLNode (s[i++]); } } void print (GLNode* p) { if (!p) return ; if (p->tag==0 ) cout<<p->atom; else { cout<<"(" ; for (int i=0 ;i<p->sub->size ();i++){ print ((*p->sub)[i]); if (i+1 <p->sub->size ()) cout<<"," ; } cout<<")" ; } } int depth (GLNode* p) { if (!p) return 0 ; if (p->tag==0 ) return 1 ; int mx=0 ; for (auto x:*p->sub) mx=max (mx,depth (x)); return mx+1 ; } };
10, 树(Tree)
(1), 二叉树 (Binary Tree)
二叉树定义
二叉树是每个结点最多有两个子树的树结构。它可以是空树,也可以有根节点和两个不相交的左、右子树。
二叉树操作
创建:根据给定的数据构建二叉树。
插入:在指定位置插入新元素。
删除:从指定位置删除元素。
遍历:前序、中序、后序或层次遍历。
查找:按值查找特定元素的位置。
高度与深度:计算树的高度和任意节点的深度。
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 template <typename ElemType>class BinaryTree {private :public : struct BTNode { ElemType data; BTNode* lchild; BTNode* rchild; BTNode (ElemType val = 0 ) : data (val), lchild (nullptr ), rchild (nullptr ) {} }; BTNode* root; BinaryTree () : root (nullptr ) {} ~BinaryTree () { destroy (root); } void destroy (BTNode* node) { if (node == nullptr ) return ; destroy (node->lchild); destroy (node->rchild); delete node; } bool isEmpty () const { return root == nullptr ; } BTNode* getRoot () const { return root; } void createByPreorder (vector<ElemType>& preorder, vector<ElemType>& inorder) { unordered_map<ElemType, int > mp; for (int i = 0 ; i < inorder.size (); ++i) { mp[inorder[i]] = i; } function<BTNode* (int &, vector<ElemType>&, int , int )> create = [&](int & i, vector<ElemType>& inorder, int l, int r) -> BTNode* { if (l > r) return nullptr ; int mid = mp[preorder[i]]; auto * node = new BTNode (preorder[i]); ++i; node->lchild = create (i, inorder, l, mid - 1 ); node->rchild = create (i, inorder, mid + 1 , r); return node; }; int i = 0 ; root = create (i, inorder, 0 , inorder.size () - 1 ); } void createByBackorder (vector<ElemType>& backorder, vector<ElemType>& inorder) { unordered_map<ElemType, int > mp; for (int i = 0 ; i < inorder.size (); ++i) { mp[inorder[i]] = i; } function<BTNode* (int &, vector<ElemType>&, int , int )> create = [&](int & i, vector<ElemType>& inorder, int l, int r) -> BTNode* { if (l > r) return nullptr ; int mid = mp[backorder[i]]; BTNode* node = new BTNode (backorder[i]); --i; node->rchild = create (i, inorder, mid + 1 , r); node->lchild = create (i, inorder, l, mid - 1 ); return node; }; int i = backorder.size () - 1 ; root = create (i, inorder, 0 , inorder.size () - 1 ); } void createByFullBinaryTree (const ElemType* arr, int len) { function<BTNode* (int )> create = [&](int i) -> BTNode* { if (i >= len) return nullptr ; BTNode* node = new BTNode (arr[i]); node->lchild = create (2 * i + 1 ); node->rchild = create (2 * i + 2 ); return node; }; root = create (0 ); } void preorderTraverse (void (*visit)(ElemType)) const { function<void (BTNode*)> preorder = [&](BTNode* node) { if (node == nullptr ) return ; visit (node->data); preorder (node->lchild); preorder (node->rchild); }; preorder (root); } void inorderTraverse (void (*visit)(ElemType)) const { function<void (BTNode*)> inorder = [&](BTNode* node) { if (node == nullptr ) return ; inorder (node->lchild); visit (node->data); inorder (node->rchild); }; inorder (root); } void postorderTraverse (void (*visit)(ElemType)) const { function<void (BTNode*)> postorder = [&](BTNode* node) { if (node == nullptr ) return ; postorder (node->lchild); postorder (node->rchild); visit (node->data); }; postorder (root); } void levelorderTraverse (void (*visit)(ElemType)) const { queue<BTNode*> q; q.push (root); while (!q.empty ()) { BTNode* node = q.front (); q.pop (); if (node == nullptr ) continue ; visit (node->data); q.push (node->lchild); q.push (node->rchild); } } void preorderTraverseNonRec (void (*visit)(ElemType)) const { stack<BTNode*> s; s.push (root); while (!s.empty ()) { BTNode* node = s.top (); s.pop (); if (node == nullptr ) continue ; visit (node->data); s.push (node->rchild); s.push (node->lchild); } } void inorderTraverseNonRec (void (*visit)(ElemType)) const { stack<BTNode*> s; BTNode* node = root; while (node != nullptr || !s.empty ()) { if (node != nullptr ) { s.push (node); node = node->lchild; } else { node = s.top (); s.pop (); visit (node->data); node = node->rchild; } } } void postorderTraverseNonRec (void (*visit)(ElemType)) const { stack<BTNode*> s; BTNode* node = root; BTNode* lastVisited = nullptr ; while (node != nullptr || !s.empty ()) { if (node != nullptr ) { s.push (node); node = node->lchild; } else { node = s.top (); if (node->rchild == nullptr || node->rchild == lastVisited) { visit (node->data); lastVisited = node; s.pop (); node = nullptr ; } else { node = node->rchild; } } } } int getHeight () const { function<int (BTNode*)> getHeight = [&](BTNode* node) { if (node == nullptr ) return 0 ; return max (getHeight (node->lchild), getHeight (node->rchild)) + 1 ; }; return getHeight (root); } int getLeafCount () const { function<int (BTNode*)> getLeafCount = [&](BTNode* node) { if (node == nullptr ) return 0 ; if (node->lchild == nullptr && node->rchild == nullptr ) return 1 ; return getLeafCount (node->lchild) + getLeafCount (node->rchild); }; return getLeafCount (root); } BTNode* findNode (ElemType val) const { function<BTNode* (BTNode*)> findNode = [&](BTNode* node) -> BTNode* { if (node == nullptr ) return nullptr ; if (node->data == val) return node; BTNode* left = findNode (node->lchild); if (left != nullptr ) return left; return findNode (node->rchild); }; return findNode (root); } void setRoot (BTNode *pNode) { root = pNode; } };
(2), 二叉树与树与森林 (Tree and Forest)
树(Tree)
树是一种非线性的数据结构,由n(n>=0)个结点组成,其中有一个特殊的结点称为根结点,其余结点可分为m(m>=0)个互不相交的子树。树是递归定义的,即树的结点也可以是树。
森林(Forest)
森林是m(m>=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 template <typename ElemType>class TreeForest {private :public : typedef typename BinaryTree<ElemType>::BTNode BTNode; struct CSNode { ElemType data; vector<CSNode*> children; explicit CSNode (ElemType val = 0 ) : data(val) { } }; BTNode* treeToBinaryTree (CSNode* treeRoot) { function<BTNode* (CSNode*)> treeToBinaryTree = [&](CSNode* tree) -> BTNode *{ if (tree == nullptr ) return nullptr ; auto * binary = new BTNode (tree->data); if (tree->children.empty ()) return binary; binary->lchild = treeToBinaryTree (tree->children[0 ]); BTNode* cur = binary->lchild; for (int i=1 ; i<tree->children.size (); i++) { cur->rchild = treeToBinaryTree (tree->children[i]); cur = cur->rchild; } return binary; }; return treeToBinaryTree (treeRoot); } CSNode* binaryTreeToTree (BTNode* binaryRoot) { function<CSNode* (BTNode*)> binaryTreeToTree = [&](BTNode* binary) -> CSNode* { if (binary == nullptr ) return nullptr ; CSNode* tree = new CSNode (binary->data); if (binary->lchild == nullptr ) return tree; tree->children.push_back (binaryTreeToTree (binary->lchild)); BTNode* cur = binary->lchild; while (cur->rchild != nullptr ) { tree->children.push_back (binaryTreeToTree (cur->rchild)); cur = cur->rchild; } return tree; }; return binaryTreeToTree (binaryRoot); } BTNode* forestToBinaryTree (vector<CSNode*>& forest) { if (forest.empty ()) return nullptr ; BTNode* binaryRoot = treeToBinaryTree (forest[0 ]); BTNode* cur = binaryRoot; for (int i=1 ; i<forest.size (); i++) { cur->rchild = treeToBinaryTree (forest[i]); cur= cur->rchild; } return binaryRoot; } vector<CSNode*> binaryTreeToForest (BTNode* binaryRoot) { vector<CSNode*> forestRoots; if (binaryRoot == nullptr ) return forestRoots; BTNode * cur = binaryRoot; while (cur != nullptr ) { BTNode * tmp = cur->rchild; cur->rchild = nullptr ; forestRoots.push_back (binaryTreeToTree (cur)); cur = tmp; } return forestRoots; } void treePreorderTraverse (CSNode* root, void (*visit)(ElemType)) const { visit (root->data); for (CSNode* child : root->children) { treePreorderTraverse (child, visit); } } void treePostorderTraverse (CSNode* root, void (*visit)(ElemType)) const { for (CSNode* child : root->children) { treePostorderTraverse (child, visit); } visit (root->data); } void forestPreorderTraverse (vector<CSNode*>& forest, void (*visit)(ElemType)) const { for (auto &tree : forest) treePreorderTraverse (tree, visit); } };
(3), 二叉搜索树 (Binary Search Tree)
定义
二叉搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,它满足以下性质:
每个结点的值大于其左子树中所有结点的值;
每个结点的值小于其右子树中所有结点的值;
操作
查找:从根结点开始,如果根结点的值等于查找的值,则查找成功;如果查找的值小于根结点的值,则在左子树中继续查找;如果查找的值大于根结点的值,则在右子树中继续查找。如果查找的值不在树中,则查找失败。
插入:在二叉搜索树中插入一个结点,需要找到该结点的父结点,然后根据其值与父结点值的比较结果确定是作为左子结点还是右子结点。
删除:在二叉搜索树中删除一个结点,需要找到该结点的父结点,然后根据其值与父结点值的比较结果确定是作为左子结点还是右子结点。如果被删除的结点是叶子结点,则直接删除;如果被删除的结点只有一个子结点,则用该子结点替换被删除的结点;如果被删除的结点有两个子结点,则用其右子树中的最小结点替换被删除的结点。
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 template <typename KeyType, typename ElemType>class BinarySearchTree {private : struct BSTNode { KeyType key; ElemType data; BSTNode* lchild; BSTNode* rchild; BSTNode (KeyType k, ElemType val) : key (k), data (val), lchild (nullptr ), rchild (nullptr ) {} }; BSTNode* root; BSTNode* search (BSTNode* node,KeyType key) const { if (node == nullptr || node->key == key) return node; if (key < node->key) return search (node->lchild, key); else return search (node->rchild, key); } void insert (BSTNode*& node, KeyType k, ElemType e) const { if (node == nullptr ) return ; if (k < node->key) { if (node->lchild == nullptr ) node->lchild = new BSTNode (k, e); else insert (node->lchild, k, e); } else if (k > node->key) { if (node->rchild == nullptr ) node->rchild = new BSTNode (k, e); else insert (node->rchild, k, e); } else { node->data = e; } } void remove (BSTNode*& node, KeyType key) { if (node == nullptr ) return ; if (key < node->key) remove (node->lchild, key); else if (key > node->key) remove (node->rchild, key); else { if (node->lchild == nullptr ) { BSTNode *temp = node; node = node->rchild; delete temp; } else if (node->rchild == nullptr ) { BSTNode *temp = node; node = node->lchild; delete temp; } else { BSTNode *temp = node->rchild; while (temp->lchild != nullptr ) temp = temp->lchild; node->key = temp->key; node->data = temp->data; remove (node->rchild, temp->key); } } } void inorderTraverse (BSTNode* node, void (*visit)(KeyType, ElemType)) const { if (node == nullptr ) return ; inorderTraverse (node->lchild, visit); visit (node->key, node->data); inorderTraverse (node->rchild, visit); } public : BinarySearchTree () : root (nullptr ) {} ~BinarySearchTree () { destroy (root); } void destroy (BSTNode* node) { if (node == nullptr ) return ; destroy (node->lchild); destroy (node->rchild); delete node; } bool isEmpty () const { return root == nullptr ; } BSTNode* search (KeyType key) const { return search (root, key); } BSTNode* searchNonRec (KeyType key) const { BSTNode* node = root; while (node != nullptr && node->key != key) { if (key < node->key) node = node->lchild; else node = node->rchild; } return node; } void insert (KeyType key, ElemType data) { if (root == nullptr ) { root = new BSTNode (key, data); return ; } insert (root, key, data); } void remove (KeyType key) { remove (root, key); } void inorderTraverse (void (*visit)(KeyType, ElemType)) const { inorderTraverse (root, visit); } };
(4), 平衡二叉树 (AVl树)
定义
AVL树是一种自平衡的二叉搜索树,它满足以下性质:
每个结点的左子树和右子树的高度差不超过1;
每个结点的左子树和右子树都是AVL树;
操作
在二叉搜索树的基础上,增加以下操作:
平衡化:在插入或删除结点后,检查每个结点的平衡因子,如果平衡因子超过1,则进行相应的旋转操作,以保持AVL树的性质。
旋转操作:包括左旋、右旋、左右旋和右左旋四种操作,用于调整树的结构,以保持AVL树的性质。
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 179 180 181 182 183 184 template <typename KeyType, typename ElemType>class AVLTree {private : struct AVLNode { KeyType key; ElemType data; AVLNode* lchild; AVLNode* rchild; int height; AVLNode (KeyType k, ElemType val) : key (k), data (val), lchild (nullptr ), rchild (nullptr ), height (1 ) {} }; AVLNode* root; AVLNode* leftRotate (AVLNode* node) { if (node== nullptr ) return nullptr ; auto right = node->rchild; node->rchild = right->lchild; right->lchild = node; updateHeight (node); updateHeight (right); return right; } AVLNode* rightRotate (AVLNode* node) { if (node== nullptr ) return nullptr ; auto left=node->lchild; node->lchild=left->rchild; left->rchild=node; updateHeight (node); updateHeight (left); return left; } AVLNode* leftRightRotate (AVLNode* node) { if (node== nullptr ) return nullptr ; node->lchild=leftRotate (node->lchild); return rightRotate (node); } AVLNode* rightLeftRotate (AVLNode* node) { if (node== nullptr ) return nullptr ; node->rchild= rightRotate (node->rchild); return leftRotate (node); } int getHeight (AVLNode* node) const { if (node == nullptr ){ return 0 ; } return node->height; } void updateHeight (AVLNode* node) { if (node== nullptr ) return ; node->height=std::max (getHeight (node->lchild), getHeight (node->rchild))+1 ; } int getBalance (AVLNode* node) const { if (node== nullptr ) return 0 ; return getHeight (node->lchild)- getHeight (node->rchild); } void balance (AVLNode*& node) { int b= getBalance (node); if (b>1 ) { if (getBalance (node->lchild)<0 ) { node = leftRightRotate (node); } else { node = rightRotate (node); } } else if (b<-1 ) { if (getBalance (node->rchild)>0 ) { node = rightLeftRotate (node); } else { node = leftRotate (node); } } } void insertNode (AVLNode*& node, KeyType key, ElemType data) { if (node== nullptr ) { node = new AVLNode (key, data); return ; } if (key < node->key) { insertNode (node->lchild, key, data); } else if (key > node->key) { insertNode (node->rchild, key, data); } else { node->data = data; } updateHeight (node); balance (node); } void removeNode (AVLNode*& node, KeyType key) { if (node== nullptr ) { return ; } if (key < node->key) { removeNode (node->lchild, key); } else if (key > node->key) { removeNode (node->rchild, key); } else { if (node->lchild== nullptr &&node->rchild== nullptr ) { delete node; node = nullptr ; } else if (node->lchild== nullptr ) { auto temp = node; node = node->rchild; delete temp; } else if (node->rchild== nullptr ) { auto temp = node; node = node->lchild; delete temp; } else { auto temp = node->rchild; while (temp->lchild!= nullptr ) { temp = temp->lchild; } node->key = temp->key; node->data = temp->data; removeNode (node->rchild, temp->key); } } updateHeight (node); balance (node); } AVLNode* search (AVLNode* node, KeyType key) const { if (node== nullptr ) return nullptr ; if (key < node->key) { return search (node->lchild, key); } else if (key > node->key) { return search (node->rchild, key); } else { return node; } } void inorderTraverse (AVLNode* node ,void (*visit)(KeyType, ElemType)) const { if (node== nullptr ) return ; inorderTraverse (node->lchild, visit); visit (node->key, node->data); inorderTraverse (node->rchild, visit); } public : AVLTree () : root (nullptr ) {} ~AVLTree () { destroy (root); } void destroy (AVLNode* node) { if (node== nullptr ) return ; destroy (node->lchild); destroy (node->rchild); delete node; } [[nodiscard]] bool isEmpty () const { return root== nullptr ; } AVLNode* search (KeyType key) const { return search (root, key); } void insert (KeyType key, ElemType data) { insertNode (root, key,data); } void remove (KeyType key) { removeNode (root, key); } void inorderTraverse (void (*visit)(KeyType, ElemType)) const { inorderTraverse (root, visit); } [[nodiscard]] bool isAVLTree () const { return abs (getBalance (root))<=1 ; } };
(5), 哈夫曼树 (Huffman Tree)
哈夫曼树定义
哈夫曼树(Huffman Tree)是一种特殊的二叉树,它是一种带权路径长度(WPL)最短的二叉树。哈夫曼树是一种最优二叉树,它的带权路径长度(WPL)是所有带权路径长度中最小的。
哈夫曼树的构造
哈夫曼树的构造过程如下:
创建N个权值均为W的叶子结点。
将这些结点构造成一个森林。
哈夫曼树的性质
哈夫曼树的每个结点都有两个子结点。
哈夫曼树的每个结点的权值都大于等于其子结点的权值。
哈夫曼树的带权路径长度(WPL)是所有带权路径长度中最小的。
哈夫曼树的应用
哈夫曼编码:哈夫曼树可以用于构造哈夫曼编码,哈夫曼编码是一种无损压缩编码,它可以有效地减少数据的存储空间。
哈夫曼树可以用于数据压缩和解压缩。
哈夫曼树可以用于数据传输和通信。
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 class HuffmanTree {private : struct HuffmanNode { int weight; bool isLeaf; ```cpp class HuffmanTree {private : struct HuffmanNode { int weight; bool isLeaf; char ch; HuffmanNode* lchild; HuffmanNode* rchild; HuffmanNode (int w = 0 ) : weight (w),isLeaf (false ), ch ('\0' ) ,lchild (nullptr ), rchild (nullptr ) {} }; HuffmanNode* ht; public : HuffmanTree () { } ~HuffmanTree () { destroyHuffman (ht); } int getWPL () const { int wpl=0 ; function<void (HuffmanNode*)> dfs = [&](HuffmanNode* node) { if (node== nullptr ) return ; wpl += node->weight; dfs (node->lchild); dfs (node->rchild); }; dfs (ht); return wpl; } void generateHuffmanCode (string& code) { vector<int > cnt (256 , 0 ) ; for (auto c : code) { cnt[c]++; } for (int i = 0 ; i < 256 ; i++) { if (cnt[i] > 0 ) { cout << (char ) i << ": " << cnt[i] << endl; } } priority_queue<HuffmanNode*, vector<HuffmanNode*>, function<bool (HuffmanNode*, HuffmanNode*)>> pq ( [](HuffmanNode* a, HuffmanNode* b) { return a->weight < b->weight; }); for (int i=0 ;i<256 ;i++) { if (cnt[i] > 0 ) { HuffmanNode* node = new HuffmanNode (cnt[i]); node->ch = (char ) i; node->isLeaf = true ; pq.push (node); } } while (pq.size () > 1 ) { HuffmanNode* left = pq.top (); pq.pop (); HuffmanNode* right = pq.top (); pq.pop (); HuffmanNode* parent = new HuffmanNode (left->weight + right->weight); parent->lchild = left; parent->rchild = right; pq.push (parent); } ht = pq.top (); } string getHuffmanCode (char ch) const { string code; function<void (HuffmanNode*, string)> dfs = [&](HuffmanNode* node, const string& path) { if (node == nullptr ) return ; if (node->isLeaf ) { if (node->ch == ch) { code = path; } return ; } dfs (node->lchild, path + "0" ); if (!code.empty ()) return ; dfs (node->rchild, path + "1" ); }; dfs (ht, "" ); return code; } void destroyHuffman (HuffmanNode* node) { if (node== nullptr ) return ; destroyHuffman (node->lchild); destroyHuffman (node->rchild); delete node; } };
(6), B-, B+树 (B-Tree, B+Tree)
B-树定义
B-树是一种自平衡的多路搜索树,它满足以下性质:
每个结点最多有m个孩子(m阶B-树);
根结点至少有2个孩子(m阶B-树);
每个非根结点至少有ceil(m/2)-1个关键字(m阶B-树);
每个非根结点最多有m-1个关键字(m阶B-树);
所有叶子结点都在同一层;
B+树定义
B+树是一种自平衡的多路搜索树,它满足以下性质:
每个结点最多有m个孩子(m阶B+树);
根结点至少有2个孩子(m阶B+树);
每个非根结点至少有ceil(m/2)-1个关键字(m阶B+树);
每个非根结点最多有m-1个关键字(m阶B+树);
所有叶子结点都在同一层;
B-树和B+树的比较
B-树和B+树都是自平衡的多路搜索树,它们都满足以下性质:
每个结点最多有m个孩子(m阶B-树);
根结点至少有2个孩子(m阶B-树);
每个非根结点至少有ceil(m/2)-1个关键字(m阶B-树);
每个非根结点最多有m-1个关键字(m阶B-树);
所有叶子结点都在同一层;
不同点
B-树每个结点最多有m-1个关键字,而B+树每个结点最多有m-2个关键字(因为B+树的叶子结点不存储关键字);
B-树每个结点的子树指针数=关键字数+1,而B+树每个结点的子树指针数=关键字数(因为B+树的叶子结点不存储关键字);
B-树的非叶子结点只用于索引,而B+树的非叶子结点只用于索引,而叶子结点存储实际数据;
B-树和B+树的用途
B-树和B+树都是自平衡的多路搜索树,它们都广泛应用于数据库和文件系统中。B-树和B+树的主要用途包括:
数据库索引:B-树和B+树都可以用于数据库索引,它们可以有效地提高数据库的查询性能。
文件系统:B-树和B+树都可以用于文件系统,它们可以有效地提高文件系统的读写性能。
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 class BTree {private : struct BTreeNode { vector<int > keys; vector<BTreeNode*> children; int keyNum; bool isLeaf; BTreeNode (int m, bool leaf = false ) : keys (m-1 ), children (m),keyNum (0 ) ,isLeaf (leaf) { for (int i = 0 ; i < m; i++) children[i] = nullptr ; } }; BTreeNode* root; int order; int minKeyNum; void splitChild (BTreeNode* parent, int idx) const { if (parent == nullptr || idx < 0 || idx >= parent->children.size () || parent->children[idx] == nullptr ) { return ; } BTreeNode* child = parent->children[idx]; int splitPos = minKeyNum; BTreeNode* newChild = new BTreeNode (order, child->isLeaf); for (int i = splitPos + 1 ; i < order - 1 ; i++) { newChild->keys[i - (splitPos + 1 )] = child->keys[i]; newChild->keyNum++; } child->keyNum = splitPos; if (!child->isLeaf) { for (int i = splitPos + 1 ; i < order; i++) { newChild->children[i - (splitPos + 1 )] = child->children[i]; child->children[i] = nullptr ; } } for (int i = parent->keyNum; i > idx; i--) { if (i < parent->keys.size ()) { parent->keys[i] = parent->keys[i - 1 ]; } } parent->keys[idx] = child->keys[splitPos]; parent->keyNum++; for (int i = parent->keyNum; i > idx + 1 ; i--) { if (i < parent->children.size ()) { parent->children[i] = parent->children[i - 1 ]; } } parent->children[idx + 1 ] = newChild; } void insertNonFull (BTreeNode* node, int key) { int i = node->keyNum - 1 ; if (node->isLeaf) { while (i >= 0 && key < node->keys[i]) { node->keys[i+1 ] = node->keys[i]; i--; } node->keys[i+1 ] = key; node->keyNum++; } else { while (i >= 0 && key < node->keys[i]) i--; i++; if (node->children[i]->keyNum == order - 1 ) { splitChild (node, i); if (key > node->keys[i]) i++; } insertNonFull (node->children[i], key); } } bool removeKey (BTreeNode* node, BTreeNode*& father, int key, int childIdx) { int i = 0 ; while (i < node->keyNum && key > node->keys[i]) i++; if (i < node->keyNum && key == node->keys[i]) { if (node->isLeaf) { for (int j = i; j < node->keyNum - 1 ; j++) node->keys[j] = node->keys[j + 1 ]; node->keyNum--; return true ; } BTreeNode* cur = node->children[i]; while (!cur->isLeaf) cur = cur->children[cur->keyNum]; int pred = cur->keys[cur->keyNum - 1 ]; node->keys[i] = pred; return removeKey (node->children[i], node, pred, i); } if (node->isLeaf) return false ; int child = i; if (node->children[child]->keyNum == minKeyNum) { if (child > 0 && node->children[child - 1 ]->keyNum > minKeyNum) { borrowFromLeft (node, child); } else if (child < node->keyNum && node->children[child + 1 ]->keyNum > minKeyNum) { borrowFromRight (node, child); } else { if (child < node->keyNum) mergeChild (node, child); else mergeChild (node, child - 1 ), child--; } } return removeKey (node->children[child], node, key, child); } int getPredecessor (BTreeNode* node, int idx) { if (node== nullptr ) return -1 ; BTreeNode * child = node->children[idx]; if (child== nullptr ) return -1 ; while (!child->isLeaf) { child = child->children[child->keyNum]; } return child->keys[child->keyNum-1 ]; } void mergeChild (BTreeNode* parent, int idx) { BTreeNode* left = parent->children[idx]; BTreeNode* right = parent->children[idx + 1 ]; left->keys[left->keyNum++] = parent->keys[idx]; for (int i = 0 ; i < right->keyNum; i++) left->keys[left->keyNum++] = right->keys[i]; if (!left->isLeaf) { for (int i = 0 ; i <= right->keyNum; i++) left->children[left->keyNum - right->keyNum + i] = right->children[i]; } for (int i = idx; i < parent->keyNum - 1 ; i++) parent->keys[i] = parent->keys[i + 1 ]; parent->keyNum--; for (int i = idx + 1 ; i <= parent->keyNum; i++) parent->children[i] = parent->children[i + 1 ]; parent->children[parent->keyNum + 1 ] = nullptr ; delete right; } void borrowFromLeft (BTreeNode* parent, int idx) { if (parent == nullptr || idx <= 0 ) throw runtime_error ("Error: No left sibling" ); BTreeNode* child = parent->children[idx]; BTreeNode* leftSib = parent->children[idx-1 ]; if (child == nullptr || leftSib == nullptr || leftSib->keyNum <= minKeyNum) return ; for (int i = child->keyNum; i > 0 ; --i) { child->keys[i] = child->keys[i-1 ]; } child->keys[0 ] = parent->keys[idx-1 ]; if (!child->isLeaf) { for (int i = child->keyNum + 1 ; i > 0 ; --i) { child->children[i] = child->children[i-1 ]; } child->children[0 ] = leftSib->children[leftSib->keyNum]; } child->keyNum++; parent->keys[idx-1 ] = leftSib->keys[leftSib->keyNum-1 ]; leftSib->keyNum--; } int getSuccessor (BTreeNode* node, int idx) { if (node == nullptr ) return -1 ; BTreeNode* child = node->children[idx]; if (child == nullptr ) return -1 ; while (!child->isLeaf) { child = child->children[0 ]; } return child->keys[0 ]; } void borrowFromRight (BTreeNode* parent, int idx) { BTreeNode* c = parent->children[idx]; BTreeNode* r = parent->children[idx + 1 ]; c->keys[c->keyNum] = parent->keys[idx]; if (!c->isLeaf) c->children[c->keyNum + 1 ] = r->children[0 ]; c->keyNum++; parent->keys[idx] = r->keys[0 ]; for (int i = 0 ; i < r->keyNum - 1 ; i++) r->keys[i] = r->keys[i + 1 ]; if (!r->isLeaf) for (int i = 0 ; i < r->keyNum; i++) r->children[i] = r->children[i + 1 ]; r->keyNum--; } pair<BTreeNode*,int > findFather (BTreeNode* node, BTreeNode* child) { if (node == nullptr || child == nullptr ) return {nullptr , -1 }; int i=0 ; for (; i <= node->keyNum; ++i) { if (node->children[i] == child) return {node, i}; if (i < node->keyNum && node->keys[i] > child->keys[0 ]) break ; } return findFather (node->children[i], child); } void fixNodeUnderflow (BTreeNode* parent, int idx) { if (parent == nullptr || idx < 0 || idx > parent->keyNum) return ; BTreeNode* curNode = parent->children[idx]; if (curNode == nullptr || curNode->keyNum >= minKeyNum) return ; if (idx > 0 && parent->children[idx-1 ]->keyNum > minKeyNum) { borrowFromLeft (parent, idx); } else if (idx < parent->keyNum && parent->children[idx+1 ]->keyNum > minKeyNum) { borrowFromRight (parent, idx); } else { if (idx == parent->keyNum) idx--; mergeChild (parent, idx); if (parent->keyNum < minKeyNum) { auto [father,i] = findFather (root, parent); if (father != nullptr ) { fixNodeUnderflow (father, i); } } } } bool isKeyExist (BTreeNode* node, int key) const { if (node == nullptr ) return false ; int i=0 ; for (; i < node->keyNum; ++i) { if (node->keys[i] == key) return true ; if (node->keys[i] > key) break ; } if (node->isLeaf) return false ; return isKeyExist (node->children[i], key); } public : BTree (int m) : order (m), minKeyNum ((m + 1 ) / 2 - 1 ), root (new BTreeNode (m, true )) {} ~BTree () { destroy (root); } void destroy (BTreeNode* node) { if (node == nullptr ) return ; for (int i = 0 ; i <= node->keyNum; ++i) { destroy (node->children[i]); } delete node; node= nullptr ; } bool search (int key) const { return isKeyExist (root, key); } void insert (int key) { if (root->keyNum == order - 1 ) { BTreeNode* newRoot = new BTreeNode (order, false ); newRoot->children[0 ] = root; splitChild (newRoot, 0 ); root = newRoot; } insertNonFull (root, key); } bool remove (int key) { BTreeNode* tmpFather = nullptr ; bool state = removeKey (root, tmpFather, key, -1 ); if (root->keyNum == 0 && !root->isLeaf) { BTreeNode* oldRoot = root; root = root->children[0 ]; delete oldRoot; } return state; } void printTree () const { if (root == nullptr || root->keyNum == 0 ) { cout << "B-Tree is empty!" << endl; return ; } vector<BTreeNode*> curLayer; curLayer.push_back (root); int level = 1 ; while (!curLayer.empty ()) { vector<BTreeNode*> nextLayer; cout << "Level " << level << ": " ; for (auto node : curLayer) { if (node == nullptr || node->keyNum == 0 ) continue ; for (int i = 0 ; i < node->keyNum; ++i) { cout << node->keys[i] << " " ; } cout << "| " ; for (int i = 0 ; i <= node->keyNum; ++i) { if (node->children[i] != nullptr ) { nextLayer.push_back (node->children[i]); } } } cout << endl; curLayer = nextLayer; level++; } } }; class BPlusTree {private : struct BPlusNode { vector<int > keys; vector<BPlusNode*> children; int keyNum; bool isLeaf; BPlusNode* nextLeaf; explicit BPlusNode (int m,bool leaf=false ) :keys(m),children(m+1 ),keyNum(0 ),isLeaf(leaf),nextLeaf(nullptr){ fill (children.begin (),children.end (),nullptr ); } }; int order, minKeyNum; BPlusNode* root; BPlusNode* headLeaf; void splitChild (BPlusNode* p,int idx) { BPlusNode* y=p->children[idx]; BPlusNode* z=new BPlusNode (order,y->isLeaf); int mid=order/2 ; if (y->isLeaf){ for (int i=mid;i<y->keyNum;i++) z->keys[z->keyNum++]=y->keys[i]; y->keyNum=mid; z->nextLeaf=y->nextLeaf; y->nextLeaf=z; for (int i=p->keyNum;i>idx;i--) p->keys[i]=p->keys[i-1 ]; p->keys[idx]=z->keys[0 ]; }else { for (int i=mid+1 ;i<y->keyNum;i++) z->keys[z->keyNum++]=y->keys[i]; for (int i=mid+1 ;i<=y->keyNum;i++) z->children[i-(mid+1 )]=y->children[i]; int up=y->keys[mid]; y->keyNum=mid; for (int i=p->keyNum;i>idx;i--) p->keys[i]=p->keys[i-1 ]; p->keys[idx]=up; } for (int i=p->keyNum+1 ;i>idx+1 ;i--) p->children[i]=p->children[i-1 ]; p->children[idx+1 ]=z; p->keyNum++; } void insertNonFull (BPlusNode* x,int k) { int i=x->keyNum-1 ; if (x->isLeaf){ while (i>=0 && k<x->keys[i]){ x->keys[i+1 ]=x->keys[i]; i--; } x->keys[i+1 ]=k; x->keyNum++; }else { while (i>=0 && k<x->keys[i]) i--; i++; if (x->children[i]->keyNum==order){ splitChild (x,i); if (k>x->keys[i]) i++; } insertNonFull (x->children[i],k); } } BPlusNode* findLeafNode (int k) const { BPlusNode* cur=root; while (!cur->isLeaf){ int i=0 ; while (i<cur->keyNum && k>=cur->keys[i]) i++; cur=cur->children[i]; } return cur; } void borrowFromLeft (BPlusNode* p,int idx) { BPlusNode* c=p->children[idx]; BPlusNode* l=p->children[idx-1 ]; if (c->isLeaf){ for (int i=c->keyNum;i>0 ;i--) c->keys[i]=c->keys[i-1 ]; c->keys[0 ]=l->keys[--l->keyNum]; p->keys[idx-1 ]=c->keys[0 ]; c->keyNum++; }else { for (int i=c->keyNum;i>0 ;i--) c->keys[i]=c->keys[i-1 ]; c->keys[0 ]=p->keys[idx-1 ]; p->keys[idx-1 ]=l->keys[--l->keyNum]; for (int i=c->keyNum+1 ;i>0 ;i--) c->children[i]=c->children[i-1 ]; c->children[0 ]=l->children[l->keyNum+1 ]; c->keyNum++; } } void borrowFromRight (BPlusNode* p,int idx) { BPlusNode* c=p->children[idx]; BPlusNode* r=p->children[idx+1 ]; if (c->isLeaf){ c->keys[c->keyNum++]=r->keys[0 ]; for (int i=0 ;i<r->keyNum-1 ;i++) r->keys[i]=r->keys[i+1 ]; r->keyNum--; p->keys[idx]=r->keys[0 ]; }else { c->keys[c->keyNum++]=p->keys[idx]; p->keys[idx]=r->keys[0 ]; for (int i=0 ;i<r->keyNum-1 ;i++) r->keys[i]=r->keys[i+1 ]; for (int i=0 ;i<=r->keyNum;i++) r->children[i]=r->children[i+1 ]; r->keyNum--; } } void mergeChild (BPlusNode* p,int idx) { BPlusNode* l=p->children[idx]; BPlusNode* r=p->children[idx+1 ]; if (l->isLeaf){ for (int i=0 ;i<r->keyNum;i++) l->keys[l->keyNum++]=r->keys[i]; l->nextLeaf=r->nextLeaf; }else { l->keys[l->keyNum++]=p->keys[idx]; for (int i=0 ;i<r->keyNum;i++) l->keys[l->keyNum++]=r->keys[i]; for (int i=0 ;i<=r->keyNum;i++) l->children[l->keyNum-r->keyNum+i]=r->children[i]; } for (int i=idx;i<p->keyNum-1 ;i++) p->keys[i]=p->keys[i+1 ]; for (int i=idx+1 ;i<=p->keyNum;i++) p->children[i]=p->children[i+1 ]; p->keyNum--; delete r; } void fixUnderflow (BPlusNode* p,int idx) { if (p->children[idx]->keyNum>=minKeyNum) return ; if (idx>0 && p->children[idx-1 ]->keyNum>minKeyNum) borrowFromLeft (p,idx); else if (idx<p->keyNum && p->children[idx+1 ]->keyNum>minKeyNum) borrowFromRight (p,idx); else { if (idx==p->keyNum) idx--; mergeChild (p,idx); if (p!=root && p->keyNum<minKeyNum){ auto [fa,i]=findFather (root,p); if (fa) fixUnderflow (fa,i); } } } pair<BPlusNode*,int > findFather (BPlusNode* cur,BPlusNode* child) { if (cur->isLeaf) return {nullptr ,-1 }; for (int i=0 ;i<=cur->keyNum;i++){ if (cur->children[i]==child) return {cur,i}; if (i<cur->keyNum && child->keys[0 ]<cur->keys[i]) break ; } return findFather (cur->children[cur->keyNum],child); } public : explicit BPlusTree (int m) :order(m),minKeyNum(m/2 ),root(new BPlusNode(m,true)),headLeaf(root){ } void insert (int k) { if (root->keyNum==order){ BPlusNode* s=new BPlusNode (order,false ); s->children[0 ]=root; splitChild (s,0 ); root=s; } insertNonFull (root,k); } bool search (int k) const { BPlusNode* l=findLeafNode (k); for (int i=0 ;i<l->keyNum;i++) if (l->keys[i]==k) return true ; return false ; } bool remove (int k) { BPlusNode* leaf=findLeafNode (k); int i=0 ; while (i<leaf->keyNum && leaf->keys[i]!=k) i++; if (i==leaf->keyNum) return false ; for (int j=i;j<leaf->keyNum-1 ;j++) leaf->keys[j]=leaf->keys[j+1 ]; leaf->keyNum--; if (leaf!=root && leaf->keyNum<minKeyNum){ auto [fa,idx]=findFather (root,leaf); if (fa) fixUnderflow (fa,idx); } if (root->keyNum==0 && !root->isLeaf){ BPlusNode* old=root; root=root->children[0 ]; delete old; } return true ; } void printLeafLink () { BPlusNode* p=root; while (!p->isLeaf) p=p->children[0 ]; while (p){ for (int i=0 ;i<p->keyNum;i++) cout<<p->keys[i]<<" " ; p=p->nextLeaf; } cout<<"\n" ; } };
11, 图
(1), 广搜与深搜(图的遍历)
定义
广搜(BFS):从图的某个顶点出发,先访问该顶点,然后依次访问该顶点的所有未被访问过的邻接顶点,再按此规则访问这些邻接顶点的未被访问过的邻接顶点,直到所有顶点都被访问过。
深搜(DFS):从图的某个顶点出发,先访问该顶点,然后依次访问该顶点的所有未被访问过的邻接顶点,再按此规则访问这些邻接顶点的未被访问过的邻接顶点,直到所有顶点都被访问过。
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 class GraphTraversal {public : static void DFS_AM (const vector<vector<int >>& graph, int startIdx, vector<bool >& visited, void (*visit)(int )) { if (visited[startIdx]) return ; visited[startIdx] = true ; visit (startIdx); for (int i = 0 ; i < graph[startIdx].size (); ++i) { if (graph[startIdx][i] == 1 ) { DFS_AM (graph, i, visited, visit); } } } static void DFS_AL (const vector<vector<int >>& graph, int startIdx, vector<bool >& visited, void (*visit)(int )) { if (visited[startIdx]) return ; visited[startIdx] = true ; visit (startIdx); for (int nxt: graph[startIdx]) { DFS_AL (graph, nxt, visited, visit); } } static void BFS_AM (const vector<vector<int >>& graph, int startIdx, vector<bool >& visited, void (*visit)(int )) { queue<int > q; q.push (startIdx); while (!q.empty ()) { int cur = q.front (); q.pop (); if (visited[cur]) continue ; visited[cur] = true ; visit (cur); for (int i = 0 ; i < graph[cur].size (); ++i) { if (graph[cur][i] == 1 ) { q.push (i); } } } } static void BFS_AL (const vector<vector<int >>& graph, int startIdx, vector<bool >& visited, void (*visit)(int )) { queue<int > q; q.push (startIdx); while (!q.empty ()) { int cur = q.front (); q.pop (); if (visited[cur]) continue ; visited[cur] = true ; visit (cur); for (int nxt: graph[cur]) { q.push (nxt); } } } static void traverseAll_AM (const vector<vector<int >>& graph, void (*visit)(int )) { vector<bool > visited (graph.size(), false ) ; for (int i = 0 ; i < graph.size (); ++i) { if (!visited[i]) { BFS_AM (graph, i, visited, visit); } } } static void traverseAll_AL (const vector<vector<int >>& graph, void (*visit)(int )) { vector<bool > visited (graph.size(), false ) ; for (int i = 0 ; i < graph.size (); ++i) { if (!visited[i]) { BFS_AL (graph, i, visited, visit); } } } };
(2), 最小生成树
Prim算法介绍
Prim算法是一种贪心算法,用于在加权无向图中找到最小生成树(MST)。给定一个加权无向图,该算法从一个顶点开始,逐步扩展生成树,每次选择一个与生成树相连且权重最小的顶点加入生成树,直到所有顶点都被包含在生成树中。使用与稠密图相关的邻接矩阵表示图,时间复杂度为O(V^2),其中V为顶点数。
Kruskal算法介绍
Kruskal算法也是一种贪心算法,用于在加权无向图中找到最小生成树(MST)。该算法按照边的权重从小到大排序,依次选择权重最小的边加入生成树,直到所有顶点都被包含在生成树中。使用并查集数据结构来判断加入边是否会形成环,时间复杂度为O(ElogV),其中E为边数,V为顶点数。
并查集
并查集是一种数据结构,用于处理一些不相交集合的合并及查询问题。它支持两种操作:查找(Find)和合并(Union)。查找操作可以确定一个元素所属的集合,合并操作可以将两个集合合并为一个集合。并查集通常用于解决图论中的连通性问题,例如判断图中是否存在环。
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 class Union { vector<int > father; int n; public : Union (int n) : father (n), n (n) { for (int i = 0 ; i < n; ++i) { father[i] = i; } } int find (int x) { if (father[x] == x) { return x; } father[x] = find (father[x]); return father[x]; } void join (int x, int y) { int fx = find (x); int fy = find (y); if (fx != fy) { father[fx] = fy; } } bool isConnected (int x, int y) { return find (x) == find (y); } }; class GraphConnectivity {public : static void Prim (const vector<vector<pair<int , int >>>& graph) { if (graph.empty ()) return ; int n = graph.size (); vector<vector<int >> resEdges; vector<bool > inMST (n, false ) ; long long totalWeight = 0 ; inMST[0 ] = true ; for (int cnt = 0 ; cnt < n - 1 ; ++cnt) { int minWeight = INT_MAX; int u = -1 , v = -1 ; for (int i = 0 ; i < n; ++i) { if (!inMST[i]) continue ; for (auto & edge : graph[i]) { int neighbor = edge.first; int weight = edge.second; if (!inMST[neighbor] && weight < minWeight) { minWeight = weight; u = i; v = neighbor; } } } if (minWeight == INT_MAX) { cout << "The graph is disconnected, Minimum Spanning Tree does not exist!" << endl; return ; } resEdges.push_back ({u, v, minWeight}); totalWeight += minWeight; inMST[v] = true ; } cout << "Edges of Minimum Spanning Tree (start vertex end vertex):" << endl; for (auto & e : resEdges) { cout << e[0 ] << " " << e[1 ] << endl; } cout << "Total weight of Minimum Spanning Tree: " << totalWeight << endl; } static void Kruskal (int n,vector<vector<int >>& edges) { sort (edges.begin (), edges.end (), [](vector<int >& a, vector<int >& b) { return a[2 ] < b[2 ]; }); Union uf (n) ; vector<vector<int >> resEdges; long long totalWeight = 0 ; for (auto & edge: edges) { if (uf.isConnected (edge[0 ], edge[1 ])) continue ; uf.join (edge[0 ], edge[1 ]); resEdges.push_back (edge); totalWeight += edge[2 ]; } for (auto & edge: resEdges) { cout<<edge[0 ] << " " << edge[1 ] << endl; } cout << totalWeight << endl; } };
(3), 拓扑排序与关键路径
定义
拓扑排序是对有向无环图(DAG)的顶点进行排序,使得对于图中的每一条有向边 (u, v),顶点 u 在排序中都出现在顶点 v 之前。拓扑排序的结果可以唯一确定一个拓扑序列
应用场景
关键字路径问题:在项目中,关键路径指的是从开始到结束的一系列活动(或任务),这些活动的完成顺序不能改变,否则整个项目将无法按时完成。
找到第i个关键路径:在拓扑排序的基础上,可以找到从源点到汇点的最长路径。
找到每个节点的 最早开始时间和最晚开始时间,从而确定关键路径。
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 class DAGApplications {public : static vector<int > topologicalSort (const vector<vector<int >>& graph) { vector<int > inDegrees (graph.size(), 0 ) ; vector<int > res; for (int i=0 ;i<graph.size ();i++){ for (int j:graph[i]){ inDegrees[j]++; } } queue<int > q; for (int i = 0 ; i < inDegrees.size (); ++i) { if (inDegrees[i] == 0 ) { q.push (i); } } while (!q.empty ()) { int cur = q.front (); q.pop (); res.push_back (cur); for (int nxt: graph[cur]) { inDegrees[nxt]--; if (inDegrees[nxt] == 0 ) { q.push (nxt); } } } return res; } static void criticalPath (const vector<vector<pair<int , int >>>& graph, vector<vector<int >>& criticalEdges, int & projectTime) { int n=graph.size (); vector<int > inDegrees (graph.size(), 0 ) ; for (int i=0 ;i<graph.size ();i++){ for (auto [j, w] : graph[i]){ inDegrees[j]++; } } vector<int > VE (n,0 ) ; queue<int > q; int c=0 ; for (int i = 0 ; i < inDegrees.size (); ++i) { if (inDegrees[i] == 0 ) { q.push (i); VE[i] = 0 ; } } while (!q.empty ()) { int cur=q.front (); q.pop (); c++; for (auto [nxt, w]:graph[cur]){ inDegrees[nxt]--; if (inDegrees[nxt] == 0 ) { q.push (nxt); } VE[nxt]=max (VE[nxt],VE[cur]+w); } } if (c != n) { cout << "The graph is not a DAG." << endl; return ; } vector<int > VL (n,INT_MAX) ; vector<int > outDegrees (graph.size(), 0 ) ; vector<vector<pair<int , int >>> reverseGraph (n); for (int i=0 ;i<graph.size ();i++){ for (auto [j, w] : graph[i]){ outDegrees[i]++; reverseGraph[j].push_back ({i,w}); } } projectTime=0 ; for (int i = 0 ; i < outDegrees.size (); ++i) { if (outDegrees[i] == 0 ) { q.push (i); VL[i]=VE[i]; projectTime=max (projectTime,VL[i]); } } while (!q.empty ()) { int cur=q.front (); q.pop (); for (auto [pre, w]:reverseGraph[cur]){ outDegrees[pre]--; if (outDegrees[pre]== 0 ) { q.push (pre); } VL[pre]=min (VL[pre],VL[cur]-w); } } for (int i=0 ;i<n;i++){ for (auto [j, w]:graph[i]){ if (VE[i]+w==VL[j]){ criticalEdges.push_back ({i,j}); } } } } };
(4), 最短路径
定义
最短路径问题是指在一个加权图中,找到从源点到汇点的最短路径。最短路径问题有多种算法,包括Dijkstra算法、Bellman-Ford算法和Floyd-Warshall算法。
应用场景
地图导航:在地图导航中,最短路径算法可以用于计算从起点到终点的最短路径。
网络路由:在网络中,最短路径算法可以用于计算从一个路由器到另一个路由器的最佳路径。
项目管理:在项目中,最短路径算法可以用于计算从项目开始到结束的最短时间。
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 class ShortestPath {public : static void Dijkstra (const vector<vector<pair<int , int >>>& graph, int startIdx, vector<int >& dist) { dist.resize (graph.size (), INT_MAX); priority_queue<pair<int ,int >, vector<pair<int ,int >>, greater<pair<int ,int >>> pq; pq.push ({0 ,startIdx}); dist[startIdx]=0 ; while (!pq.empty ()) { auto [d, cur] = pq.top (); pq.pop (); if (d > dist[cur]) continue ; for (auto [nxt, w]: graph[cur]) { if (dist[cur] + w<dist[nxt]) { dist[nxt] = dist[cur] + w; pq.push ({dist[nxt], nxt}); } } } } static void Floyd (int n,const vector<vector<int >>& edges, vector<vector<int >>& dist, vector<vector<int >>& path) { dist.resize (n,vector <int >(n,INT_MAX)); path.resize (n,vector <int >(n,-1 )); for (int i=0 ;i<n;i++) { dist[i][i]=0 ; } for (auto & edge:edges){ dist[edge[0 ]][edge[1 ]]=edge[2 ]; path[edge[0 ]][edge[1 ]]=-2 ; } for (int k=0 ;k<n;k++){ for (int i=0 ;i<n;i++) { for (int j = 0 ; j < n; j++) { if (dist[i][k]!=INT_MAX && dist[k][j]!=INT_MAX && dist[i][j] > dist[i][k] + dist[k][j]) { dist[i][j] = dist[i][k] + dist[k][j]; path[i][j] = k; } } } } } static void printPath (int start, int end, vector<vector<int >>& paths, vector<int >& path) { if (start==end) { path.push_back (start); return ; } int mid=paths[start][end]; if (mid==-1 ){ cout<<"Not reachable" <<endl; return ; } if (mid==-2 ){ path.push_back (start); path.push_back (end); return ; } printPath (start,mid,paths,path); path.pop_back (); printPath (mid,end,paths,path); } };
12, 搜索
(1), 线性探测哈希
这里用一个有限map来表示
当发生哈希冲突时,线性探测会顺序检查下一个下标,直到找到空闲位置或遍历完所有下标。
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 template <typename K, typename V>class Unordered_Map_Limit {private : static constexpr int BASE = 1000 ; vector<pair<K, V>> data; bool * is_occupied; int hash (const K& key) const { size_t hash_val = std::hash<K>{}(key); return static_cast <int >(hash_val % BASE); } int find (const K& key) const { int idx = hash (key); for (int i = 0 ; i < BASE; ++i) { int cur_idx = (idx + i) % BASE; if (!is_occupied[cur_idx]) return -1 ; if (data[cur_idx].first == key) return cur_idx; } return -1 ; } public : Unordered_Map_Limit () : data (BASE), is_occupied (new bool [BASE]{false }) {} ~Unordered_Map_Limit () { delete [] is_occupied; } void insert (const K& key, const V& value) { int idx = find (key); if (idx != -1 ) { data[idx].second = value; return ; } int hash_idx = hash (key); for (int i = 0 ; i < BASE; ++i) { int cur_idx = (hash_idx + i) % BASE; if (!is_occupied[cur_idx]) { data[cur_idx] = make_pair (key, value); is_occupied[cur_idx] = true ; break ; } } } bool erase (const K& key) { int idx = find (key); if (idx == -1 ) return false ; is_occupied[idx] = false ; return true ; } bool get (const K& key, V& value) const { int idx = find (key); if (idx == -1 ) return false ; value = data[idx].second; return true ; } V& operator [](const K& key) { int idx = find (key); if (idx != -1 ) return data[idx].second; int hash_idx = hash (key); for (int i = 0 ; i < BASE; ++i) { int cur_idx = (hash_idx + i) % BASE; if (!is_occupied[cur_idx]) { data[cur_idx] = make_pair (key, V ()); is_occupied[cur_idx] = true ; return data[cur_idx].second; } } throw std::out_of_range ("Unordered_Map_Limit is full!" ); } bool empty () const { for (int i = 0 ; i < BASE; ++i) { if (is_occupied[i]) return false ; } return true ; } int size () const { int cnt = 0 ; for (int i = 0 ; i < BASE; ++i) { if (is_occupied[i]) cnt++; } return cnt; } };
(2), 无序字典(Unordered Dictionary)
使用哈希函数,当发生哈希冲突时,使用链地址法解决。
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 template <typename K, typename V>class Unordered_Map {private : static constexpr int BASE = 1000 ; vector<vector<pair<K, V>>> data; const pair<K, V> EMPTY_NODE; int hash (const K& key) const { size_t hash_val = std::hash<K>{}(key); return static_cast <int >(hash_val % BASE); } int find_index (const K& key) const { int idx = hash (key); for (const auto & bucket : data[idx]) { if (bucket.first == key) return idx; } return -1 ; } typename vector<pair<K, V>>::iterator find_iter (const K& key) { int idx = hash (key); for (auto it = data[idx].begin (); it != data[idx].end (); ++it) { if (it->first == key) return it; } return data[idx].end (); } void update (const K& key, const V& value) { auto it = find_iter (key); if (it != data[hash (key)].end ()) { it->second = value; } } public : Unordered_Map () : data (BASE), EMPTY_NODE (K (), V ()) {} ~Unordered_Map () = default ; void insert (const K& key, const V& value) { if (find_index (key) != -1 ) { update (key, value); return ; } int hash_idx = hash (key); data[hash_idx].emplace_back (key, value); } bool erase (const K& key) { int idx = hash (key); auto it = find_iter (key); if (it == data[idx].end ()) return false ; data[idx].erase (it); return true ; } V get (const K& key) const { int idx = hash (key); for (const auto & bucket : data[idx]) { if (bucket.first == key) return bucket.second; } return EMPTY_NODE.second; } V& operator [](const K& key) { int idx = hash (key); auto it = find_iter (key); if (it != data[idx].end ()) return it->second; data[idx].emplace_back (key, V ()); return data[idx].back ().second; } bool empty () const { for (int i = 0 ; i < BASE; ++i) { if (!data[i].empty ()) return false ; } return true ; } int size () const { int cnt = 0 ; for (int i = 0 ; i < BASE; ++i) { cnt += data[i].size (); } return cnt; } }; template <typename K>class Unordered_Set {private : static constexpr int BASE = 1000 ; vector<vector<K>> data; const K EMPTY_NODE; int hash (const K& key) const { size_t hash_val = std::hash<K>{}(key); return static_cast <int >(hash_val % BASE); } bool find (const K& key) const { int idx = hash (key); for (const auto & val : data[idx]) { if (val == key) return true ; } return false ; } public : Unordered_Set () : data (BASE), EMPTY_NODE (K ()) {} ~Unordered_Set () = default ; void insert (const K& key) { if (find (key)) return ; int hash_idx = hash (key); data[hash_idx].push_back (key); } bool erase (const K& key) { int idx = hash (key); for (auto it = data[idx].begin (); it != data[idx].end (); ++it) { if (*it == key) { data[idx].erase (it); return true ; } } return false ; } bool exist (const K& key) const { return find (key); } bool empty () const { for (int i = 0 ; i < BASE; ++i) { if (!data[i].empty ()) return false ; } return true ; } int size () const { int cnt = 0 ; for (int i = 0 ; i < BASE; ++i) { cnt += data[i].size (); } return cnt; } };
13, 排序
(1), 冒泡排序
冒泡排序是一种简单的排序算法,它重复地遍历要排序的列表,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历列表的工作是重复地进行直到没有再需要交换,也就是说该列表已经排序完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void bubbleSort (vector<int >& arr, bool log=false ) { for (int i=0 ;i<arr.size ()-1 ;i++){ for (int j=0 ;j<arr.size ()-1 -i;j++){ if (arr[j]>arr[j+1 ]) { swap (arr[j],arr[j+1 ]); } } if (log) { for (int j: arr) { cout<<j<<" " ; } cout<<endl; } } }
(2), 选择排序
选择排序是一种简单的排序算法,它的工作原理是每次从待排序的元素中选择最小(或最大)的一个,存放到排序序列的起始位置,直到全部待排序的元素排完。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void selectionSort (vector<int >& arr, bool log=false ) { for (int i=0 ;i<arr.size ()-1 ;i++){ int minIdx = i; for (int j=i+1 ;j<arr.size ();j++) { if (arr[j] < arr[minIdx]) { minIdx = j; } } swap (arr[i], arr[minIdx]); if (log) { for (int j: arr) { cout << j << " " ; } cout << endl; } } }
(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 void insertionSort (vector<int >& arr, bool log = false ) { if (arr.empty () || arr.size () <= 1 ) return ; vector<int > sorted; sorted.reserve (arr.size ()); for (int i = 0 ; i < arr.size (); i++) { int l = 0 , r = (int )sorted.size () - 1 ; int idx = (int )sorted.size (); while (l <= r) { int mid = l + (r - l) / 2 ; if (arr[i] <= sorted[mid]) { idx = mid; r = mid - 1 ; } else { l = mid + 1 ; } } sorted.insert (sorted.begin () + idx, arr[i]); } arr = move (sorted); if (log) { for (int num : arr) cout << num << " " ; cout << endl; } }
(4), 希尔排序
希尔排序是一种基于插入排序的排序算法,它的工作原理是先将待排序的元素按照一定的间隔分组,对每组元素进行插入排序,然后逐渐缩小间隔,重复以上过程,直到间隔为1,最后进行一次插入排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void shellSort (vector<int >& arr, bool log=false ) { for (int gap = arr.size ()/2 ; gap > 0 ; gap /= 2 ) { for (int i = gap; i < arr.size (); i++) { int j = i; int temp = arr[i]; while (j >= gap && arr[j-gap] > temp) { arr[j] = arr[j-gap]; j -= gap; } arr[j] = temp; } } if (log) { for (int num: arr) { cout<<num<<" " ; } cout<<endl; } }
(5), 快速排序
快速排序是一种基于分治法的排序算法,它的工作原理是选择一个基准元素,将待排序的元素分为两部分,一部分小于基准元素,一部分大于基准元素,然后对这两部分分别进行快速排序,最后将两部分合并起来。
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 void quickSort (vector<int >& arr, bool log=false , int randomType = 0 ) { function<void (int , int )> quickSort = [&](int left, int right) { if (left >= right) { return ; } int pivot = getPivot (arr, left, right, randomType); int i = left, j = right; while (i <= j) { while (arr[i] < pivot) i++; while (arr[j] > pivot) j--; if (i <= j) { swap (arr[i], arr[j]); i++; j--; } } quickSort (left, j); quickSort (i, right); if (log) { for (int num: arr) { cout<<num<<" " ; } cout<<endl; } }; quickSort (0 , arr.size ()-1 ); } int getPivot (vector<int >& arr, int left, int right, int type) { int pivotIdx = left; switch (type) { case 0 : pivotIdx = left; break ; case 1 : pivotIdx = right; break ; case 2 : pivotIdx = left + (right - left) / 2 ; break ; case 3 : srand ((unsigned )time (nullptr )); pivotIdx = left + rand () % (right - left + 1 ); break ; } return arr[pivotIdx]; }
(6), 归并排序
归并排序是一种基于分治法的排序算法,它的工作原理是将待排序的元素分为两部分,分别对这两部分进行归并排序,最后将两部分合并起来。
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 void mergeSort (vector<int >& arr, bool log=false ) { function<void (int , int )> mergeSort = [&](int left, int right) { if (left >= right) { return ; } int mid = left + (right - left) / 2 ; mergeSort (left, mid); mergeSort (mid + 1 , right); vector<int > temp (right - left + 1 ) ; int i = left, j = mid + 1 , k = 0 ; while (i <= mid || j <= right) { if (i== mid+1 ) { temp[k++] = arr[j++]; } else if (j == right+1 ) { temp[k++] = arr[i++]; } else if (arr[i] <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } for (int m=0 ;m<temp.size ();m++) { arr[left+m] = temp[m]; } }; mergeSort (0 , arr.size ()-1 ); if (log) { for (int num:arr) { cout<<num<<" " ; } cout<<endl; } }
(7), 堆排序
堆排序是一种基于堆数据结构的排序算法,它的工作原理是将待排序的元素构建成一个大顶堆(或小顶堆),然后将堆顶元素与堆底元素交换,将最大(或最小)元素放到正确的位置,然后对剩余的元素重新构建堆,重复以上过程,直到堆为空。
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 void heapSort (vector<int >& arr, bool log = false ) { function<void (int ,int , bool )> heapify = [&](int cur,int n, bool type) { if (type) { while (cur && arr[cur] < arr[(cur-1 )/2 ]) { swap (arr[cur], arr[(cur-1 )/2 ]); cur = (cur-1 )/2 ; } } else { while (cur*2 +1 <n) { int left = cur*2 +1 ; int right = cur*2 +2 ; int maxIdx = left; if (right<n && arr[right]<arr[maxIdx]) { maxIdx = right; } if (arr[cur]>arr[maxIdx]) { swap (arr[cur], arr[maxIdx]); cur = maxIdx; } else { break ; } } } }; for (int i=0 ;i<arr.size ();i++) { heapify (i, i+1 , true ); } vector<int > sortedArr; for (int i=0 ;i<arr.size ();i++) { sortedArr.push_back (arr[0 ]); swap (arr[0 ], arr[arr.size ()-1 -i]); heapify (0 , arr.size ()-1 -i, false ); if (log) { for (int j: sortedArr) { cout<<j<<" " ; } cout<<endl; } } arr = sortedArr; }
通过这个,产生有限队列
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 template <typename ElemType, typename STL = std::vector<ElemType>, typename Compare = std::less<ElemType>> class PriorityQueue {private : STL heap; Compare cmp; void up (int idx) { while (idx > 0 ) { int parent = (idx - 1 ) / 2 ; if (cmp (heap[parent], heap[idx])) { std::swap (heap[parent], heap[idx]); idx = parent; } else { break ; } } } void down (int idx) { int size = heap.size (); while (true ) { int left = 2 * idx + 1 ; int right = 2 * idx + 2 ; int target = idx; if (left < size && cmp (heap[target], heap[left])) { target = left; } if (right < size && cmp (heap[target], heap[right])) { target = right; } if (target != idx) { std::swap (heap[idx], heap[target]); idx = target; } else { break ; } } } public : PriorityQueue () = default ; PriorityQueue (const PriorityQueue&) = delete ; PriorityQueue& operator =(const PriorityQueue&) = delete ; PriorityQueue (PriorityQueue&&) = default ; PriorityQueue& operator =(PriorityQueue&&) = default ; ~PriorityQueue () = default ; void push (const ElemType& val) { heap.push_back (val); up (heap.size () - 1 ); } void push (ElemType&& val) { heap.push_back (std::move (val)); up (heap.size () - 1 ); } void pop () { if (empty ()) { throw std::out_of_range ("PriorityQueue::pop: queue is empty" ); } std::swap (heap[0 ], heap.back ()); heap.pop_back (); if (!empty ()) { down (0 ); } } ElemType& top () { if (empty ()) { throw std::out_of_range ("PriorityQueue::top: queue is empty" ); } return heap[0 ]; } const ElemType& top () const { if (empty ()) { throw std::out_of_range ("PriorityQueue::top: queue is empty" ); } return heap[0 ]; } bool empty () const noexcept { return heap.empty (); } size_t size () const noexcept { return heap.size (); } void clear () noexcept { heap.clear (); } };
结束~