问题描述:
输入一个链表,按链表值从尾到头的顺序打印结点值。
解题思路及知识点
由题意选择三种思路:
- 采用循环方法从尾到头打印单链表
- 采用递归方法从尾到头打印单链表
- 借助栈的特性从尾到头打印单链表
链接方式存储的线性表简称为链表(Linked List),data域--存放结点值的数据域,next域--存放结点的直接后继的地址(位置)的指针域(链域),链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的,每个结点只有一个链域的链表称为单链表(Single Linked List)。
typedef struct node{ //结点类型定义
DataType data; //结点的数据域
struct node *next;//结点的指针域
}ListNode,*LinkList;
- LinkList和ListNode是不同名字的同一个指针类型(命名的不同是为了概念上更明确);
- *LinkList类型的指针变量head表示它是单链表的头指针;
- ListNode类型的指针变量p表示它是指向某一结点的指针;
- 生成结点变量的标准函数
p=( ListNode *)malloc(sizeof(ListNode));//函数malloc分配一个类型为ListNode的结点变量的空间,将其首地址放入指针变量p中
单链表的建立有头插法、尾插法两种方法。
头插法
从一个空表开始,每次读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直至读入结束标志为止。头插法建表过程如下图所示:
尾插法
头插法建立链表虽然算法简单,但生成的链表中结点的次序和输人的顺序相反。若希望二者顺序一致, 可采用尾插法建表。 该方法是将新结点插到当前单链表的表尾上。为此需增加一个尾指针r,使之指向当前单链表的表尾。在这里采用尾插法建立单链表,尾插法建表过程如下图所示:
栈(stack)又名堆栈,它是一种运算受限的线性表,特点“后进先出”。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
- 进栈(PUSH)算法
①若TOP≥n时,则给出溢出信息,作出错处理(进栈前首先检查栈是否已满,满则溢出;不满则作②);
③S(TOP)=X,结束(X为新进栈的元素);
- 退栈(POP)算法
①若TOP≤0,则给出下溢信息,作出错处理(退栈前先检查是否已为空栈, 空则下溢;不空则作②);
②X=S(TOP),(退栈后的元素赋给X):
③TOP=TOP-1,结束(栈指针减1,指向栈顶)。
思路一:采用循环方法从尾到头打印单链表
该方法为定义一个自己需要找到的指针,最开始为NULL;然后每当找到一个位置时该指针前移,直至找到头指针。
#include<stdio.h>
#include<stdlib.h>
typedef struct node //结点类型定义
{
int data;
struct node* next;
}node,*list; //list为结构指针类型
void csh(list *l) //初始化单链表
{
*l = (list)malloc(sizeof(node)); //建立头结点
(*l) ->next = NULL; //建立空的单链表
}
void wcjb(list l,int i) //尾插法建立单链表
{
node *r, *s;
s =(node*)malloc(sizeof(node)); //建立新节点
if (s == NULL) //验证空间申请是否成功
{
printf("内存分配失败\n");
return; //若分配内存不成功,则返回继续分配。
}
r = l;
while (r->next) //找到最后一个节点
{
r = r->next;
}
s->data = i;
r->next = s; //将s结点插入表尾
s->next = NULL;
}
void xhfxdy(list l)
{
node *r, *h;
r = NULL;
if (l == NULL) //判断输入链表是否为空
{
printf("\n\t原链表为空请重新输入\n\n");
return;
}
printf("采用循环方法从尾到头打印单链表:\t");
while(r !=l) //在从尾到头前执行循环,直到到头结点
{
h = l;
while (h->next!= r) //当未找到自己需要的结点时执行循环
{
h = h->next;
}
if (h == l)
{
printf("\n");
return;
}
printf("%d\t", h->data);
r = h; //每次指定自己需要找得节点位置
}
}
void dylb(list l) //顺序打印输入的链表,为验证链表建立的正确性,可自行删除
{
node *r;
r = l ->next;
printf("顺序打印通过尾插法建立的单链表:\t\t");
while (r) //执行循环输出每个节点值
{
printf("%d\t", r->data);
r = r->next;
}
printf("\n\n");
}
int main()
{
node *l; //定义及初始化链表
csh(&l);
wcjb(l, 1); //尾插法建表
wcjb(l, 3);
wcjb(l, 2);
wcjb(l, 4);
wcjb(l, 8);
wcjb(l,6);
wcjb(l, 5);
dylb(l);
xhfxdy(l); //采用循环方法从尾到头打印单链表
xhfxdy(NULL); //验证链表为空
return 0;
}
思路二:采用递归方法从尾到头打印单链表
该方法为通过递归找到表尾,然后依次向前输出,直至头指针。
#include<stdio.h>
#include<stdlib.h>
typedef struct node //结点类型定义
{
int data;
struct node* next;
}node,*list; //list为结构指针类型
void csh(list *l) //初始化单链表
{
*l = (list)malloc(sizeof(node)); //建立头结点
(*l) ->next = NULL; //建立空的单链表
}
void wcjb(list l,int i) //尾插法建立单链表
{
node *r, *s;
s =(node*)malloc(sizeof(node)); //建立新节点
if (s == NULL) //验证空间申请是否成功
{
printf("内存分配失败\n");
return; //若分配内存不成功,则返回继续分配。
}
r = l;
while (r->next) //找到最后一个节点
{
r = r->next;
}
s->data = i;
r->next = s; //将s结点插入表尾
s->next = NULL;
}
void dgfxdy(list l,list l1)
{
if (l== NULL) //判断输入链表是否为空
{
printf("\n\t原链表为空请重新输入\n\n");
return;
}
if (l->next != NULL) //找到最后一个节点
{
dgfxdy(l->next,l1);
printf("%d\t", l->next->data); //通过递归实现从尾到头输出
}
if (l->next == NULL)
{
printf("采用递归方法从尾到头打印单链表:\t");
}
if(l==l1) //为了最后测试结果清晰,可选择删除
printf("\n");
}
void dylb(list l) //顺序打印输入的链表,为验证链表建立的正确性,可自行删除
{
node *r;
r = l ->next;
printf("顺序打印通过尾插法建立的单链表:\t\t");
while (r) //执行循环输出每个节点值
{
printf("%d\t", r->data);
r = r->next;
}
printf("\n\n");
}
int main()
{
node *l; //定义及初始化链表
csh(&l);
wcjb(l, 1); //尾插法建表
wcjb(l, 3);
wcjb(l, 2);
wcjb(l, 4);
wcjb(l, 8);
wcjb(l,6);
wcjb(l, 5);
dylb(l);
dgfxdy(l, l); //采用递归方法从尾到头打印单链表
dgfxdy(NULL, NULL); //验证链表为空
return 0;
}
思路三:借助栈的特性从尾到头打印单链表
该方法为借助栈的特性“后进先出”,将链表数据域从头到尾依次压入栈中,然后再进行出栈。
#include<iostream>
#include<cstdlib>
#include<stack>
using namespace std;
typedef struct node //结点类型定义
{
int data;
struct node* next;
}node, *list; //list为结构指针类型
void csh(list *l) //初始化单链表
{
*l = (list)malloc(sizeof(node)); //建立头结点
(*l)->next = NULL; //建立空的单链表
}
void wcjb(list l, int i) //尾插法建立单链表
{
node *r, *s;
s = (node*)malloc(sizeof(node)); //建立新节点
if (s == NULL) //验证空间申请是否成功
{
printf("内存分配失败\n");
return; //若分配内存不成功,则返回继续分配。
}
r = l;
while (r->next) //找到最后一个节点
{
r = r->next;
}
s->data = i;
r->next = s; //将s结点插入表尾
s->next = NULL;
}
void dylb(list l) //顺序打印输入的链表,为验证链表建立的正确性,可自行删除
{
node *r;
r = l->next;
cout<<"顺序打印通过尾插法建立的单链表:\t\t"
while (r) //执行循环输出每个节点值
{
printf("%d\t", r->data);
r = r->next;
}
printf("\n\n");
}
void jzfxdy(list l)
{
if (l == NULL) //判断输入链表是否为空
{
cout<< "\n\t原链表为空请重新输入\n"<<endl;
return;
}
node* p ;
p = l;
stack<int>a; //定义一个容器a
while (p) //将链表从头到尾依次压入栈
{
a.push(p->data);
p = p->next;
}
cout << "借助栈的特性从尾到头打印单链表:\t";
int len = a.size(); //栈元素个数
for (int i = 1; i < len; i++) //出栈即从尾到头打印原链表
{
cout << a.top()<<"\t";
a.pop();
}
cout<<"\n\n";
}
int main()
{
node *l; //定义及初始化链表
csh(&l);
wcjb(l, 1); //尾插法建表
wcjb(l, 3);
wcjb(l, 2);
wcjb(l, 4);
wcjb(l, 8);
wcjb(l, 6);
wcjb(l, 5);
dylb(l);
jzfxdy(l); //采用递归方法从尾到头打印单链表
jzfxdy(NULL); //验证链表为空
return 0;
}
牛客网在线编程剑指offer测试通过
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector <int> result;
stack<int> arr;
ListNode* p = head;
while (p)
{
arr.push(p->val);
p = p->next;
}
int len = arr.size();
for (int i = 0; i < len; i++)
{
result.push_back(arr.top());
arr.pop();
}
return result;
}
};
注意问题:
- 在查找时要注意链表为NULL的情况;
- 建立新结点需调用函数malloc, 头文件要加 #include<stdlib.h>;
- 此处建立的是带头结点的单链表,输出结果时要去除头结点;
- 使用stack<int> a函数需要添加头文件 #include<stack>。