Linked list

Outline

  • 什麼是 Linked list?
  • Leetcode常見題型
  • Linked list 與 Pointer
  • Linux kernel 中的Linked list

什麼是Linked list?

假設有一串數組,該如何儲存於記憶體?

8 6 5 9 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

我看Joma幹片學到的

他還為此拍了一部解釋影片

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)); //20

e.g.,

補充: 為何偏移量不是20?

offsetof(struct student, list)); //20
struct student
{
    char name[16];
    int id;
    struct list_head list;
};
name
id
list

x

x+16

x+20

Data structure alignment

Reference

Made with Slides.com