有关链表的经典面试题 (一)

1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
  顺序表:内存中地址连续,优点是随机访问比较便捷快速,创建也比较简单,随机查找比较方便,可以直接给出下标,排序也方便                简单。
              缺点: 不够灵活,删除增加的工作量叫大,比较麻烦,长度不能实时变化
              适用场景:适用于需要大量访问元素的 而少量增添/删除元素的程序
单链表:内存中地址不是连续的,优点是插入删除比较方便,长度可以实时变化。
              缺点: 不支持随机查找,查找元素需要遍历。
              适用场景 : 适用于需要进行大量增添/删除元素操作 而对访问元素无要求的程序

2.从尾到头打印单链表  
void PrintTailToHead(Node* pHead)  //递归实现从尾到头打印单链表
{
       if (pHead == NULL)
       {
              return;
       }
       PrintTailToHead(pHead->next);  //递归时,下一次的pHead其实是上一个pHead的next
       printf("%d ", pHead->data);
}

3.删 除一个无头单链表的非尾节点  
void EraseNonTail(Node* pos)  //删除一个无头链表的非尾节点
{
       Node* next;
       assert(pos&&pos->next);  //pos不能为空也不能为尾
       next = pos->next;  
       pos->data = next->data;  //先让后面的值覆盖掉前面的值
       pos->next = next->next;  //再让前面的节点指向后面节点的后面  即1 2 3 4 5变为1 2 4 4 5,再变为1 2 4 5
       free(next);  //把下一个节点释放掉
}

4.在无头单链表的一个节点前插入一个节点 
思路一:
先链上,再交换其值。但此方法需要额外编写一个Swap函数,略微复杂。

思路二:
先交换其值,再链接。

思路二的代码如下:
void InsertFront(Node* pos, DataType x)  //在无头单链表的一个节点前插入一个节点
{
       Node* next, *tmp;
       assert(pos);
       next = pos->next;  //记录插入点之后的节点
       tmp = BuyNode(pos->data);  //增容一个节点,其值为插入位置节点的值
       pos->data = x;  //将要插入节点值赋给插入位置节点
       pos->next = tmp;  //将其链起来
       tmp->next = next;
}

5.单链表实现约瑟夫环 
Node* Josephus(Node* hus, size_t k)  //单链表实现约瑟夫环
{
       Node* man, *next;
       assert(hus);  //环不为空
       man = hus;  //记录环的头
       while (man->next != man)  //当最后只剩一个节点时结束
       {
              int count = k;   //记录步数
              while (--count)    //走k-1步
              {
                     man = man->next;
              }
              next = man->next;  //为了删除第k个节点,记录其下一个节点
              man->data = next->data;  //将下一节点的值赋给第k个节点
              man->next = next->next;  //使值已经改变的第k个节点指向下一节点的下一节点
              free(next);  //释放下一节点。即原1 2 3 4 5走3步时,变为1 2 4 4 5,再变为1 2 4 5
       }
       return man;  //返回最后只剩一个节点的man(幸存者)
}

6.逆置/反转单链表(重中之重) 

思路一:
将指针逆置,本来1->2->3->4,改为1<-2<-3<-4。
void ReverseList(Node** ppNode)  //逆置反转
{
       Node* n0, *n1, *n2 ;
       if (*ppNode == NULL)
       {
              return;
       }
       n0 = NULL;
       n1 = *ppNode;
       n2 = n1->next;
       while (n1)  //n1为空时结束
       {
              //逆置
              n1->next = n0;
              //后移
              n0 = n1;
              n1 = n2;
              if (n2 != NULL)  //if(n1==NULL)  { break; }
              {
                     n2 = n2->next;
              }
       }
       *ppNode = n0;
}
思路二:
利用逐个头插到另一个空节点的方法。
void ReverseList(Node** ppNode)  //逆置反转
{
       Node* newHead = NULL;
       Node* cur = *ppNode;
       while (cur != NULL)
       {
              Node* next = cur->next;  //记录下一个节点
              
              cur->next = newHead; //头插
              newHead = cur;  //cur变为头结点
              cur = next;
       }
       *ppNode = newHead;  
}

7.单链表排序(冒泡排序&快速排序) 
冒泡排序:
void SortList(Node* pHead)  //冒泡排序(不需要头,所以不传二级指针)
{
       //此处不能用断言,只有一定不为空时才能用断言
       if (pHead == NULL || pHead->next == NULL)
       {
              return;
       }
       Node* tail = NULL;
       while (tail != pHead)
       {
              int exchange = 0;  //标记变量,如果链表本身已经有序,一趟比较之后不用再做比较
              Node* cur = pHead, *next = cur->next;  //为什么这条语句不可以放在全部while外定义?
              while (next != tail)
              {
                     if (cur->data > next->data)
                     {
                           exchange = 1;
                           DataType t = cur->data;
                           cur->data = next->data;
                           next->data = t;
                     }
                     cur = cur->next;
                     next = next->next;
              }
              if (exchange == 0)
              {
                     break;
              }
              tail = cur;
       }
}

8.合并两个有序链表,合并后依然有序 (重中之重)
Node* MergeList(Node* list1, Node* list2)  //两个有序链表合并后仍为有序链表
{
       Node* list,*tail;
       if (list1 == NULL)
       {
              return list2;
       }
       if (list2 == NULL)
       {
              return list1;
       }   //两者都为空时反正返回为空
       if (list1->data < list2->data)  //先确定list
       {
              list = list1;
              list1 = list1->next;
       }
       else
       {
              list = list2;
              list2 = list2->next;
       }
       tail = list;
       while (list1&&list2)
       {
              if (list1->data < list2->data)
              {
                     tail->next = list1;
                     list1 = list1->next;
              }
              else
              {
                     tail->next = list2;
                     list2 = list2->next;
              }
              tail = tail->next;  //因为此时新链表已经链上了元素,tail后有元素
       }
       if (list1)  //如果全部比较后,list1还有元素,直接链到tail后
       {
              tail->next = list1;
       }
       if (list2)  //如果全部比较后,list2还有元素,直接链到tail后
       {
              tail->next = list2;
       }
       return list;  //list不能动,因为它标识了新链表的头
}

9.查找单链表的中间节点,要求只能遍历一次链表
利用快慢指针。
Node* FindMidNode(Node* pHead)  //找单链表的中间节点
{
       Node* slow = pHead, *fast = pHead;
       while (fast && fast->next && fast->next->next) //预防fast为空,奇数时预防fast的next为空,偶数时保证输出前一个元素
       {
              slow = slow->next;  //slow每次走一步,fast每次走两步,当fast到尾时,slow在中间
              fast = fast->next->next;
       }
       return slow;
}

10.查找单链表的倒数第k个节点,要求只能遍历一次链表
利用快慢指针。先考虑不足K个节点时,再考虑相差k步和k-1步的差异,差k步在fast为空时结束,差k-1步在fast为尾时结束。
Node* FindKNode(Node* pHead,size_t k)
{
       Node* slow = pHead, *fast = pHead;
       while (--k)   // --k走k-1步,k--走k步(上去就减去了1)
       {
              if (fast == NULL);
              {
                     return NULL;
              }
              fast = fast->next;
       }
       while (fast->next)  //快慢指针差k-1步时,fast到尾即可结束
       {
              slow = slow->next;
              fast = fast->next;
       }
       return slow;
}







猜你喜欢

转载自blog.csdn.net/gunqu_d/article/details/78254175