[[id[id_[id_[id_1297[id_493927199]7591]655[id_[id_[id_495957126]58058169]5426[id_421845368]6]5673]13209284][id_49719[id_152[id_19048[id_695580725]64]85481]988]30[id_1324685047]4311]d_7[id_324416065]9471603]
[id_824955103]
链表由若干个结构单元构成,这些单元在内存中并非必须连续排列;每个单元内含有一个数据项以及一个指向后续单元的链接指针。
依据指针的指向及数量,链表一般可划分为单链表、双向链表以及循环链表三种类型。
实际使用中,双向链表更为常用。
若对首个结构体实施直接操作,编程过程中可能会遭遇若干棘手难题,诸如:
此类任务可能相当复杂,或者需要在程序中特别处理,这对编程构成了挑战。实际上,只需在首部结构之前添加一个头文件,问题便能迎刃而解。
在多数情况下,我们习惯将 Header 设定在位置 0,尽管我个人对此并不赞同。
单链表
[id_1044590037]
1type Node struct [id_2060582320]
2 Data interface{}
3 Next *Node
4}
显而易见,链表的访问机制与顺序表存在显著差异,这就导致了代码在结构上发生了较大变动。
初始化
1func New() *Node {
2 return &Node{}
3}
取值
1func (n *Node) Get(i int) (interface{}, error) {
2 if i < 0 {
3 return nil, errors.New("index out of range")
4 }
5 for j := 0; j < i; j++ {
6 n = n.Next
7 if n == nil {
8 return nil, errors.New("index out of range")
9 }
10 }
11 return n.Data, nil
12}
插入
1func (n *Node) Insert(i int, data interface{}) error {
2 if i < 0 {
3 return errors.New("index out of range")
4 }
5 for j := 0; j < i; j++ {
6 n = n.Next
7 if n == nil {
8 return errors.New("index out of range")
9 }
10 }
11 n.Next = &Node{Data: data, Next: n.Next}
12 return nil
13}
删除
1func (n *Node) Delete(i int) error {
2 if i < 0 {
3 return errors.New("index out of range")
4 }
5 for j := 0; j < i; j++ {
6 n = n.Next
7 if n == nil {
8 return errors.New("index out of range")
9 }
10 }
11 n.Next = n.Next.Next
12 return nil
13}
创建链表
创建链表根据节点插入位置的不同,可以分为头插法和尾插法。
头插法:
1func CreateHead(data []interface{}) *Node {
2 var head *Node = nil
3 for _, v := range data {
4 head := &Node{Data: v, Next: head}
5 }
6 return head
7}
尾插法:
1func CreateTail(data []interface{}) *Node {
2 var head *Node = nil
3 var tail *Node = nil
4 for _, v := range data {
5 if head == nil {
6 head = &Node{Data: v, Next: nil}
7 tail = head
8 } else {
9 tail.Next = &Node{Data: v, Next: nil}
10 tail = tail.Next
11 }
12 }
13 return head
14}
双向链表
双向链表的节点结构可以表示为:
1type Node struct {
2 Data interface{}
3 Prev *Node
4 Next *Node
5}
即每个节点多维护一个指向前一个节点的指针。
每次插入或删除节点时,需要同时修改前后节点的指针。
循环链表
循环链表是一种特殊的链表结构,其特点是链表的第一个节点与最后一个节点相互连接,即链表的起始节点的前一个节点指向链表的结束节点,而链表的结束节点的下一个节点又指向链表的起始节点。
与单链表相比,最大的区别在于遍历的边界条件不同。
在循环链表中,从链表的起始节点开始进行遍历,直至达到链表的末尾节点。此时,该末尾节点的直接后继节点恰好是链表的起始节点。基于此,遍历操作需要满足的边界条件是:
1for n.Next != head {
2 // ...
3}
一些练习LeetCode 707 设计链表
LeetCode 707 设计链表
一道链表设计题,基本涵盖了链表的常用操作。
这道题目不可轻视,一旦认真完成这一链表编程任务,你便会发现,在链表操作中,存在诸多不易把握的边界情况。
1type MyLinkedList struct {
2 Val int
3 Next *MyLinkedList
[id_139438102]}
5
6
7func Constructor() MyLinkedList {
8 // return a dummy node
9 return MyLinkedList{
10 Val: -1,
11 Next: nil,
12 }
13}
14
15
16func (this *MyLinkedList) Get(index int) int {
17 cur := this.Next
18 for i := 0; cur != nil; i++ {
19 if i == index {
20 return cur.Val
21 } else {
22 cur = cur.Next
23 }
24 }
25 return -1
26}
27
28
29func (this *MyLinkedList) AddAtHead(val int) {
30 new := &MyLinkedList{
31 Val: val,
32 Next: this.Next,
33 }
34 this.Next = new
35 return
36}
37
38
39func (this *MyLinkedList) AddAtTail(val int) {
40 cur := this
41 for cur.Next != nil {
42 cur = cur.Next
43 }

44 new := &MyLinkedList{
45 Val: val,
46 Next: nil,
47 }
48 cur.Next = new
49}
50
51
52func (this *MyLinkedList) AddAtIndex(index int, val int) {
53 if index < 0 {
54 this.AddAtHead(val)
55 return
56 }
57 if index == 0 {
58 this.AddAtHead(val)
59 return
60 }
61 cur := this
62 for i := 0; i < index; i++ {
63 if cur.Next != nil {
64 cur = cur.Next
65 } else {
66 if i == index {
67 this.AddAtTail(val)
68 return
69 } else {
70 return
71 }
72 }
73 }
74 new := &MyLinkedList{
75 Val: val,
76 Next: cur.Next,
77 }
78 cur.Next = new
79}
80
81
82func (this *MyLinkedList) DeleteAtIndex(index int) {
83 cur := this
84 for i := 0; i < index; i++ {
85 if cur.Next == nil {
86 return
87 }
88 cur = cur.Next
89 }
90 if cur.Next != nil {
91 cur.Next = cur.Next.Next
92 }
93 return
94}
95
96
97/**
98您的MyLinkedList对象将被创建并按照以下方式调用:
99 * obj := Constructor();
100 * param_1 := obj.Get(index);
101 * obj.AddAtHead(val);
102 * obj.AddAtTail(val);
103 * obj.AddAtIndex(index,val);
104 * obj.DeleteAtIndex(index);
105 */
此类单链表结构相对简单,将其转变为双向链表的任务亦非艰巨,特此留作读者们的思考练习。
LeetCode 21 合并两个有序链表
LeetCode 21 合并两个有序链表
这是一道基础题目,也是教材上的一道例题。
1func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
2 dummy := &ListNode{}
3 cur := dummy
4 l1 := list1
5 l2 := list2
6 for l1 != nil && l2 != nil {
7 若list1的规模较小,则将list1的节点进行接入;若list1规模较大,则采取相反的操作。
8 if l1.Val < l2.Val {
9 cur.Next = l1
10 l1 = l1.Next
11 } else {
12 cur.Next = l2
13 l2 = l2.Next
14 }
15 // cur 后移
16 cur = cur.Next
17 }
18 若list1与list2中尚存未处理的节点,则将这些节点依次附加至尾部。
19 if l1 != nil {
20 cur.Next = l1
21 }
22 if l2 != nil {
23 cur.Next = l2
24 }
25
26 return dummy.Next
27}
LeetCode 203 移除链表元素
LeetCode 203 移除链表元素
同样是基础题目,遍历一遍遇到就删就可以了。
1func removeElements(head *ListNode, val int) *ListNode {
2 dummy := &ListNode{
3 Val: -1,
4 Next: head,
5 }
6 cur := dummy
7 for cur.Next != nil {
8 if cur.Next.Val == val {
9 cur.Next = cur.Next.Next
10 } else {
11 cur = cur.Next
12 }
13 }
14 return dummy.Next
15}
LeetCode 19 删除链表的倒数第 N 个结点
LeetCode 19 删除链表的倒数第 N 个结点
若事先掌握了链表的长度,解决此题将变得极其容易,其难度几乎等同于小学的数学问题。然而,在大多数情况下,我们仅能获取链表的头指针,却无法得知链表的长度。
这个问题同样涉及双指针技术。我们首先让第一个指针从链表的首部出发,向前移动 n 个位置;与此同时,第二个指针保持静止。从第 n+1 步起,第二个指针也开始从链表的首部向前移动。由于两个指针始终保持 n 个位置的差距,因此当第一个指针抵达链表的末端时,第二个指针恰好位于倒数第 n 个节点上。
1func removeNthFromEnd(head *ListNode, n int) *ListNode {
2 dummy := &ListNode{
3 Val: -1,
4 Next: head,
5 }
6 fast := dummy
7 slow := dummy
8 for i := 0; i < n; i++ {
9 fast = fast.Next
10 }
11
12 for fast.Next != nil {
13 slow = slow.Next
14 fast = fast.Next
15 }
16 slow.Next = slow.Next.Next
17 return dummy.Next
18}
LeetCode 83 删除排序链表中的重复元素
LeetCode 83 删除排序链表中的重复元素
题目并不复杂。解决此题的最基本方法是对链表进行遍历,一旦发现当前节点的数值与紧随其后的节点数值相同,便将其后一个节点移除。
鉴于本题目要求至少保留一个关键点,且不会发生首节点被删除的情形,因此无需引入虚拟的首节点。
1func deleteDuplicates(head *ListNode) *ListNode {
2 cur := head
3 for cur != nil && cur.Next != nil {
4 if cur.Val == cur.Next.Val {
5 cur.Next = cur.Next.Next
6 } else {
7 cur = cur.Next
8 }
9 }
10 return head
11}
本文来自作者[admin]投稿,不代表芝麻开门立场,如若转载,请注明出处:https://aizmkm.com/zshi/202507-2380.html
评论列表(3条)
我是芝麻开门的签约作者“admin”
本文概览:上一节我们介绍了线性表的顺序存储实现,这一节阐述线性表的链式存储实现。...
文章不错《线性表链式存储实现介绍:链表分类及首结构操作问题》内容很有帮助