前言:
在面试中,最经常被提及的就是链表,,但又因为需要对指针进行操作,凡是涉及到指针的,都需要我们具有良好的编程基础才能确保代码没有任何错误。
链表是一种动态的数据结构,因为在创建链表时,我们不需要知道链表的长度,当插入一个结点时,只需要为该结点分配内存,然后调整指针的指向来确保新结点被连接到链表中。所以,它不像数组,内存是一次性分配完毕的,而是每添加一个结点分配一次内存。正是因为这点,所以它没有闲置的内存,比起数组,空间效率更高。
在这里我整理了一部分的链表面试题,希望对大家有所帮助。
1. 从尾到头打印单链表
方法一:可以把链表中链接节点的指针反转过来,改变链表的方向,然后就可以从头到尾输出了 。但是这种方法会改变链表本身的结构。是否改变链表的结构?这取决于题目的要求。
方法二:可以考虑用栈来实现。遍历链表的顺序是从头到尾,而输出的顺序是从尾到头,这就是典型的“后进先出”。每经过一个节点,就把该节点的数据存放到栈中,遍历结束后再从栈中取出来。而栈本质上就是递归,此处可以用递归来实现。
方法三:可以多遍历几遍,每次遍历到链表的末尾,打印;再从头遍历到末尾的前面一个节点,打印;如此反复。
void PrintTailToHead(pNode plist)
{
if (plist == NULL)
return;
if (plist->next == NULL)
printf("%d", plist->data);
pNode cur = plist;
pNode tail = NULL;
while (plist != tail)
{
cur = plist;
while (cur->next != tail)
{
cur = cur->next;
}
printf("%d-->", cur->data);
tail = cur;
}
printf("Over\n");
}
void PrintTailToHeadR(pNode pList)//递归实现
{
if(pList != NULL)
{
if(pList->next != NULL)
{
PrintTailToHeadR(pList->next);
}
printf("%d-->", pList->data);
}
}
2. 删除一个无头单链表的非尾节点
这道题的意思是不给头节点,只给需要删除的这个非尾节点。很多人第一次接触这个题目有点束手无策,因为我们要删除一个单链表的某个节点,需要让这个节点的上一个节点指向这个节点的下一个节点,从而达到删除的目的。而此题不给头节点就会让人没有头绪,因为单向链表只能单向遍历,不能找到它前面的节点,而相交于找前面一个节点,我们可以找后面一个节点,因为本节点中就含有指向下一个节点的指针。我们可以把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除,那其实也就是相当于将当前的结点删除。
void EraseNotTail(pNode pos)//把后面一个节点的值赋给它本身,再删除它后面的那个节点
{
pNode del = NULL;
assert(pos != NULL);
assert(pos->next != NULL);
del = pos->next;
pos->data = pos->next->data;
pos->next = pos->next->next;
free(del);
del = NULL;
}
3.在一个无头节点的插入一个节点
此题跟上一题有些相似,单向链表只能在某个无头节点的后面插入一个节点,再把这两个节点的数据交换。
void InsertNode(pNode pos, Datatype d)
{
pNode NewNode = BuyNode(d);
NewNode->next = pos->next;
pos->next = NewNode;
Datatype tmp = pos->data;
pos->data = NewNode->data;
NewNode->data = tmp;
}
4.单链表实现约瑟夫环
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题