Professional Documents
Culture Documents
04 链表
04 链表
头指针 L 线性链表 结点
指针域 next
data
a1 a2 an
next …
^
头结点 首结点
01 typedef struct LNode{
02 ElemType data;
03 struct LNode *next;
04 }LNode,*LinkList;
练习 1 :说出下列算法的功能。
a1 a2 an
…
^
p = L->next
计数 j=1
L p 获取第 i 个元素
a1 a2 a2
…
^
p = p->next
计数 j=2
获取链表元素
01 Status GetElem_L(LinkList L, int i, ElemType *e){
02 int j; LNode *p; //i 从 1 开始
03 p=L->next; j=1; //p 指向第一个元素
04 while(p&&j<i) {p=p->next;++j;} //p 移动到 i 的位
置
05 if(!p||j>i) return ERROR; // 移动到末尾,没找到
06 *e=p->data; L p
07 return OK;
08 } a 1
时间复杂度 O(n) 。
练习 2 :将链表 Lb 连接到 La 之后。
练习 3 :实现链表的求长 Length(L) 。
练习 4 :实现链表的定位 Locate(L,x) 。
创建链表(尾插) 读入数据 a1,a2,
…,an
头指针 L 尾指针 r
^
创建链表(尾插)
L r p
^ ^
创建链表(尾插)
L r p
a1
^ ^
创建链表(尾插)
L r p
a1
r->next=p
创建链表(尾插)
L r p
a1
r=p
创建链表(尾插)
L r p
a1 a2
^ ^
创建链表(尾插)
L r p
a1 a2
r->next=p
创建链表(尾插)
L r p
a1 a2
r=p
01 void CreatList_L(LinkList *L,int n){
// 创建链表,共 n 个元素
02 LinkList p,r; int i;
03 r=*L=(LinkList)malloc(sizeof(LNode));// 创建头结点
04 r->next=NULL; //r 是尾结点(初始时头尾相同)
05 printf("Input %d elements of Link list: ",n);
// printf 相当于 C++ 的 cout
06 for(i=0;i<n;++i){
07 p=(LinkList)malloc(sizeof(LNode));// 分配新结点
08 scanf("%d",&p->data); // 相当于 C++ 的 cin
09 p->next=NULL;
10 r->next=p; // 尾节点的 next 指向 p L r p
11 r=p; //p 是新的尾节点
12 }
a 1
13 }
^ ^
销毁链表
L pre p
a1 a2 a3
销毁链表
L pre p
a1 a2 a3
销毁链表
L pre p
a2 a3
销毁链表
L pre p
a2 a3
销毁链表
L pre p
a2 a3
void Destroy(linkList L){
linkList pre=L->next,p=pre->next;
while(p){
free(pre);
pre=p;
p=p->next;
}
free(pre);
free(L);
}
插入 p
ai-1 ai
s e
插入 p
ai-1 ai
s e
插入 p
ai-1 ai
注意插入顺序: s e
1. 中指后
2. 前指中
01 Status ListInsert_L(LinkList L, int i, ElemType e){
02 LinkList p,s; int j;
03 p=L; j=0; //p 指向头结点
04 while(p&&j<i-1) {p=p->next; ++j;}
//p 指向 j=i-1 的位置
05 if(!p||j>i-1)return ERROR;
06 s=(LinkList)malloc(sizeof(LNode));// 分配新结点
07 s->data=e;
08 s->next=p->next; // 中指后 ai-1 ai
09 p->next=s; // 前指中 p
10 return OK;
s
11 } // 找到 i 结点需要修改指针 p 的值,时间复杂度 O(n) e
练习 5 :读入数据 a1,a2,…,an ,就地建立逆序链表 an -> an-1 -> …-> a1 。
“ 头插”:在头结点之后插入新节点。
^
“ 头插”:在头结点之后插入新节点。
a1
^
“ 头插”:在头结点之后插入新节点。
a1 a2
^ ^
“ 头插”:在头结点之后插入新节点。
a1 a2
^
“ 头插”:在头结点之后插入新节点。
尾插得到的链表顺序与插入顺序相同 ,
L 头插得到的链表顺序与插入顺序相反。
a1 a2
^
01 void CreatList_L(LinkList *L,int n){
// 用“头插”创建逆序链表,共 n 个元素
02 LinkList p; int i;
03 *L=(LinkList)malloc(sizeof(LNode));// 创建头结点
04 L->next=NULL;
05 printf("Input %d elements of Link list: ",n);
06 for(i=0;i<n;++i){
07 p=(LinkList)malloc(sizeof(LNode));// 分配新结点
08 scanf("%d",&p->data);
// 将 p 插入到 L 之后:
09 p->next=L->next; // 中指后
10 L->next=p; // 前指中
11 }
12 }
删除 q
p q
ai-1 ai ai+1
p q
ai-1 ai ai+1
删除节点:前指后
01 Status ListDelete_L(LinkList L, int i, ElemType *e){
02 LinkList p,q; int j;
03 p=L; j=0; //p 指向头结点
04 while(p->next&&j<i-1){ p=p->next; ++j;}
//p 指向 i-1 的位置
07 if(!(p->next)||j>i-1) return ERROR;
08 q=p->next; // q 指向 i 的位置
09 p->next=q->next;// 前指后
10 *e=q->data;
11 free(q); // 相当于 C++ 的 delete p q
12 return OK;
ai-1 ai ai+1
13 } // 时间复杂度 O(n)
练习 6 :删除链表中所有小于 a 和大于 b 的元素。
练习 7 :设有链表 L={a1,b1,a2,b2,…,an,bn} ,
把它拆成两个链表:
La={a1,a2,…,an} ,
Lb={bn, bn-1,…,b1} 。
Lc 的当前尾节点
归并 rc pa
Lc = La
3 11
…
pb
Lb 5 24
…
无需为 Lc 分配新的存储空间
rc pa
Lc = La
3 11
…
rc->next=pa pb
Lb 5 24
…
rc pa
Lc = La
3 11
…
rc=pa pb
Lb 5 24
…
rc pa
Lc = La
3 11
…
pb pa=pa->next
Lb 5 24
…
rc pa
Lc = La
3 11
…
rc->next=pb pb
Lb 5 24
…
pa
Lc = La
3 11
…
rc pb
Lb 5 24
…
rc=pb
pa
Lc = La
3 11
…
rc pb
Lb 5 24
…
pb=pb->next
pa
Lc = La
3 11
…
rc pb
Lb 5 24
…
rc->next=pa
rc pa
rc=pa
Lc = La
3 11
…
pb
Lb 5 24
…
rc pa
Lc = La
3 11
…
pa=pa->next
pb
Lb 5 24
…
rc
45 56
pa=^
…
^
pb
31 65 78
…
^
rc
45 56
…
pb
31 65 78
…
^
rc->next=pb
01 void MergeList_L(LinkList La,LinkList Lb,LinkList *Lc){// 归并
02 LinkList pa,pb,rc; rc pa
03 pa=La->next;pb=Lb->next; //pa,pb 各自指向首节点 3
04 *Lc=rc=La; //Lc 与 La 一样, rc 是 Lc 的尾节点
05 while(pa&&pb){ pb
06 if(pa->data<=pb->data){ 5
07 rc->next=pa; rc=pa; pa=pa->next;
//pa 数据较小,则 pc 的下一结点指向 pa ,然后以 pa 为新的尾结点,最后 pa 移
动到它的下一结点
08 }else{
09 rc->next=pb; rc=pb; pb=pb->next; rc
10 } pa=^
45 56
11 }
12 rc->next=pa?pa:pb; pb
13 free(Lb); 65 78
14 } // 时间复杂度 O(na+nb) ^
循环单向链表
a1 a2 an
…
循环双向链表
prior
L a1 a2 … an
next
练习 8 :给定循环双链表,删除第一个值为 x 的节点。
4 3 5 1
L p
4 3 5 1
p 之前的序列已经排好顺序了
把 p 插到前面序列中的合适位置上
L p q
4 3 5 1
备份 p->next 到 q
L p q
4 3 5 1
^
L p q
4 3 5 1
^
L p q
4 3 5 1
^
L p q
4 3 5 1
^
void sort(linkList L) { p
linkList p,pre,q;
p=L->next->next; // 第二个节点
L->next->next=NULL;// 首节点构成有序序列 pre
while(p!=NULL){
q=p-next; // 备份
// 找插入的位置 pre :
pre=L;
while(pre->next!=NULL && pre->next->data < p->data)
pre=pre->next;
// 把 p 插入到 pre 和 pre->next 之间:
p->next=pre->next; pre->next=p; // 中指后,前指中
p=q;
}
}
练习 11 :对链表两两交换相邻结点。
L p q r
1 2 3
L p q r
1 2 3
练习 12 :给定两个链表,链表每个数据是一位整数( 0~
9 ),它们逆序表示一个整数。求这两个整数的和,结果保
存在同样的链表中。
例 342+465=807 。
Input : L1= 2->4->3,
L2= 5->6->4 ,
Output : 7->0->8 。
func addTwoNumbers(L1 *ListNode, L2 *ListNode) *ListNode { //GO 语言
head := &ListNode{Val: 0, Next: nil} // 和的头结点
current := head // 当前位
carry := 0 // 上一位的进位
for L1 != nil || L2 != nil { // 每一位相加
var x, y, z int //x,y 是两个相加的值
if L1 == nil { // 超过最高位
x = 0 // 高位补零
} else {
x = L1.Val
}
if L2 == nil {
y = 0
} else {
y = L2.Val
}
z = x + y + carry
current.Next = &ListNode{Val: z%10, Next: nil}// 存储这一位的结果
current = current.Next
carry = z/10 // 进位
if L1 != nil {
L1 = L1.Next
}
if L2 != nil {
L2 = L2.Next
}
} //for 结束。相加结束
if carry > 0 { // 如果还有进位,创建新节点
current.Next = &ListNode{Val: carry%10, Next: nil}
}
return head.Next // 返回首节点(最低位)
}
练习 13 :给定一个链表。如果表长是奇数,则返回中间结点,如果
表长度是偶数,则返回中间第二个结点。要求不事先求出表长。
例 L=[1 2 3 4 5] ,返回 3 ,
L=[1 2 3 4 5 6] ,返回 4 。
快慢指针法
( 1 )用快慢指针法找到中间结点 am ,由此得到两个链表
a1 -> …-> am 和翻转 am+1 -> …-> an
( 2 )把 am+1 -> …-> an 翻转为 an-> …-> am+1
( 3 )合并 a1 -> …-> am 和 an-> …-> am+1
练习 15 :给定一个链表,如果有环路,找出环路的起点。
要求就地实现。
1 2 3 5
环路起点: 3 6
设置两个指针: slow 和 fast ,起始位置在链表的开头。
每次 fast 前进两步, slow 前进一步。
如果 fast 可以走到尽头,那么说明没有环路;
如果存在一个时刻 fast 和 slow 相遇,说明有环路。
求环路起点:
( 1 ) slow 和 fast 第一次相遇后,将 fast 移动到链表开头, slow
不动。
( 2 )让 slow 和 fast 每次都前进一步。
( 3 )当 slow 和 fast 第二次相遇时,相遇的节点即为环路的起点
。
PQ 长 a ,
QR 长 b , b R : fast 与 slow 第一次相
环路长 c 。 遇
a Q
P
2(a+b+n1c)=a+b+n2c
a+b= (n2-2n1)c=nc
ListNode *detectCycle(ListNode *head) {//head 是第一个节点
ListNode *slow = head, *fast = head;
do { // 判断是否存在环路 :
if (!fast || !fast->next) return nullptr; // 无环
fast = fast->next->next; // 走两步
slow = slow->next; // 走一步
} while (fast != slow); // 循环结束表示有环路
// 找环路起点 :
fast = head;
while (fast != slow){
slow = slow->next; fast = fast->next;
}
return fast;
}