数据结构之线性表
1.线性表基础
1.定义
零个或多个数据元素的有限序列
2.关键点
- 序列 :元素之间有顺序 元素存在多个,则第一个元素无前驱,最后一个元素无后驱。
- 有限 : 元素个数有限
3.线性表的抽象数据类型
InitList(*L) 初始化数组 建一个空的线性表L
ClearList(*L)将线性表L清空
GetElem(L,i,*e)将线性表L中的第i个位置元素返回个e
LocatElem(L,e)在线性表中查找与e相等的元素,如果查找成功,返回该元素在表中的序号
ListInsert(*L,i,e)在线性表L中的第i个位置插入e
ListDelete(*L,i,*e)删除线性表中的第i个位置的元素,并用e返回其值
ListLength(L)返回线性表L的元素个数
2.线性表的顺序存储结构
1.定义:
指的是用一段地址联系的存储单元一次存储线性表的数据元素
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
Elemtype data[MAXSIZE]; /*数组存储元素最大值为MAXSIZE*/
int length; /*线性表当前长度*/
}SqList;
2.顺序存储结构的三个属性:
- 存储空间的起始位置
- 线性表的最大存储容量
- 线性表的当前长度
3.数组长度的和线性表的长度的区分:
数组长度:存放线性表的存储空间的长度,存储分配后这个量一般是不变的
线性表的长度:线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是不断变化的
4.顺序存储结构的插入操作:
-
如果插入的位置不和理 抛出异常
-
如果线性表长度大于数组长度,抛出异常或动态增加容量
-
从最后一个元素开始向前遍历到第i个位置,分别将他们都往后移动一个位置
-
将要插入元素填入位置i处。
-
表长加1
//初始条件:顺序线性表L已存在,i<=i<=ListLength(L) //操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 Status ListInsert(SqList *L,int i,ElemType e) { int k; if(L->length==MAXSIZE)//线性表已满 return ERROR; if(i<1 || i>l->length+1)//i不在范围内 return ERROR; if(i<=L->length) { for(k=L->length-1;k>=i-1;k--)//最后往前 元素后移 L->data[k+1]=L->data[k]; } L->data[i-1]=e;//插入新元素 L->length++; return OK; }
5.删除操作:
-
如果删除位置不合适,抛出异常
-
取出删除元素
-
从删除元素开始,向后遍历到最后一个元素的位置,分别将他们向前移动一个位置
-
表长减1
//初始条件同上 //操作结果:删除L中第i个数据元素,并用e返回其值,L的长度减一 Status ListDelete(SqList *L,int i,ElemType *e) { int k; if(l->length==0)//线性表为空 return ERROR; if(i<1 || i>L->length) return ERROR; *e=L->data[i-1]; if(i<L->length) { for(k=i;k<L->length;k++) L->data[k-1]=L->data[k]; } L->length--; return OK; }
6.线性表的顺序存储结构的优缺点
优点:
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
缺点:
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间“碎片”
3.线性表的链式存储结构
1.定义
存储数据元素信息的域称为数据域
存储直接后继位置的域称为指针域。指针域中存储的信息称做为指针或链。
这两部分信息组成数据元素ai的存储映像,称为结点。
n个结点链结成一个链表,即为线性表的链式存储结构,因此链表的每个结点中只包含一个指针域
链表中第一个结点的存储位置叫头指针,线性链表的最后一个结点指针为空(null或^)
//线性表的单链表存储结构
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList//定义LinkList
2.头结点
有时,为了更方便的进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。
头结点的数据域可以为空,也可以存储如线性表的长度等附加信息,头结点的指针域指向第一个结点的指针。
3.头指针与头结点的异同
头指针:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空,头指针是链表的必要元素
头结点:
- 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义
- 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了
- 头结点不一定是链表必须元素
4.单链表的读取
获取链表第i个数据的算法思路:
-
声明一个结点p指向链表第一个结点,初始化j从1开始
-
当就j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
-
若到链表末尾p为空,说明第一个元素不存在
-
否则查找成功,返回结点p的数据
//操作结果:用e返回L中第i个元素的值 Status GetElem(LinkList L,int i,ElemType *e){ int j; LinkList p; p=L->next;//p指向L中的第一个结点 j=1;//计数器 while(p && j<i){ p=p->next; ++j; } if(!p || j>i) return ERROR; *e = p->data; return OK; }
5.单链表的插入与删除
将s结点插入p结点后面:s->next = p->next; p->next = s;
单链表第i个数据插入结点的算法思路:
- 声明一个结点p指向链表第一个结点,初始化j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
- 若到链表末尾p为空,说明第一个元素不存在
- 否则查找成功,在系统中生成一个空结点s
- 将数据元素e赋值给s->data
- 单链表的插入标准语句s->next = p->next; p->next =s;
- 返回成功
删除结点p后的结点:p->net = p->next->next
//操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p =*L;
j=1;
while(p&&j<i){
p=p->next;
++j;
}
if(!p || j>i)
return ERROR;
s = (LinkList)malloc(sizeof(Node));
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
单链表第i个数据删除结点的算法思路:
- 声明一个结点p指向链表第一个结点,初始化j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
- 若到链表末尾p为空,说明第一个元素不存在
- 否则查找成功,将欲删除的结点p->next赋值给q
- 单链表的删除标准语句p->next =q->next
- 将q结点中的数据值赋值给e,作为返回
- 释放q结点
- 返回成功
对于插入或删除数据越频繁的操作,单链表的效率优势就越明显
//操作结果:删除L的第i个数据元素,并用e返回值,L的长度减1
Status ListDelete(LinkList *L,int i,ElemType e){
int j;
LinkList p,q;
p=*L;
j=1;
while(p->next &&j<i){
p=p->next;
++j;
}
if(!(p->next) || j>i)
return ERROR;
q=p->next;
p->next=q->next;
*e =q->data;
free(q);
return OK;
}
6.单链表的整表创建
创建单链表的过程就是一个动态生成链表的过程,即从空表的初始状态起,依次建立各元素的结点,并逐个插入链表
单链表整表创建的算法思路:
- 声明一结点p和计数变量i;
- 初始化一空链表L;
- 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
- 循环:
- 生成一新结点赋值给p
- 随机生成一数字赋值给p的数据域p->data
- 将p插入到头结点与前一新结点之间
//随机产生n个元素的值,建立带表头结点的单链线性表L
void CreateListHead(LinkList *L,int n){
LinkList p;
int i;
srand(time(0));//初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next=NULL;//建立一个带头结点的单链表
for(i=0;i<n;i++){
p=(LinkList)malloc(sizeof(Node));
p->data=rand()%100+1;
p->next = (*L)->next;
(*L)->next=p;
}
}
7.单链表的整表删除
单链表整表删除的算法思路:
- 声明一结点p和q
- 将第一个结点赋值给p
- 循环:
-
将下一节点赋值给q
-
释放p
-
将q赋值给p
//操作结果:将L重置为空表 Status ClearList(LinkList *L){ LinkList p,q; p=(*L)->next;//p指向第一个结点 while(p){ q=p->next; free(p); p=q; } (*L)->next=NULL;//头结点指针域为空 return OK; }
8.单链表结构与顺序存储结构优缺点
存储分配方式:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能:
- 查找 顺序存储结构O(1) 单链表O(n)
- 插入和删除 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
单链表在线处某位置的指针后,插入和删除时时间仅为O(1)
空间性能:
- 顺序存储结构需要分配存储空间,分大了,浪费,分小了,易发生上溢
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
9.数组和链表的区别
- 从逻辑结构角度来看
- 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
- 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
- 从内存存储角度来看
- (静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
- 链表从堆中分配空间, 自由度大但申请管理比较麻烦.
4循环链表
定义
将单链表中终端结点的指针段由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表
5.双向链表
定义
双向链表是在单链表的每个结点中设置一个指向其前驱结点的指针域.
typedef struct DulNode
{
ElemType data;
struct DulNode *prior; /*直接前驱指针*/
struct DulNode *next; /*直接后驱指针*/
}DulNode,*DuLinkList;
- 通过看《大话数据结构》复习总结
浙公网安备 33010602011771号