线性结构的定义:
若结构是非空有限集,则有且仅有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
可表示为:(a1 , a2 , ……, an)
线性结构的特点:
① 只有一个首结点和尾结点;
② 除首尾结点外,其他结点只有一个直接前驱和一个直接后继。
简言之,线性结构反映结点间的逻辑关系是 一对一 的
线性结构包括线性表、堆栈、队列、字符串、数组等等,其中,最典型、最常用的是线性表
2.1 线性表的类型定义
同一线性表中的元素必定具有相同特性
- 数据的逻辑结构是指数据元素之间的逻辑关系,是用户按使用需要建立的。 对
- 线性表的逻辑结构定义是唯一的,不依赖于计算机。 对
- 线性结构反映结点间的逻辑关系是一对一的。 对
- 一维向量是线性表,但二维或N维数组不是。 错
- “同一数据逻辑结构中的所有数据元素都具有相同的特性”是指数据元素所包含的数据项的个数都相等。 错
ADT List {
数据对象: D={ai | ai∈ElemSet, i =1,2,…,n, n≥0 }
数据关系: R={<ai-1,ai> | ai-1, ai∈D, i=2,…,n}
基本操作:
InitList(&L) 操作结果:构造一个空的线性表L。
DestroyList(&L)初始条件:线性表L已存在。操作结果:销毁线性表L。
ClearList(&L) 初始条件:线性表L已存在。操作结果:将L重置为空表。
…………
}
2.2线性表的顺序表示和实现
顺序表的表示
线性表的顺序表示又称为顺序存储结构或顺序映像
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
简言之,逻辑上相邻,物理上也相邻
用一组地址连续的存储单元依次存储线性表的元素,可通过数组V[n]来实现
线性表顺序存储特点:
1) 逻辑上相邻的数据元素,其物理上也相邻;
2)若已知表中首元素在存储器中的位置,则其他元素存放位置亦可求出(利用数组下标)。计算方法是(参见存储结构示意图):
设首元素a1的存放地址为LOC(a1)(称为首地址),
设每个元素占用存储空间(地址长度)为L字节,
则表中任一数据元素的存放地址为:
LOC(ai) = LOC(a1) + L *(i-1)
LOC(ai+1) = LOC(ai)+L
例如:
一个一维数组M,下标的范围是0到9,每个数组元素用相邻的5个字节存储。存储器按字节编址,设存储数组元素M[0]的第一个字节的地址是98,则M[3]的第一个字节的地址是 113
线性表的动态分配顺序存储结构
# define MAXSIZE 100
// 线性表存储空间的初始分配量
Typedef struct
{
ElemType *elem; //表基址
int length; //表长(特指元素个数)
} SqList;
顺序表的实现(或操作)
.修改 通过数组的下标便可访问某个特定
元素并修改之 核心语句: V[i]=x;
显然,顺序表修改操作的时间效率是T(n)=O(1)
顺序表的初始化
Status InitList( SqList &L )
{ //构造一个空的线性表L。
L.elem = new ElemType[MAXSIZE];
if ( ! L.elem ) exit ( OVERFLOW ) ;
// 存储分配失败
L.length = 0; // 空表长度为0
return OK;
} // InitList_Sq
销毁顺序表
//c++
void DestroyList(SqList &L)
{
if (L.elem) delete[ ] L.elem; //释放存储空间
L.length=0;
L.elem=NULL;
}
//c版
void DestroyList(SqList *L)
{
if (L->elem) free(L->elem); //释放存储空间
L->length=0;
L->elem=NULL;
}
清空顺序表
void ClearList(SqList &L)
{
L.length=0; //将线性表的长度置为0
}
判断顺序表是否为空
bool IsEmpty(SqList L)
{
if (L.length == 0)
return true;
else
return false;
}
获取线性表中的某个数据元素内容
Status GetElem(SqList L,int i,ElemType &e)
{
if (i<1||i>L.length) return ERROR;
e = L.elem[i-1]; //第i-1的单元存储第i个数据
return OK;
}
在线性表中查找值为e的数据元素
int LocateElem(SqList L,ElemType e)
{
for (i=0; i< L.length; i++)
if (L.elem[i] == e) return i+1;
return 0;
}
顺序表的插入操作
在线性表的第i个位置前插入一个元素
主要实现步骤:(n为表长)
将第n至第i 位的元素向后移动一个位置;
将要插入的元素写到第i个位置;
表长加1。
注意:事先应判断 插入位置i 是否合法? 表是否已满?
应当有1≤i≤n+1 或 i=[1,n+1]
顺序表的插入操作
Status ListInsert_Sq(SqList &L, int i , ElemType e)
{
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;
顺序表的删除操作
删除线性表的第i个位置上的元素要实现步骤:(n为表长)
将第i +1至第n 位的元素向前移动一个位置;
表长减1。
事先需要判断,删除位置i 是否合法?
应当有1≤i≤n 或 i=[1, n]
Status ListDelete_Sq(SqList &L, int i, Elemtype e)
{
if((i<1)||(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;
}
若在长度为n的线性表上删除第i位元素,向前移动元素的次数f(n)为:f(n)=n – i
思考:若删除尾结点,则根本无需移动(特别快);
若删除首结点,则表中元素全部要前移(特别慢);
两个线性表的归并
算法要求:
已知:线性表 A、B,分别由单链表 LA , LB 存储,其中数据元素按值非递减有序排列,
要求:将 A ,B 归并为一个新的线性表C , C 的数据元素仍按值非递减排列 。设线性表 C 由单链表 LC 存储。
假设:A=(3,5,8,11),B=(2,6,8,9,11)
预测:合并后 C =( 2 , 3 , 5 , 6 , 8 , 8 , 9 , 11,11 )
线性结构(包括表、栈、队、数组)的定义和特点:
仅一个首、尾结点,其余元素仅一个直接前驱和 一个直接后继。
逻辑结构:“一对一” 或 1:1
存储结构:顺序、链式
运 算 :修改、插入、删除
特征:逻辑上相邻,物理上也相邻;
优点:随机查找快 O(1)
缺点:插入、删除慢 O(n)
2.3线性表的链式表示和实现
链式存储:用一组任意的存储单元存储线性表中的数据元素。
存储特点:逻辑上相邻,物理上不一定相邻。
讨论1:每个存储结点都包含两部分:数据和 指针域 。
讨论2:在单链表中,除了首元结点外,任一结点的存储位置
由 其直接前驱结点的链域的值
指示。
1) 结点: 数据元素的存储映像。由数据域和指针域两部分组成;
2)链表: n 个结点由指针链组成一个链表。它是线性表的链式
存储映像,称为线性表的链式存储结构。
3)单链表、双链表、多链表、循环链表:
结点只有一个指针域的链表,称为单链表或线性链表;
有两个指针域的链表,称为双链表;
有多个指针域的链表,称为多链表;
首尾相接的链表称为循环链表。
头指针是指向链表中第一个结点(或为头结点或为首元结点)的指针。是一个具体的地址(值)
单链表可由一个头指针唯一确定。
单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名。
头结点是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息;
首元结点是指链表中存储线性表第一个数据元素a1的结点。
线性表的单链表存储结构
Typedef struct Lnode {
ElemType data; //数据域
struct Lnode *next; //指针域
}Lnode, *LinkList; // *LinkList为Lnode类型的指针
指针的本质就是地址!
结点创建和赋值
LNode *p;
p = new LNode;
p->data=20;
p->next=NULL ;
初始化线性表L
生成新结点作头结点,用头指针L指向头结点。
头结点的指针域置空。
Status InitList_L(LinkList &L)
{
L = new LNode;
if ( ! L ) exit ( OVERFLOW ) ;
L->next = NULL;
return OK;
}
销毁线性表L
Status DestroyList_L(LinkList &L)
{
LinkList p;
while(L)
{
p = L;
L = L->next;
delete p;
}
return OK;
}
清空线性表L
Status ClearList(LinkList L)
{// 将L重置为空表
LinkList p,q;
p=L->next; //p指向第一个结点
while(p) //没到表尾
{q=p->next; delete p; p=q;}
L->next=NULL; //头结点指针域为空
return OK;
}
求线性表L的长度
nt ListLength_L(LinkList L)
{ //返回L中数据元素个数
LinkList p;
p=L->next; //p指向第一个结点
count=0;
while(p){//遍历单链表,统计结点数
++count;
p=p->next;}
return count;
}
判断线性表L是否为空
bool ListEmpty(LinkList L)
{//若L为空表,则返回1,否则返回0
if(L->next) //非空
return true;
else
return false;
}
获取线性表L中的某个数据元素内容
难点:单链表中想取得第i个元素,必须从头指针出
发寻找(顺藤摸瓜),不能随机存取 。
思路:要修改第i个数据元素,关键是要先找到该结
点的指针p,然后用p->data=new_value 即可
Status GetElem_L(LinkList L, int i, ElemType &e)
{ // L为带头结点的单链表的头指针
// 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
P = L->next; j=1; // 初始化 ,p指向第一个结点,j为计数器
while(p&&j<i) //顺指针向后查,直到p指向第i个元素或p为空
{
p = p->next;
++j;
}
if(!p || j>i)
return ERROR; // 第i个元素不存在
e=p->data; // 取第i个元素
return OK;
} // GetElem_L
检索值为e的数据元素
LNode *LocateElem_L (LinkList L,Elemtype e)
{
p=L->next;
while(p && p->data!=e)
p=p->next;
return p; //返回L中值为e的数据元素的位置,查找失败返回NULL
}
在线性表L中插入一个数据元素
Status ListInsert_L(LinkList L, int i, ElemType e)
{
p=L; j=0;
while(p&&j<i−1){p=p->next;++j;} //寻找第i−1个结点
if(!p||j>i−1)return ERROR; //i大于表长 + 1或者小于1
s=new LNode;//生成新结点*s
s->data=e; //将结点*s的数据域置为e
s->next=p->next; //将结点*s插入L中
p->next=s;
return OK;
}
删除线性表L中第i个数据
Status ListDelete_L(LinkList L,int i,ElemType &e)
{
p=L; j=0;
while( ){//寻找第i个结点,并令p指向其前驱
p=p->next; ++j;
}
if(!(p->next)||j>i-1) return ERROR; //删除位置不合理
r=p->next; //临时保存被删结点的地址以备释放
p->next=r->next; //改变删除结点前驱结点的指针域
e=r->data; delete r;//释放结点
return OK;
}
尾插法
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->next=NULL; r->next=p; //插入到表尾
r=p; //r指向新的尾结点
}
}
头插法
Void CreateList_L (LinkList &L, int n) {
//逆位序输入n个元素的值,建立带表头结点的单链线性表L.
L = new LNode;
L->next = NULL; // 先建立一个带头结点的单链表
for ( i =n; i>0; --i)
{
p = new LNode; // 生成新结点
cin>>p ->data; // 输入元素值
p->next = L->next ;
L->next = p; // 插入到表头
}
} // CreateList_L
效率
查找
因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为 O(n)
插入和删除
因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为 O(1)。
但是对于指定位置的插入和删除操作,由于要从头查找前驱结点,所耗时间复杂度为 O(n) 。
优点
数据元素的个数可以自由扩充
插入、删除等操作不必移动数据,只需修改链接指针,修改效率较高
缺点
存储密度小
存取效率不高,必须采用顺序存取,即存取数据元素时,只能按链表的顺序进行访问(顺藤摸瓜)
循环链表
A.特点:从任一结点出发均可找到表中其他结点。
B. 与单链表的区别:循环条件
单链表 ----- p = NULL 或 p ->next =NULL
循环链表----- p= head 或 p->next = head
C. 设立尾指针:可以使链表合并简化
双向链表
双向链表插入
- p->prior->next=p->next;
- p->next->prior=p->prior;