2.1线性表的定义和特点
1.线性表的定义:线性表是具有数据元素的一个序列
总结:
- 线性表由n(n>=0)个数据元素(结点),,...,组成的有限序列
- 数据元素个数n:表的长度
- n=0时称为空表
- 非空的线性表记作:(,,...,)
- 数据元素(1<=i<=n)只是一个抽象的符号,具体含义在不同情况下可以不同
2.线性表的特点:
线性表有以下逻辑特征:
- 在非空的线性表,一个开始结点,它没有直接前趋,而仅有一个直接后继$a_2
- 一个终端结点,它没有直接后继,而仅有一个直接前趋
- 其余的内部结点(2<=i<=n-1)都一个直接前趋和一个直接后继
2.2线性表的实际应用
线性表的顺序存储结构存在以下问题:
- 存储空间分配不灵活
- 运算的空间复杂度高
总结:
- 线性表中数据元素的类型可以为简单类型,也可以为复杂类型
- 许多实际应用问题所涉的基本操作有很大,不应为每个具体应用单独编写一个程序
- 从具体应用中抽象出共性的逻辑结构和基本操作(抽象数据类型),然后实现其存储结构和基本操作
2.3线性表的类型定义
抽象数据类型线性表定义如下:
基本操作
- 构造
- 销毁
- 重置
- 判断空
- 计算元素个数
- 返回指定元素的值
- 返回满足的元素位序
- 返回前趋
- 返回后继
- 插入元素
2.4线性表的顺序表示与实现
在计算机内,线性表有两种基本的存储结构:和
1.线性表的顺序存储定义与特点
线性表的顺序表示又称为或
:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构
:1.以物理位置相邻表示逻辑关系
2.任一元素均可随机存取
2.线性表的顺序存储结构
线性表顺序存储结构 。知道某个元素的存储位置就可以计算其他元素的存储位置
3.顺序表中元素存储位置的计算
(用C语言实现时,地址加1,要看是什么数据类型,不同的数据类型加的值不一样,对于int而言,地址加1就相当于地址加4。)
顺序表的存储结构图示如下:
4.线性表与数组对比
从顺序表的定义上可以看出,
注:C99标准已支持数组长度动态定义。C99前[]内只能是,不能包含变量
一维数组的定义方式:
5.顺序表结构类型定义
5.1顺序表的静态结构体定义
顺序表的结构体定义如下
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
typedef struct{
ElemType elem[LIST_INIT_SIZE]; //数组
int length; //当前长度
}SqList; //顺序表的类型
将多项式的顺序存储结构类型定义如下
#define MAXSIZE 1000 //多项式可能达到的最大长度
//利用结构体将存放一个变量的数组转变为可以存放两个的数组
typedef struct{ //多项式非零项的定义
float p; //系数
int e; //指数
}Polynomial; //复杂结构类型
typedef struct{
Polynomial *elem; //存储空间的基地址
int length; //多项式中当前项的个数
}SqList; //多项式的顺序存储结构类型为SqList
例:图书表的顺序存储结构类型定义
#define MAXSIZE 10000
typedef struct{
char no[20];
char name[50];
float price;
}Book;
typedef struct{
Book *elem;
int length;
}SqList;
5.2顺序表(数组)的静态和动态分配对比
5.2.1 顺序表(数组)的静态分配
typedef struct{
ElemType data[MaxSize];
int length;
}SqList; //顺序表类型
数组名data[MaxSize]存放的是数组首元素地址,即*data,但data[MaxSize]的空间已经分配好固定的值
5.2.2顺序表(数组)的动态分配
typedef struct{
ElemType *data;
int length;
}SqList; //顺序表类型
*data存放的是数组首元素的地址
- C语言的内存动态分配
//c语言中为动态数组分配空间
SqList L;
L.data=(ElemType*)malloc(sizeof(ElemType)*MaxSize);
- (ElemType*)为强制类型转换,根据不同的转换类型将m字节长度的地址空间进行划分,例如:int类型就将m/4,数组中可以存放m/4个元素,而*为指针,因为结构体中定义数组元素为指针
- ,开辟m字节长度的地址空间,并返回这段空间的首地址
- ,计算变量x的长度
- ,释放指针p所指变量的存储空间,即彻底删除一个变量
- C++的内存动态分配
- 功能:申请用于存放T类型对象的内存空间,并依据初值列表赋以初值
- 结果值:
- 成功:T类型的指针,指向新分配的内存
- 失败:0(NULL)
//c++语言中为动态数组分配空间
int *p1=new int;
//或
int *p1=new int(10);
- 功能:释放指针P所指向的内存。P必须是new操作的返回值
6.C++中的参数传递
- 函数调用时传送给形参表的实参必须与形参三个一致:
- 参数传递有两种方式
- 传值(参数为整型、实型、字符型等)
- 传地址:
1. 参数为指针变量
2. 参数为引用类型(&a,&b等)
3. 参数为数组名
6.1传值调用
- 传值调用时在函数中修改值,对应的实参的值不会改变
6.2传地址调用
6.2.1传地址调用--指针变量作参数
- 形参变化影响实参(通过交换两个指针变量所指向的空间中的值)
- 形参变化不影响实参(通过交换两个指针变量所指向的空间)
7.顺序表的基本操作
7.1线性表L的初始化(参数用引用)
Status InitList_Sq(SqList &L){ //构造一个空的顺序表
L.elem=new ElemType[MAXSIZE]; //为顺序表分配存储空间
if(!L.elem) //存储分配失败
exit(OVERFLOW);
L.length=0; //空表长度为0(刚分配好空间初始化表里没有一个元素)
return OK;
}
7.2销毁,清空,返回长度,判断非空简单操作
- 销毁线性表L
void DestroyList(SqList &L){
if(L.elem)
delete L.elem; //释放存储空间
}
- 清空线性表
void ClearList(SqList &L){
L.length=0; //将线性表的长度置为0
}
- 求线性表L的长度
int GetLength(SqList L){
return (L.length);
}
- 判断线性表L是否为空
int IsEmpty(SqList L){
if(L.length==0)
return 1;
else
return 0;
}
7.3顺序表的取值
根据位置i获取相应位置数据元素的内容
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;
}
2.5线性表的链式存储结构
线性表的链式表示又称为非顺序映像或链式映像 链式存储结构:结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
2.5.1线性表的链式表示和实现
- 用一组物理位置任意的存储单元来存放线性表的数据元素
- 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的
- 链表中元素的逻辑次序和物理次序不一定相同
2.5.2链表有关概念
- 结点:数据元素的存储映像。由数据域和指针域两部分组成
- 数据域:存储元素数值数据
- 指针域:存储直接后继结点的存储位置
- 链表:n个结点由指针链组成一个链表。
它是线性表的链式存储映像,称为线性表的链式存储结构
2.5.3链表种类
- 单链表:结点只有一个指针域的链表,称为单链表或线性链表
- 双链表:结点有两个指针域的链表,称为双链表
- 循环链表:首尾相接的链表称为循环链表
2.5.4链表结构
- 头指针:是指向链表中第一个结点的指针
- 首元结点:是指链表中存储第一个数据元素的结点
- 头结点:是在链表的首元结点之前附设的一个结点
- 空表:
- 无头结点时,头指针为空时表示空表
- 有头结点时,当头结点的指针域为空时表示空表
- 头结点的优点:
- 便于首元结点的处理:首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无须进行特殊处理
- 便于空表和非空表的统一处理:无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了
- 头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表总长度
- 链表的特点:
- 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
- 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等
2.5.5单链表的定义
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域(嵌套定义结构体)
}Lnode,*LinkList; //LinkList为指向结构体Lnode的指针类型
定义链表L :
LinkList L; //定义链表L(LinkList是指针类型,后面的L不用加*号)
//或者
LNode *L;
定义结点指针p:
LNode *p; //定义结点指针p
//或者
LinkList p;
例:定义一个单链表存储学生学号,姓名,成绩
typedef Struct student{
char num[8]; //数据域
char name[8]; //数据域
int score; //数据域
struct student *next; //指针域
}Lnode,*LinkList;
为了统一链表的操作,通常这样定义(将数据域单独定义为一个结构体):
typedef Struct{
char num[8]; //数据域
char name[8]; //数据域
int score; //数据域
}ElemType;
typedef struct Lnode{
ElemType data; //数据域
struct Lnode *next; //指针域
}Lnode,*LinkList;
2.5.6链表的基本操作
1. 单链表的初始化
- 即构造一个带头结点的空表
步骤:
- 生成新结点作为头结点,用头指针L指向头结点
- 将头结点的指针域置空
Status InitList_L(LinkList &L){
L=new LNode; //或者L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
return OK;
}
2.判断单链表是否为空
空表:链表中无元素,称为空链表
思路:判断头结点指针域是否为空
int ListEmpty(LinkList L){ //为空则返回1,否则返回0
if(L->next) //为空
return 0;
else
return 1;
}
3.单链表的销毁
思路:从头指针开始,依次释放所有结点
Status DestroyList_L(LinkList &L){ //销毁单链表L
LNode *p; //或者LinkList p;
while(L){
p=L;
L=L->next;
delete p;
}
return OK;
}
4.单链表的清空
思路:依次释放所有结点,并将头结点指针域设置为空
Status ClearList(LinkList &L){ //将L重置为空表
Lnode *p,*q; //或LinkList p,q;
p=L->next;
while(p){ //没到表尾
q=p->next;
delete p;
p=q;
}
L->next=NULL; //头结点指针域为空
return OK;
}
5.求单链表表长
思路:从首元结点开始,依次计数所有结点
int ListLength_L(LinkList L){ //返回L中数据元素个数
LinkList p;
p=L->next; //p指向第一个结点
i=0;
while(p){ //遍历单链表,统计结点数
i++;
p=p->next;
}
}
6.取值(取单链表中第i个元素的内容)
Status GetElem_L(LinkList L, int i, ElemType &e){ //获取线性表L中的某个数据元素的内容,通过变量e返回
p=L->NEXT; j=1; //初始化
while(p && j<1){ //向后扫描,直到p指向第i个元素p为空
p=p->next; ++j;
}
if(!p || j>i) //第i个元素不存在
return ERROR;
e=p->data; //取第i个元素
return OK;
}
7.按值查找-根据指定数据获取该数据所在的位置(地址)
1.获取指定数据的地址
Lnode *LocateElem_L(LinkList L,Elemtype e){
//在线性表L中查找值为e的数据元素
//找到,则返回L中值为e的数据元素的地址,查找失败则返回NULL
p=L->next;
while(p && p->data!=e)
p=p->next;
return p;
}
2.根据指定数据获取该数据位置序号
//在线性表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;
}
8.插入
//在L中第i个元素之前插入数据元素e
Status ListInsert_L(LinkList &L, int i, ElemType e){
p=0;j=0;
while(p && j<i-1){ //寻找第i-1个结点,p指向i-1结点
p=p->next;
++j;
}
if(!p || j>i-1) //i大于表长+1或者小于1,插入位置非法
return ERROR;
s=new LNode; //生成新结点s
s->data=e; //将结点s的数据域置为e
s->next=p->next; //将结点s插入L中
p->next=s;
return OK;
}
9.删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
p=L;j=0;
while(p->next && j<i-1){
p=p->next; //寻找第i个结点,并令p指向其前趋
++j;
}
if(!(p->next) || j>i-1){ //删除位置不合理
return ERROR;
}
q=p->next; //临时保存被删结点的地址以备释放
p->next=q->next; //改变删除结点前趋结点的指针域
e=q->data; //保存删除结点的数据域
detele q; //释放删除结点的空间
return OK;
}
10.建立单链表
头插法-元素插在链表头部
2.5.7单链表基本操作的算法时间效率分析
void CreateList_H(LinkList &L,int n){
L=new LNode;
L->next=NULL; //先建立一个带头结点的单链表
}