剑指offer 6:链表(从头到尾打印链表)
链表的数据结构
struct ListNode { int value; ListNode* next; };
那么在链表的末尾添加一个节点的代码如下:
void insert(ListNode** pHead, int value) { ListNode* pNew = new ListNode; pNew->value = value; pNew->next = NULL; if (*pHead == NULL) { *pHead = pNew; } else { ListNode* temp = *pHead; while (temp->next != NULL) { temp = temp->next; } temp->next = pNew; } }
在上面的代码中,我们要特别注意函数的第一个参数是pHead是一个指向指针的指针。当我们往一个空链表中插入一个节点时,新插入的节点就是链表的头指针。由于此时会改动头指针,因此必须把pHead参数设为指向指针的指针,否则出了这个函数pHead仍然是一个空指针。
这这里指针的指针可以这样理解——pHead是一个指向指针的指针,*pHead是一个指针,链表的结点也是指针类型,那么pHead就可以指向多个指针,这样就可以把链表的所有结点连接起来形成一条链。
由于链表中的内存不是一次性分配的,因而我们无法保证链表的内存和数组一样时连续的。因此,如果想在链表中找到它的第i个节点,那么我们只能从头节点开始,沿着指向下一个节点的指针遍历链表,它的时间效率为O(n)。而在数组中,我们可以根据下标在O(1)时间内找到第i个元素。下面是在链表中找到第一个含有某值的节点并删除该节点的代码:
void RemoveNode(ListNode** pHead, int value) { if (pHead == NULL && (*pHead) == NULL)//如果链表为空 { return; } ListNode* pdelete = NULL; if ((*pHead)->vaule == value) { pdelete = *pHead; *pHead = (*pHead)->next; } else { ListNode* pNode = *pHead; while (pNode->next != NULL && pNode->next->value != value) { pNode = pNode->next; } if (pNode->next != NULL && pNode->next->value == value) { pdelete = pNode->next; pNode->next = Pnode->next->next; } } if (pdelete != NULL) { delete pdelete; pdelete = NULL; } }
从尾到头打印链表
我们可以用栈实现这种顺序。每经过一个节点的时候,把该节点放到一个栈中。当遍历完整个链表后,再从栈顶开始逐个输出节点的值,此时输出的结点的顺序已经反转过来了。这种思路的实现代码如下:
void printf_RList(ListNode *pHead) { stack<ListNode*>nodes; ListNode* pNode = pHead; while (pNode != NULL) { nodes.push(pNode); pNode = pNode->next; } while (!nodes.empty()) { pNode = node.top(); cout << pNode << " "; nodes.pop(); } }
既然想到了用栈来解决这个函数,而递归在本质上就是一个栈结构,于是很自然地又想到了用递归来实现。要实现反过来的链表,我们每访问到一个节点的时候,先递归输出它后面的节点,再输出该节点自身,这样链表的输出结果就反过来了。
基于这样的思路不难写出如下代码:
void printf_RList(ListNode* pHead) { if (pHead != NULL) { if (pHead->next != NULL) { printf_RList(pHead->next); } cout << pHead->value<<" "; } }