有关单链表的面试题分析及代码实现

来源:互联网 发布:国内微电网数据统计 编辑:程序博客网 时间:2024/05/20 08:23

看下面的程序前,先创建一个单链表(仅供参考):

typedef int DataType;


typedef struct Node
{
DataType data;
struct Node* next;
}Node, *pNode, *pList;



1.删除无头单链表的非尾结点

void EraseNotTail(pNode pos);

      在单链表中,一般情况下要删除一个节点,我们会找到当前节点的前一个节点和后一个节点,需要把前一个节点的next指向后一个节点的地址,才可以删除当前节点;但是这里给的是一个无头的链表,函数只传进来一个要删除的节点,这该怎么删除呢?

我们要换一种删法:

    看下图,现在指定要删除data1节点,可以通过把data2的值赋给data1,这样就把data1的值覆盖了,把data1的next指向data3,然后把data2节点删除掉

看代码:    

pos是要删除的节点的地址 

//删除无头单链表的非尾结点void EraseNotTail(pNode pos){pNode del = pos->next;pos->data = pos->next->data;pos->next = pos->next->next;free(del);}


2.逆置/翻转单链表

void ReverseList(pList* pplist);

    单链表是只能往前走,不能往回走,那么怎样逆置一个单链表呢?

我们可以新建一个链表newplist;然后遍历旧链表*pplist,每找到一个节点,就把这个节点头插到新链表上(注意是在新表头插入),直到把旧链表遍历完,把所有节点都头插到新链表上,这时候新链表已经是旧链表的逆序了,然后在把旧的表头地址赋给新链表的表头,这是旧的链表也是逆序的了


看逆置单链表的代码:

void ReverseList(pList* pplist)    //*pplist是链表的头指针{assert(pplist);pNode tmp = NULL;pList newlist = NULL;         //这里创建一个新链表,来放翻转的pNode cur = *pplist;if ((*pplist == NULL) || ((*pplist)->next == NULL)){return;}while (cur != NULL)        {tmp = cur;cur = cur->next;       //这里不能写在下面,若写在下面tmp->next = NULL; 就把cur的next置成空了tmp->next = newlist;newlist = tmp;}*pplist = newlist;return;}


3.在无头单链表的一个非头结点前插入一个节点

void InsertFrontNode(pNode pos,DataType d);

      什么意思呢?就是给你一个节点的位置,这个节点不是头结点,然后在这个节点的前面插一个数据为d的节点。

怎么办呢?

    这里也没有告诉链表的头指针,所以我们无法知道当前节点的前一个节点,所以无法用传统的方法插入;

    这里用到思想是插到当前位置的后面,然后在交换值

   把要插入的节点先插入到当前节点的后面,然后在交换两个节点的data,这样就等于插在了当前节点的前面,看字不如看下图


代码如下:

void InsertFrontNode(pNode pos, DataType d)    //插到当前位置的后面,然后在交换值{pNode newNode = (pNode)malloc(sizeof(Node));newNode->data = d;newNode->next = NULL;newNode->next = pos->next;pos->next = newNode;DataType tmp = pos->data;pos->data = newNode->data;newNode->data = tmp;return;}


4.查找链表中间节点

pNode FindMidNode(pList plist);

直接说思想,这里用的是快慢指针的思想,遍历链表时,一个fast指针每次走两个节点,一个slow每次走一个节点,它们是同时从表头出发的,所以当fast指针走到链表 的尾部时,这个slow慢指针肯定才走到链表中间,这样就找到了链表的中间节点。(这里不考虑链表的节点个数是偶数还是奇数,如果是偶数个节点,最中间两个节点的任意一个都算中间节点 )

看代码:

pNode FindMidNode(pList plist){pNode fast = plist;pNode slow = plist;while (fast != NULL&&fast->next != NULL){fast = fast->next->next;slow = slow->next;}return slow;}



5.删除单链表的倒数第K个节点(k>1&&k<链表长度)时间复杂度为O(N);
void DelKNode(pList* pplist, int k);

什么意思呢?

删除链表的倒数第k个节点,k=1等于是尾删,k=链表长度等于头删,所以规定(k>1&&k<链表长度),且只能遍历一次链表

怎么办呢?

这里也用的是快慢指针的思想;先让快指针走k个节点,然后慢指针再开始走,当快指针走到链表的尾部时,慢指针就走到了倒数第K个节点


下面来看代码理解:

void DelKNode(pList* pplist, int k)   //*pplist是表头指针,k是要删除的倒数第几个数{assert(pplist);if (k <= 1 || k >= GetLinkListLen(*pplist))  //GetLinkListLen()是计算链表的长度的函数,下面有代码{printf("输入有误\n");return;}pNode fast = *pplist;pNode slow = *pplist;pNode del = NULL; while (fast->next != NULL)    //快指针走到表尾时跳出循环{--k;                    if (k <= 0)                //当快指针走了k步后,慢指针开始走,快指针也走{slow = slow->next;}fast = fast->next;}del = slow->next;slow->data = slow->next->data;slow->next = slow->next->next;free(del);}

int GetLinkListLen(pList plist)   //求链表的长度,辅助上面代码实现的函数{int count = 0;pNode cur = plist;while (cur != NULL){count++;cur = cur->next;}return count;}




6.逆序打印单链表
void PrintReversely(pList plist);


可能你会觉得逆序打印,就直接把链表用上面逆置链表的方法逆置了然后在打印;这样虽然可以但是很麻烦,因为我们只要逆序打印,所以不用逆置后在打印,如果逆置后在打印反而增加代码量,我们就直接逆序打印,这样就会增加程序的效率。

下面我们看怎么逆序打印单链表:

这里我们要使用递归的思想,看下面这段话

       要打印逆序打印,就得先打印最后一个节点,要打印最后一个节点,得先打印倒数第二个节点,要打印倒数第二个节点,得先打印倒数第三个节点,依次类推,要打印正数第二个,就得先打印正数第一个节点。这就是递归这思想,带着这个思想看代码:


void PrintReversely(pList plist){pNode cur = plist;if (cur->next){PrintReversely(cur->next);}printf("%d->", cur->data);}





0 0