链表的面试题总结
链表在面试中是非常容易的考点,所以在这里总结一下,希望对大家有所帮助
首先,我们给出链表的基本结构,和基本的操作,创建一个结点,打印链表的结点,尾插法加入结点。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int DataType;
typedef struct SListNode{
DataType _pData;
struct SListNode* _pNext;
}SListNode;
SListNode* BuySListNode(DataType x); //
void SListPrint(SListNode* pHead); //
void SListPushBack(SListNode** ppHead, DataType x);
SListNode* BuySListNode(DataType x){//创建一个新的结点
SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
cur->_pData = x;
cur->_pNext = NULL;
return cur;
}
void SListPrint(SListNode* pHead){//打印链表中的结点
SListNode* cur = pHead;
if (pHead == NULL)
{
printf("List is Full!\n");
return;
}
else
{
while (cur)
{
printf("%d ",cur->_pData);
cur = cur->_pNext;
}
printf("\n");
}
}
void SListPushBack(SListNode** ppHead, DataType x){//尾插法
SListNode* newNode = BuySListNode(x);
SListNode* pCur = *ppHead;
if (pCur == NULL)
{
*ppHead = newNode;
}
else{
while (pCur->_pNext!= NULL)
{
pCur = pCur->_pNext;
}
pCur->_pNext = newNode;
}
}
int main(){
SListNode* s = NULL;
SListPushBack(&s,1);
SListPushBack(&s,2);
SListPushBack(&s,3);
SListPushBack(&s,4);
SListPushBack(&s,5);
SListPrint(s);
return 0;
}
此时我们就创建好一个链表,链表的内容为:1 2 3 4 5
①从尾到头打印链表
方法中我们定义一个tail指针,每次把tail指针当作结尾的标致向前移动,然后遍历链表打印尾结点
void PrintListTailToHead(SListNode* pHead){
SListNode* tail = NULL;
SListNode* cur = pHead;
while (tail!=pHead)
{
cur = pHead;
while (cur->_pNext!= tail)
{
cur = cur->_pNext;
}
printf("%d ",cur->_pData);
tail = cur;
}
}
②删除无头单链表的非尾结点(不能遍历)
要删除某个结点,首先我们用Find函数查找出这个结点,然后把这个结点的地址返回,供下一次调用。
我们使用替换法删除结点,用next指针标记pos的下一个结点,用next->data覆盖pos->data,然后把next释放,逻辑上pos指的结点就被删除了
SListNode* Find(SListNode* pHead,DataType x){
SListNode* cur = pHead;
while (cur)
{
if(cur->_pData == x)
return cur;
cur = cur->_pNext;
}
return NULL;
}
void DeleteNotTailNode(SListNode* pos){
SListNode* next = pos->_pNext;
pos->_pData = next->_pData;
pos->_pNext = next->_pNext;
free(next);
}
③在无头单链表的一个结点前插入一个结点(不能遍历)
方法和之前的替换法相当,用后插法在结点的后面插入一个值,用要插入的值改变pos的值,那么在逻辑上就是在pos前面插入一个值
void InsertFrontNode(SListNode* pos,DataType x){
SListNode* next = pos->_pNext;
SListNode* newNode = BuySListNode(pos->_pData);
newNode->_pNext = next;
pos->_pNext = newNode;
pos->_pData = x;
}
④约瑟夫环问题
每次按照传入的参数,让cur走相应的步数,然后按照替换法删除在逻辑上删除cur指的结点
SListNode* JosephCircle(SListNode* pHead,int k){
SListNode* cur = pHead;
SListNode* next;
int count = k;
while (cur->_pNext != cur)
{
count = k;
while (count--)
{
cur = cur->_pNext;
}
next = cur->_pNext;
cur->_pData = next->_pData;
cur->_pNext = next->_pNext;
free(next);
}
return cur;
}
⑤单链表的逆置(不开辟新空间)
方法一:
函数中,我们用n1,n2,n3三个指针联动跑,按照while循环里面的顺序,最后一次出来的时候n2,n3指的是空,n1指的是之前链表的尾部,现在返回n1就是返回新链表的头指针
SListNode* ReverseList(SListNode** ppHead){
SListNode* n1;
SListNode* n2;
SListNode* n3;
n1 = *ppHead;
n2 = (*ppHead)->_pNext;
n3 = n2->_pNext;
n1->_pNext = NULL;
while (n2!=NULL)
{
n2->_pNext = n1;
n1 = n2;
n2 = n3;
if(n3!=NULL){
n3 = n3->_pNext;
}
}
return n1;
}
方法二:
SListNode* ReverseList(SListNode** ppHead){
SListNode* newList = NULL;
SListNode* cur = *ppHead;
SListNode* next = cur->_pNext;
while (cur!=NULL)
{
cur->_pNext = newList;
newList = cur;
cur = next;
if(next)
next = next->_pNext;
}
return newList;
}
⑥链表的冒泡排序
void SListBubbleSort(SListNode* pHead){
SListNode* cur = pHead;
SListNode* next = cur->_pNext;
SListNode* tail = NULL;
DataType temp;
int flag = 0;
while (tail!=pHead)
{
cur = pHead;
next = cur->_pNext;
while (next != tail)
{
if(cur->_pData > next->_pData){
temp = cur->_pData;
cur->_pData = next->_pData;
next->_pData = temp;
flag = 1;
}
cur = cur->_pNext;
next = next->_pNext;
}
if(flag == 0){
return;
}
tail = cur;
}
}
⑦把两个链表合成一个有序链表
注意情况:两个链表有可能不一样长,有可能为空
SListNode* SListMeget(SListNode** list1,SListNode** list2){
SListNode* tail;
SListNode* list;
if(list1 == NULL)//任何一个链表为空,就返回另外一个链表,即使另外一个链表也为空,
return *list2;
if(list2 == NULL)
return *list1;
if((*list1)->_pData > (*list2)->_pData){//确定第一个值比较小的链表,作为新链表的开始
list = tail = (*list2);
(*list2) = (*list2)->_pNext;
}
if((*list1)->_pData < (*list2)->_pData){
list = tail = (*list1);
(*list1) = (*list1)->_pNext;
}
while (*list1 && *list2)//两个链表都没结束的时候,做比较,把新链表的tail->_pNext指向较小的链表
{
if((*list1)->_pData < (*list2)->_pData){
tail->_pNext = *list1;
(*list1) = (*list1)->_pNext;
}
else{
tail->_pNext = (*list2);
(*list2) = (*list2)->_pNext;
}
tail = tail->_pNext;
}
if(*list1){
tail->_pNext = (*list1);
}
if(*list2){
tail->_pNext = (*list2);
}
return list;
}
⑧找到链表的中间链表(只能遍历一遍链表)
函数中,使用一个快指针fast,使用一个慢指针slow,slow走一步,fast走两步,那么在fast走到结尾的时候,slow走了fast的一半,所以slow就走在链表的中间
SListNode* FindMidNode(SListNode* pHead){
SListNode* fast = pHead;
SListNode* slow = pHead;
while (fast && fast->_pNext)
{
fast = fast->_pNext->_pNext;
slow = slow->_pNext;
}
return slow;
}
⑨找到倒数第k个结点(只能遍历一次链表)
方法中首先让快指针和慢指针相差k个位置,那么在快指针到达尾结点的时候,慢指针就在倒数第K个结点
SListNode* FindNodeFromBackOfNumberK(SListNode* pHead,int k){
SListNode* fast = pHead;
SListNode* slow = pHead;
while (k--)
{
if(fast){
fast = fast->_pNext;
}
}
while (fast)
{
fast = fast->_pNext;
slow = slow->_pNext;
}
return slow;
}
⑩判断链表是否带环
fast指针一次走两步,slow指针一次走一步,若带环,那么两个指针在环里面一定会相遇
SListNode* IsCircle(SListNode* pHead){ SListNode* fast = pHead; SListNode* slow = pHead; while (fast && fast->_pNext) { fast = fast->_pNext->_pNext; slow = slow->_pNext;
if(fast == slow){
}return NULL;} ⑪判断环的大小,也就是环里面有多少结点return fast; }
我们使用之前判断是否带环的函数来确定指针在环里面相遇的点,在按照这个点跑一圈,就是这个环的长度。
int CircleLength(SListNode* pHead){ SListNode* meet = IsCircle(pHead); SListNode* cur = meet->_pNext; int cnt =1; while (cur != meet) { cnt++; cur = cur->_pNext; } return cnt; }
⑫判断环的入口点:
判断环的入口点讲解图:SListNode* EnterNode(SListNode* pHead){ SListNode* cur = pHead; SListNode* meet = IsCircle(pHead); while (cur != meet) { cur = cur->_pNext; meet = meet->_pNext; } return cur; }
此后我还会继续在文章中补充关于链表的题目,敬请关注!
Tip:限于编者水平,文章有很多不足之处,欢迎指正。
如需转载,请注明出处。