线性表学习笔记之链表
原创博文,转载请注明 出处
链表分类:单链表,插入删除和查找的时间复杂度均为O(n)
双链表,插入、删除和查找的时间复杂度为O(1)
循环链表,表中最后一个节点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环。
静态链表,借助数组来描述线性表的链式存储结构,这儿的指针是结点的相对地址。和顺序表一样需要预先分配一块连续的内存空间。以next==0作为其结束的标志。
综合应用:
1.设计一个递归算法,删除不带头节点的单链表L中所有值为x的节点。
思路:可以设计一个函数f(L,x)删除以L为首结点指针的单链表中所有值为x的结点,那么f(L->next,x)则是删除以L->next为首结点指针的单链表中所有值等于x的结点。
借助一个递归工作栈,深度为O(n),时间复杂度为O(n)
void Del_x(Linklist &L, ElemType x){
LNode *p; //p指向待删除结点
if(L==NULL)
return;
if(L->data==x){
p=L;
L=L->next;
free(p);
Del_x(L, x);
}
else
Del_x(L->next, x);
}
1
void
Del_x(Linklist &
L, ElemType x){
2
LNode *p;
//
p指向待删除结点
3
4
if
(L==
NULL)
5
return
;
6
if
(L->data==
x){
7
p=
L;
8
L=L->
next;
9
free(p);
10
Del_x(L, x);
11
}
12
else
13
Del_x(L->
next, x);
14
}
2. 设L为带头结点 的单链表,编写算法实现从尾到头反向输出每个结点的值。
思路:方法一、将链表逆置,改变链表的方向。
方法二、借助一个栈,将结点放入栈中。在遍历完整个链表后,再从栈顶开始输出结点值。
方法三、使用递归,当访问一个结点时,先递归输出它后面的结点,再输出该结点自身。实现如下
void
R_Print(LinkList L){
2
if
(L->next!=
NULL){
3
R_Print(L->
next)
4
}
5
print(L->
next)
6
}
1
void
R_Print(LinkList L){
2
if
(L->next!=
NULL){
3
R_Print(L->
next)
4
}
5
print(L->
next)
6
}
3.试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点唯一)
LinkList Delete_Min(LinkList &L){
LNode *pre=L, *p=L->next; //p为工作指针,pre指向其前驱
LNode *minpre=pre, *minp=p;
while(p!=NULL){
if(p->data<minpre->data){
minp=p;
minpre=pre;
}
pre=p;
p=p->next;
}
minpre->next=minp->next;
free(minp);
return L;
}
1
LinkList Delete_Min(LinkList &
L){
2
LNode *pre=L, *p=L->next;
//
p为工作指针,pre指向其前驱
3
LNode *minpre=pre, *minp=
p;
4
5
while
(p!=
NULL){
6
if
(p->data<minpre->
data){
7
minp=
p;
8
minpre=
pre;
9
}
10
pre=
p;
11
p=p->
next;
12
}
13
minpre->next=minp->
next;
14
free(minp);
15
return
L;
16
}
4.编写算法将带头结点的单链表就地逆置,就地指辅助空间为O(1)
方法一:将头结点摘下,然后从第一结点开始,依次前插入到头节点后面(头插法建立链表),直到最后一个节点为止。
LinkList Reverse(LinkList &L){
p=L->next;
L->next=NULL;
while(p!=NULL){
r=p->next;
p->next=L->next;
L->next=p;
p=r;
}
return L;
}
1
LinkList Reverse(LinkList &
L){
2
p=L->
next;
3
L->next=
NULL;
4
while
(p!=
NULL){
5
r=p->
next;
6
p->next=L->
next;
7
L->next=
p;
8
p=
r;
9
}
10
return
L;
11
}
方法二:将结点的next域指向前驱结点。在处理完最后一个结点时,需要将头结点的指针指向它。时间复杂度均为O(n)
LinkList Reverse(LinkList &L){
LNode *pre,*p=L->next,*r=p->next;
p->next=NULL;
while(r!=NULL){
pre=p;
p=r;
r=r->next;
p->next=pre;
}
L->next=p;
return L;
}
1
LinkList Reverse(LinkList &
L){
2
LNode *pre,*p=L->next,*r=p->
next;
3
p->next=
NULL;
4
while
(r!=
NULL){
5
pre=
p;
6
p=
r;
7
r=r->
next;
8
p->next=
pre;
9
}
10
L->next=
p;
11
return
L;
12
}
5. 有一个带头结点的单链表L,设计一个算法使其元素递增有序。
思路:采用直接插入排序的算法,先构成只含一个数据结点的有序单链表,然后依次扫描单链表中剩下的结点*p。
void Sort(LinkList &L){
LNode *p=L->next, *pre
LNode *r=p->next;
p->next=NULL;
p=r;
while(p!=NULL){
r=p->next;
pre=L;
while(pre->next!=NULL&&pre->next->data<p->data)
pre=pre->next;
p->next=pre->next;
pre->next=p;
p=r;
}
}
1
void
Sort(LinkList &
L){
2
LNode *p=L->next, *
pre
3
LNode *r=p->
next;
4
p->next=
NULL;
5
p=
r;
6
while
(p!=
NULL){
7
r=p->
next;
8
pre=
L;
9
while
(pre->next!=NULL&&pre->next->data<p->
data)
10
pre=pre->
next;
11
p->next=pre->
next;
12
pre->next=
p;
13
p=
r;
14
}
15
16
}
6.在单链表L中删除p所指结点,能够实现在O(1)的时间内删除该结点?
思路:传统的做法需要顺序查找删除结点的前驱结点,再修改链接。但是时间复杂度为O(n)。由于我们知道该结点的下一结点P->next,所以我们只需要将下一结点的数据复制到该结点,然后删除它的下一结点。如果该结点位于链表尾部 即P=NULL,这时候我们需要从链表的头结点开始顺序遍历给定节点的前驱结点,这时虽然时间复杂度为O(n),但在平均情况下,仍为O(1)。
7.给定两个单链表,编写算法找出两个链表的公共结点。
思路:比较麻烦的做法就是在第一个链表上顺序遍历每个节点,每遍历一个结点都要在第二个链表上顺序遍历所有结点。找到两个相同的结点,该算法时间复杂度为O(len1*len2)。
由于每个单链表结点只有一个next域,因此从第一个公共结点开始,之后所有的结点都是重合的,拓扑形状看起来像Y,而不是X。但是,两个链表有可能不一样长,所以我们需要截取长链表多余的部分。
LinkList Search_Common(LinkList &L1,LinkList &L2){
int len1=Length(L1),len2=Length(L2);
LinkList longList,shorList;
if(len1>len2){
longList=L1->next;shortList=L2->next;
dist=len1-len2;
}
else{
longList=L2->next,shortList=L1->next;
dist=len2-len1;
}
while(dist--)
longList=longList->next
while(longList!=NULL){
if(longList==shortList)
return longList;
else{
longList=longList->next;
shortList=shortList->next;
}
}
return NULL;
}
1
LinkList Search_Common(LinkList &L1,LinkList &
L2){
2
int
len1=Length(L1),len2=
Length(L2);
3
LinkList longList,shorList;
4
if
(len1>
len2){
5
longList=L1->next;shortList=L2->
next;
6
dist=len1-
len2;
7
}
8
else
{
9
longList=L2->next,shortList=L1->
next;
10
dist=len2-
len1;
11
}
12
while
(dist--
)
13
longList=longList->
next
14
while
(longList!=
NULL){
15
if
(longList==
shortList)
16
return
longList;
17
else
{
18
longList=longList->
next;
19
shortList=shortList->
next;
20
}
21
}
22
return
NULL;
23
}
8.设C={a1,b1,a2,b2,...,an,bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1,a2,...,an}, B={bn,...,b2,b1}.
思路:采用头插法新建B表,而A表则使用尾插法。
LinkList DisCreat(LinkList &A){
LinkList B=(LinkList)malloc(sizeof(LNode))
B->next=NULL;
LNode *p=A->next;
LNode *ra=A; //ra始终指向A的尾结点
while(p!=NULL){
ra->next=p;
ra=p;
p=p->next;
q=p->next;
p->next=B->next;
B->next=p;
p=q;
}
ra->next=NULL;
return B;
}
1
LinkList DisCreat(LinkList &
A){
2
LinkList B=(LinkList)malloc(
sizeof
(LNode))
3
B->next=
NULL;
4
LNode *p=A->
next;
5
LNode *ra=A;
//
ra始终指向A的尾结点
6
while
(p!=
NULL){
7
ra->next=
p;
8
ra=
p;
9
p=p->
next;
10
q=p->
next;
11
p->next=B->
next;
12
B->next=
p;
13
p=
q;
14
}
15
ra->next=
NULL;
16
return
B;
17
}
9.假设有两个按元素值递增次序排列的线性表,均以单链形式存储。请编写算法将这两个链表归并为一个按元素值递减的单链表,并要求利用原来两个单链表的结点存放归并后的链表。
思路:由于两个链表均是递增的,将其合并时,从第一个结点起进行比较,将小的结点存入链表中,同时后移工作指针。由于要求链表元素递减,故采用头插法。
void MergeList(LinkList &A,LinkList &B){
LNode *pa=A->next, *pb=B->next, *r;
A->next=NULL;
while(pa&&pb){
if(pa->data<pb->data){
r=pa->next;
pa->next=A->next;
A->next=pa;
pa=r;
}
else{
r=pb->next;
pb->next=A->next;
A->next=pb;
pb=r;
}
}
if(pa)
pb=pa;
while(pb){
r=pb->next;
pb->next=A->next;
A->next=pb;
pb=r;
}
}
1
void
MergeList(LinkList &A,LinkList &
B){
2
LNode *pa=A->next, *pb=B->next, *
r;
3
A->next=
NULL;
4
while
(pa&&
pb){
5
if
(pa->data<pb->
data){
6
r=pa->
next;
7
pa->next=A->
next;
8
A->next=
pa;
9
pa=
r;
10
}
11
else
{
12
r=pb->
next;
13
pb->next=A->
next;
14
A->next=
pb;
15
pb=
r;
16
}
17
}
18
if
(pa)
19
pb=
pa;
20
while
(pb){
21
r=pb->
next;
22
pb->next=A->
next;
23
A->next=
pb;
24
pb=
r;
25
}
26
}
10.设A和B 是两个单链表带头结点,其中元素递增有序。设计一个算法从A和B中公共元素产生链表C,要求不破坏A和B的结点。
思路:尾插法新建链表C ,要求不破坏A和B的结点,所以才有比较复制的方法。
void Get_Common(LinkList &A , LinkList &B){
LNode *p=A->next, *q=B->next, *r, *s;
LinkList C =(LinkList)malloc(sizeof(LNode));
r=C;
while(p!=NULL&&q!=NULL){
if(p->data<q->data)
p=p->next;
else if(p->data>q->data)
q=q->next;
else{
s=(LNode *)malloc(sizeof(LNode));
s->data=q->data;
r->next=s;
r=s;
p=p->next;
q=q->next;
}
}
r->next=NULL;
}
1
void
Get_Common(LinkList &A , LinkList &
B){
2
LNode *p=A->next, *q=B->next, *r, *
s;
3
LinkList C =(LinkList)malloc(
sizeof
(LNode));
4
r=
C;
5
while
(p!=NULL&&q!=
NULL){
6
if
(p->data<q->
data)
7
p=p->
next;
8
else
if
(p->data>q->
data)
9
q=q->
next;
10
else
{
11
s=(LNode *)malloc(
sizeof
(LNode));
12
s->data=q->
data;
13
r->next=
s;
14
r=
s;
15
p=p->
next;
16
q=q->
next;
17
}
18
}
19
r->next=
NULL;
20
}
11. 已知两个链表A和B分别表示两个集合,其元素递增有序。编制函数,求A与B的交集,并存放于A的链表中。
思路:采用归并思想,设置两个工作指针pa和pb,对两个链表进行扫描,当同时出现在两集合中的元素才链接到结果表中,且仅保留一个,其他的结点全部释放。
当一个链表遍历结束后,释放另一个表剩下的全部结点。
LinkList Union(LinkList &A , LinkList &B){
pa=A->next;
pb=B->next;
pc=A;
LNode *u;
while(pa&&pb){
if(pa->data==pb->data){
pc->next=pa;
pc=pa;
pa=pa->next;
u=pb;
pb=pb->next;
free(u);
}
else if(pa->data>pb->data){
u=pb;
pb=pb->next;
free(u);
}
else{
u=pa;
pa=pa->next;
free(u)
}
while(pa){
u=pa
pa=pa->next;
free(u)
}
while(pb){
u=pb
pa=pb->next;
free(u)
}
pc->next-NULL;
free(B);
return A;
}
}
1
LinkList Union(LinkList &A , LinkList &
B){
2
pa=A->
next;
3
pb=B->
next;
4
pc=
A;
5
LNode *
u;
6
while
(pa&&
pb){
7
if
(pa->data==pb->
data){
8
pc->next=
pa;
9
pc=
pa;
10
pa=pa->
next;
11
u=
pb;
12
pb=pb->
next;
13
free(u);
14
}
15
else
if
(pa->data>pb->
data){
16
u=
pb;
17
pb=pb->
next;
18
free(u);
19
}
20
else
{
21
u=
pa;
22
pa=pa->
next;
23
free(u)
24
}
25
while
(pa){
26
u=
pa
27
pa=pa->
next;
28
free(u)
29
}
30
while
(pb){
31
u=
pb
32
pa=pb->
next;
33
free(u)
34
}
35
pc->next-
NULL;
36
free(B);
37
return
A;
38
}
39
}
12.设计一个算法用于判断带头结点的循环双链表是否对称。
思路:让P从左向右扫描,q从右向左扫描。直到它们指向同一结点(结点个数为奇数时)或相邻(结点个数为偶数时)为止,若它们所指结点值相同,则继续进行下去,否则返回0,若比较全部相同则返回1。
int Symmetry(DLinkList L){
DNode *p=L->next, *q=L->prior;
while(p!=q&&p->next!=q)
if(p->data==q->data){
p=p->next;
q=q->prior;
}
else
return 0;
return 1;
}
1
int
Symmetry(DLinkList L){
2
DNode *p=L->next, *q=L->
prior;
3
while
(p!=q&&p->next!=
q)
4
if
(p->data==q->
data){
5
p=p->
next;
6
q=q->
prior;
7
}
8
else
9
return
0
;
10
return
1
;
11
}
13. 设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。
void Del_All(LinkList &L){
LNode *p, *pre, *minp, *minpre;
while(L->next!=L){
p=L->next;pre=L;
minp=p,minpre=L;
while(p!=L){
if(p->data<minp->data){minp=p;minpre=pre;}
pre=p;
p=p->next;
}
printf("%d",minp->data);
minpre->next=minp->next;
free(minp);
Del_All(L)
}
free(L);
}
1
void
Del_All(LinkList &
L){
2
LNode *p, *pre, *minp, *
minpre;
3
while
(L->next!=
L){
4
p=L->next;pre=
L;
5
minp=p,minpre=
L;
6
while
(p!=
L){
7
if
(p->data<minp->data){minp=p;minpre=
pre;}
8
pre=
p;
9
p=p->
next;
10
}
11
printf(
"
%d
"
,minp->
data);
12
minpre->next=minp->
next;
13
free(minp);
14
Del_All(L)
15
}
16
free(L);
17
}
14. 已知一个带有表头结点的单链表,假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第K个位置上的结点。若查找成功,算法输出该结点的data域的值,否则,只返回0。
设计思想:定义两个指针变量p和q,初始时均指向头结点的下一个结点,即链表的第一个结点。p指针沿链表移动;当p指针移动到第k个结点时,q指针开始于p指针同步移动;当p指针移动到最后一个结点时,q指针所指示的结点为倒数第k个结点。
typedef int ElemType;
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
int Search(LinkList L, int k){
LNode *p=L->next, *q=L->next;
int count=0;
while(p!=NULL){
if(count<k)count ++;
else q=q->next;
p=p->next;
}
if(count<k)
return 0;
else{
printf("%d",q->data);
return 1;
}
}
1
typedef
int
ElemType;
2
typedef
struct
LNode{
3
ElemType data;
4
struct
LNode *
next;
5
}LNode, *
LinkList;
6
7
int
Search(LinkList L,
int
k){
8
LNode *p=L->next, *q=L->
next;
9
int
count=
0
;
10
while
(p!=
NULL){
11
if
(count<k)count ++
;
12
else
q=q->
next;
13
p=p->
next;
14
}
15
if
(count<
k)
16
return
0
;
17
else
{
18
printf(
"
%d
"
,q->
data);
19
return
1
;
20
}
21
22
}
15.设头指针为L的带有表头结点的非循环双向链表,其每个结点除了有pred(前驱指针),data和next域外,还有一个访问频度域freq。在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L ,x )运算时,令元素值为x的结点中freq域的值增1,并使此链表中结点保持按访问频度递减的序列排列,同时最近访问的结点排在频度相同的结点的前面,以便使频繁访问的结点总是靠近表头。使编写符合上述要求的Locate(L ,x )运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。
设计思想: 首先找到链表中数据值为x的结点,查到后,将结点从链表上摘下,然后顺着结点的前驱查到该结点的插入位置。频度递减,且排在同频度的第一个。
DLinkList Locate(DLinkList &L, ElemType x){
DNode *p=L->next, *q;
while(p&&p->data!=x)
p=p->next;
if(!p){
printf("不存在值为x的结点\n");
exit(0)
}
else{
p->freq++;
p->next->pred=p->pred;
p->pred->next=p->next;
q=p->pred;
while(q->freq<p->freq&&q!=L)
q=q->pred;
p->next=q->next;
q->next->pred=p;
q->next=p;
p->pred=q;
}
return p; //返回值为x的结点的指针
}
1
DLinkList Locate(DLinkList &
L, ElemType x){
2
DNode *p=L->next, *
q;
3
while
(p&&p->data!=
x)
4
p=p->
next;
5
if
(!
p){
6
printf(
"
不存在值为x的结点\n
"
);
7
exit(
0
)
8
}
9
else
{
10
p->freq++
;
11
p->next->pred=p->
pred;
12
p->pred->next=p->
next;
13
q=p->
pred;
14
while
(q->freq<p->freq&&q!=
L)
15
q=q->
pred;
16
p->next=q->
next;
17
q->next->pred=
p;
18
q->next=
p;
19
p->pred=
q;
20
}
21
return
p;
//
返回值为x的结点的指针
22
}
欢迎查看关于顺序表的学习,见上篇 http://www.cnblogs.com/tracylining/p/3394038.html

