链表是软件中一种最基本的数据结构,它是用链式存储结构实现数据存储的线性表。它较顺序表(如数组)而言在插入和删除数据时不必移动其后的大批量元素。现在给你一些整数,然后会频繁地插入和删除其中的某些元素,会在其中某些时候让你查找某个元素或者输出当前链表中所有的元素。
本题要实现的功能是:
-
链表创建(函数:HeadNode *create() )。根据输入数据的顺序创建包含头结点的链表,新数据总是插入到链表首结点之前,如果原链表为空链表,则新结点作为链表首结点。
-
输出链表(函数:OprStatus show(HeadNode *head) )。将整个链表的数据依次输出。如果链表为空,则不能执行输出操作,返回枚举值“ERROR”,否则输出链表数据,返回枚举值“OK”。
-
删除链表数据(函数:OprStatus delete(HeadNode *head, int i) )。删除链表中第i号元素,i从1开始计数。如果存在第i号元素,删除之并返回枚举值“OK”,否则无法执行删除操作,返回枚举值“ERROR”。
-
插入数据(函数:OprStatus insert(HeadNode *head, int i, int data) )。在链表的第i号位置插入元素data,i从1开始计数,i>=1且i<=链表实际结点个数+1,当i的值恰好比链表结点个数多1时,则在链表尾部插入新结点。如果插入成功则返回枚举值“OK”,否则返回枚举值“ERROR”。
-
数据获取(函数:Node *get(HeadNode *head, int i) )。获取链表中的第i号元素,i>=1且i<=链表实际结点个数,如果找到第i号元素,则返回第i好元素内存地址,否则返回NULL。
本题中需要使用到的相关类型和辅助函数:
1.枚举类型OprStatus,表示操作状态,声明如下:
/**
* 枚举类型表示操作状态
* OK:表示操作成功
* ERROR:表示操作失败
*/
typedef enum
{
OK,
ERROR
} OprStatus;
2. 链表中的结点类型声明如下:
/**
* 链表中的结点类型声明
* */
typedef struct Node
{
int data;
struct Node *next;
} Node, *NodePoint;
3.头结点类型声明声明如下:
/**
* 头结点类型声明
*/
typedef struct HeadNode
{
int n;//链表中结点个数
NodePoint first; //指向链表的第一个结点指针
} HeadNode;
4.判断malloc是否申请空间成功函数
/**
* 判断malloc是否申请空间成功,如果失败则退出整个程序;
*/
void mallocJudge(void *point)
{
if (point == NULL)exit(0);
}
5.判断在字符串str中是否包含子串subStr,包含返回:true,否则返回:false,定义如下:
/**
* 判断在字符串str中是否包含子串subStr,包含返回:true,否则返回:false
*/
bool contains(char *str, char *subStr)
{
return strstr(str, subStr) != NULL;
}
6.字符串分离函数。通过splitor将str分离成多个字符串,函数原型如下,此函数需要你自行编写!
char **split(char *str, char *splitor);
本题作答要求:
1.本题已经编写了头文件的引用部分,链表结点类型、链表头结点类型、枚举类型、mallocJudge函数、contains函数如下:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define MAX 100 //输入的单行字符串最大长度
/**
* 链表中的结点类型声明
* */
typedef struct Node
{
int data;
struct Node *next;
} Node, *NodePoint;
/**
* 头结点类型声明
*/
typedef struct HeadNode
{
int n;//链表中结点个数
NodePoint first; //指向链表的第一个结点指针
} HeadNode;
/**
* 枚举类型表示操作状态
* OK:表示操作成功
* ERROR:表示操作失败
*/
typedef enum
{
OK,
ERROR
} OprStatus;
/**
* 判断malloc是否申请空间成功,如果失败则退出整个程序;
*/
void mallocJudge(void *point)
{
if (point == NULL)exit(0);
}
/**
* 判断在字符串str中是否包含子串subStr,包含返回:true,否则返回:false
*/
bool contains(char *str, char *subStr)
{
return strstr(str, subStr) != NULL;
}
2.main函数本题也已经完成,代码如下:
int main()
{
int oprNumber;//操作统计次数
char inputs[MAX];//保存输入的单行操作字符串
char **strs = NULL;
int index;//位置,从1开始计数
int data;//数据元素
HeadNode *head = create(); //创建链表
scanf("%d", &oprNumber); //获取操作次数
getchar();//获取操作统计次数后面的空串,让后续的字符串能被成功获取
for (int i = 0; i < oprNumber; ++i)
{
gets(inputs);
if (contains(inputs, "show"))//链表展示
{
if (show(head) == ERROR)
{
puts("Link list is empty");
}
}
else if (contains(inputs, "delete"))//删除数据
{
strs = split(inputs, " ");
index = atoi(strs[1]);//将字符串转变成整数
if (delete(head, index) == OK)
{
puts("delete OK");
}
else
{
puts("delete fail");
}
}
else if (contains(inputs, "insert"))//插入数据
{
strs = split(inputs, " ");
index = atoi(strs[1]);
data = atoi(strs[2]);
if (insert(head, index, data) == OK)
{
puts("insert OK");
}
else
{
puts("insert fail");
}
}
else //获取第i号数据
{
strs = split(inputs, " ");
index = atoi(strs[1]);
Node *p = get(head, index);
if (p == NULL)
{
puts("get fail");
}
else
{
printf("%d", p->data);
}
}
}
return 0;
}
3.请你编写如下函数并提交:
1.链表创建函数:HeadNode *create()
2.链表输出函数:OprStatus show(HeadNode *head)
3.结点删除函数:OprStatus delete(HeadNode *head, int i)
4.结点插入函数:OprStatus insert(HeadNode *head, int i, int data)
5.字符串分离函数:char **split(char *str, char *splitor)
6.获取第i号结点函数:Node *get(HeadNode *head, int i)
输入描述
1. 输入数据只有一组,第一行有n+1个整数,第一个整数是这行余下的整数数目n,后面是n个整数。这一行整数是用来初始化列表的,并且输入的顺序与列表中的顺序相反,也就是说如果列表中是1、2、3那么输入的顺序是3、2、1。
2. 第二行有一个整数m,代表下面还有m行。每行有一个字符串,字符串是“get”,“insert”,“delete”,“show”中的一种。如果是“get”或者“delete”,则其后跟着一个整数a,代表获得或者删除第a个元素;如果是“insert”,则其后跟着两个整数a和e,代表在第a个位置前面插入e;“show”之后没有整数。
输出描述
1. 如果获取成功,则输出该元素,如果获取失败,则输出“get fail”。
2. 如果删除成功则输出“delete OK”,如果删除失败则输出“delete fail”。
3. 如果插入成功则输出“insert OK”,否则输出“insert fail”。
4.如果是“show”则输出列表中的所有元素,如果列表是空的,则输出“Link list is empty”。
注:所有的双引号均不输出。
提示
提示:
1. 因为输入数据中含有大量的插入和删除操作(不管你信不信,反正我信了),所以必须使用链表,否则很可能会超时。这也是考查链表的特性。
2. 初始化链表的元素是倒序的,这个使用题目中创建列表的方法(从头部插入)就可以了。
总结:
本题考查的是链表的特性。怎样判断何时使用顺序表何时使用链表呢?就要看它们的特点了。
1. 顺序表的特点是随机存取、随机访问,即如果查询操作比较频繁的话使用顺序表比较合适;
2. 链表的特点是插入和删除时不必移动其后的节点,如果插入和删除操作比较频繁的话使用链表比较合适。
HeadNode *create()
{
HeadNode *head;
Node *p,*tail;
int data;
head=(HeadNode*)malloc(sizeof(HeadNode));
head->first=NULL;
scanf("%d",&head->n);
for(int i=0;i<head->n;i++){
p=(Node*)malloc(sizeof(Node));
scanf("%d",&data);
if(i==0){
p->data=data;
head->first=p;
p->next=NULL;
tail=p;
}else{
p->data=data;
head->first=p;
p->next=tail;
tail=head->first;
}
}
return head;
}
//2.链表输出函数:
OprStatus show(HeadNode *head)//输出函数
{
if(head->first==NULL) return ERROR;
Node *p=NULL;
for(p=head->first;p!=NULL;p=p->next){
if(p==NULL) printf("%d",p->data);
printf("%d ",p->data);
}
printf("\n");
return OK;
}
//3.结点删除函数:
OprStatus delete(HeadNode *head, int i){
if(head->n==0) return ERROR;
if(i==1){
Node *t=head->first;
head->first=t->next;
free(t);
head->n--;
return OK;
}
Node *tail,*p;
tail=head->first;
p=tail->next;
for(int j=2;j<=head->n;j++){
if(j==i){
tail->next=p->next;
free(p);
head->n--;
return OK;
}
tail=p;
p=p->next;
}
return ERROR;
}
//4.结点插入函数:
OprStatus insert(HeadNode *head, int i, int data){
if(i>(head->n+1)) return ERROR;
Node *pi;
if(i==1){
pi=(Node*)malloc(sizeof(Node));
pi->data=data;
pi->next=head->first;
head->first=pi;
head->n++;
return OK;
}
Node *tail;
tail=head->first;
for(int j=2;j<=(head->n+1);j++){
if(j==i){
pi=(Node*)malloc(sizeof(Node));
pi->data=data;
pi->next=tail->next;
tail->next=pi;
head->n++;
return OK;
}
tail=tail->next;
}
return ERROR;
}
//5.字符串分离函数:
char **split(char *str, char *splitor)
{
int len=strlen(str),i=0;
char **result=(char**)malloc(sizeof(char*)*(len+1));
char *token=strtok(str,splitor);
result[0]=token;
while(token!=NULL){
i++;
token=strtok(NULL,splitor);//继续获取下一个标记
result[i]=token;
}
result[len]='\0';
return result;
}
//6.获取第i号结点函数:
Node *get(HeadNode *head, int i)
{
if(head->first==NULL) return NULL;
Node *p=head->first;
int n=0;
while(p){
n++;
if(n==i) return p;
p=p->next;
}
return NULL;
}