线性表
线性表定义
线性表(Linear List) : 由n个具有相同特性的数据元素(结点)a1, a2,…组成的有限序列。
- 其中数据元素的个数n定义为表的长度。
- 当n=0时称为空表将非空的线性表(n>0)记作: (a1, a2, …an)
线性表特性
线性表的逻辑特征是:
- 在非空的线性表, 有且仅有一个开始结点a1,它没有直接前趋,而仅有一个直接后继a2;
- 有且仅有一个终端结点an,它没有直接后继,而仅有一个直接前趋an-1;
- 其余的内部结点都有且仅有一个直接前趋ai-1.和一个直接后继ai+1。
- 元素个数有限。
- 线性表是一种逻辑结构,表示元素之间一对一相邻的关系。1233
- 线形表顺序存储结构占用一片连续的存储空间。知道某个元素的存储位置就可以计算其他元素的存储位置。
线性表的顺序表示
线性表的基本操作(抽象数据类型定义)
-
InitList(&L)
//初始化操作,建立一个空的线性表L -
DestertList(&L)
//销毁已存在的线性表L -
ClearList(&L)
//将线性表清空 -
ListInsert(&L, i, e)
//在线性表L中第i个位置插入新元素e -
ListDelete(&L i, &e)
//删除线性表L中第i个位置元素,用e返回 -
lsEmpty(L)
//若线性表为空,返回true,否则false -
ListLength(L)
//返回线性表L的元素个数 -
LocateElem(L, e)
// L中查找与给定值e相等的元素,若成功返回该元素在表中的序号,否则返回0 -
GetElem(L,i, &e)
// 将线性表L中的第i个位置元素返回给e
定义线性表
#define MAXSIZE 100
typedef struct{
ElemType *elem;
int length;
} SqList;
顺序表基本操作的实现
1.构造顺序表
Status InitList Sq(SqlList &L){//构造一个空的顺序表L
L.elem= new ElemType[MAXSIZE]; //1.为顺序表分配空间
if(!L.elem) exit(OVERFLOW); //2.判断是否有足够的存储空间:存储分配失败退出,分配成功,则继续执行
L.length=0; //3.空表:长度为0
return OK;
}
//status用来返回本函数是否执行成功:1.OK 2.ERROR 3.OVERFLOW
//!L.elem :如果不存在
2.销毁顺序表
void DestroyList(SqList &L) {
if (L.elem)
delete L.elem; //释放存储空间
}
3.顺序表的取值(根据位置 i 获取相应位置数据元素的内容) 随机存取
Status GetElem(SqList L, int i, ElemType &e){
if (i<1 || i>L.length) return ERROR; //第一步:判断i值是否合理,若不合理,返回ERROR
e=L.elem[i-1]; //第二步:elem[i-1]单元存储第i个数据元素
return OK; //查找成功
}
//e=L.elem[i-1]; 返回的是数组下标,而不是元素位置,所以是i—1
4.顺序表的查找
算法思想:
- 在线性表L中查找与指定值e相同的数据元素的位置
- 从表的一端开始,逐个进行记录的关键字和给定值的比较。找到,返回该元素的位置序号,未找到,返回0。
按值查找:时间复杂度:最好O(1) 平均O(n) 最坏O(n)
int LocateElem (SqList L, ElemType e){//在顺序表L中查找值为e的数据元素,返回其序号
for(i=0;i< L.length;i++)
if(L.elem[i]==e) return i+1; //查找成功,返回序号i+1
return 0; //查找失败,返回0
}
5.顺序表的插入
算法思想:
- 判断插入位置i是否合法。
- 判断顺序表的存储空间是否已满,若已满返回ERROR。
- 将第n至第i位的元素依次向后移动一个位置,空出第i个位置。
//从后向前插
Status ListInsert (SqList &L,int i ,ElemType e)
{ //在顺序表工中第i个位置插人新的元素e,i值的合法范围是1≤i≤L.length+1
if((i<1) || (i>L.length+1)) return ERROR; //i值不合法
if (L. length==MAXSIZE) return ERROR; //当前存储空间已满
for (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;
}
插入操作的时间复杂度:最好O(1) 平均O(n) 最坏O(n)
6.顺序表的删除
算法思想:
- 判断删除位置i是否合法(合法值为1≤i≤n) 。
- 将欲删除的元素保留在e中。
- 将第i+1至第n 位的元素依次向前移动一个位置。
- 表长减1,删除成功返回OK。
Status ListDelete (SqList &L, int i)
{//在顺序表L中删除第i个元素,i值的合法范围是1≤i≤L.length
if((i<1)1I (i>L. length)) return ERROR; //i 值不合法
for (j=i;j<=L. length-1;j++)
L.elem[j-1]=L.elem[j]; //被删除元素之后的元素前移
--L.length; //表长减1
return OK;
}
删除操作:时间复杂度:最好O(1) 平均O(n) 最坏O(n)
顺序表(线性表的顺序存储结构)的特点
- 利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致
- 在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等
这种存取元素的方法被称为随机存取法。
顺序表优缺点
优点:
- 存储密度大(结点本身所占存储量/结点结构所占存储量)
- 可以随机存取表中任一元素
缺点:
- 在插入、删除某一元素时,需要移动大量元素
- 浪费存储空间
- 属于静态存储形式,数据元素的个数不能自由扩充
线性表的链式表示
链式存储结构特点
-
结点在存储器中的位置是任意的,即逻辑上相邻的数据元素,在物理上不一定相邻;
-
线性表的链式表示又称为非顺序映像或链式映像。
-
用一组物理位置任意的存储单元来存放线性表的数据元素。
-
这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
-
链表中元素的逻辑次序和物理次序不一定相同。
单链表
单链表是线性表的链式存储。只能从头节点依次向后遍历。
- 结点: 数据元素的存储映像。由数据域和指针域两部分组成
数据域
指针域 - 链表: n个结点由指针链组成一个链表。
它是线性表的链式存储映像,称为线性表的链式存储结构。 - 单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名。 若头指针名是L,则把链表称为表L。
几个节点差别p31
头指针: 是指向链表中第一个结点的指针
首元结点: 是指链表中存储第一个数据元素a,的结点
头结点:是在链表的首元结点之前附设的一个结点;
表空条件
单链表基本操作
0.单链表的定义
p30
typedef struct Lnode{
ElemType
data;
struct Lnode *next;} LNode, *LinkList;
1.单链表的初始化
Status InitList (LinkList &L){//构造一个空的单链表L
L=new LNode; //生成新结点作为头结点,用头指针工指向头结点
L->next=NULL; //头结点的指针域置空
return OK;
}
2.头插法建立单链表
引入head结点的优点:
- 可以使插入删除操作统一
- 非空表和空表操作统一
void CreateList_H(Linkist &L,int n)
{ //逆位序输入n个元素的值,建立带表头结点的单链表LL=new LNode;
L->next=NULL; //先建立一个带头结点的空链表
A
for(i=0;i<n;++i){
p=new LNode; //生成新结点*p
cin>>p->data; //输入元素值赋给新结点*p的数据域
p->next=L->next; L->next=p;//将新结点*p插入到头结点之后
}
}
3.尾插法建立单链表
void CreateList_R(LinkList &L,int n)
{//正位序输人n个元素的值,建立带表头结点的单链表L
L=new LNode;
L->next=NULL; //先建立一个带头结点的空链表
r=L; //尾指针r指向头结点
for(i=0;i<n; ++i)
p=new LNode; //生成新结点
cin>>p->data; //输入元素值赋给新结点*p的数据域
p->next=NULL;
r->next=p;//将新结点*p插入尾结点*工之后
r=p; //r指向新的尾结点*p
)
4.单链表的取值
Status GetElem(LinkList L,int i,ElemType &e)
{//在带头结点的单链表工中根据序号i获取元素的值,用e返回L中第i个数据元素的值
p=L->next;j=1; //初始化,p指向首元结点,计数器j初值赋为1
while(p&&j<i) //顺链域向后扫描,直到p为空或p指向第i个元素
{
p=p->next; //p指向下一个结点
++j; //计数器j相应加1
}
if(!pl1j>i)return ERROR;//i值不合法,i>n或i≤0
e=p->data; //取第i个结点的数据域
return OK;
}
5.单链表的按值查找
//按值找地址
LNode *LocateElem(LinkList L, ElemType e){//在带头结点的单链表工中查找值为e的元素
p=L->next; //初始化,p指向首元结点
while(p && p->data !=e) //顺链域向后扫描,直到p为空或p所指结点的数据域等于e
p=p->next; //p指向下一个结点
return p; //查找成功返回值为e的结点地址p,查找失败p为NULL
}
//按值找i在表中的位置?
6.单链表的插入
//在第i个位置插入
Status ListInsert (LinkList &L,int i,ElemType e)
{//在带头结点的单链表工中第i个位置插入值为e的新结点
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
}
7.单链表的删除
//删除第i个元素
Status ListDelete(LinkList &L,int i){
//在带头结点的单链表工中,删除第i个元素p=L;j=0;
while((p->next)&&(j<i-1)) //查找第i-1个结点,p指向
{p=p->next; ++j;}
if( !(p->next)||(j>i-1)) return ERROR;
q=p->next;
p->next=q->next;
delete q;
return OK;
}
求单链表的表长
int ListLength_L(LinkList L){//返回L中数据元素个数LinkList p;
p=L->next; //p指向第一个结点
i=o;
while(p){ //遍历单链表,统计结点数
i++;
p=p->next;
}
}
链表(链式存储结构)的特点
- 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
- 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等。
- 链表是顺序存取法。
- 链顺序表是随机存取法。
循环链表
1.循环链表定义:是一种头尾相接的链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)。
2.优点: 从表中任一结点出发均可找到表中其他结点。
3.注意: 由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断它们是否等于头指针。
4.带尾指针的循环单链表
5.循环链表的和单链表的区别
循环链表的操作和单链表基本一致,差别在于遍历时,判别当前指针p是否指向表尾节点的条件不同。
单链表 : p != null; 或 p->next != null;
循环单链表 :p != L; 或 p->next != L; (L为头指针)
顺序表和链表的比较
p41 .p50
双向链表
双向链表定义
在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有两个方向不同的链,故称为双向链表。
双向链表的结构可定义如下
typedef struct DuLNode{
Elemtype data;
struct DuLNode *prior, *next;
}DuLNode, *DuLinkList;
双循环链表
在插入、删除时,需同时修改两个方向上的指针,两者的操作的时间复杂度均为On)。
双向链表操作
1.插入操作
1. s->prior=p->prior;
2. p->prior->next=s;
3. s->next=p;
4. p->prior=s;
//完整算法
void ListInsert_DuL(DuLinkList &L, Int i, ElemType e) {//在带头结点的双向循环链表L中第i个位置之前插入元素e
if(!(p=GetElemP_DuL(L,i))) return ERROR;
s=new DuLNode;
s ->date = e;
s ->prior = p ->prior;
p ->prior ->next = s;
s ->next = p;
p ->prior = s;
return OK;
}
2.删除操作
1.p->prior->next=p->next;
2.p->next->prior=p->prior;
//完整算法
void ListDelete_DuL(DuLink &L,Int i, ElemType &e){//删除带头结点的双向循环链表L的第i个元素
if(!(p=GetElemP_DuL(Li)))return ERROR;
e = p-> data;
p-> prior -> next = p -> next;
p-> next -> prior = p-> prior;
free(p);
return OK;
}