回炉重造之数据结构【二】线性表
前情回顾
第一章 绪论
文章目录
线性结构特点:在数据元素的非空有限集中
(1)存在唯一的一个被称作“第一个”的数据元素;
(2)存在唯一的一个被称作“最后一个”的数据元素;
(3)除第一个之外,集合中的每个数据元素均只有一个前驱;
(4)除最后一个之外,集合中的每个数据元素均只有一个后继
线性表的定义和基本操作
线性表的定义
1、线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列;
线性表的位序从1开始
2、特点:除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继。
线性表的顺序表示
顺序表的定义
1、线性表的顺序存储又称顺序表,通常用数组来描述线性表的顺序存储结构
2、特点:
(1)逻辑上相邻的两个元素在物理位置上也相邻
(2)可随机存取
(3)存储密度高,每个结点值存储数据元素
线性表中元素的位序是从1开始的,而数组中元素的下标是从0开始的
3、静态分配
#define MAXSIZE 50; //定义线性表最大长度
typedef struct {
ElemType data[MAXSIZE];//顺序表的元素
int length;//顺序表的当前长度
}SqList; //顺序表的类型定义
4、动态分配语句
L.data=(ElemType *)malloc(sizeof(ElemType)*InitSize);
5、(1)判空:L.length==0;
(2)判满:L.length==MAXSIZE;
顺序表上基本操作的实现
1、插入操作
位序1≤i≤L.length+1范围内可插入
bool LinstInsert(SqList &L,int i,ElemType e){
if(L.length>=MAXSIZE || i<1 || i>L.length+1) //判断i范围是否有效,存储空间是否已满
return false;
//j作为位序
for(int j=L.length;j>=i;j--) //将第i个元素及之后的元素后移
L.data[j]=L.data[j-1];
/*j作为下标
for(int j=L.Length-1;j>=i-1;j--)
L.data[j+1]=L.dat[j];
*/
L.data[i-1]=e;
L.length++;
renturn true;
}
最好情况:表尾插入,时间复杂度O(1)
最坏情况:表头插入,时间复杂度O(n)
平均时间复杂度O(n)
平均移动次数
(1)若表长为n,则插入位置有n个,若在任意结点后插入为 ( n − 1 ) 2 (n-1)\over 2 2(n−1);在任意结点前插入为 ( n + 1 ) 2 (n+1)\over2 2(n+1)
(2)若在任意位置插入,则插入位置有n+1个,平均移动次数为 1 n + 1 ∑ i = 1 n + 1 ( n − i + 1 ) = n 2 {1\over n+1}\displaystyle\sum_{i=1}^{n+1} (n-i+1)={n\over 2} n+11i=1∑n+1(n−i+1)=2n
2、删除操作
删除1≤i≤L.length范围内某个元素
bool ListDelete(SqList &L,int i,ElemType &e){
if(i<1||i>L.length) //判断i的范围是否有效
return false;
e=L.data[i-1]; //将被删除的元素赋值给e
for(int j=i;j<L.length;j++) //将第i个位置后的元素前移
L.data[j-1]=L.data[j];
L.length--; //线性表长度减1
return true;
}
最好情况:删除表尾元素,时间复杂度O(1)
最坏情况:删除表头元素,时间复杂度O(n)
平均时间复杂度O(n)
平均移动次数
删除位置有n个,平均移动次数为 1 n ∑ i = 1 n + 1 ( n − i ) = n − 1 2 {1\over n}\displaystyle\sum_{i=1}^{n+1} (n-i)={n-1\over 2} n1i=1∑n+1(n−i)=2n−1
3、按值查找(顺序查找)
在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SqList L,ElemType e){
int i;
for(i=0;i<L.length;i++)
if(L.data[i]==e)
return i+1; //下标为i的元素值等于e,返回其位序i+1
return 0; //退出循环,说明查找失败
}
最好情况:查找的元素在表头,时间复杂度O(1)
最坏情况:查找的元素在表尾或不存在,时间复杂度O(n)
平均时间复杂度O(n)
4、按位查找时间复杂度:O(1)
线性表的链式表示
链式线性表不需要使用地址连续的存储再远,只需修改指针达到相关目的
插入和删除不需要移动元素,失去可随机存储的优点
非随机存取
单链表的定义
1、单链表:指通过一组任意的存储单元来存储线性表中的数据元素
2、结构:除了存放元素自身的信息外,还需存放一个指向其后继的指针
3、定义
typedef struct LNode{ //定义单链表结点类型
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
4、判空:
(1)带头结点:L->next==NULL
;
(2)不带头结点:L==NULL
;
5、头结点与头指针的区分
不管带不带头结点,头指针都始终指向链表的第一个结点,而头结点是带头结点的链表中的第一个结点,结点内通常不存储信息
单链表上基本操作的实现
1、建立单链表
(1)头插法
逆序建立单链表,每次插入表头
void CreateLinkList_HEAD(LinkList &L,int n) //头插法,逆序建立单链表
{
LNode *s;int x;
L=(LinkList)malloc(sizeof(LNode)); //创建头结点
L->next=NULL; //初始为空表
for(int i=0;i<n;i++){
scanf("%d",&x); //输入结点的值
s=(LNode *)malloc(sizeof(LNode)); //创建新节点
s->data=x;
s->next=L->next;
L->next=s;
}
}
每个结点插入的时间为O(1),总时间复杂度为O(n)
(2)尾插法
正向建立单链表,每次插入表尾
void CreatLinkList_TAIL(LinkList &L,int n)
{
int x;
LNode *s,*r=L; //r为表尾指针
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
for(int i=0;i<n;i++)
{
scanf("%d",&x);
s=(LNode *)malloce(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
}
r->next=NULL; //尾结点指针置空
}
总时间复杂度O(n)
2、查找
(1)按位查找
//按位查找,返回第i个元素(带头结点)
LNode *GetElem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=L; //L指向头结点,头结点是第0个结点(不存数据)
while(p!=NULL && j<i){ //循环找到第i个结点
p=p->next;
j++;
}
return p;
}
平均时间复杂度O(n)
(2)按值查找
//按值查找,找到数据域==e的结点
LNode *LocateElem(LinkList L,ElemType e){
LNode *p=L->next;
//从第1个结点开始查找数据域为e的结点
while(p!=NULL && p->data !=e)
p=p->next;
return p;//找到后返回该结点指针,否则返回NULL
}
平均时间复杂度O(n)
3、求表的长度
int Length(LinkList L){
int len=0;
LNode *p=L;
while(p->next !=NULL){
p=p->next;
len++;
}
return len;
}
平均时间复杂度O(n)
4、插入
思路:画图,找到第i-1个元素
核心代码
p=GetElem(L,i-1); //查找插入位置的前驱结点
s->next=p->next;
p->next=s;
语句2和3的顺序不能颠倒
查找前驱时间复杂度O(n),若在给定的结点后面插入时间复杂度O(1)
5、删除
思路:查找第i-1个元素
p=GetElem(L,i-1); //查找删除位置的前驱结点
q=p->next; //令q指向被删除结点
p->next=q->next; //将*q结点从链中“断开”
free(q);
双链表
单链表访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)
双链表结点中有连个指针prior和next,分别指向其前驱结点和后继结点
双链表的插入操作
s->next=p->next; //将结点*s插入到结点*p之后
p->next->prior=s;
s->prior=p;
p->next=s;
第1和2步必须在第4步之前
双链表的删除操作
p->next=q->next;
q->next->prior=p;
free(q);
循环链表
循环单链表
1、在循环单链表中,表尾结点*r的next域指向L,故表中没有指针域为NULL的结点
2、循环单链表判空:头结点的指针是否等于头指针:L->next==L
循环单链表不舍头指针仅设尾指针会方便某些操作,如两个线性表合并成一个
循环双链表
1、循环双链表中,头结点的prior指针还要指向表尾结点
2、在循环双链表L中,某结点*p为尾结点时,p->next==L
;当循环双链表为空表时,其头结点的prior域和next域都等于L
a静态链表
1、静态链表借助数组来描述线性表的链式存储结构,结点包含数据域data和指针域next;指针是结点的相对地址(数组下标),又称游标;
2、静态链表需要预先分配一块连续的内存空间
3、静态链表以next==-1作为其结束的标志
顺序表和链表的比较
顺序表 | 链表 | |
---|---|---|
存取(读写)方式 | 顺序存取、随机存取 | 顺序存取 |
逻辑结构与物理结构 | 逻辑上相邻,物理上也相邻 | 逻辑上相邻,物理上不一定相邻 |
按值查找 | (无序)O(n);(有序) O( l o g 2 n log_2n log2n) | O(n) |
按序号查找 | O(1) | O(n) |
插入、删除 | 平均需要移动半个表长的元素 | 修改相关结点的指针域 |
选取存储结构的因素
(1)基于存储的考虑
难以估计线性表的长度或存储规模——不宜采用顺序表;
链表——存储密度第,链式存储结构的存储密度小于1;
(2)基于运算的考虑
反复插入、删除——链表
(3)基于环境的考虑
顺序表易于实现,基于数组
链表基于指针
一元多项式的表示及相加
1、若只对多项式进行“求值”等不改变多项式的系数和直属的运算,采用类似于顺序表的顺序存储结构
2、一元多项式相加的运算规则:对于两个一元多项式中所有指数相同的项,对应系数相加,若其和不为0,则构成“和多项式”中的一项;对于两个一元多项式中所有指数不相同的项,则分别复抄到“和多项式”中去。