声明:本博客仅为本人学习途中做的笔记 采自青岛大学王卓老师的视频教学 主要内容为算法思路,具体代码实现还需修改后才能运行,望各位看官多多包涵,您的点赞与评论是对我最大的肯定!
线性表是具有相凤特性的数据元素的一个有限序列
( a 1 , a 2 , . . . a i − 1 , a i , a i + 1 , . . . , a n ) (a_1, a_2, ... a_i-_1, a_i,a_i+_1, ..., a_n) (a1,a2,...ai−1,ai,ai+1,...,an) \qquad \qquad \quad \qquad \qquad | \qquad \qquad \qquad | \qquad \qquad \quad |
\qquad \qquad \qquad \qquad 起始结点 \qquad \quad 数据元素 \qquad 终端结点
同一线性表中的元素必定具有相同特性,数据元素之间的关系是线性关系
从以上例子可看出线性表的逻辑特征是:
在非空的线性表,有且仅有一个开始结点a 1 _1 1,它没有直接前趋,而仅有一个直接后继a 2 _2 2; 有且仅有一个终端结点a n _n n ,它没有直接后继,而仅有一个直接前趋a n − 1 _n-_1 n−1;
其余的内部结点a i _i i (2 ≤ i _i i ≤ n-1)都有且仅有一个直接前趋a i − 1 _i-_1 i−1和一个直接后继a i + 1 _i+_1 i+1。
线性表是一种典型的线性结构
顺序存储定义:把逻辑上相邻的数据元素储存在物理上相邻的储存单元中的顺序结构
顺序表(元素)
需要加载头文件:
typedef struct {
ElemType *data;
int length
}SqlList; //顺序表类型
SqlList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*MaxSize);
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status 是函数的类型,其值是函数结果状态代码
typedef int Status;
typedef char ElemType;
静态:
#define MAXSIZE 100
typedef struct{
ElemType elem[MAXSIZE];
int length;
} SqlList;
动态数组:
typedef struct{
ElemType *elem;
int length;
} SqlList; //顺序表类型
L.elem=(ElemType*)malloc(sizeof(ElemType)*MAXSIZE);
算法思想:
在线性表L中查找与指定值e相同的数据元素的位置
从表的一端开始,逐个进行记录的关键字和给定值的比较。找到,返回该元素的位置序号,未找到,返回0。
int LocateELem(SqlList L, ElemType e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素 )
for(i=0;i<L.length;i++)
if(L.elem[i]==e) return i+1;//查找成功,返回序号
return 0;//查找失败,返回0
}
算法思想:
1.判断插入位置i是否合法。
2.判断顺序表的存储空间是否已满,若已满返回ERROR。
3.将第n至第i位的元素依次向后移动一个位置,空出第i个位置.
4.将要插入的新元素e放入第i个位置。
5.表长加1,插入成功返回OK。
Status Listinsert_Sq(SqlList &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;
}
算法思想:
1.判断删除位置i是否合法(合法值为1si≤n)。
2.将欲删除的元素保留在e中
3.将第i+1至第n位的元素依次向前移动一个位置。
4.表长减1,删除成功返回OK。
Status ListDelete_Sq(SqlList &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.length[j];//被删除元素后的元素前移
L.length--; //表长减1
return OK;
}
(1)利用数据元素的存储位置表示线性表中相邻数据元素之间的前后大系,
即线性表的逻辑结构与存储结构一致
(2)在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。
因此可以粗略地认为,访问每个元素所花时间相等
这种存取元素的方法被称为随机存取法
优点:
存储密度大(结点本身所占存储量/结点结构所占存储量)
可以随机存取表中任一元素
缺点:
在插入、删除某一元素时,
要移动大量元素
浪费存储空间
属于静态存储形式,数据元素的个数不能自由扩充
结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
线性表的链式表示又称为非顺序映像或链式映像
用一组物理位置任意的存储单元来存放线性表的数据元素。
这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
链表中元素的逻辑次序和物理次序不一定相同。
1.结点:数据元素的储存映像。由数据域和指针域两部分组成
2.链表:n个结点由指针链组成一个链表。
它是线性表的链式储存映像,称为线性表的链式储存结构
3.单链表,双链表,循环链表:
- 结点只有一个指针域的链表,称为单链表或线性链表
- 结点有两个指针域的链表,称为双链表
- 首尾相接的链表称为循环链表
4.头指针,头结点和首元结点:
头指针:是指向链表中第一个结点的指针
首元结点:是指链表中存储第一个数据元素a 1 _1 1的结点
头结点:是在链表的首元结点之前附设的一个结点
单链表由表头唯一决定,因此单链表可以用头指针的名字来命名,若头指针名是L,则把链表称为表L
类型定义:
typedef Struct{
char num[8]; //数据域
char name[8]; //数据域
int score; //数据域
}ElemType;
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType *data; //结点的数据域
struct Lnode *next; //结点的指针域
}Lnode, *LinkList; //LinkList为指向结构体Lnode的指针类型
变量定义:
定义链表L:LinkList L;//指向头结点,定义头指针
定义结点指针p: LNode *p; 等价于 LinkList p;
类型定义:
p=L; //p指向头结点
s=L->next //s指向首元结点
p=p->next //p指向下一结点
单链表的销毁
清空单链表
求单链表表长
判断链表是否为空
取值:取单链表中第i个元素的内容
查找:
插入:在第i个结点前插入新结点
删除:删除第i个结点
单链表的建立
构造一个空表
算法步骤:
1.生成新结点作头结点,用头指针L指向头结点
2.将头结点的指针域置空
算法描述:
Status InitList_L(LinkList &L){
或L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
return OK;
}
算法思路:判断头结点指针域是否为空
int ListEmpty(LinkList L){
if(L->next)//非空
return 0;
else
return 1;
}
算法思路:从头指针开始,依次释放所有结点
Status DestoryList_L(LinkList &L){
Lnode *p; //或LinkList p
while(L){
p=L;
L=L->next;
delete p;
}
return OK;
}
链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然在)
算法思路:依次释放所有结点,并将头结点指针域置为空
Status ClearList(LinkList &L){
Lnode *p,*q; //或LinkList p,q
p=L->next;
while(p){ //没到表尾
q=p->next;
delete p;
p=q;
}
L->next=NULL;//头结点指针域为空
return OK;
}
算法思路:从首元结点开始依次计数所有节点
int ListLength_L(LinkList L){//返回L中数据元素个数
LinkList p;
p=L->next; //p指向第一个结点
i=0;
while(p){ //遍历单链表,统计结点数
i++;
p=p->next;
}
return i;
}
算法思路:分别取出表中第3个元素和第15个元素
从链表的头指针出发,顺着链域next逐个节点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构算法步骤:
1.从第一个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p=L->next
2.j做计数器,累计当前扫描过的结点数,j初值为1
3.当p指向扫描到的下一结点时,计数器j+1
4.当j==i时,p所指的结点就是要找的第i个结点
算法描述:
Status GetElem_L(LinkList L, int i, ElemType &e){
//获取线性表L中某个数据元素的内容,通过变量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;
return OK;
}//GetElem_L
算法步骤:
1.从第一个结点起,依次和e相比较。
2.如果找到一个其值与e相等的数据元素,则返回其在链表中的位置或地址;
3.如果查遍整个链表都没有找到其值和e相等的元素,则返回0或NULL。
算法描述:
Lnode *LocateElem_L(LinkList L, ElemList e){
//在线性表L中查找值为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;
}
算法步骤
1.首先找到a i − 1 _{i-1} i−1的储存位置p
2.生成一个数据域为e的新结点s
3.插入新结点:1)新结点的指针域指向结点a i _i i
2)结点a i − 1 _{i-1} i−1的指针域指向新结点1.s->next=p->next
2.p->next=s
Status ListInsert_L(LinkList &L, int i, ElemType e){
p=L;j=0;
while(p && j<i-1){p=p->next;++j;}//寻找第i-1个结点,p指向i-1结点
if(!p||j>i-1) return ERROR;//i大于表长+1或者小于1,插入位置非法
s=new LNode; s->data=e;
s->next=p->next;//将结点s插入L中
p->next=s;
return OK;
}//ListInsert_L
算法步骤:
1.首先找到a i − 1 _{i-1} i−1的储存位置p,保存要删除的a i _i i的值
2.令p—>next 指向a i + 1 _{i+1} i+1
3.释放结点a i _i i的空间p->next=p->next->next
算法描述:
//将线性表L中第i个数据元素删除
Status ListDelete_L(LinkList &L, int i, ElemType &e){
p=L;j=0;
while(p->next && j<i-1){p=p->next;++j; }
//寻找第i个结点,并令p指向其前驱
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.从一个空表开始,重复读入数据;
2.生成新结点,将读入数据存放到新结点的数据域中
3.从最后一个结点开始,依次将各结点插入到链表的前端
例如:建立链表L(a, b, c, d, e)
1.在内存中找到一块空间作为头结点并将头结点的指针域置空
L=(LinkList)malloc(sizeof(LNode));
生成空间 数据域放上新元素
LNode p; p->data=a;
将新结点插入到所有结点之前 新结点接到头结点后面
p->next=L->next; L->next=p;
LNode p; p->data=a n-1;
p->next=L->next; L->next=p;
算法描述:
void CreateList_H(LinkList &L, int n){
L=new LNode;
L->next=NULL;//先建立一个带头结点的单链表
for(i=n;i>0;--i){
p=(LNode*malloc(sizeof(LNode)));//生成新结点
scanf(p->data);//输入元素值
p->next=L->next;//插入到表头
L->next=p;
}
}//CreateList_H
1.从一个空表L开始,将新结点逐个插入到链表的尾部(尾指针r指向链表
的尾结点。
2.初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点。
p->data=a i _i i
p-next=NULL
r->next=p
r=p
算法描述:
//正位序输入n个元素的值,建立带表头结点的单链表L
void CreateList_R(LinkList &L, int n){
L=new LNode; L->next=Null;
r=L; //尾指针r指向头结点
for(i=0;i<n;++i){
p=(LNode*malloc(sizeof(LNode)));//生成新结点
scanf(p->data);//输入元素值
p->next=NULL;
r->next=p; //插入到表尾
r=p; //r指向新的尾结点
}
}//CreateList_R
循环链表:是一种头尾相接的链表(即:表中最后一个结点的指计域指向头结点,整个链表形成一个环)。
优点:从表中任一结点出发均可找到表中其他结点
注意:
由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断它们是否等于头指针。
带尾指针循环链表的合并
算法描述:
LinkList Connext(LinkList Ta, LinkList Tb){
//假设Ta,Tb都是非空的单循环链表
p=Ta->next;//p存表头结点
Ta->next=Tb->next->next;//Tb表头连结Ta表尾
delete Tb->next;//释放Tb表头结点
Tb->next=p;//修改指针
return Tb;
}
双向链表的结构可定义如下:
typedef struct DuLNode{
Elemtype data;
struct DuLNode *prior,*next;//前驱指针与后继指针
}DuLNode,*DuLinkList;
双向循环链表
和单链的循环表类似,双向链表也可以有循环表
让头结点的前驱指针指向链表的最后一个结点
让最后一个结点的后继指针指向头结点。
1.s->prior=p->prior
2.p->prior->next=s
3.s->next=p
4.p->prior=s
void ListInsert_DuL(DuLinkList &L,int i,ElemType e){
//在带头结点的双向循环链表L中第i个位置之前插入元素e
if(!(p=GetElemP_DuL(L,i))) return ERROR;
s=new DuLNode; s->date=e;
s->prior=p->prior; p-prior->next=s;
s-next=p; p->prior=s;
return OK;
}//ListInsert_DuL
1.p->prior->next=p->next;
2.p->next->prior=p->prior;
void ListDelete_DuL(DuLink &L, int i, ElemType &e){
//删除带头结点的双向循环链表L的第i个元素,并用e返回
if(!(p=GetElemP_DuL(L,i))) return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return OK;
}//ListDelete_DuL
链式存诸结构的优点:
链式存储结构的缺点:
问题描述:
假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=AUB
La=(7,5,3,11) Lb=(2,6,3) -----> La=(7,5,3,11,2,6)算法步骤:
依次取出Lb中的每个元素,执行以下操作:
1.在La中查找该元素
2.如果找不到,则将其插入La的最后
void union(List &La,List Lb){
La_len=ListLength(La);
Lb_len=ListLength(Lb);
for(i=1;i<=Lb_len;i++){
GetElem(Lb,i,e);
if(!LocateElem(La,e))
ListInsert(&L,++La_len,e)
}
}
问题描述:
已知线性表La和Lb中的数据元素按值非递减有序排列,现要求将La和Lb归并为一个新的线性表Lc,且Lc中的数据元素仍按值非递减有序排列。
La=(1,7,8) Lb=(2,4,6,8,10,11) -----> Lc=(1,2,4,6,7,8,8,10,11)算法步骤:
(1)创建一个空表Lc
(2)依次从La或Lb中"摘取"元素值较小的结点插入到Lc表,直至其中一个表变空为止
(3)继续将La或Lb其中一个表的剩余结点插入在Lc表的最后
void MergeList_Sq(SqlList LA,SqlList LB,SqlList &LC){
pa=LA.elem;
pb=LB.elem;
//指针pa和pb的初值分别指向两个表的第一个元素
LC.length=LA.length+LB.length;
//新表长度为待合并两表的长度之和
LC.elem=new ElemType[LC.length];
//为合并后的新表分配一个数组空间
pc=LC.elem;
//指针pc指向新表的第一个元素
pa_last=LA.elem+LA.length-1;
//指针pa_last指向LA表的最后一个元素
pb_last=LB.elem+LB.length-1;
//指针pb_last指向LB表的最后一个元素
while(pa<=pa_last && pb<=pb_last){
//两个表都非空
if(*pa<=*pb)*pc++=*pa++;
//依次“摘取”两表中值较小的结点
else *pc++=*pb++;
}
while(pa<=pa_last)*pc++=*pa++;
//LB表已到达表尾,将LA中剩余元素加入LC
while(pb<=pb_last)*pc++=*pb++;
//LA表已到达表尾,将LB中剩余元素加入L
}//MergeList Sq
void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){
pa=La->next; pb=Lb->next;
pc=Lc=La; //用La的头结点作为Lc的头结点
while(pa&&pb){
if(pa->data<=pb->data){
pc->next=pa;
pc=pa;
pa=pa->next;
}else{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb; //插入剩余段
delete Lb; //释放Lb的头结点
}