数据结构(C语言版)第二章线性表
目录
2.1 线性表的定义和特点
定义:由n(n>=0)个数据特性相同的数据元素构成的有限序列称为线性表。
线性表中元素的个数n(n>=0)定义为线性表的长度,当n=0时称之为空表。
特点:
①存在唯一的一个被称作“第一个”的数据元素;
②存在唯一的一个被称作“最后一个”的数据元素;
③除第一个元素外,结构中的每个数据元素均只有一个直接前驱;
④除最后一个元素外,结构中的每个数据元素均只有一个直接后继;
2.2 线性表的定义类型?
1.InitList(&L)? 构造一个空的线性表L
2.CreateList(&L)? 将数据存入线性表L
3.GetElem(L,i,&e) 用e返回线性表L中第i个位置元素的值
4.LocateElem(L,e)? 查找与元素e相同的元素在线性表L中的位置
5.ListInsert(&L,i,e)? 在线性表L中第i个位置之前插入新的数据元素e
6.ListDelete(&L,i)? 删除线性表L的第i个元素
7.TraverseList(L)? 遍历线性表L
8.ListEmpty(L)? 判断线性表L是否为空表
9.ListLength(L)? 判断线性表L的长度
10.DestroyList(&L)? 销毁线性表L
11.ClearList(&L)? 清空线性表L
2.3 线性表的顺序表示和实现?
2.3.1 线性表的顺序表示
定义:线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称为线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表。
线性表是顺序存储,因此满足顺序存储的特点,在顺序存储中,通常都是借助数组来描述的,所以在C语言中可用动态分配的一维数组来表示线性表。
2.3.2 顺序表基本操作的实现(算法描述)
1.顺序表的存储结构:
#include<stdio.h>
#define MAXSIZE 100
#define ERROR 0
#define OK 1
typedef int ElemType;
typedef int Status;
typedef struct{
ElemType *elem;
int length;
}Sqlist;
2.初始化(构造一个空的线性表)
Status InitList(Sqlist &L)
{
L.elem=new ElemType[MAXSIZE];
//L.else=(ElemType *)malloc(sizeof(ElemType)*MAXSIZE);
if(L.elem==NULL)
return ERROR;//exit(OVERFLOW);
L.length=0;
return OK;
}
3.将数据存入顺序表
void CreateList(Sqlist &L)
{
int n,e;
printf("请输入要存入的元素个数:");
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&L.elem[i]);
L.length++;
}
}
4.取值
Status GetElem(Sqlist L,int i,ElemType &e)
{
if(i<1||i>L.length) return ERROR;//判断i值是否合理
e=L.elem[i-1];//将L.elem[i-1]位置的元素值赋给e
return OK;
}
5.查找
int LocateElem(Sqlist L,ElemType e)
{
for(int i=0;i<L.length;i++){
if(L.elem[i]==e)
return i+1;//查找成功,返回序号i+1
}
return ERROR;//查找失败,返回ERROR
}
6.插入
Status ListInsert(Sqlist &L,int i,ElemType e)
{
if(i<1||i>L.length) return ERROR;//判断输入的i值是否合理
if(L.length==MAXSIZE) return ERROR;//判断当前存储是否已满
for(int j=L.length-1;j>=i-1;j--){//利用循环将插入位置之后的元素从后往前依次后移
L.elem[j+1]=L.elem[j];
}
L.elem[i-1]=e;//将e放到第i个位置处
L.length++;//线性表长度加1
return OK;
}
7.删除
Status ListDelete(Sqlist &L,int i)
{
if(i<1||i>L.length) return ERROR;//判断i值是否合理
for(int j=i;j<L.length;j++){//利用循环将i之后的元素依次往前移位
L.elem[j-1]=L.elem[j];
}
L.length--;//线性表长度减1
return OK;
}
8.遍历
void TraverseList(Sqlist L)
{
for(int i=0;i<L.length;i++){
printf("%d ",L.elem[i]);
}
}
9.获取顺序表的长度
int ListLength(Sqlist L)
{
return L.length;
}
2.4 线性表的链式表示和实现
2.4.1 线性表的链式表示
节点包括两个域:数据域和指针域。
指针为数据元素之间的逻辑关系的映像,即逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻,因此,这种存储结构为非顺序映像或链式映像。
为了方便处理数据,我们会在单链表的第一个节点之前附设一个节点,称之为头节点。
链表中存储第一个数据元素的节点称为首元节点。
头指针是指向链表中第一个节点的指针。
增加头节点的作用:
①便于首元节点的处理
②便于空表和非空表的统一处理?
2.4.2 单链表基本操作的实现 (算法描述)
1.单链表的存储结构
#include<iostream>
using namespace std;
#include<cstdlib>
typedef int ElemType;
typedef int Status;
#define OK 1
#define ERROR 0
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
2.初始化(单链表的初始化)
Status InitList(LinkList &L){//单链表的初始化
L=new LNode;
L->next=NULL;
return OK;
}
3.将数据存入单链表(前插法和后插法)
前插法:
void CreateList_H(LinkList &L,int n){//前插法
L=new LNode;
L->next=NULL;
LNode *p;
for(int i=0;i<n;i++){
p=new LNode;
cin>>p->data;
p->next=L->next;
L->next=p;
}
}
后插法:
void CreateList_H(LinkList &L,int n){//后插法
L=new LNode;
L->next=NULL;
LNode *q,*p;
q=L;
for(int i=0;i<n;i++){
p=new LNode;
p->next=NULL;
cin>>p->data;
q->next=p;
q=p;
}
}
4.取值
Status GetElem(LinkList L,int i,ElemType &e){
//在带头节点的单链表中获取第i个节点的值,并赋给e
LNode *p;
p=L->next;//初始化,p指向首元节点
int j=1;//计数器j的值赋为1
while(p!=NULL&&j<i){//查找第i个节点的位置
p=p->next;
j++;
}
if(p==NULL||j>i)//当要查找的节点i的值不合法,即i>n或者i<1
return ERROR;
e=p->data;//将第i个节点的值赋给e
return OK;
}
5.查找
LinkList LocateElem(LinkList L,ElemType e){
//在带头节点的单链表中查找值为e的元素是否存在
LNode *p;
p=L->next;//初始化,p指向首元节点
while(p!=NULL){//顺链向后查找
if(p->data==e)
break;//当找到目标节点时,跳出循环
else
p=p->next;//没有找到时,p的指向往后移位
}
return p;//查找成功返回e的节点地址p,若失败则返回p为NULL
}
6.插入
Status ListInsert(LinkList &L,int i,ElemType e){
//在带头节点的单链表L中的第i个位置插入值为e的新节点
LNode *p;
p=L;//初始化,p指向头节点
int j=0;//计数器j的值为1
while(p!=NULL&&j<i-1){//查找第i-1个节点,p指向该节点
p=p->next;
j++;
}
if(p==NULL||j>i-1)//当插入点不合法时,即i的值大于n+1或i<1时
return ERROR;
LNode *q;
q=new LNode;//生成一个新的节点q
q->data=e;//将q的data域赋为e
q->next=p->next;//将节点q的指针域指向第i个节点
p->next=q;//将第i-1个节点的指针域指向节点q
return OK;
}
7.删除
Status ListDelete(LinkList &L,int i){//删除指定节点的元素
LNode *q,*p;
p=L;
int j=0;
while(p->next!=NULL&&j<i-1){//查找第i-1个节点,使指针p指向该节点
p=p->next;
j++;
}
if(p->next==NULL||j>i-1)//当i>n或者i<1时,删除位置不合理
return ERROR;
q=p->next;//临时保存待删除的节点的地址以备释放
p->next=q->next;//改变待删除节点前驱节点的指针域
free(q);//释放删除节点的空间
return OK;
}
8.遍历
void TraverseList(LinkList L){//遍历输出链表中的元素
LNode *p;
p=L;
while(p->next!=NULL){
p=p->next;
cout<<p->data;
cout<<" ";
}
}
9.获取单链表中节点的个数
int Listlength(LinkList L){//获取链表中节点的个数
int count=0;
LNode *p;
p=L;
while(p->next!=NULL){
count++;
p=p->next;
}
return count;
}
10.单链表的转置?
void TransposeList_H(LinkList &L,int i){
LNode *p,*q,*r;
p=L;
for(int j=0;j<i-1;j++){
p=p->next;
}
q=p->next;
p->next=NULL;
while(q!=NULL){//利用前插法思想
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
}
11.查找单链表中间节点的元素
Status LocateElem_h(LinkList L,ElemType &e){
LNode *p,*q;
p=L;
q=L;
while(q->next!=NULL){//思想就是p走一步,q走两步,当等于NULL时,此时p正好在中间节点处
p=p->next;
q=q->next;
if(q->next!=NULL)
q=q->next;
}
e=p->data;
return OK;
}
2.4.3 循环链表
循环链表是另一种形式的链式存储结构。
特点:表中最后一个节点的指针域指向头节点。
优点:从表中的任何一个节点出发均可找到表中的其他节点。
循环单链表的操作与单链表基本一致。
差别仅在于:在链表遍历时,判别当前指针p是否指向表尾节点的终止条件不同。
在单链表中,判别条件为p!=NULL或p->next=NULL
而在循环单链表中,判别条件为p!=L或p->next !=L
在循环链表中设立尾指针而不设头指针,可使一些操作简化。
例如:暨南大学2020年考研真题
某线性表中最常用1的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用( )存储方式最节省运算时间。【暨南大学2020年】
A.单链表
B.仅有头指针的单向循环链表
C.双链表
D.仅有尾指针的单向循环链表
解析:
【答案】D
【考点】线性表-----线性表的实现
【解析】采用带有尾指针的链表可以快速找到表中最后一个节点,方便在线性表的最后一个元素之后插入一个元素。采用仅有尾指针的单向循环链表,表中最后一个节点的下一个节点就是头节点,可以快速删除第一个元素。因此,采用仅有尾指针的单向循环链表时,在最后一个元素之后插入一个元素和删除第一个元素的时间复杂度都是O(1),最节省运算时间。
例如:2021年考研408真题
已知头指针h指向一个带头节点的非空单循环链表,节点结构为data? next,其中next是指向直接后继节点的指针,p是尾指针,q是临时指针。现要删除该链表的第一个元素,正确的语句序列是( )。【全国联考2021年】
A. h->next=h->next->next;q=h->next;free(q);B.q=h->next;h->next=h->next->next;free(q);
C.q=h->next; h->next=q->next;if(p!=q) p=h;free(q);
D.q=h->next;h->next=q->next;if(p==q) p=h;free(q);
解析:
【答案】D
【考点】线性表-----线性表的实现
【解析】由于头指针h指向一个带头节点的非空单循环链表,所以删除该链表的第一个元
素(即第一个节点)的过程如下:首先将临时指针q指向头指针h所指节点的直接后继,即q=
h->next;,此时临时指针q指向的是该链表的第一个节点。然后将头指针h所指节点的后继指
针指向临时指针q所指节点的直接后继,即h->next=q->next;或h->next=h->next->next;。
如果临时指针q所指节点是链表的唯一节点,即p==q,那么修改尾指针p的指向,即if(p==
q) p=h;。最后删除临时指针q所指节点,也就是释放其所占空间,即 free(q);。
在遇到将两个线性表合并成一个表时,也可以更加简便,仅需将第一个表的尾指针指向第二个表的第一个节点,第二个表的尾指针指向第一个表的头节点,然后释放第二个表的头节点即可。
例如:桂林电子科技大学2015年考研真题
指针p1和p2分别指向两个无头节点的非空单循环链表中的尾节点,要将两个链表链接成一个新的单循环链表,应执行的操作为( )。【桂林电子科技大学2015年】
A. p1->next=p2->next;p2->next=p1->next;
B. p2->next=p1->next; p1->next=p2->next;
C. p=p2->next;p1->next=p;p2->next=p1->next;
D. p=p1->next;p1->next=p2->next;p2->next=p;
解析:
【答案】D
【考点】线性表-----线性表的实现
2.4.4? 双向链表?
?为什么会有双向链表?
答:在单链表中,若要查找节点的直接前驱,则必须从表头指针出发,这样查找下来很费时间,即时间复杂度为O(n),为克服单链表这种单向性的缺点,双向链表就诞生了。
顾名思义,双向链表的节点中有两个指针域,一个指向直接前驱,一个指向直接后继。
节点结构如下所示:
因此我们在定义链表存储结构时,要多定义一个指针*prior,用来指向节点的直接前驱。
?在双向循环链表中也有循环表,链表中存有两个环。
双向链表的相关考研真题:
1.2016年考研408真题
已知一个带有表头节点的双向循环链表L,节点结构为prev? data? next
其中,prev和next分别是指向其直接前驱和直接后继节点的指针。现要删除指针p所指
的节点,正确的语句序列是( )。【全国联考2016年】
A.p->next->prev=p->prev; p->prev->next=p->prev; free(p);B.p->next->prev=p->next; p->prev->next=p->next; free(p);
C.p->next->prev=p->next; p->prev->next=p->prev; free(p);
D.p->next->prev=p->prev; p->prev->next=p->next; free(p);
解析:
【答案】D
【考点】线性表-----线性表的实现
【解析】双向循环链表的删除操作与单链表类似,都是使得链表不断开。设指针p所指的节点为x,则要删除x,需修改指向x的指针,即x的直接后继的前驱指针和x的直接前驱的后继指针。删除过程分为三个步骤:
①将x的直接后继的前驱指针指向x的直接前驱,即p->next->prev=p->prev;。
②将x的直接前驱的后继指针指向x的直接后继,即p->prev->next=p->next;。
③删除x,即free(p);,此时会删除x的数据域data,以及两个指针域prev和next。
【提示】步骤①和②执行的先后次序可以颠倒。
2.在双向循环链表中,在p指针所指的结点后插入q所指向的新结点,其修改指针的操作是()。
A.p->next=q; q->prior=p; p->next->prior=q; q->next=q;B.p->next=q; p->next->prior=q; q->prior=p; q->next=p->next;
C.q->prior=p; q->next=p->next; p->next->prior=q; p->next=q;
D.q->prior=p; q->next=p->next; p->next=q; p->next->prior=q;
解析:
【答案】C
【考点】线性表-----线性表的实现
2.2.5 线性表的合并?
线性表的合并在下面链接中,大家阅读即可。
http://t.csdnimg.cn/UlHsIhttp://t.csdnimg.cn/UlHsI
在考研计算机中,线性表考题除了选择题外,还会有大题,相关的大题大多都是考察删除,插入,合并,转置等操作,和算法复杂度相结合,相关的大题会在以后慢慢更新,最后祝大家学习进步,天天开心!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!