题目: 输入一个链表,输出该链表中倒数第k个结点。
思路1:为了得到倒数第k个结点,很自然的想法是先走到链表的尾端,再从尾端回溯k步。可是我们从链表结点的定义可以看出本题中的链表是单向链表,单向链表的结点只有从前往后的指针而没有从后往前的指针,因此这种思路行不通。
思路2:定义两个指针。快指针从链表的第一个节点开始遍历向前走k-1。慢指针保持不动;从第k步开始,慢指针也开始从链表的第一个节点开始遍历。由于两个指针的距离保持在k-1,当快指针到达链表的尾指结点时,慢指针正好是倒数第k个结点。
图解:
小问题:
1、输入pHead指针为NULL。由于代码会试图访问空指针指向的内存,程序会崩溃。
2、输入以pHead为头结点的链表的结点总数少于k。由于在for循环中会在链表向前走k-1步,仍然会由于空指针造成崩溃。
3、输入的参数k为0或负数,同样会造成程序的崩溃。
这里牵扯到代码的鲁棒性,即健壮性。指程序能够判断输入是否合乎规范要求,并对不符合要求的输入予以合理的处理。
代码:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int ELEM_TYPE;
typedef struct Node
{
ELEM_TYPE mdata;
struct Node* pnext;
}Node, *Link;
void Init(Link phead)//初始化单链表
{
assert(phead != NULL);
if (phead == NULL)
{
return;
}
phead->pnext = NULL;//将头结点的指针域置为空
}
static Link CreatNode()//创建节点
{
struct Node *pnewnode = (struct Node*)malloc(sizeof(struct Node));
assert(pnewnode != NULL);
pnewnode->pnext = NULL;
return pnewnode;
}
bool InsertHead(Link phead, ELEM_TYPE val)//头插法
{
if (phead == NULL)
{
return false;
}
struct Node* pnewnode = CreatNode();
pnewnode->mdata = val;
pnewnode->pnext = phead->pnext;
phead->pnext = pnewnode;
return true;
}
void Print(Link phead)//打印链表
{
if (phead == NULL)
{
return;
}
struct Node* pCur = phead->pnext;
while (pCur != NULL)
{
printf("%d ", pCur->mdata);
pCur = pCur->pnext;
}
printf("\n");
}
Node * Find_k(Link pHead, int k) //寻找倒数第k个节点
{
if (k <= 0)
{
return NULL;
}
Node* pfast = pHead->pnext;
Node* pslow = pfast;
for (int i = 0; i < k - 1; i++)
{
if (pfast->pnext!= NULL)
{
pfast = pfast->pnext;
}
else
{
return NULL;
}
}
while (pfast->pnext != NULL)
{
pfast = pfast->pnext;
pslow = pslow->pnext;
}
return pslow;
}
int main()
{
Node head;//定义一个头结点
Init(&head);//初始化单链表
for (int i = 0; i < 6; i++)
{
InsertHead(&head, 100 + i);//头插法
}
Print(&head);
Node *t= Find_k(&head, 3);
printf("%d\n", t->mdata);
return 0;
}
运行结果: