======================= 基础知识 =======================
包含数据域(内置类型,任意定义的结构), 指针域(绝对地址:pointer, 相对地址:下标...),通过指针域的值指向下一个位置,形成一个线性结构;

复杂度:插入O(1), 查找O(n), 更新O(1), 删除O(1);

特点:不适合快速定位查找数据,适合动态的插入和删除数据场景

  • 扩展:
  1. 单向链表: 只能向下寻找下一个节点
  2. 双向链表: 可以向下寻找下一个节点,也可以向上寻找上一个节点;
  3. hash 链表:通过对每一个链表对应的数据存储到一个hash map 中,这样可以快速获取某个值对应的节点位置,从而解决上面说的查找复杂度O(n)-> O(1);

======================= 代码演示 =======================
简单实现(基于Node);

 1 struct Node {
 2     Node(int val): data(val), next(nullptr){}
 3     Node(): Node(-1) {}
 4     int data;
 5     Node *next;
 6 };
 7 
 8 int main(){ 
 9     Node *head = new Node(1);
10     head->next = new Node(2);
11     head->next->next = new Node(3);
12 
13     Node *temp = head;
14     while(temp->next) {
15         cout << temp->data << "->";
16         temp = temp->next;
17     }
18     cout << temp->data << endl;
19     return 0;
20 }
Node

 

简单实现(基于数组):同种数据结构可能存在多种表现形式(trie实现方式也是这样),重点是数据结构自身特点以及针对解决的问题;

 1 int data[10];
 2 int nt[10];
 3 
 4 void add(int index, int nxt, int val) {
 5     nt[index] = nxt;
 6     data[nxt] = val;
 7 }
 8 int main()
 9 {
10     int head = 4;
11     add(4, 7, 1);
12     add(7, 2, 2);
13     add(2, 8, 3);
14     add(8, 5, 9);
15 
16     while(nt[head] != 0) {
17         printf("%d -> ", data[nt[head]]);
18         head = nt[head];
19     }
20     printf("\n");
21     return 0;
22 }
Node(using array)

 

面对对象方式:

 1 struct Node {
 2     Node(int val): data(val), next(nullptr){}
 3     Node(): Node(-1) {}
 4     int data;
 5     Node *next;
 6 };
 7 
 8 class List{
 9     Node *pre;
10     int size;
11 
12     public:
13     List(): size(0) {
14         pre = new Node();  //前置节点为-1;
15     }
16 
17     void __del(Node *pn) {
18         if(pn->next) __del(pn->next);
19         delete pn;
20         return;
21     }
22     ~List() {__del(pre);}
23 
24     void addNode(int val) {
25         Node *temp = pre;
26         while(temp->next) temp = temp->next;
27         temp->next = new Node(val);
28         size += 1;
29         return;
30     }
31     bool searchNode(int val) {
32         if(!size) return false;
33         Node *temp = pre;
34         while(temp->next) {
35             if(temp->next->data == val) return true;
36             temp = temp->next;
37         }
38         return false;
39     }
40 
41     void delNode(int val) {
42         if(!size) return;
43         Node *temp = pre;
44         while(temp->next) {
45             if(temp->next->data == val) {
46                 temp->next = temp->next->next;
47                 size -= 1;
48                 break;
49             }
50             temp = temp->next;
51         }
52         return ;
53     }
54 
55     void output() {
56         if(!size) return;
57         Node *temp = pre->next;
58         while(temp->next){
59             cout << temp->data << "->";
60             temp = temp->next;
61         }
62         cout << temp->data << endl;
63         return;
64     }
65 
66     int getSize() {
67         return size;
68     }
69 };
70 
71 int main() {
72     List *l1 = new List();
73     for(int i = 10; i < 25; ++i) l1->addNode(i);
74     cout << "list size is: " << l1->getSize() << endl;
75     l1->output();
76     
77     cout << "search 10 in list is " << l1->searchNode(10) << endl;;
78 
79     cout << "search 25 in list is " << l1->searchNode(25) << endl;;
80 
81     l1->delNode(18);
82     l1->output();
83     return 0;
84 }
OOP

 

OS 中对于内存使用(待更新):

======================= 经典问题 =======================

1. 环形链表,以及找到第一个重复节点(leetcode141, 202)
解决思路:
a.通过hash map 实现对环形链表中每一个节点存储下面,当到达重复出现的位置hash map 中可以找到;

 1 class Solution {
 2 public:
 3     bool hasCycle(ListNode *head) {
 4         if(!head) return false; 
 5         unordered_set<ListNode*> us;
 6         while(head) {
 7             if(us.find(head) != us.end()) return true;
 8             us.insert(head);
 9             head = head->next;
10         }
11         return false;
12     }
13 };
hashMap_Solution

