链表经典面试题一

题型一:从尾到头打印链表
方法一:递归方式

void printtailtohead1(ListNode* ppHead)//从尾到头打印链表
{
     if (!ppHead)
          return;
     printtailtohead1(ppHead->pNext);
     printf("%d->", ppHead->data);
}

方法二:遍历链表
思想:定义一个指针end指向NULL,再定义一个当前节点cur指向指向链表的最后一个节点,输出最后一个节点的数据,再将cur的值赋给end,相当于end前移一个节点,再次使cur->next=end;循环结束条件是end指向了头节点。如下图所示:

void printtailtohead2(ListNode* pHead)//从尾到头打印链表
{
     ListNode* end = NULL;
     while (pHead != end)
     {
          ListNode* cur = pHead;
          while (cur->pNext != end)
          {
              cur = cur->pNext;
          }
          printf("%d->", cur->data);
          end = cur;
     }    

}

题型二:删除无头链表的非尾节点(要求不能遍历链表)

分析:这个题的思想十分简单,不能遍历链表就意味着找到要删除这个节点的前一个节点,这样就给我们的删除带来了一些困难,不过,换一个方向思考,那是不是可以将要删除的这个节点的数据用其后一个节点覆盖呢?答案是可以的,并且这个的操作十分简单,简单的代码实现如下:

void DelNodeNoHead(ListNode* pos)//删除无头链表的非尾节点
{
     assert(pos&&pos->pNext);
     ListNode* Next = pos->pNext;
     pos->pNext = Next->pNext;
     pos->data = Next->data;
     free(Next);
}

题型三:在一个无头链表前插入一个数据(要求不能遍历链表)

分析:由于不能遍历整个链表,所以就不能找到指定位置的前一个节点,所以插入也变得优点困难,但是换一中思路,如果就在当前节点后面插入一个链表,再将链表里面的数据交换,是不是就可以达到在指定位置前面插入一个节点,如下图所示:

void InsertNoHead(ListNode* pos,TypeDate x)//在指定为之前插入一个节点
{
     ListNode* NewNode = BuyNode(pos->data);
    NewNode->pNext=pos->pNext;
     pos->pNext=NewNode;
     pos->data = x;
}

测试代码如下:

testInsertNoHead()
{
     ListNode* S = NULL;//初始化链表
     ListNode* pos = NULL;
     SListPushback(&S, 2);
     SListPushback(&S, 4);
     SListPushback(&S, 6);
     SListPushback(&S, 8);
     SListPushback(&S, 10);
     SListPushback(&S, 12);
     SListPushback(&S, 14);
     SListPrint(S);
     pos = Find(S, 8);
     InsertNoHead(pos,7);
     SListPrint(S);
}

题型四:单链表实现约瑟夫环:
分析:首先我们应该了解什么事约瑟夫环:
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后 结果+1即为原问题的解。
在这里我们返回剩的最后一个人,看谁是最后的幸运者:
解决约瑟夫环的问题,首先要确定要思路,数到k的人要出列,那也就是说删除第k个节点,然后再继续循环,又从0开始数数,数到k,那么这个人就出列,也就是删除该位置的节点,循环结束条件是:当当前节点指向自己,也就能意味着只剩下了一个人了,返回这个人的值,看和自己推倒的结果是否是一致的。(为了构成环,我们需要将尾节点的next指向头节点),根据这个思路我们就可以很容易写出代码了,代码如下:

ListNode* JosephCycle(ListNode* pHead, int k)//约瑟夫环
{
     ListNode* tail = pHead;
     ListNode* cur = pHead;
     if (pHead==NULL)
          return NULL;
     //构成环
     while (tail->pNext)
     {
          tail = tail->pNext;
     }
     tail->pNext = pHead;
     while (cur!=cur->pNext)
     {
          ListNode* NextNode = NULL;
          int count = k;
          while (--count)//循环count-1次
          {
              cur = cur->pNext;
          }
          NextNode = cur->pNext;
          cur->data = NextNode->data;
          cur->pNext = NextNode->pNext;
          free(NextNode);
     }
     return cur;
}

