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
- 475
