在之前的“单链表基本操作”博客中,介绍了一些关于单链表的增删查改等基本操作。在本文中,对单链表的操作升级,列举一些常见的有关单链表的常见笔试题。
注意:该单链表还是无头结点,单向,无环的。有关链表的信息与“单链表基本操作中”相同。
1. 逆序打印单链表
链表正常的输出顺序是从头开始输出,如果逆序的话,最后输出的是头结点,先输出的是最后一个节点,此过程可以通过递归来实现:在打印上一个节点之前,先打印下一个节点,依次往后。递归的终止条件是节点为NULL
void LinkListReversePrint(LinkListNode* head)
{
if(head == NULL)
{
//链表为空
return;
}
LinkListReversePrint(head->next);//在输出该上个节点之前,先输出下个结点
printf("%c ",head->data);
}
2. 在指定位置pos前插入节点,不允许遍历链表
在单链表的基本操作中,也实现了该算法。不过在任意位置之前插入结点要找到该位置的上一个节点。这样可以比较麻烦,因为之前已经编写过在任意位置之后插入节点,所以可以利用该算法来实现。
首先可以在指定位置之后插入节点,使该节点的数据域为指定位置的数据域,而将指定位置的数据域改为要插入的元素值value,这样就可以与在指定位置之前插入节点达到同样的效果。如图所示:
代码如下:
//在任意位置pos之前插入节点,数据域为value
void LinkListInsertEx(LinkListNode** phead,LinkListNode* pos,LinkListType value)
{
if(phead == NULL || pos == NULL)
{
//非法输入
return;
}
if(*phead == NULL)
{
//空链表
return;
}
if(*phead == pos)
{
//指定位置为头节点,相当于头插
LinkListPushFront(phead,value);//调用头插函数
return;
}
LinkListInsertAfter(phead,pos,pos->data);//在指定位置之后插入节点,数据域为指定位置的数据
pos->data = value;//将指定位置的数据替换为value
return;
}
该算法的时间复杂度和空间复杂度分别为O(n)和O(1)。
注意:当链表节点的数据较复杂时,该方法就不适用了。
3. 删除指定位置pos处(非尾节点)的节点,不能遍历链表
与上述在指定位置插入节点类似。要删除pos处的节点就要从头开始遍历链表找到删除位置的前一个节点。但是,题目要求不能遍历链表,pos之前的节点需遍历找到,而pos之后的结点则不用遍历。
所以该问题可以转化为:先将pos处的数据域替换为pos之后结点的数据域。再删除pos之后的结点,这样,便可以达到相同的效果。
因为要删除的结点是最后一个结点时,在它之后没有节点,所以该方法不适用于删除尾节点的情形。而题目又正好指定删除的位置不是为节点。所以:
(1)如果是空链表,则删除失败
(2)如果是非空链表,替换数据域后,直接删除pos之后的结点即可。
void LinkListErase2(LinkListNode** phead, LinkListNode* pos)
{
if(phead == NULL || pos ==NULL)
{
//非法输入
return;
}
if(*phead == NULL)
{
//空链表
return;
}
LinkListNode* to_delete = pos->next;//保存pos之后的结点即要删除的结点
pos->data = to_delete->data;//进行数据域的替换
pos->next = to_delete->next;//使pos指向to_delete之后的结点
LinkListNodeDestory(to_delete);//销毁pos之后的结点
return;
}
4. 单链表实现约瑟夫环
首先,需要了解一下约瑟夫环。以5个结点的带环单链表(此时,最后一个节点指向第一个节点)为例,指定数字2。