测试代码如下:

testJosephCycle()
{
     ListNode* S = NULL;//初始化链表
     ListNode* pos = NULL;
     SListPushback(&S, 1);
     SListPushback(&S, 2);
     SListPushback(&S, 3);
     SListPushback(&S, 4);
     SListPushback(&S, 5);
     SListPushback(&S, 6);
     SListPushback(&S, 7);
     SListPushback(&S, 8);
     pos = JosephCycle(S, 5);
     printf("幸存者是:%d\n", pos->data);
}

题型五:逆置/反转单链表

方法一:迭代法,从头到尾改变单链表的指向,将NULL置为新的头节点,再将原先的头节点置为NULL;详细过程如图:

void Listreservalone(ListNode** ppHead)//逆置链表,迭代法
{
     ListNode* Node1=*ppHead;
     ListNode* Node2 = Node1->pNext;
     ListNode* Node3 = Node2->pNext;
     if ((*ppHead) == NULL||(*ppHead)->pNext==NULL)
     {
          return *ppHead;
     }
     while (Node2 != NULL)
     {
          Node2->pNext = Node1;
          Node1 = Node2;
          Node2 = Node3;
          if (Node3 != NULL)
          {
              Node3 = Node3->pNext;
          }
     }
     (*ppHead)->pNext = NULL;
     (*ppHead) = Node1;
}

方法二:头删头插法,设置一个指针指向当前链表的头节点,用一个临时变量来保存当前节点。再创建一个新的头节点,将临时变量头插给给个新的头节点。在循环条件内,当前节点持续后移。这样就可以将这个链表逆置过来,这个链表的头节点就是新建的头节点。

ListNode* Listreservaltwo(ListNode* pHead)//逆置链表,头删头插的方法
{
     ListNode* NewHead=NULL;
     ListNode* tmp;
     ListNode* cur=pHead;
     while (cur)
     {
          tmp = cur;
          cur = cur->pNext;
          tmp->pNext = NewHead;//头插
          NewHead = tmp;
     }
     return NewHead;
}

测试代码如下:

testListreservaltwo()
{
     ListNode* S = NULL;//初始化链表
     ListNode* head = NULL;
     SListPushback(&S, 1);
     SListPushback(&S, 2);
     SListPushback(&S, 3);
     SListPushback(&S, 4);
     SListPushback(&S, 5);
     SListPushback(&S, 6);
     SListPushback(&S, 7);
     SListPushback(&S, 8);
     SListPrint(S);
     head=Listreservaltwo(S);
     SListPrint(head);
}

题型六:单链表的冒泡排序:

分析:链表的冒泡排序和普通排序并没有很大区别,思想都是一样的。我们需要创建三个指针,两个指针用来遍历整个链表,一个指针用来控制循环结束条件。,如下图所示 :

void Bubblesort(ListNode* pHead)//单链表的冒泡排序
{
     ListNode* cur;
     ListNode* Next;
     ListNode* tail = NULL;
     int flag = 0;
     while (tail != pHead->pNext)
     {
          cur = pHead;
          Next = pHead->pNext;
          while (Next != tail)
          {
              if (cur->data > Next->data)
              {
                   int tmp = cur->data;
                   cur->data = Next->data;
                   Next->data = tmp;
                   flag = 1;
              }
              cur = cur->pNext;
              Next = Next->pNext;
          }
          tail = cur;//尾每次前移动一位
          if (flag == 0)//表示一次都没有交换,则 本身就是有序的,就不需要遍历了
          {
              break;
          }
     }
}
测试代码如下:
testBubblesort()
{
     ListNode* S = NULL;//初始化链表
     SListPushback(&S, 1);
     SListPushback(&S, 22);
     SListPushback(&S, 3);
     SListPushback(&S, 9);
     SListPushback(&S, 10);
     SListPushback(&S, 6);
     SListPushback(&S, 7);
     SListPushback(&S, 18);
     Bubblesort(S);
     SListPrint(S);
}

猜你喜欢

转载自blog.csdn.net/ffsiwei/article/details/80683654