快慢指针(fast_pointer:step = 2; slow_pointer: step = 1): 无环链表中,快慢指针永远不会相遇,但是环状链表终究会重合;

注意点: 快慢指针重合的条件:在开始时快慢指针也是重合的!

 

 1 class Solution {
 2 public:
 3     bool hasCycle(ListNode *head) {
 4         if( head == NULL) return false;
 5         ListNode* Slow = head ,* Fast = head ; 
 6 
 7         while((Fast->next != NULL) && (Fast->next->next !=NULL))
 8         {
 9             Slow = Slow->next;
10             Fast = Fast->next->next;
11             if(Slow == Fast) return true;
12         }
13         return false; 
14     }
15 };
快慢指针

2. 翻转链表前k个节点(区间段节点) (leetcode92) (加强版:25)
---代码技巧: 虚拟头节点的使用来解决; 一旦链表的头节点有可能被改编,就可能需要用到虚拟头节点,可以有效减少边界条件的判断;

1 //默认k 小于链表长度,这里利用递推有效保存了必要的数据
2 Node *reverseKNode(Node *head, int k) {
3     if(k == 1) return head;
4     Node *tail = head->next, *np = reverseKNode(head->next, k - 1);
5     head->next = tail->next;  // 这两步很锻炼思维能力
6     tail->next = head;
7     return np;
8 }
ReverseNode

--锻炼虚拟节点技巧(leetcode 725):  优点在于可以把一些复杂的边界条件,归类为同一种操作方式,但是要理清楚哪些节点变动到哪里;

 1 /**
 2  * Definition for singly-linked list.
 3  * struct ListNode {
 4  *     int val;
 5  *     ListNode *next;
 6  *     ListNode() : val(0), next(nullptr) {}
 7  *     ListNode(int x) : val(x), next(nullptr) {}
 8  *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 9  * };
10  */
11 class Solution {
12 public:
13     vector<ListNode*> splitListToParts(ListNode* head, int k) {
14         vector<ListNode*> ans;
15 
16         int cnt = 0;
17         ListNode *pre = new ListNode(0, head);
18         while(head) cnt += 1, head = head->next;
19         int each = cnt / k, more = cnt % k;
20 
21 
22         for(int i = 0; i < k; ++i) {
23             int loop = each;
24             ans.push_back(pre->next);
25 
26             head = pre;
27             while(loop--) head = head->next;
28             if(more) head = head->next, more -= 1;
29 
30             if(head->next) {
31                 pre->next = head->next;
32                 head->next = nullptr;
33             }
34         }
35 
36         return ans;
37     }
38 };
PreNode

 

3. 复制复杂的链表:leetcode.offer35)

核心在于如何保存链表(有些节点还未分配的情况下)之间的相互关系(map 映射,插入新生成的节点在原节点之后,然后在打开成2个节点);

下面是通过自身节点链接到新节点来实现映射;

 1 /*
 2 // Definition for a Node.
 3 class Node {
 4 public:
 5     int val;
 6     Node* next;
 7     Node* random;
 8 
 9     Node(int _val) {
10         val = _val;
11         next = NULL;
12         random = NULL;
13     }
14 };
15 */
16 class Solution {
17 public:
18     Node* copyRandomList(Node* head) {
19         if(!head) return nullptr;
20 
21         unordered_map<Node*, Node*> um;
22         Node *pre = new Node(0), *temp_o = head, *temp_n = pre;
23 
24         while(temp_o){
25             Node *n = new Node(temp_o->val);
26             um[temp_o] = n;
27             temp_n->next = n;
28             temp_n = temp_n->next;
29             temp_o = temp_o->next;
30         }
31 
32         temp_n = pre;
33         temp_o = head;
34         while(temp_o){
35             temp_n->next->random = um[temp_o->random];
36             temp_o = temp_o->next;
37             temp_n = temp_n->next;
38         }
39         return pre->next;
40     }
41 };
不用map 映射方式

 

======================= 应用场景 =======================
(可以扩展细节)

  1. 操作系统内的动态内存分配;
    通过链表将原始 或 释放后的空闲内存碎片串联起来,管理剩余的内存空间;
  2. LRU缓存淘汰算法(hash链表方式实现数据串联):
    如果当前缓存只能存放4 个数据,当缓存未命中时,需要将新数据存入缓存中,此时会将新数据放在链表尾部,然后将头部节点删除;

 

posted on 2021-11-23 23:29  学海一扁舟  阅读(69)  评论(0编辑  收藏  举报