一、判断题
1、
答案:F
解析:
访问结点的时间复杂度为O(N)
2、
答案:F
3、
答案:F
解析:
将长度为n的单链表链接在长度为m的单链表之后的算法的时间复杂度为( )
A.O(1) B.O(n) C.O(m) D.O(m+n)
解释:
要插入到长度为m的单链表,需要找到表尾,这个过程的时间复杂度为o(m),连接的时间复杂度为0(1),所以总的时间复杂度为0(m)
因此如果仅仅是合并的话,时间复杂度为O(1)
4、
答案:T
二、选择题
1、
答案:D
解释:t的下一个为h,然后h重新再指向t,这样h一直都是指向首元结点
2、
答案:C
3、
答案:B
4、
答案:B
解析:
最少的比较次数就是,第二个链表的第一个数就比第一个链表的最大的数还要大。
5、
答案:C
解析:
在一个具有n个节点的单链表中:
(1)在地址为.…的结点之前/之后、插入/删除某一个结点的话,时间复杂度为O(1);
(2)在第 i 个结点之前/之后、插入/删除一个新结点,或者,删除第 i 个结点,时间复杂度为O(n);
(3)在一个具有n个结点的有序单链表中插入一个新结点并仍然保持有序的时间复杂度是O(n);
在n个结点的*顺序表*中,算法的时间复杂度是O(1)的操作是://注意这里是顺序表,可以随机访问;
A.访问第i个结点和求第i个结点的直接前驱
B.在第i个结点后插入一个新结点
C.删除第i个结点
D.将n个结点从小到大排序
答案是A.
假设顺序表L,长度为n,求第i个节点L[i],直接前驱L[i-1],因此为O(1)
答案B需要移动n-i个节点,因此为O(n)
答案C也需要移动n-i个节点
答案D根据排序方法不同最慢O(n^2),最快O(nlogn)
一定要看清是顺序表还是链表,看清所给的条件,然后再进行判断
6、
答案:C
解析:
要找到那个点的话,需要从头开始找,最坏的情况是在最后一个
7、
答案:B
8、
答案:C
解析:
因为,从头按照顺序遍历一遍肯定能插入,时间复杂度为O(n);
9、
答案:B
10、
答案:B
11、
答案:A
12、
答案:C
13、
答案:A
14、
答案:B
三、函数题
1、带头结点的单链表就地逆置 (10 分)
本题要求编写函数实现带头结点的单链线性表的就地逆置操作函数。L是一个带头结点的单链表,函数ListReverse_L(LinkList &L)要求在不新开辟节点的前提下将单链表中的元素进行逆置,如原单链表元素依次为1,2,3,4,则逆置后为4,3,2,1。
函数接口定义:
void ListReverse_L(LinkList &L);
其中 L 是一个带头结点的单链表。
裁判测试程序样例:
//库函数头文件包含
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
//函数状态码定义
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef int ElemType; //假设线性表中的元素均为整型
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
Status ListCreate_L(LinkList &L,int n)
{
LNode *rearPtr,*curPtr; //一个尾指针,一个指向新节点的指针
L=(LNode*)malloc(sizeof (LNode));
if(!L)exit(OVERFLOW);
L->next=NULL; //先建立一个带头结点的单链表
rearPtr=L; //初始时头结点为尾节点,rearPtr指向尾巴节点
for (int i=1;i<=n;i++){ //每次循环都开辟一个新节点,并把新节点拼到尾节点后
curPtr=(LNode*)malloc(sizeof(LNode));//生成新结点
if(!curPtr)exit(OVERFLOW);
scanf("%d",&curPtr->data);//输入元素值
curPtr->next=NULL; //最后一个节点的next赋空
rearPtr->next=curPtr;
rearPtr=curPtr;
}
return OK;
}
void ListReverse_L(LinkList &L);
void ListPrint_L(LinkList &L){
//输出单链表
LNode *p=L->next; //p指向第一个元素结点
while(p!=NULL)
{
if(p->next!=NULL)
printf("%d ",p->data);
else
printf("%d",p->data);
p=p->next;
}
}
int main()
{
LinkList L;
int n;
scanf("%d",&n);
if(ListCreate_L(L,n)!= OK) {
printf("表创建失败!!!\n");
return -1;
}
ListReverse_L(L);
ListPrint_L(L);
return 0;
}
/* 请在这里填写答案 */
输入格式:
第一行输入一个整数n,表示单链表中元素个数,接下来一行共n个整数,中间用空格隔开。
输出格式:
输出逆置后顺序表的各个元素,两个元素之间用空格隔开,最后一个元素后面没有空格。
输入样例:
4
1 2 3 4
输出样例:
4 3 2 1
答案:
原地逆置的时候,刚开始本来是想从最后一个开始一个个向前方,但是根据链表的特性,不太好操作,因此,想到第一个位置紧邻的是第二个,第二个位置紧邻的是第三个,第三个位置紧邻的是第四个,依次类推,这要这条线不断,就可以不断找到下一个,然后依次将第二个拿到第一个的前面,第三个拿到第二个的前面,第四个拿到第三个的前面。
实现这个的重点是保证这条线不断,同时又能有一个可以独立出来。因此可以得到如图所示的过程
注意:
q在指向与p相同的位置的时候,p紧接着一定要向后移动,改变位置,因为,如果在q的基础上进行了插入L的操作,那么p的这条线就断了,r->next=L->next;
,这样p所指的下一个也变了,因为,p、q指向相同的位置,对于q的操作就相当于对于p的操作。
void ListReverse_L(LinkList &L)
{
LinkList p,r;
p=L->next;
L->next=NULL;//这就意味着,必须要从头开始一个个元素重新插入;
while(p)
{
r=p;//p一直向下移动,r则负责插入;
p=p->next;
r->next=L->next;
L->next=r;
//p=p->next;//不能在这个位置;
}
}
这里可能会以为L指向的变了,那样q指向的下一个就不是L中本身应该指向的顺序,其实这样的理解是错误的,这是链表,不管L怎么变,q指向的都不会变。仍然会沿着这条线。
四、编程题
1、jmu-ds-单链表的基本运算
实现单链表的基本运算:初始化、插入、删除、求表的长度、判空、释放。
(1)初始化单链表L,输出L->next的值;
(2)依次采用尾插法插入元素:输入分两行数据,第一行是尾插法需要插入的字符数据的个数,第二行是具体插入的字符数据。
(3)输出单链表L;
(4)输出单链表L的长度;
(5)判断单链表L是否为空;
(6)输出单链表L的第3个元素;
(7)输出元素a的位置;
(8)在第4个元素位置上插入‘x’元素;
(9)输出单链表L;
(10)删除L的第3个元素;
(11)输出单链表L;
(12)释放单链表L。
输入格式:
两行数据,第一行是尾插法需要插入的字符数据的个数,第二行是具体插入的字符数据。
输出格式:
按照题目要求输出
输入样例:
5
a b c d e
输出样例:
0
a b c d e
5
no
c
1
a b c x d e
a b x d e
答案:
#include <bits/stdc++.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -2
typedef char ElemType;
typedef int Status;
typedef struct LNode
{
ElemType data;
struct LNode* next;
} LNode,*LinkList;
Status InitList(LinkList &L,int n)
{
LinkList p,r;
p=L;//必须是等于L,因为插入的话,要找到插入位置的前一个;
for(int i=0;i<n;i++)
{
r=(LinkList)malloc(sizeof(LNode));
if(!r) exit(OVERFLOW);
scanf(" %c",&r->data);//注意输入空格%c;
r->next=p->next;
p->next=r;
p=r;
}
return OK;
}
void PintfLink(LinkList L)
{
LinkList p;
p=L->next;
while(p)
{
if(p==L->next) printf("%c",p->data);
else printf(" %c",p->data);
p=p->next;
}
printf("\n");
}
void EmptyList(LinkList L)
{
if(L->next==NULL) printf("yes\n");
else printf("no\n");
}
Status LocateList(LinkList L,int n)
{
LinkList p;
p=L->next;
int i=1;
while(p&&i<n)
{
p=p->next;
i++;
}
if(!p||i>n) return ERROR;
printf("%c\n",p->data);
return OK;
}
Status LoList(LinkList L,ElemType e)
{
LinkList p;
p=L->next;
int i=1;
while(p)
{
if(p->data==e)
{
printf("%d\n",i);
return 0;
}
else
{
p=p->next;
i++;
}
}
return 0;
}
Status InsertList(LinkList &L,int n,ElemType e)
{
LinkList p,r;
p=L;
int i=0;
while(p&&i<n-1)
{
p=p->next;
i++;
}
if(!p||i>n-1) return ERROR;
else
{
r=(LinkList)malloc(sizeof(LNode));
if(!r) exit(OVERFLOW);
r->data=e;
r->next=p->next;
p->next=r;
p=r;
}
return OK;
}
Status DeleteList(LinkList &L,int n)
{
LinkList p,r;
p=L;
int i=0;
while(p->next&&i<n-1)
{
p=p->next;
i++;
}
if(!(p->next)||i>n-1) return ERROR;
else
{
r=p->next;
p->next=r->next;
free(r);
}
return OK;
}
int main()
{
LinkList L;
L=(LinkList)malloc(sizeof(LNode));
if(!L) exit(OVERFLOW);
L->next=NULL;
printf("0\n");
int n;
scanf("%d",&n);
getchar();
InitList(L,n);
PintfLink(L);
printf("%d\n",n);
EmptyList(L);
LocateList(L,3);
LoList(L,'a');
InsertList(L,4,'x');
n++;
PintfLink(L);
DeleteList(L,3);
n--;
PintfLink(L);
free(L);
return 0;
}
注意:
(1)对于scanf,scanf只有在遇到\n,也就是是回车时才结束输入,但是遇到空格和tab时不会停止读取。
用scanf不是不显示空格,而是用scanf接收字符串的话,在串首遇到空格的话,跳过,继续寻找下一个非空格字符,在串中遇到空格时,结束字符串的输入。所以如果用户输入" abcd efg"的话,scanf取得的字符串为"abcd"。
scanf(" %c",&r->data);这样的话可以忽略空格键,如果%c前面没有空格的话,输入会停止。
(2)记住scanf的用法,如果只想输入某一个字符,而忽略空格的话,就要采用scanf(" %c",&r->data);
的样式,
即使是前面有两个空格,这样也对,即使是空格在后面,这样也对。
(3)如果对于true和false不太会使用的话,可以在设定的函数中输出相应的结果。
需要再看
2、 两个有序链表序列的合并 (20 分)
已知两个非降序链表序列S1与S2,设计函数构造出S1与S2合并后的新的非降序链表S3。
输入格式:
输入分两行,分别在每行给出由若干个正整数构成的非降序序列,用−1表示序列的结尾(−1不属于这个序列)。数字用空格间隔。
输出格式:
在一行中输出合并后新的非降序链表,数字间用空格分开,结尾不能有多余空格;若新链表为空,输出NULL。
输入样例:
1 3 5 -1
2 4 6 8 10 -1
输出样例:
1 2 3 4 5 6 8 10
答案
#include <bits/stdc++.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -2
typedef int ElemType;
typedef int Status;
typedef struct LNode
{
ElemType data;
struct LNode* next;
} LNode,*LinkList;
Status IinitList(LinkList &A)
{
A=(LinkList)malloc(sizeof(LNode));
if(!A) exit(OVERFLOW);
A->next=NULL;
LinkList r,p;
p=A;
int n;
while(scanf("%d",&n)&&n!=-1)
{
r=(LinkList)malloc(sizeof(LNode));
if(!A) exit(OVERFLOW);
r->next=NULL;
r->data=n;
r->next=p->next;
p->next=r;
p=r;
}
return OK;
}
Status PrintList(LinkList A)
{
LinkList p;
p=A->next;
if(p==NULL)
{
printf("NULL\n");
return 0;
}
else
{
while(p)
{
if(p==A->next) printf("%d",p->data);
else printf(" %d",p->data);
p=p->next;
}
}
return OK;
}
Status UnionList(LinkList &A,LinkList &B,LinkList &C)
{
LinkList pa,pb,pc;
pa=A->next;
pb=B->next;
C=A;//因为是合并,所以可以直接让C=A;
pc=C;//因为需要找到插入位置的前一个位置,所以应该从头开始;
while(pa&&pb)
{
if(pa->data<=pb->data)
{
pc->next=pa;
pc=pa;//因为是合并,所以就让pc一直接在pa或者是pb后面,这样的话,如果某一个为空的话,就直接让pc接在没空的地方就行了;
pa=pa->next;
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;//插入剩余段;
free(B);//释放LB头结点
return OK;
}
int main()
{
LinkList A,B,C;
IinitList(A);
//PrintList(A);
IinitList(B);
C=(LinkList)malloc(sizeof(LNode));
if(!C) exit(OVERFLOW);
C->next=NULL;
UnionList(A,B,C);
PrintList(C);
return 0;
}
注意:
(1)因为是合并,所以就让pc一直接在pa或者是pb后面,这样的话,如果某一个为空的话,就直接让pc接在没空的地方就行了;
需要再看
3、两个有序链表序列的交集
已知两个非降序链表序列S1与S2,设计函数构造出S1与S2的交集新链表S3。
输入格式:
输入分两行,分别在每行给出由若干个正整数构成的非降序序列,用−1表示序列的结尾(−1不属于这个序列)。数字用空格间隔。
输出格式:
在一行中输出两个输入序列的交集序列,数字间用空格分开,结尾不能有多余空格;若新链表为空,输出NULL。
输入样例:
1 2 5 -1
2 4 5 8 10 -1
输出样例:
2 5
答案
#include <bits/stdc++.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -2
typedef int ElemType;
typedef int Status;
typedef struct LNode
{
ElemType data;
struct LNode* next;
} LNode,*LinkList;
Status IinitList(LinkList &A)
{
A=(LinkList)malloc(sizeof(LNode));
if(!A) exit(OVERFLOW);
A->next=NULL;
LinkList r,p;
p=A;
int n;
while(scanf("%d",&n)&&n!=-1)
{
r=(LinkList)malloc(sizeof(LNode));
if(!A) exit(OVERFLOW);
r->next=NULL;
r->data=n;
r->next=p->next;
p->next=r;
p=r;
}
return OK;
}
Status PrintList(LinkList A)
{
LinkList p;
p=A->next;
if(p==NULL)
{
printf("NULL\n");
return 0;
}
else
{
while(p)
{
if(p==A->next) printf("%d",p->data);
else printf(" %d",p->data);
p=p->next;
}
}
return OK;
}
Status JiaoList(LinkList &A,LinkList &B,LinkList &C)
{
LinkList pa,pb,pc;
pa=A->next;
pb=B->next;
//C=A;//这里不要有
pc=C;//因为需要找到插入位置的前一个位置,所以应该从头开始;//千万不要写成 pc=C->next;这样是错误的;
while(pa&&pb)
{
if(pa->data==pb->data)
{
pc->next=pa;
// pc=pc->next;//写在这里是错误的;
pa=pa->next;
pb=pb->next;
pc=pc->next;//这里不要写错,写成pc->next=pc;
}
else if(pa->data<=pb->data)
{
pa=pa->next;
}
else
pb=pb->next;
}
return OK;
}
int main()
{
LinkList A,B,C;
IinitList(A);
//PrintList(A);
IinitList(B);
C=(LinkList)malloc(sizeof(LNode));
if(!C) exit(OVERFLOW);
C->next=NULL;
JiaoList(A,B,C);
PrintList(C);
return 0;
}
注意:
1、Status JiaoList(LinkList &A,LinkList &B,LinkList &C)
这个函数到底是怎么写的。
(1)在这个函数声明的时候或者是定义的时候,不要忘记&号。
(2)pa和pb都指向了下一个,但是pc指向C的头结点,因为要从第一个位置开始插入,所以要先找到插入的前一个位置。
(3)如果两者数值相等的话,就让pc的下一个指向pa,然后pa和pb都后移一位,pc再指向pc自身插入数据的位置,以便于下一个数据的插入,这里千万不要写错了。
这里与插入不同的点是:插入需要创立新的结点,但是这里已经有现成的结点,也就是pa或者pb中的某一个结点,所以不需要借助r来插入数据,只需要一个pc即可完成数据的插入和移动连接。
if(pa->data==pb->data)
{
pc->next=pa;
// pc=pc->next;//写在这里是错误的;
pa=pa->next;
pb=pb->next;
pc=pc->next;//这里不要写错,写成pc->next=pc;
}
注意,这里的pc=pc->next一定要写在pa=pa->next;和pb=pb->next;后面,因为,将相等的值插入之后,先将这个值从A和B链表中脱离出来,如果写在pa=pa->next;和pb=pb->next;之前的话,就会造成将pa的剩余部分接在pc后面。
与前面的逆置道理类似,同时自己错的点也雷同。
(4)如果pb的数值比pa数值大的话,那么pa向后移,如果pa数值更大的话,pb同理也要后移。
(5)C=A;//这里不要有,因为求交集与求并集是不同的,求交集的话,不一定是pa或pb中的哪个数想同。