Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
You are on page 1of 73

数据域 data

头指针 L 线性链表 结点
指针域 next

data
a1 a2 an
next …
^
头结点 首结点
01 typedef struct LNode{
02 ElemType data;
03 struct LNode *next;
04 }LNode,*LinkList;
练习 1 :说出下列算法的功能。

Status A(LinkList L){


if(L&&L->next){
Q=L; L=L->next; P=L;
while(P->next) P=P->next;
P->next=Q; Q->next=NULL;
}
return OK;
} // 第一个节点变成头节点 L ,链表末尾附加一个空节点。
void BB(LNode *s,LNode *q){// 设尾节点的 next 是头节点
p=s;
while (p->next!=q) p=p->next;
p->next=s;
} //q 前驱节点的 next 指向 s

void AA(LNode *pa,LNode *pb){//pa,pb 是同一链表的两个结点


BB(pa,pb);
BB(pb,pa);
} // 把一个链表分成两个链表
L p 获取第 i 个元素

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 的节点。

bool delete_DL(DlinkList L,int x) {


DlinkList p=L->next; // 首节点
while(p!=L&&p->data!=x) p=p->next; // 找到节点 x
if(p!=L){ //p==L 表示链表中没有 x
p->next->prior=p->prior; // 前向指针跳过 p (前指后)
p->prior->next=p->next; // 后向指针跳过 p (前指后)
free(p);
return true; p
}else return false;
}
x
练习 9 :链表就地逆置。 用头插
void converse(LinkList L) {
LinkList p,q;
p=L->next; // 首节点
L->next=NULL;
while(p) {
q=p; //q 是当前结点
p=p->next; //p 是下一个结点
// 头插:把 q 插入头结点后面:
q->next=L->next; // 中指后
L->next=q; // 前指中
}
}
练习 10 :链表排序。 插入排序

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=[1 2 3 4 5] ,返回 L=[2 1 4 3 5] 。

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 。

快慢指针法

设置两个指针: slow 和 fast ,起始位置是首结点。


每次 fast 前进两步, slow 前进一步,
fast 到头则结束,此时 slow 就是所求结点。
linkList middleNode(linkList h){//h 是首节点
linkList slow=h,fast=h;
if(!h->next) return h;// 只有一个结点
if(!h->next->next) return h->next;// 只有两个结点
while(fast&&fast->next){
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
练习 14 :给定一个链表 a1 -> a2 -> …-> an ,把它变成 a1 -> an
-> a2 -> an-1 -> a3 … 。

( 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

fast 与 slow 第一次相遇时, 重置 fast ,它到达 Q 时,走过长度 a ,


slow 走过长度是 a+b+n1c, slow 与它走过长度相同,
fast 走过长度 a+b+n2c 。 所以 slow 相对于 Q 的长度是 b+a=nc ,
fast 走过长度是 slow 的 2 倍,所以 说明 slow 也到达 Q 。

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;
}

You might also like