**
**
线性表的定义:用数据元素的有限序列表示
同一线性表中的元素必定具有相同特性
总结:
①线性表中数据元素的类型可以为简单类型,也可以为复杂类型。
②许多实际应用问题所涉的基本操作有很大相似性,不应为每个具体应用单独编写一个程序。
③从具体应用中抽象出共性的逻辑结构和基本操作(抽象数据类型),然后实现其存储结构和基本操作。
**
**
线性表的顺序表示又称为顺序存储结构或顺序映像。
①顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。(简言之,逻辑上相邻,物理上也相邻)
②顺序存储方法:用一组地址连续的存储单元依次存储线性表的元素,可通过数组V[n]来实现。
顺序表的类型定义
#define MAXSIZE 100 //最大长度
typedef struct {
ElemType *elem; //指向数据元素的基地址
int length; //线性表的当前长度
}SqList;
例子:图书表的顺序存储结构类型定义
#define MAXSIZE 10000 //图书表可能达到的最大长度
typedef struct //图书信息定义
{
char no[20]; //图书ISBN
char name[50]; //图书名字
float price; //图书价格
}Book;
typedef struct
{
Book *elem; //存储空间的基地址
int length; //图书表中当前图书个数
}SqList; //图书表的顺序存储结构类型为SqList
线性表的重要基本操作
1. 初始化2. 取值3. 查找4. 插入5. 删除
销毁线性表L
void DestroyList(SqList &L)
{
if (L.elem) delete[]L.elem; //释放存储空间
}
清空线性表L
void ClearList(SqList &L)
{
L.length=0; //将线性表的长度置为0
}
1.初始化线性表L (参数用指针)
status InitList_Sq(SqList *L){ //构造一个空的顺序表L
L-> elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(! L-> elem) exit(OVERFLOW); //存储分配失败
L-> length=0; //空表长度为0
return OK;
}
int GetElem(SqList L,int i,ElemType &e)
{
if (i<1||i>L.length) return ERROR;
//判断i值是否合理,若不合理,返回ERROR
e=L.elem[i-1]; //第i-1的单元存储着第i个数据
return OK;
}
3.查找(根据指定数据获取数据所在的位置)
在线性表L中查找值为e的数据元素
int LocateELem(SqList L,ElemType e)
{
for (i=0;i< L.length;i++)
if (L.elem[i]==e) return i+1;
return 0;
}
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;
}
status ListDelete_Sq(SqList &L,int i){
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;
}
顺序表(顺序存储结构)的特点
(1)利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致
(2)在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等
这种存取元素的方法被称为随机存取法
顺序表的优缺点
优点:
存储密度大(结点本身所占存储量/结点结构所占存储量)
可以随机存取表中任一元素
缺点:
在插入、删除某一元素时,需要移动大量元素
浪费存储空间
属于静态存储形式,数据元素的个数不能自由扩充
2.3 线性表的链式表示和实现
链式存储结构
结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
线性表的链式表示又称为非顺序映像或链式映像。
各结点由两个域组成:
数据域:存储元素数值数据
指针域:存储直接后继结点的存储位置
与链式存储有关的术语
1、结点:数据元素的存储映像。由数据域和指针域两部分组成
2、链表: n 个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构
3、单链表、双链表、循环链表:
结点只有一个指针域的链表,称为单链表或线性链表
有两个指针域的链表,称为双链表
首尾相接的链表称为循环链表
4、头指针、头结点和首元结点
头指针是指向链表中第一个结点的指针
首元结点是指链表中存储第一个数据元素a1的结点
头结点是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息
在链表中设置头结点有什么好处
⒈便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其它位置一致,无须进行特殊处理;
⒉便于空表和非空表的统一处理
无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了。
头结点的数据域内装的是什么
头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值。
链表(链式存储结构)的特点
(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
(2)访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等
这种存取元素的方法被称为顺序存取法
**
空表
单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名
若头指针名是L,则把链表称为表L
单链表的存储结构定义
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
// *LinkList为Lnode类型的指针
注意区分指针变量和结点变量两个不同的概念
指针变量p:表示结点地址
结点变量*p:表示一个结点
1.初始化(构造一个空表 )
【算法步骤】
(1)生成新结点作头结点,用头指针L指向头结点。
(2)头结点的指针域置空。
【算法描述】
Status InitList_L(LinkList &L){
L=new LNode;
L->next=NULL;
return OK;
}
几个简单基本操作的算法实现
销毁
Status DestroyList_L(LinkList &L){
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
清空
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;
}
求表长
“数”结点:
指针p依次指向各个结点
从第一个元素开始“数”
一直“数”到最后一个结点
求表长
int ListLength_L(LinkList L){
//返回L中数据元素个数
LinkList p;
p=L->next; //p指向第一个结点
i=0;
while(p){//遍历单链表,统计结点数
i++;
p=p->next; }
return i;
}
判断表是否为空
int ListEmpty(LinkList L){
//若L为空表,则返回1,否则返回0
if(L->next) //非空
return 0;
else
return 1;
}
1
2. 取值
【算法步骤】
从第1个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p = L->next。
j做计数器,累计当前扫描过的结点数,j初值为1。
当p指向扫描到的下一结点时,计数器j加1。
当j = i时,p所指的结点就是要找的第i个结点。
//获取线性表L中的某个数据元素的内容
Status GetElem_L(LinkList L,int i,ElemType &e){
p=L->next;j=1; //初始化
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
3.查找(根据指定数据获取数据所在的位置)
从第一个结点起,依次和e相比较。
如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址;
如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”。
//在线性表L中查找值为e的数据元素
LNode *LocateELem_L (LinkList L,Elemtype e) {
//返回L中值为e的数据元素的地址,查找失败返回NULL
p=L->next;
while(p &&p->data!=e)
p=p->next;
return p;
}
//在线性表L中查找值为e的数据元素
int LocateELem_L (LinkList L,Elemtype e) {
//返回L中值为e的数据元素的位置序号,查找失败返回0
p=L->next; j=1;
while(p &&p->data!=e)
{p=p->next; j++;}
if(p) return j;
else return 0;
}
4 插入(插在第 i 个结点之前)
将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间
s->next=p->next; p->next=s
【算法步骤】
(1)找到ai-1存储位置p
(2)生成一个新结点*s
(3)将新结点*s的数据域置为x
(4)新结点*s的指针域指向结点ai
(5)令结点*p的指针域指向新结点*s
//在L中第i个元素之前插入数据元素e
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;
}//ListInsert_L
5将表的第i个结点删去
步骤:
(1)找到ai-1存储位置p
(2)保存要删除的结点的值
(3)令p->next指向ai的直接后继结点
(4)释放结点ai的空间
删除(删除第 i 个结点)
(1)找到ai-1存储位置p
(2)临时保存结点ai的地址在q中,以备释放
(3)令p->next指向ai的直接后继结点
(4)将ai的值保留在e中
(5)释放ai的空间
//将线性表L中第i个数据元素删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
p=L;j=0;
while(p->next &&j<i-1){//寻找第i个结点,并令p指向其前驱
p=p->next; ++j;
}
if(!(p->next)||j>i-1) return ERROR; //删除位置不合理
q=p->next; //临时保存被删结点的地址以备释放
p->next=q->next; //改变删除结点前驱结点的指针域
e=q->data; //保存删除结点的数据域
delete q; //释放删除结点的空间
return OK;
}//ListDelete_L
链表的运算时间效率分析
1. 查找: 因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为 O(n)。
2. 插入和删除: 因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为 O(1)。
但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为 O(n) 。