「迷路终可找到引领,漆黑也有星。」
目录
第二章 线性表
定义
线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列。
基本操作
2.1.线性表的顺序存储
定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
2.1.1.算法分析
1.查找
对含有n个记录的表:
平均查找长度ASL:
假设每个记录的查找概率相等(Pi=1/n):
2.插入
平均移动次数E:
3.删除
平均移动次数E:
4.时间复杂度
平均时间复杂度: O(n)
2.1.2.优缺点
优点:
- 存储密度大
- 可以随机存取表中任一元素
缺点 :
- 在插入、删除某一元素时,需要移动大量元素
- 浪费存储空间
- 属于静态存储形式,数据元素的个数不能自由扩充
2.1.3.代码部分-基本操作
1)查找
2)插入
2)删除
2.2.线性表的链式存储
1.链式存储结构-单链表
2.2.1 算法分析
1.查找
对含有n个记录的表:
平均查找长度ASL:
假设每个记录的查找概率相等(Pi=1/n):
2.插入
平均移动次数E:
3.删除
平均移动次数E:
4.时间复杂度
平均时间复杂度: O(n)
2.2.2 优缺点
优点:
缺点 :
2.2.3 代码部分
定义
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域
]Lnode, *LinkList; //LinkList为指向结构体Lnode的指针类型
定义链表L: LinkList L;
定义结点指针p: LNode *p; //LinkList P;
1 取值——取单链表中第i个元素的内容
【算法步骤】
- 用指针p指向首元结点,用j做计数器,初值赋为1。
- 从第1个结点(L->next ) 顺链扫描,用指针p指向当前扫描到的结点,p初值 p = L->next.
- 当p指向扫描的下一结点时,计数器j加一。
- 当j == i 时,p所指的结点就是要找的第i个结点。
【算法描述】
Status GetElem(LinkList L,int i,ElemType &e){ //根据序号i获取线性表L中某数据元素的值,通过变量e返回
p = L->next;j=1; //初始化,p指向首元结点,计数器j初值为1
while(p&&j<i){ //向后扫描,直到p指向第i个元素或p为空
p=p->next; //p指向下一个结点
++j; //计数器j加一
}
if (!p||j>i) return ERROR; //i值不合法,第i个元素不存在
e=p->data; //取第i个结点的数据域
return OK;
}//GetElem_L
2 查找
1 按值查找——根据指定数据获取该数据所在的地址
【算法步骤】
- 从第一个结点起,依次和定值e相比较。
- 如果找到一个值与e相等的数据元素,则返回其在链表中的地址。
- 如果查遍整个链表都没有找到其值和e相等的元素,则返回0或NULL。
【算法描述】
Lnode *LocateElem_L(LinkList L , Elemtype e){
//在带头结点的单链表L中查找值为e的数据元素
p=L->next; //初始化,p指向首元结点
while (p && p->data!=e) //向后扫描,直到p为空或P所指结点的数据域为e
p=p->next; //p指向下一个结点
return p; //查找成功,返回值为e的结点地址p;查找失败,返回NELL
}
2 按值查找——根据指定数据获取该数据位置序号
【算法描述】
int LocateElem_L(LinkList L , Elemtype e){
//在带头结点的单链表L中查找值为e的位置序号
p=L->next; //初始化,p指向首元结点
j=1;
while (p && p->data!=e) //向后扫描,直到p为空或P所指结点的数据域为e
p=p->next; //p指向下一个结点
j++;
if (p) rfeturn j;
else retrun 0; //返回L中值为e 的数据元素的位置序号,查找失败返回0
}
3 插入
在第n个结点前插入值为e的新结点。
【算法步骤】
【算法描述】
4 删除
【算法步骤】
【算法描述】
5 建立
1)头插法(前插法)
【算法步骤】
- 创建一个只有头结点的空链表。
- 生成新结点,将读入数据存放到新结点的数据域中。
- 从最后一个结点开始,依次将各结点插到链表前端。
【算法描述】
void CreateList_H(LinkList &L,int n){
L=new LNode;
L->next=NULL; //先建立一个带头结点的单链表
for(i=n;i>0;--i){
p=new LNode; //生成新结点p*
cin>>p->data; //输入元素值赋值给新结点*p的数据域
p->next=L->next; //*插入到表头
L->next=p;
}
}
关键步骤
p->next=L->next;
L->next=p;
2)尾插法
【算法步骤】
- 创建一个只有头结点的空链表,将新结点逐个插入到链表尾部,尾指针r指向链表的尾结点。
- 初始时,r和L均指向头结点,每读入一个数据元素申请一个新结点,将新结点插入到尾结点后,r指向新结点。
【算法描述】
void CreateList_R(LinkList &L,int n)
//正位序输入N个元素的值,建立带头结点的单链表L
L=new LNode;
L->next=NULL; //建立一个带头节点的空链表
r=L; //尾指针r指向头结点
for(i=0;i<r;i++){
p=new LNode; //生成新结点
cin>>p->data; //输入元素赋给新结点*p的数据域
p->next=NULL;
r->next=p; //将新结点*p插入尾结点*r后
r=p; //r指向新的尾结点*p
}
}
关键步骤
p->data; //输入元素赋给新结点*p的数据域
p->next=NULL;
r->next=p; //将新结点*p插入尾结点*r后
r=p;
6 循环链表
7 双向链表
1)双向链表的插入
- s->prior=p->prior;
- p->prior->next=s;
- s->next=p;
- p->prior=s;
【算法】
2)双向链表的删除
- p->prior->next=p->next;
- p->next->prior=p->prior;
【算法】
补充
补充1 判断链表是否为空
【算法思路】判断头结点指针域是否为空
int ListEmpty (LinkList L){ //若L为空表,则返回1,否则返回0
if(L->next) //非空
return 0;
else
return 1;
}
补充2 单链表的销毁
【算法思路】从头指针开始,依次释放所有结点
Status DestroyList_L(LinkList &L){ //销毁单链表L
Lnode *p; //或LinkList p;
while(){
p = L;
L = L->next;
delete p;
}
return OK;
}
结束条件: L==NULL
循环条件: L!=NULL
补充3 清空链表
【算法思路】依次释放所有结点,并将头结点指针域设置为空
Status ClearList(LinkList & L){ //将L重置为空表
LinkList p,q; //或Lnode *p ,*q;
p=L->next; //将头结点地址赋值给指针p
while (p){
q=p->next; //*将p的next域的地址赋值给下一节点q
delete p;
p=q; //反复执行
}
L->next=NELL; //将头结点指针域置空
return OK;
}
结束条件: p==NULL
循环条件: p!=NULL
补充4 求单链表表长
【算法思路】从首元结点开始,依次计数所有结点。
int ListLength_L(LinkList L){ //返回L中数据元素个数
LinkList p; //或LNode *p
p=L->next; //p指向第一个结点
i=0;
while(p){ //遍历单链表,统计结点数,循环
i++;
p=p->next;
}
return i;
}
总结
类型定义
typedef struct LNode(
ElemType data;
struct LNode *next;
}LNode, *LinkList;
变量定义:
LinkList L;
LNode *p.*s;
重要操作:
p=L; //p指向头结点
s=L->next; //s指向首元结点
p=p->next; //p指向下一结点
查找表头结点 | 查找表尾结点 | 查找结点*p的前驱节点 | |
---|---|---|---|
带头结点的单链表L | L->next 时间复杂度O(1) | 从L->next依次向后遍历时间复杂度O(n) | 通过p->next无法找到其前驱 |
带头结点仅设头指针L的循环单链表 | L->next 时间复杂度O(1) | 从L->next依次向后遍历时间复杂度O(n) | 通过p->next可以找到其前驱 时间复杂度O(n) |
带头结点仅设尾指针R的循环单链表 | R->next 时间复杂度O(1) | R 时间复杂度O(1) | 通过p->next可以找到其前驱 时间复杂度O(n) |
带头结点的双向循环链表L | L->next 时间复杂度O(1) | L->prior 时间复杂度O(1) | L->prior 时间复杂度O(1) |
2.6 顺序表和链表的比较
链式存储结构的优点:
- 结点空间可以动态申请和释放;
- 数据元素的逻辑次序靠结点的指针来指示,插入和删除不需要移动数据元素。
链式存储结构的缺点:
- 存储密度小,每个结点的指针域需要额外占用存储空间。
存储密度=结点数据本身占用的空间/结点占用的空间总量
2.7 线性表的应用
1 线性表的合并
【问题描述】
【算法步骤】
【算法】
2 有序表的合并——用顺序表实现
【问题描述】
【算法步骤】
【算法】
时间复杂度:
空间复杂度:
3 有序表的合并——用链表实现
【问题描述】
【算法步骤】
【算法】
时间复杂度:
2.8 案例引用
1 一元多项式的运算
2 稀疏多项式的运算
【案例分析】
- 创建一个新数组C
- 分别从头遍历比较a和b的每一项
- 一个多项式遍历完毕时,将另一个剩余项依次复制到c中即可
- 指数相同,对应系数相加,若其和不为零,则在c中增加一个新项
- 指数不相同,则将指数较小的项复制到c中
顺序存储结构存在问题
- 存储空间分配不灵活
- 运算空间复杂度高
3 多项式创建
4 多项式相加
5 图书管理系统