一、线性表
线性表是具有相同数据类型的n个数据元素的有限序列。
相同:每个数据元素所占空间一样大。
有限:有次序(有序性)<ai,ai+1>
Eg:所有整数按递增次序排列,不是线性表。(所有整数是无限的)
ai是线性表中的"第i个"元素线性表中的位序。
a1是表头元素,an是表尾元素。
除第一个元素外,每个元素有且仅有一个直接前驱。
除最后一个元素外,每个元素有且仅有一个直接后继。
一个线性表中的数据元素属于同一个数据对象(同一性)。
线性表是一种逻辑结构,而顺序表和链表是指存储结构。
二、顺序表
- 用顺序存储的方式实现的线性表。
1.顺序表的实现
- 静态分配(存储空间静态)
代码如下(示例):
#define MAXSIZE 10
Typedef struct {
ElemType data[MAXSIZE]; //静态数组存放的数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
Status Initlist(SqList &L){
L.length=0;//顺序表的初始长度为0
return OK;
}
- 动态分配
#define InitSize 10
typedef struct{
ElemType *data;
int MaxSize;
int length;
}SqList;
Status InitList(SqList &L){
L.data=new ElemType[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的数组空间
if(!L.data) return ERROR; //存储空间分配失败退出
L.length=0; //空表长度为0
return OK;
}
2.取值
- 根据指定的位置序号i,获取顺序表中第i个数据元素的值。 随机存取
代码如下(示例):
Status GetElem(SqList L,int i,ElemType &e){
if(i<1||i>L.length) return ERROR; //判断i值是否合理
e=L.data[i-1]; //data[i-1]存储第i个数据元素
return OK;
}
【算法分析】时间复杂度为O(1)
3.查找
- 根据指定的元素值e,获取顺序表中第1个值与e相等的元素。
代码如下(示例):
int LocateElem(SqList L,ElemType e){
//在顺序表L中查找值为e的数据元素,返回其序号
for(int i=0;i<L.length;i++)
if(L.data[i]==e) return i+1;
return 0;
}
【算法分析】平均时间复杂度为O(n)
(n+1)/2
4.插入
代码如下(示例):
Status ListInsert(SqList &L,int i,ElemType e){
if(i<1||i>L.length+1) return ERROR;
if(L.length==MAXSIZE) return ERROR;
for(int j=L.length;j>=i;j--)
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++;
return OK;
}
【算法分析】平均时间复杂度为O(n) n/2
5.删除
代码如下(示例):
Status ListDelete(SqList &L,int i){
if(i<1||i>L.length) return ERROR;
for(int j=ij<L.length;j++)
L.data[j-1]=L.data[j];
//int e=L.data[i-1];
L.length--;
return OK;
}
【算法分析】平均时间复杂度为O(n) (n-1)/2
三、链表
1.单链表的定义
typedef struct LNode{ //定义单链表结点类型
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
强调使用的是单链表—LinkList
强调使用的是结点 ----LNode
2.单链表的初始化
//带头结点
Status InitList(LinkList &L){
L=new LNode; //生成新节点作为头结点,用头指针L指向头结点
L->next==NULL; //头结点的指针域置空
return OK;
//不带头结点
Status InitList(LinkList &L){
L=new LNode;
L==NULL;
return OK;
3.单链表的取值
Status GetElem(LinkList &L,int i,ElemType &e){
//在带头结点的单链表L中根据序号i获取元素的值,用e返回L中第i个数据元素的值
LNode *p=L->next; //初始化,p指向首元结点,计数器j初值赋为1
int j=1; //顺链域往后查找,直到p为空域或p指向第i个元素
while(p&&j<i){ //p指向下一个结点
p=p->next; //计算机j相应加1
j++;
}
if(!p||j>i) return ERROR; //i值不合法i>n或i<=0
e=p->data; //取第i个结点的数据域
return OK;
时间复杂度O(n) (n-1)/2
3.单链表的查找
LNode *LocateElem(LinkList L,ElemType e){
//在带头结点的单链表L中查找值为e的元素
LNode *p; //初始化
p=L->next; //p指向首元节点
while(p&&p->data!=e){
p=p->next;
}
return p; //返回值为e的节点地址p
3.单链表的插入
Status ListInsert(LinkList &L,int i,ElemType e){
//在带头结点的单链表L中第i个位置插入值为e的元素
LNode *p=L,*s;
int j=0;
while(p&&(j<i-1)){
p=p->next;j++;} //查找第i-1个节点,p指向该节点
if(!p||j>i-1) return ERROR; //i>n+1或i<1
s=new LNode; //生产新节点s
s->data=e; //将节点*s的数据域置为e
s->next=p->next; //将节点*s的指针域指向节点ai
p->next=s; //将节点*p的指针域指向节点*s
return OK;
3.单链表的删除
Status LiseDelete(LinkList &L,int i){
//在带头结点的单链表L中,删除第i个元素
LNode *p=L,*q;
int j=0;
while((p->next)&&(j<i-1)) //查找第i-1个节点,p指向该节点
{
p=p->next;
j++;
}
if(!(p->next)||(j>i-1)) return ERROR; //当i>n或i<1 删除位置不合理
q=p->next; //临时保存被删节点的地址以备释放
p->next=q->next; //改变删除节点前驱结点的指针域
delete q; //释放删除节点的空间
return OK;
}
3.创建单链表
- 前插法
void CreateList_H(LinkList &L,int n){
L=new LNode;
L->next=NULL; //先建立一个带头结点的空链表
for(int i=0;i<n;i++){
p=new LNode; //生成新节点*p
cin>>p->data; //输入元素值赋给新节点*p的数据域
p->next=L->next; //将新节点*p插入到头结点之后
L->next=p;
}
}
- 后插法
void CreateList_R(LinkList &L,int n){
//正位序输入n个元素的值,建立带表头节点的单链表L
LNode *p,*r; //先建立一个带头结点的空链表
L=new LNode;
L->next=NULL;
r=L; //尾指针r指向头结点
for(int i=0;i<n;i++){
p=new LNode; //生成新节点
cin>>p->data; //输入元素值赋给新节点*p的数据域
p->next=NULL;
r->next=p; //将新节点*p插入尾节点*r之后
r=p; //r指向新的尾节点*p
}
}
4.双向链表
双向链表的定义
typedef struct DuLNode{
ElemType data; //数据域
struct DuLNode *prior; //指向直接前驱
struct DuLNode *next; //指向直接后继
}DuLNode,*DuLinkList;
双向链表的插入
Status ListInsert(LinkList &L,int i,ElemType e){
LNode *p;
if(!(p=GetElem_DuL(L,i))) //在L中确定第i个元素的位置指针p
return ERROR;
s=new DuLNode; //生成新节点*s
s->data=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}
双向链表的删除
Status ListDelete(LinkList &L,int i){
LNode *p;
if(!(p=GetElem_DuL(L,i))) //在L中确定第i个元素的位置指针p
return ERROR;
p->prior->next=p->next;
p->next->prior=p->prior;
delete p; //释放被删除节点的空间
return OK;
}
四、选择
数组A[1..5,1..6]每个元素占5个单元,将其按行优先次序存储在起始地址为1000的连续的内存单元中,则元素A[5,5]的地址为:C
//1000+(4*6+5-1)*5 1000+(5*6-2)*5
A.1120
B.1125
C.1140
D.1145
用数组表示线性表的优点是(C)。
A.
便于插入和删除操作
B.
便于随机存取
C.
可以动态地分配存储空间
D.
不需要占用一片相邻的存储空间
某线性表采用顺序存储结构,每个元素占4个存储单元,首地址为100,则第12个元素的存储地址为(A)。
//100+(12-1)*4
A.144
B.145
C.147
D.148
线性表L=(a1, a2 ,……,an )用一维数组表示,假定删除线性表中任一元素的概率相同(都为1/n),则删除一个元素平均需要移动元素的个数是(C)。
A.n/2
B.(n+1)/2
C.(n-1)/2
D.n
顺序存储表示中数据元素之间的逻辑关系是由( C)表示的。
A.指针
B.逻辑顺序
C.存储位置
D.问题上下文
顺序表是线性表的( C)存储表示。
A.有序
B.连续
C.数组
D.顺序存取
以下关于顺序表的说法中,正确的是( C)。
A.顺序表可以利用一维数组表示,因此顺序表与一维数组在结构上是一致的,它们可以通用
//顺序存储不一定是一维数组
B.在顺序表中,逻辑上相邻的元素在物理位置上不一定相邻 //一定相邻
C.顺序表和一维数组一样,都可以按下标随机(或直接)访问,顺序表还可以从某一指定元素开始,向前或向后逐个元素顺序访问
D.在顺序表中每一表元素的数据类型还可以是顺序表
顺序表的优点是(C )。
A.插入操作的时间效率高
B.适用于各种逻辑结构的存储表示
C.存储密度(存储利用率)高
D.删除操作的时间效率高
以下说法正确的是(C )。
A.线性结构的基本特征是:每个结点有且仅有一个直接前趋和一个直接后继
B.线性表的各种基本运算在顺序存储结构上的实现均比在链式存储结构上的实现效率要低
C.在线性表的顺序存储结构中,插入和删除元素时,移动元素的个数与该元素位置有关
D.顺序存储的线性表的插入和删除操作不需要付出很大的代价,因为平均每次操作只有近一半的元素需要移动
向一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变,平均要移动的元素个数为( B)
A.8
B.63.5
C.63
D.7
设顺序表L中有n个数据元素,则删除该表中第i个数据元素需要移动移动C个元素。
A.n-1-i
B.n+L-i
C.n-i
D.i
以下关于顺序表的叙述中正确的是( B)
A.顺序表的优点是存储密度大且插入、删除运算效率高
B.顺序表属于静态结构
C.顺序表中所有元素可以连续也可以不连续存放
D.在有n个元素的顺序表中查找逻辑序号为i的元素的算法时间复杂度为O(n)
取线性表的第i个元素的时间同i的大小有关。 F
线性表的顺序存储表示优于链式存储表示。 F
循环链表不是线性表。F
带头结点的单循环链表中,任一结点的后继结点的指针域均不空。T
在一个设有头指针和尾指针的单链表中,执行删除该单链表中最后一个元素的操作与链表的长度无关。 F
链表中逻辑上相邻的元素,其物理位置也一定相邻。 F
链表是一种随机存取的存储结构。 F
线性表采用链式存储结构时,各个数据元素的存储单元地址一定是不连续的。 F
线性表采用链表存储时,结点和节点内部的存储空间可以是不连续的。 F
对单链表来说,只有从头结点开始才能访问到表中所有结点。 F
单链表又称为线性链表,在单链表上实施插入和删除操作(B )。
A.不需移动结点,不需改变结点指针
B.不需移动结点,只需改变结点指针
C.只需移动结点,不需改变结点指针
D.既需移动结点,又需改变结点指针
已知单链表A长度为m,单链表B长度为n,若将B连接在A的末尾,在没有链尾指针的情形下,算法的时间复杂度应为(B )。
A.
O(1)
B.
O(m)
C.
O(n)
D.
O(m+n)
以下说法错误的是 ( C)。
A.
对于线性表来说,定位运算LocateElem在顺序表和单链表上的时间复杂度均为O(n)
B.
插入、删除操作在顺序表上的实现,平均时间复杂度为O(n)
C.
在链表上实现读表元运算的平均时间复杂度为O(1)
D.
插入、删除操作在链表上的实现可在O(1)时间内完成
设一个链表最常用的操作是在末尾插入结点和删除尾结点,则选用( D)最节省时间。
//在链表的末尾插入和删除节点,需要改变相邻结点的指针域。而寻找尾节点及尾节点的前驱结点时,只有带头结点的双循环链表所需要的时间最少。
A.
单链表
B.
单循环链表
C.
带尾指针的单循环链表
D.
带头结点的双循环链表
链表要求内存中可用存储单元的地址 ▁▁▁D▁▁ 。
A.
必须是连续的
B.
部分地址必须是连续的
C.
一定是不连续的
D.
连续或不连续都可以
链表的存储密度 ▁▁▁▁C▁ 。
A.
大于 1
B.
等于 1
C.
小于 1
D.
不能确定
链表不具备的特点是( A)。
A.
可随机访问任一结点替换为错误项
B.
插入删除不需要移动元素
C.
不必事先估计存储空间
D.
所需空间与其长度成正比
一个长度为n(n>1)的简单链表h上,另设有尾指针r(指向尾结点),执行__B_操作与链表的长度有关。
//删除单链表的最后一个结点需置其前驱结点的指针域为NULL,需要从头开始依次遍历找到该前驱结点,需要O(n)的时间,与表长有关。其他操作与表长无关。
A.
删除单链表中的第一个元素
B.
删除单链表中的最后一个元素
C.
在单链表第一个元素前插入一个新元素
D.
在单链表最后一个元素后插入一个新元素
单链表的删除算法*
在带头结点的单链表中删除结点,需将指针从头结点开始向后移动,到达前驱结点处。
假设单链表的长度为 n,
若在位序 1 处删除元素,则需要移动
**0**
次指针;
若在位序 n 处删除元素,则需要移动
**n-1**
次指针;
若在位序 i(1≤i≤n) 处删除元素,则需要移动
**i-1**
次指针。
假设各位序删除元素的概率相同, 则平均需要移动
(n-1)/2
次指针。
单链表的插入算法*
在带头结点的单链表中插入结点,需将指针从头结点开始向后移动,到达前驱结点处。
假设单链表的长度为 n,
若在位序 1 处插入元素,则需要移动
**0**
次指针;
若在位序 n+1 处插入元素,则需要移动
**n**
次指针;
若在位序 i(1≤i≤n+1) 处插入元素,则需要移动
**i-1**
次指针。
假设各位序插入元素的概率相同, 则平均需要移动
**n/2**
次指针。
创建一个包括n个节点的有序单链表的时间复杂度是(C)
A.O(1) B.O(n) C.O(n^2) D.O(log2n)
设线性表有n个元素,严格说来,以下操作中,(C)在顺序表上实现要比在链表上实现的效率高。
//对于II 顺序表只需要3次交换操作,链表则需要分别找到两个结点前驱,第4个结点断链后再插入第2个结点后,效率较低。对于III,需依次顺序访问每个元素,时间复杂度相同。
I.输出第i(1<=i<=n)个元素值
II. 交换第3个和第4个元素的值
III.顺序输出这n个元素的值
A I B I III C I II D II III
关于线性表的顺序存储结构和链式存储结构的描述中,正确的是(B)
//两种存储结构有不同的适用场合,不能简单地说谁好坏
//顺序存储结构既能随机存储又能顺序存储。链式结构只能顺序存储。
I 线性表的顺序存储结构优于其链式存储结构
II 链式存储结构比顺序存储结构能更方便地表示各种逻辑结构
III 若频繁使用插入和删除结点操作,则顺序存储结构更优于链式存储结构。
IV 顺序存储结构和链式存储结构都可以进行顺序存取。
A I II III B II IV C II III D III IV
设线性表中有2n个元素,(A)在单链表上实现要比在顺序表上实现效率更高。
A 删除所有值为x的元素 //都是O(n) 但是后者移动元素更多
B 在最后一个元素的后面插入一个新元素 //顺序表
C 顺序输出前K个元素 //无区别
D 交换第i个元素和第2n-i-1个元素的值 //顺序表三次 链表有前驱和后继 更麻烦
给定有n个元素的一维数组,建立一个有序单链表的最低时间复杂度是(D)
A O(1) B O(n) C O(n^2) D O(nlog2n)
//若先建立单链表,然后插入建立有序表,则每插入一个元素需遍历链表寻找插入位置,即直接插入排序,时间复杂度O(n^2), 若先把数组排好序,再建立链表,建立链表的时间复杂度为O(n),排序最好的时间复杂度为O(n^2),总时间复杂度为O(nlog2n)
在长度为n的有序单链表插入一个新结点,并仍然保持有序的时间复杂度是(B)
A O(1) B O(n) C O(n^2) D O(nlog2n)
//设递增有序,首先找到第一个大于x的节点的直接前驱p,在p之后插入该结点,查找的时间复杂度为O(n),插入的时间复杂度为O(1),总时间复杂度为O(n)。
一个链表最常用的操作是在末尾插入结点和删除结点,则选用(A)最节省时间
A 带头结点的双循环链表
B 单循环链表
C 带尾指针的单循环链表
D 单链表
//在链表的末尾插入和删除一个结点,需要修改其相邻节点的指针域。而寻找尾节点及尾节点的前驱结点时,只有**带头结点的双循环链表**所需要的时间最少。
设对n(n>1)个元素的线性表的运算只有四种,删除第一个元素,删除最后一个元素,在第一个元素之前插入新元素,在最后一个元素之后插入新元素,则最好使用(C)
A 只有尾节点指针没有头结点指针的循环单链表
B 只有尾节点指针没有头结点指针的非循环双链表
C 只有头节点指针没有尾结点指针的循环双链表
D 既有头节点指针又有尾结点指针的循环单链表
//对于A 删除尾节点*p,需要找到*p的前一个结点,时间复杂度O(n)
//对于B,删除首节点*p,需要找到*p结点,这里没有直接给出头结点指针,而通过尾节点的prior指针找到*p结点的时间复杂度O(n)。
//对于D 删除尾节点*p,需要找到*p的前一个结点,时间复杂度为O(n)
一个链表最常用的操作是在最后一个元素后插入一个元素和删除第一个元素,则选用(C)最节省时间
A 不带头结点的单循环链表
B 双链表
C 不带头结点且有尾指针的单循环链表
D 单链表
//对于A 最后一个插入和普通单链表相同,O(n) 删除第一个元素为了保持单循环链表的性质(尾节点指针指向第一个结点),需要先遍历整个链表找到尾节点,再做删除操作,时间复杂度为O(n)。
//对于B,双链表的情况与单链表的相同,一个O(n) 一个O(1)。
//对于C,有尾结点的指针,省去了遍历链表的过程,因此都为O(1)。
//对于D,要在最后插入 需要遍历整个链表找到插入位置 O(n) 删除第一个元素为O(1)(尾指针指向第一个)而且先删除前面正向
某线性表用带头结点的循环单链表存储,头指针为head,当head->next->next=head成立时,线性表长度可能是(D)
A 0 B 1 C 2 D 可能为0或1
//对于一个空循环单链表,有head->next->next=head->next==head
//对于一个元素的循环单链表 头结点(头指针指示)的next域指向该唯一元素结点,该元素结点的next域指向头结点,有head->next->next=head
读入n值及n个整数,建立顺序表并遍历输出。
输入格式:
读入n及n个整数
输出格式:
输出n个整数,以空格分隔(最后一个数的后面没有空格)。
输入样例:
在这里给出一组输入。例如:
4
-3 10 20 78
输出样例:
在这里给出相应的输出。例如:
-3 10 20 78
#include <iostream>
#include<algorithm>
using namespace std;
#define MaxSize 10
typedef int ElemType;
typedef struct{
ElemType data[MaxSize];
ElemType length;
}Sqlist;
void InitList(Sqlist &L)
{
L.length=0;
}
void CreateList(Sqlist &L)
{
InitList(L);
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>L.data[i];
L.length++;
}
}
void PrintList(Sqlist L)
{
for(int i=0;i<L.length;i++)
{
if(i==L.length-1)
cout<<L.data[i];
else
cout<<L.data[i]<<" ";
}
}
int main()
{
Sqlist L;
InitList(L);
CreateList(L);
PrintList(L);
return 0;
}
7-2 学生顺序表的建立
分数 10
作者 郝晓玲
单位 西南石油大学
完成对一个顺序表的建立,表中的每个元素是同学们的学号、姓名和三门课程的成绩,输入5个同学的信息,然后显示在屏幕上。(要求利用顺序表的基本操作)
输入格式:
首先收入学生人数5,然后依次输入5个学生的学号、姓名和三门课的成绩
输出格式:
输入5个学生的学号、姓名和三门课的成绩
输入样例:
5
01 张三 89 89 89
02 李四 90 90 90
03 王五 89 89 89
04 钱六 97 97 97
05 赵倩 90 90 90
输出样例:
1 张三 89.0 89.0 89.0
2 李四 90.0 90.0 90.0
3 王五 89.0 89.0 89.0
4 钱六 97.0 97.0 97.0
5 赵倩 90.0 90.0 90.0
#include <iostream>
#include <iomanip>
using namespace std;
//定义学生结构体
struct Students
{
int ID;
string name;
double score1;
double score2;
double score3;
}students[5];
int main()
{
int n;
cin>>n;//输入学生数
//输入学生数据
for(int i=0;i<n;i++)
{
cin>>students[i].ID>>students[i].name>>students[i].score1>>students[i].score2>>students[i].score3;
}
cout.precision(1);
//输出学生数据
for(int i=0;i<n;i++)
{
cout<<students[i].ID<<" "<<students[i].name<<" "<<fixed<<students[i].score1<<" "<<fixed<<students[i].score2<<" "<<fixed<<students[i].score3;
if(i!=n-1) cout<<endl;
}
return 0;
}
7-3 基于顺序存储结构的图书信息表的创建和输出
分数 20
作者 周咏梅
单位 广东外语外贸大学
定义一个包含图书信息(书号、书名、价格)的顺序表,读入相应的图书数据来完成图书信息表的创建,然后统计图书表中的图书个数,同时逐行输出每本图书的信息。
输入格式:
输入n+1行,其中前n行是n本图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔,价格之后没有空格。最后第n+1行是输入结束标志:0 0 0(空格分隔的三个0)。其中书号和书名为字符串类型,价格为浮点数类型。
输出格式:
总计n+1行,第1行是所创建的图书表中的图书个数,后n行是n本图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔。其中价格输出保留两位小数。
输入样例:
在这里给出一组输入。例如:
9787302257646 Data-Structure 35.00
9787302164340 Operating-System 50.00
9787302219972 Software-Engineer 32.00
9787302203513 Database-Principles 36.00
9787810827430 Discrete-Mathematics 36.00
9787302257800 Data-Structure 62.00
9787811234923 Compiler-Principles 62.00
9787822234110 The-C-Programming-Language 38.00
0 0 0
输出样例:
在这里给出相应的输出。例如:
8
9787302257646 Data-Structure 35.00
9787302164340 Operating-System 50.00
9787302219972 Software-Engineer 32.00
9787302203513 Database-Principles 36.00
9787810827430 Discrete-Mathematics 36.00
9787302257800 Data-Structure 62.00
9787811234923 Compiler-Principles 62.00
9787822234110 The-C-Programming-Language 38.00
#include<iostream>
using namespace std;
#define MaxSize 100
#define OK 1
#define ERROR 0
typedef struct{
char number[100];
char name[120];
float price;
}ElemType;
typedef int Status;
typedef struct{
ElemType *elem;
int length;
}SqList;
//创建表
Status initSqList(SqList &L){
L.elem=new ElemType[MaxSize];
// if(L.elem == Null)
// return ERROR;
L.length=0;
return OK;
}
//插入
Status insertSqList(SqList &L,int i,ElemType e){
if(i<1||i>L.length+1)//判断i是否超范围
return ERROR;
if(L.length==MaxSize)// 判断是不是最大长度
return ERROR;
for(int j=L.length-1;j>=i;j--)//排队
L.elem[j+1]=L.elem[j];
L.elem[i-1]=e;
L.length++;
return OK;
}
Status printSqList(SqList L){
for(int i=0;i<L.length-1;i++)
printf("%s %s %.2f\n",L.elem[i].number,L.elem[i].name,L.elem[i].price);
printf("%s %s %.2f",L.elem[L.length-1].number,L.elem[L.length-1].name,L.elem[L.length-1].price);
}
main(){
SqList L;
ElemType e;
//创建空表
initSqList(L);
int i=1;
while(1){
scanf("%s %s %f",e.number,e.name,&e.price);
if(e.number[0]=='0'&&e.name[0]=='0'&&e.price==0){
break;
}
insertSqList(L,i,e);
i++;
}
cout<<L.length<<endl;
printSqList(L);
}
总结
本文介绍了顺序表和链表的基本操作以及基本例题。
。