单链表的基本操作之习题篇
1、有一个线性表(a1,a2,a3,…an),n>=2,采用带头结点的单链表L存储,设计一个算法将其就地逆置,空间复杂度为O(1)
解题思路1:
用指针p遍历原单链表,先将头节点L的next域置为NULL而变成一个空链表,然后节点采用头插法插入L中。
示例图:
对应算法如下:
void ReverseLinkList(LinkList& L)
{
LNode* p = L->next, * q;
L->next = NULL; //将L看成只有一个头节点的空表
while (p) //遍历节点
{
q = p->next; //保持p的余下节点
p->next = L->next; //将节点插入新链表的首部
L->next = p;
p = q; //p继续遍历余下的节点
}
}
实验截图:
也还可以有第二种解法:
让pre指向开始节点,p指向节点pre的后继节点,post指向节点P后继节点。先置pre所指节点的next为NULL(逆置后它就变成尾节点),修改节点p的next域使其指向节点pre(改变指针方向),然后让pre、p同步后移一个节点,依次进行指针逆置,当p=NULL时,表示pre指向原单链表的尾节点,将L的next域指向节点pre即可
算法如下:
void ReverseLinkList2(LinkList& L)
{
LNode* pre = L->next, * p, * post;
p = pre->next;
pre->next = NULL;
while (p)
{
post = p->next;
p->next = pre;
pre = p;
p = post;
}
L->next = pre;
}
2、设有一个线性表(a1,a2,a3,…an),其中n时大于1的偶数,采用带头节点的单链表L存放,设计一个空间复杂度尾O(1)的算法,将其节点顺序该为(a1,an,a2,an-1,…,an/2,an/2-1)
解题思路:
1)通过快慢指针找到第n/2个节点p,分割为带头节点的含前面n/2个节点的单链表,后n/2个节点的不带头节点的单链表L1。
2)采用头插法将不带头节点的单链表L1逆置。
3)采用尾插法将两个单链表节点依次合并。
算法如下:
//查找中间位置的节点
LNode* Findmiddle(LinkList& L)
{
LNode* p = L, *q = L;
while (q != NULL && q->next != NULL)
{
p = p->next; //慢指针后移一步
q = q->next->next; //快指针后移两步
}
return p;
}
//逆置不带头节点的单链表L1
void Reverse(LinkList& L1)
{
LNode* p = L1, *temp;
L1 = NULL; //初始置空
while (p) //遍历节点
{
temp = p->next;
p->next = L1; //将节点p插入表头
L1 = p;
p = temp;
}
}
//合并L和L1的节点
void Union(LinkList& L, LinkList L1)
{
LNode* p = L->next, *q = L1, *t; //t为尾节点指针
t = L;
while (p && q) //遍历所以节点
{
t->next = p; //节点p插入L的表尾
t = p;
p = p->next;
t->next = q; //节点q插入L的表尾
t = q;
q = q->next;
}
t->next = NULL; //尾节点置空
}
//求解算法
void solve(LinkList& L)
{
LNode* L1, *p, *q;
p = Findmiddle(L);
L1 = p->next;
p->next = NULL; //断开
Reverse(L1);
Union(L, L1);
}
实验截图
3、给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,我们使用整数pos 来表示链表尾连接到链表中的位置(索引从О开始)。如果pos是-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
解题思路:
可以把这题想象成追击问题,slow走一步,fast走两步
1)设置快慢指针slow和fast,fast一定时先进环,这时slow走了入环前距离的一半。
2)随着slow进环,fast已经在环里走了一段,走了多少跟环的大小有关系,假设slow进环的时候,slow跟fast的距离时N,fast开始追slow。
3)slow没往前走一步,fast往前走两步,每追一次,判断一下相遇,没追一次,fast和slow的距离变化是 N,N-1,N-2,…,2,1,0 。当追上时slow和fast就重叠了追上了。
思路图:
实现代码
bool detectCycle(LinkList& L)
{
LNode* slow = L, * fast = L;
LinkList p;
p = L;
while (fast && fast->next) //遍历单链表
{
slow = slow->next; //慢指针走一步
fast = fast->next->next; //快指针走两步
if (slow == fast) //判断是否相遇
{
return ture;
}
}
return flase;
}
4、有一个带头节点的单链表,设计一个算法,求出单链表倒数第K个节点的值
解题思路
1)可以设置两个初始指针fast和slow,他们都指向头节点p,然后fast开始先走k次。(k<N)N为链表长度。
2)当fast走了k次后,两个指针fast和slow开始同步一起走,直到fast==NULL,链表走到空节点,这时slow指向的位置就是单链表倒数的第k位置的节点。
思路图:
实现代码:
//有一个单链表,设计一个算法,求出单链表倒数第K个节点的值
Status find_k(LinkList& L, int k,ElemType &e)
{
LNode* p = L->next;
LNode* fast, * slow; //定义两个指针
fast = slow = p;
while (k--) //fast先走k步
{
if (fast) //判断k是否超过链表的长度
fast = fast->next;
else
return -1;
}
while (fast) //两个指针同时走
{
fast = fast->next;
slow = slow->next;
}
e = slow->data; //e存放倒数第k的位置的值
return 1;
}
结果截图:
5、有一个单链表L,其中可能出现值域重复的节点,设计一个算法删除值域重复的节点
解题思路:
用p遍历单链表,然后删除节点p之后所有值与节点p重复的节点。为此,用post遍历节点p之后的节点,q始终指向节点postq的前驱节点,若postq->data==p->data,通过节点q来删除postq节点,否则q、postq同步后移一个节点
代码实现:
//有一个单链表L,其中可能出现值域重复的节点,设计一个算法删除值域重复的节点
void dels1(LinkList& L)
{
LNode* p = L->next, * q, * postq;
while (p)
{
q = p; //q指向p节点的后继节点
postq = q->next; //postq指向节点q的后继节点
while (postq) //查找是否有与p重复的节点
{
if (postq->data == p->data) //是重复的节点
{
q->next = postq->next; //删除postq节点
free(q); //释放postq节点的空间
postq = q->next; //让postq指向节点q的下一个节点
}
else //postq不是重复节点
{
q = postq; //q、postq同步后移一步
postq = postq->next;
}
}
p = p->next; //p移向下一个节点
}
}
本题时间复杂度为O(n*n),空间复杂度为O(1)。
【扩展】上面的解法时间复杂度为O(n*n),空间复杂度为O(1)。,若已知单链表中节点值范围,可以通过一个数组来判断重复,从而降低时间复杂度,假设单链表中所有节点的值的取值范围 [0,9] ,可以增加一个数组arr[0~9]来表示当前为止节点值是否出现过(初始值元素值都为0),在遍历单链表节点时,若节点p不是重复节点,置arr[p->data]为1,跳过他,否则删除节点p
实现代码:
void dels2(LinkList& L)
{
LNode* pre = L, * p = pre->next;
int arr[9];
for (int i = 0; i < 10; i++) //初始化数组的值为0
arr[i] = 0;
while (p)
{
if (arr[p->data] == 0) //节点p不是重复节点
{
arr[p->data] = 1; //表示p->data的值出现过
pre = p; //pre、 p同步后移一步
p = pre->next;
}
else //节点p时重复节点
{
pre->next = p->next; //删除节点p
free(p); //释放节点p的空间
p = pre->next;
}
}
}
这种解法时间复杂度为O(n),空间复杂度为O(m) (m为节点值的取值范围)
这是一种空间换时间的算法
结果截图:
总结
通过这几个经典题目的练习,让我明白要学会灵活使用单链表你要知道结构体存储的值和指针域是这样存放的。了解一个节点的前驱和后继,并且还要熟练的掌握对他们最基本的增删改查。而且在算法的设计时,你要考虑到算法的时间复杂度和空间复杂度,有时一些算法会使用空间换取时间,来达到更高的效率,也会使用时间换空间的算法来达到更高的效率。