记录来自《剑指offer》的算法题目
链表应该是面试时被提及最频繁的数据结构,它的结构简单,由若干个结点连接成链状结构,其创建、插入结点、删除结点等操作都只需要20行左右的代码就能实现,其代码量比较适合面试。
假设单链表的结点定义如下:
struct ListNode{
int m_nValue;
ListNode* m_pNext;
};
往链表的末尾添加一个结点的代码如下:
// 在链表结尾插入一个结点
void AddToTail(ListNode** pHead, int value){
ListNode* pNew = new ListNode();
pNew->m_nValue = value;
pNew->m_pNext = NULL;
if (*pHead == NULL){
*pHead = pNew;
}
else
{
ListNode* pNode = *pHead;
while (pNode->m_pNext != NULL)
pNode = pNode->m_pNext;
pNode->m_pNext = pNew;
}
}
这里第一个参数pHead
是一个指向指针的指针,因为在往一个空链表插入一个结点时,这个结点就是链表的头指针,也就是会改动头指针,因此必须把pHead
参数设为指向指针的指针,否则出了这个函数pHead
仍然是空指针。其测试代码如下:
int main(void){
ListNode* t = NULL;
for (int i = 0; i < 10;i++)
AddToTail(&t, i);
return 0;
}
删除某个结点的代码如下:
// 删除给定数值的结点
void RemoveNode(ListNode** pHead, int value){
if (pHead == NULL || *pHead == NULL)
return;
ListNode* pToBeDeleted = NULL;
if ((*pHead)->m_nValue == value){
pToBeDeleted = *pHead;
*pHead = (*pHead)->m_pNext;
}
else{
ListNode* pNode = *pHead;
while (pNode->m_pNext != NULL && pNode->m_pNext->m_nValue != value)
pNode = pNode->m_pNext;
if (pNode->m_pNext != NULL && pNode->m_pNext->m_nValue == value){
pToBeDeleted = pNode->m_pNext;
pNode->m_pNext = pNode->m_pNext->m_pNext;
}
}
if (pToBeDeleted != NULL){
delete pToBeDeleted;
pToBeDeleted = NULL;
}
}
下面是这道算法题的题目:
输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
这里假设不能修改链表的结构,首先肯定是需要遍历整个链表,遍历也是从头到尾的顺序,但输出顺序却是从尾到头,所以这里可以使用栈来进行辅助。因此,实现代码如下:
// 从尾到头打印链表,迭代输出
void PrintListReversingly_Iteratively(ListNode* pHead){
std::stack<ListNode*> nodes;
ListNode* pNode = pHead;
while (pNode != NULL){
nodes.push(pNode);
pNode = pNode->m_pNext;
}
while (!nodes.empty()){
pNode = nodes.top();
cout<<pNode->m_nValue<<" ";
nodes.pop();
}
cout << endl;
}
这里既然想到使用栈来实现函数,而递归本质上就是一个栈结构,所以自然想到可以用递归来实现。实现代码如下:
// 递归版本
void PrintListReversingly_Recursively(ListNode* pHead){
if (pHead != NULL){
if (pHead->m_pNext != NULL){
PrintListReversingly_Recursively(pHead->m_pNext);
}
cout << pHead->m_nValue << " ";
}
}
更完整的代码例子可以查看从尾到头打印链表。