Linked list
Outline
- 什麼是 Linked list?
- Leetcode常見題型
- Linked list 與 Pointer
- Linux kernel 中的Linked list
什麼是Linked list?
假設有一串數組,該如何儲存於記憶體?
| 8 | 6 | 5 | 1 | 3 |
|---|
分配一段連續的記憶體

分配不連續的記憶體並以指標連接


因為記憶體是可以隨機存取(random access)的,導致兩種配置方式在資料操作上(e.g., 新增、刪除)的時間及空間成本各有不同。
陣列 vs. 串列
| Linked List | Array | |
|---|---|---|
| 空間 | 較複雜(需要額外pointer) | 較節省 |
| 查詢第i個元素 | O(n) | O(1) |
| 刪除第i個元素 | O(1) | O(n) |
| 優點 | 大小可不固定 | 隨機存取 |
使用時機
- 無法預期資料數量或頻繁變動資料數量時。
- 需要頻繁地新增/刪除資料時。
- 不需要快速查詢資料。
補充:如果array大小固定,python中的list與go中的slice是如何實現?

維護Dynamic Array


typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;
Dynamic Array 如何擴增?

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;Leetcode 常見題型

* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
}func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
head := &ListNode{0, nil}
node := head
for l1 != nil && l2 != nil {
if l1.Val <= l2.Val {
node.Next = l1
l1 = l1.Next
} else {
node.Next = l2
l2 = l2.Next
}
node = node.Next
}
if l1 == nil {
node.Next = l2
} else {
node.Next = l1
}
return head.Next
}利用dummy node作為起始

func reverseList(head *ListNode) *ListNode {
}func reverseList(head *ListNode) *ListNode {
var front *ListNode
curr, next := head, head
for curr != nil {
next = curr.Next
curr.Next = front
front, curr = curr, next
}
return front
}front
curr
next
1. 先記住下一個
2. 往回指
3. 更新front, curr
front
curr

func removeNthFromEnd(head *ListNode, n int) *ListNode {
}func removeNthFromEnd(head *ListNode, n int) *ListNode {
start := &ListNode{0, nil}
b, f := start, start
b.Next = head
for i := 0; i <= n; i++ {
f = f.Next
}
for f != nil {
b = b.Next
f = f.Next
}
b.Next = b.Next.Next
return start.Next
}假設list長度是m
f先走n -> 剩下m-n
b出發直到f到底 -> 走m-n -> 停在倒數n

func detectCycle(head *ListNode) *ListNode {
}func detectCycle(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return nil
}
slow, fast := head.Next, head.Next.Next
for fast != nil && fast.Next != nil && slow != fast {
slow = slow.Next
fast = fast.Next.Next
}
if fast == slow {
fast = head
} else {
return nil
}
for fast != slow {
fast = fast.Next
slow = slow.Next
}
return slow
}LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
lRUCache.get(1); // return 1
lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
lRUCache.get(2); // returns -1 (not found)
lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
lRUCache.get(1); // return -1 (not found)
lRUCache.get(3); // return 3
lRUCache.get(4); // return 4
Linked list 與 Pointer
參考jserv: Linked list

以刪除節點為例,考慮到target有可能是head
void remove_list_node(List *list, Node *target)
{
Node *prev = NULL;
Node *current = list->head;
// Walk the list
while (current != target) {
prev = current;
current = current->next;
}
// Remove the target by updating the head or the previous node.
if (!prev)
list->head = target->next;
else
prev->next = target->next;
}void remove_list_node(List *list, Node *target)
{
Node **indirect = &list->head;
while (*indirect != target)
indirect = &(*indirect)->next;
*indirect = target->next;
}維護指標的指標,檢查需要被更新的位址
原本的作法

利用pointer to pointer

func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
head := &ListNode{0, nil}
node := head
for l1 != nil && l2 != nil {
if l1.Val <= l2.Val {
node.Next = l1
l1 = l1.Next
} else {
node.Next = l2
l2 = l2.Next
}
node = node.Next
}
if l1 == nil {
node.Next = l2
} else {
node.Next = l1
}
return head.Next
}以剛才的 21. Merge Two Sorted Lists 為例
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
head := &ListNode{0, nil}
ptr := &head
for l1 != nil && l2 != nil {
if l1.Val < l2.Val {
*ptr = l1
l1 = l1.Next
} else {
*ptr = l2
l2 = l2.Next
}
ptr = &((*ptr).Next)
}
if l1 == nil {
*ptr = l2
} else {
*ptr = l1
}
return head
}利用pointer to pointer
利用dummy node
Linux Kernel 中的 Linked list
// usually
struct list_node {
int data;
struct list_node *next;
}
// linux/scripts/kconfig/list.h
struct list_head {
struct list_head *next, *prev;
};與常見的linked list 之區別
使用方式如下
struct student
{
char name[16];
int id;
struct list_head list;
};該如何取得 Jerry 的名字?
struct list_node *Jerry_list = (Tom->list).next;
struct student
{
char name[16];
int id;
struct list_head list;
};
struct list_node *Jerry_list = (Tom->list).next;
struct student *Jerry = container_of(Jerry_list, struct student, list);

container_of 這個macro如何實現?
#define container_of(ptr, type, member) ({\
const typeof( ((type *)0)->member ) *__mptr = (ptr);\
(type *)( (char *)__mptr - offsetof(type,member));})可拆分成兩部份
const typeof( ((type *)0)->member ) *__mptr = (ptr);以及
(type *)( (char *)__mptr - offsetof(type,member));struct student
{
char name[16];
int id;
struct list_head list;
};| name |
| id |
| list |
x
x+16
x+20
const typeof( ((type *)0)->member ) *__mptr = (ptr);利用ptr得到該member型態同值的__mptr
以member是list為例就是x+20
(但目前不知道offset為20)
(type *)( (char *)__mptr - offsetof(type,member));這段敘述代表以零為起始位址算出member這個成員的相對位址
20
x+20
offsetof 如何實現?
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)將數值 0 強制轉型成TYPE指標型別
0 會被當作該TYPE的起始地址
因為起始位址等於 0
所以MEMBER的位址也就等於MEMBER與起始位址 0 的偏移(offset)。
offsetof(struct student, list)); //20e.g.,
補充: 為何偏移量不是20?
offsetof(struct student, list)); //20struct student
{
char name[16];
int id;
struct list_head list;
};| name |
| id |
| list |
x
x+16
x+20

Data structure alignment
Reference
Linked list
By nathanlin
Linked list
- 514