目录
- 线性表
- 线性表的定义和特点
- 线性表类型定义
- 线性表的存储结构
- 顺序存储结构
- 顺序表基本操作的实现
- 顺序表的查找
- 算法分析:平均查找长度ASL(Average Search Length)
- 顺序表的插入
- 顺序表的删除
- 补充 C/C++
- C++中的参数传递
- 引用类型做形参的三点说明
线性表
线性表的定义和特点
- 线性表
- 线性表是具有相同特性的数据元素的一个有限序列
- 由n(n>=0)个数据元素(结点)组成
- 其中数据元素的个数n定义为表的长度
- 逻辑特征
- 在非空的线性表,有且仅有一个开始结点a1,没有直接前趋,只有一个直接后继
- 有且仅有一个终端结点an,没有直接后继,只有一个直接前趋
- 其余 内部结点ai(2<=i<=n-1) 有且仅有一个直接前趋ai-1和一个直接后继ai+1
线性表类型定义
- 稀疏多项式的运算
线性表A = ((7,0),(3,1),(9,8),(5,17))
线性表B = ((8,1),(22,7),(-9,8))
- 创建一个新数组C
- 分别从头遍历比较a和b的每一项
- 若指数相同,对应系数相加,若其和不为0,则在c中增加一个新项(0则省去)
- 若指数不相同,则将指数较小的项复制到c中
- 一个多项式已遍历完毕时,将另一个剩余项依次复制到c中即可
- 抽象数据类型线性表定义
- 基本操作
1.InitList(&L) (Initialization List)
//操作结果:构造一个空的线性表L
2.DestoryList(&L)
//初始条件:线性表L已经存在
//操作结果:销毁线性表L
3.ClearList(&L)
//初始条件:线性表L已经存在
//操作结果:将线性表L重置位空表
4.ListEmpty(L)
//初始条件:线性表L已经存在
//操作结果:若线性表L为空表,则返回Ture,反之返回false
5.ListLength(L)
//初始条件:线性表L已经存在
//操作结果:返回线性表中L中数据元素个数
6.GetElem(L,i,&e)
//初始条件:线性表L已经存在,1<=i<=ListLength(L)
//操作结果:e返回线性表L中第i个数据元素的值
7.LocateElem(L,e,compare())
//初始条件:线性表L已经存在,compare()是数据元素判定函数
//操作结果:返回L中第1个与e满足compare()的数据元素的位序,若不存在返回0
........
线性表的存储结构
在计算机内,线性表有两种基本的存储结构: 顺序存储结构 和 链式存储结构。
顺序存储结构
- 顺序表的顺序存储表示
- 线性表的顺序表示又称为顺序存储结构或顺序映像
- 顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构
- 线性表第一个数据元素a1的存储位置,称为线性表的起始位置或基地址
- 线性表顺序存储结构占用一片连续的存储空间(地址连续,中间没有空出存储单元),知道某个元素的存储位置就可以计算其他元素的存储位置。
- 特点: 以物理位置相邻表示逻辑关系;任一元素均可随机存取
- 顺序表和数组
- 顺序表(元素)–>地址连续、依次存放、随机存取、类型相同<–数组(元素):用以为数组表示顺序表
- 线性表长度可变(删除/添加)|数组长度不可动态定义->用一变量表示顺序表的长度属性
顺序表定义需要一个数组+一个整形变量
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
typedef struct{
ElemType elem[LIST_INIT_SIZE];
int length; //当前长度
}SqList; //顺序表类型
例子:
1.多项式
#define MAXSIZE 1000 //多项式可能达到的最大长度
typedef struct { //多项式非零项的定义
float p; //系数
int e; //指数
}Polynomial;
typedef struct{
Plynomial *elem; //elem是是指向Plynomial,线性表内元素类型是Plynomial
int length; //多项式中当前项的个数
}SpList; //多项式的顺序存储结构类型为SqList
2.图书表
#define MAXSIZE 10000 //图书表可能达到的最大长度
typedef struct{ //图书信息定义
char no[20]; //图书ISBN
char name[50]; //图书名字
float price; //图书价格
}Book;
typedef struct{
Book *elem; //存储空间的基地址
int length; //图书表中当前图书个数
}SqList; //图书表的顺序存储结构类型为SqList
顺序表基本操作的实现
1.基本操作
1.线性表L的初始化(参数用引用)
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) exit(OVERFLOW); //存储分配失败
L.length=0; //空表长度为0
return OK;
}
2.销毁线性表L
void DestoryList(SqList &L){
if(L.elem) delete L.elem; //释放存储空间
}
3.清空线性表
void ClearList(SqList &L){
L.length=0; //将线性表的长度设置为0
}
。。。。。
2.随机存储
int GetElem(SqList L,int i,ElemType &e){
if(i<0||i>L.length)return ERROR;
e=L.elem[i-1];
return OK;
}
顺序表的查找
int LocateElem(SqList L,ElemType e){ //在线性表L查找值为e的数据元素,返回其序号(是第几个元素)
for(i=0;i<L.length;i++)
if(L.elem[i]==e)return i+1; //查找成功,返回序号
return 0; //查找失败,返回0
}
算法分析:平均查找长度ASL(Average Search Length)
- 为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值,叫做查找算法的平均查找长度
- 对含有n个记录的表,查找成功时:
- 假设每个记录的查找概率相等:
顺序表的插入
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(int 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(n)
顺序表的删除
Status ListDelete_Sq(SqList &L,int i){
if(i<1||i>L.length) return ERROR; //i值不合法
for(int j=i;j<L.length-1;j++)
L.elem[j-1]=L.elem[j]; //被删除元素之后的元素前移
L.length--; //表长减1
return OK;
}
平均查找长度:
时间复杂度:O(n)
补充 C/C++
- 数组静态/动态分配
typedef struct{ //数组静态分配
ElemType data[MaxSize];
int length;
}SqList; //顺序表类型
typedef struct{ //数组动态分配
ElemType *data;
int length;
}SqList; //顺序表类型
- C语言的内存动态分配
typedef struct{..}SqList;
SqList L; //定义了变量之后才会占用(开辟)空间
L.data=(ElemType*) malloc (sizeof (ElemType) * MaxSize);
//(ElemType*):强制转换为ElemType类型的指针,这样就可以获得这个数组(内存空间)的基地址
- malloc(m)函数,开辟m字节长度的地址空间,并返回这段空间的首地址
- sizeof(x)运算,计算变量x的长度
- free(*p),释放指针p所指变量的存储空间,即彻底删除一个变量
- C++的动态存储分配
- new 类型名T (初值列表)
功能:申请用于存放T类型对象的内存空间,并依初值列表赋予初值
结果:T类型的指针,指向新分配的内存
int *p1=new int; //从内存中动态的分配一块空间,将这块地址赋值给指针变量
int *p1=new int(10);
- delete * p
功能:释放指针p所指的内存。p必须是new操作的返回值
C++中的参数传递
- 函数调用时送给形参表的实参必须与形参三个一致
- 参数传递有两种方式
- 传值方式:参数为整形、实型、字符型等
- 传地址:参数为指针变量、引用类型、数组名
- 传值方式
把实参的值传送给函数局部工作区相应的副本中,函数使用这个副本执行必要的功能。函数修改的是副本的值,实参的值不变。
#include<iostream.h>
void swap(float m,float n){
float temp;
temp=m;
m=n;
n=temp;
}
void main(){
float a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl
}//实参a,b的值赋给形参m,n,swap只交换了m,n的值,实参不改变
2.传地址方式–指针变量作为参数
- 形参变化影响实参
#include <stdio.h>
#include <stdlib.h>
void swap(float *m,float *n){
float temp;
temp=*m;
*m=*n;
*n=temp;
printf("%f,%f",*m,*n);
}
void main(){
float a=13.2,b=23.4,*p1,*p2;
p1=&a;
p2=&b;
swap(p1,p2);
printf("%f,%f",a,b);
}
- 形参变化不影响实参
#include <stdio.h>
#include <stdlib.h>
void swap(float *m,float *n){ //只是改变了指针的名字,没有改变指针所指的内容
float *temp;
temp=m;
m=n;
n=temp;
}
void main(){...}
- 传地址方式–数组名做参数
- 传递的是数组的首地址
- 对形参数组所做的任何改变都将反映到实参数组中
#include<iostream.h>
void sub(char b[]){//不能指明长度,因为传递过来的是地址,可以改成*b
b[]="world";
}
void main(void){
char a[10]="hello";
sub(a);
printf("%d",a);
}
- 传地址方法–引用类型做参数
引用:用来给一个对象提供一个替代的名字
#include<iostream.h>
void main(){
int i=5;
int &j=i; //j引用i的地址,相当于i,j指向同一块地址
i=7;
printf("%d,%d",i,j);
}
引用类型做形参的三点说明
- 传递引用给函数与传递指针的效果是一样的,形参变化实参也发生变化
- 引用类型做参数。在内存中并没有产生实参的副本,它直接对实参操作;而一般变量做参数,形参与实参就占用不同的存储单元,所以形参变量的值是实参变量的副本。因此,当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好。(引用不会太占空间)
- 指针参数虽然也能达到与使用引用的效果,但在被调函数中需要重复使用“*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。(指针容易乱)