线性表的链式存储结构
合集下载
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
只是判断循环结束的条件不同:如果找不到结点e,循 环链表结束在头结点,而单链表结束在最后一个结点。 ****思考题。有的时候,若在循环链表中设立尾指针而 不设立头指针,可使某些操作简化。如合并两个循环 链表时。图示表达:P28图2.13。
22
2.3.4 双向循环链表
在循环链表中,访问结点的特点: 访问后继结点,只需要向后走一步,而访问前驱 结点,就需要转一圈。 结论:循环链表并不适用于经常访问前驱结点的 情况。 解决方法:在需要频繁地同时访问前驱和后继结 点的时候,使用双向链表。所谓双向链表。 双向链表就是每个结点有两个指针域。一个指向 后继结点,另一个指向前驱结点。
//寻找第i-1个结点
s=p->next;
//用s指向将要删除的结点
*e=s->data;
p->next=s->next; //删除s指针所指向的结点
free(s);
return OK;
}//P26图2-10。
18
2.3.3 循环链表 若将链表中最后一个结点的next域指向头结点,如下图:
head
带头结点的单链表结构示意图
5
链式存储结构的特点 (1)线性表中的数据元素在存储单元中的存放顺 序与逻辑顺序不一定一致; (2)在对线性表操作时,只能通过头指针进入链 表,并通过每个结点的指针域向后扫描其余结点,这 样就会造成寻找第一个结点和寻找最后一个结点所花 费的时间不等,具有这种特点的存取方式被称为顺序 存取方式。
27
p
s
图 2-9
28
完整的算法:
int DuListInsert(DuLinkList *L,int i,EntryType e)
25
(1)初始化双向循环链表DL int InitDuList(DuLinkList *DL) { DL->head=(DuLNode*)malloc(sizeof(DuLNode));
//为头结点分配存储单元 if (DL->head==NULL) return ERROR; DL->head->next=DL->head; //让头结点的next域指向自身 DL->head->prior=DL->head;//让头结点的prior域指向自身 return OK; }
11. 将链表L中第i个数据元素删除,并将其内容保存在e中。
int ListDelete(LinkList *L,int i,EntryType *e)
{
LNode *p*s;
int j;ຫໍສະໝຸດ if (i<1||i>ListLength(L)) return ERROR;
//检查i值的合理性
for(p=L->head, j=0;j<i-1;p=p->next,j++);
if (s==NULL) return ERROR;
s->data=e;
for (p=L->head,j=0;p&&j<i-1;p=p->next;j++);
//寻找第i-1个结点 s->next=p->next; p->next=s; //将s结点插入
return OK;
}//P25图2-9。
17
13
7. 在链表L中检索值为e的数据元素 LNode *LocateELem(LinkList L,EntryType e) { LNode *p; for (p=L.head->next;p&&p->data!=e;p=p->next); //寻找满足条件的结点 return(p); }
14
8. 返回链表L中结点e的直接前驱结点 LNode *PriorElem(LinkList L,LNode* e) { LNode *p; if (L.head->next==e) return NULL;
2
存储地址
100 ... 120 首元素位置 ... 144 ... 160 ...
内容
b ... c ... a ... d ...
直接后继存储地 址 120 ... 160 .. 100 ...
NULL ...
线性表链式存储结构示意图
3
术语
表示每个数据元素的两部分信息组合在一起被称 为结点;
23
prior
head
elem
(a)
(b)
next
24
用C语言实现双向循环链表的类型定义—— typedef strcut DuLNode{ //双向链表的结点类型 EntryType data; struct DuLNode *prior,*next; }DuLNODE; typedef struct{ //双向链表类型 DuLNode *head; }DuLinkList;
带头结点的单链表
为了简化对链表的操作,人们经常在链表的第一个结点之前 附加一个结点,并称为头结点。这样可以免去对链表第一个结点 的特殊处理。(头结点中数据域根据需要存放一些便于操作的信 息,如元素个数等;或不存放任何信息。其引入使得所有链表 (即使是空表)的值都不为NULL。)如下图所示:
head
a b c d^
//检测第一个结点 for (p=L.head;p->next&&p->next!=e;p=p->next); if (p->next==e) return p; esle return NULL; }
15
9. 返回链表L中结点e的直接后继结点 LNode *NextElem(LinkList L,LNode* e) { LNode *p; for(p=L.head->next;p&&p!=e;p=p->next); if (p) p=p->next; return p; }
//让next域指向它自身 else return ERROR ; }
20
2. 在循环链表CL中检索值为e的数据元素 LNode *LocateELem(LinkList CL,EntryType e) {//如果所有结点都没有满足条件,则P停在头结点;而单链表中,
P停在最末一个结点。 LNode *p; for (p=CL.head->next;(p!=CL.head)&&(p->data!=e);p=p->next); if (p!=CL.head) return p; else return NULL ; }
10
4. 求链表L的长度
int ListLength(LinkList L)
{
LNode *p;
int len;
for(p=L.head, len=0;p->next==NULL; p=p->next,len++);
return(len);
循环条件表达式 重复执行的语句
}
11
5. 判断链表L是否为空。 int ListEmpty(LinkList L) {
21
**与单链表对比其他操作如下:
1、关于销毁链表、清空链表、求链表长度、返回第i个 元素的值、插入元素、删除元素等操作,与单链表基 本相似。
2、判断链表是否为空: if (cl.head->next==cl.head) return TRUE;
else return FALSE; 3、返回结点e的直接前驱结点与直接后继结点:
假线有一线性表线性表线式存线线示意线表示每据元素的部分信息线合个数其中表示据元素容的部分被线线表线化线下线描述形式head其中headnull线在线示中常用特殊线理
2.3 线性表的链式存储结构
线性表顺序存储结构的特点 它是一种简单、方便的存储方式。它要求线性表 的数据元素依次存放在连续的存储单元中,从而利用 数据元素的存储顺序表示相应的逻辑顺序,这种存储 方式属于静态存储形式。 暴露的问题 l 在做插入或删除元素的操作时,会产生大 量的数据元素移动; l 对于长度变化较大的线性表,要一次性地 分配足够的存储空间,但这些空间常常又得不到充分 的利用; l 线性表的容量难以扩充。
带头结点的循环链表示意图 实现循环链表的类型定义与单链表完全相同,它的所 有操作也都与单链表类似。只是判断链表结束的条件有所 不同。下面我们就列举两个循环链表操作的算法示例。
19
1. 初始化链表CL:即只有一个头结点,并且头结点的 next域指向它自身。
int InitList(LinkList *CL) { CL->head=(*LNode)malloc(sizeof(LNode)); if (CL->head) {CL->head->next=CL->head; return OK;}
6
在C语言中,实现线性表的链式存储结构的类型定义 typedef strcut LNode{ //结点类型 ElemType data; struct LNode *next; }LNode; typedef struct{ //链表类型 LNode *head; }LinkList;
7
2.3.2 典型操作的算法实现 1. 初始化链表L:创建带头结点的空链表。 int InitList(LinkList *L) { L->head=(*LNode)malloc(sizeof(LNode)); //为头结点分配存储单元 if (L->head) {L->head->next=NULL; return OK;}
1
线性表的链式存储结构
线性表的链式存储结构是指用一组任意的存储单 元(可以连续,也可以不连续)存储线性表中的数据 元素。为了反映数据元素之间的逻辑关系,对于每个 数据元素不仅要表示它的具体内容,还要附加一个表 示它的直接后继元素存储位置的信息。假设有一个线 性表(a,b,c,d),可用下图2所示的形式存储:
if (L.head->next==NULL) return TRUE; else return FALSE; }
12
6. 通过e返回链表L中第i个数据元素的内容 void GetElem(LinkList L,int i,EntryType *e) {
LNode *p; int j; //j为计数器,记载所经过的结点数目 if (i<1||i>ListLength(L)) exit ERROR; //检测i值的合理性 for (p=L.head,j=0; j!=i;p=p->next,j++); //找到第i个结点 *e=p->data; //将第i个结点的内容赋给e指针所指向的存储单元中 }
26
(2)在双向循环链表DL中,第i个数据元素之前 插入数据元素e
在一个结点之前插入一个新结点的过程。 在双向循环链表中的p结点之前插入s结点应使用 下列语句序列:P30图2.16 s->next=p;//s的next域指向p结点 s->prior=p->prior;// p->prior赋给 s->prior p->prior->next=s;//p的前驱结点的next域指向s p->prior=s;// p->prior域指向s
其中表示数据元素内容的部分被称为数据域 (data);
表示直接后继元素存储地址的部分被称为指针或 指针域(next)。
单链表简化为下图描述形式
head a
b
c
d ^^
单链表结构示意图
4
其中,head是头指针,它指向单链表中的第一个结点,这是 单链表操作的入口点。由于最后一个结点没有直接后继结点,所 以,它的指针域放入一个特殊的值NULL。NULL值在图示中常 用(^)符号表示。
else return ERROR ; }
8
2. 销毁链表L:删除链表中包括头结点在内所有结点。
void DestoryList(LinkList *L)
{
LNode *p;
while (L->head){
//依次删除链表中的所有结点
p=L->head; L->head=L->head->next;
16
10. 在链表L中第i个数据元素之前插入数据元素e
int ListInsert(LinkList *L,int i,EntryType e)
{
LNode *p,*s;
int j;
if (i<1||i>ListLength(L)+1) return ERROR;
s=(LNode*)malloc(sizeof(LNode));//s为存放e的结点
free(p);
}
}//从头结点开始删,直到删完为止。
9
3. 清空链表L :删除链表中除头结点外的所有结点。 void ClearList(LinkList *L) {
LNode *p; while (L->head->next){
p=L->head->next; //p指向链表中头结点后面的第一个结点 L->head->next=p->next; //删除p结点 free(p); //释放p结点占据的存储空间 } }//从头结点后的第一个结点开始删,直到删完为止。
22
2.3.4 双向循环链表
在循环链表中,访问结点的特点: 访问后继结点,只需要向后走一步,而访问前驱 结点,就需要转一圈。 结论:循环链表并不适用于经常访问前驱结点的 情况。 解决方法:在需要频繁地同时访问前驱和后继结 点的时候,使用双向链表。所谓双向链表。 双向链表就是每个结点有两个指针域。一个指向 后继结点,另一个指向前驱结点。
//寻找第i-1个结点
s=p->next;
//用s指向将要删除的结点
*e=s->data;
p->next=s->next; //删除s指针所指向的结点
free(s);
return OK;
}//P26图2-10。
18
2.3.3 循环链表 若将链表中最后一个结点的next域指向头结点,如下图:
head
带头结点的单链表结构示意图
5
链式存储结构的特点 (1)线性表中的数据元素在存储单元中的存放顺 序与逻辑顺序不一定一致; (2)在对线性表操作时,只能通过头指针进入链 表,并通过每个结点的指针域向后扫描其余结点,这 样就会造成寻找第一个结点和寻找最后一个结点所花 费的时间不等,具有这种特点的存取方式被称为顺序 存取方式。
27
p
s
图 2-9
28
完整的算法:
int DuListInsert(DuLinkList *L,int i,EntryType e)
25
(1)初始化双向循环链表DL int InitDuList(DuLinkList *DL) { DL->head=(DuLNode*)malloc(sizeof(DuLNode));
//为头结点分配存储单元 if (DL->head==NULL) return ERROR; DL->head->next=DL->head; //让头结点的next域指向自身 DL->head->prior=DL->head;//让头结点的prior域指向自身 return OK; }
11. 将链表L中第i个数据元素删除,并将其内容保存在e中。
int ListDelete(LinkList *L,int i,EntryType *e)
{
LNode *p*s;
int j;ຫໍສະໝຸດ if (i<1||i>ListLength(L)) return ERROR;
//检查i值的合理性
for(p=L->head, j=0;j<i-1;p=p->next,j++);
if (s==NULL) return ERROR;
s->data=e;
for (p=L->head,j=0;p&&j<i-1;p=p->next;j++);
//寻找第i-1个结点 s->next=p->next; p->next=s; //将s结点插入
return OK;
}//P25图2-9。
17
13
7. 在链表L中检索值为e的数据元素 LNode *LocateELem(LinkList L,EntryType e) { LNode *p; for (p=L.head->next;p&&p->data!=e;p=p->next); //寻找满足条件的结点 return(p); }
14
8. 返回链表L中结点e的直接前驱结点 LNode *PriorElem(LinkList L,LNode* e) { LNode *p; if (L.head->next==e) return NULL;
2
存储地址
100 ... 120 首元素位置 ... 144 ... 160 ...
内容
b ... c ... a ... d ...
直接后继存储地 址 120 ... 160 .. 100 ...
NULL ...
线性表链式存储结构示意图
3
术语
表示每个数据元素的两部分信息组合在一起被称 为结点;
23
prior
head
elem
(a)
(b)
next
24
用C语言实现双向循环链表的类型定义—— typedef strcut DuLNode{ //双向链表的结点类型 EntryType data; struct DuLNode *prior,*next; }DuLNODE; typedef struct{ //双向链表类型 DuLNode *head; }DuLinkList;
带头结点的单链表
为了简化对链表的操作,人们经常在链表的第一个结点之前 附加一个结点,并称为头结点。这样可以免去对链表第一个结点 的特殊处理。(头结点中数据域根据需要存放一些便于操作的信 息,如元素个数等;或不存放任何信息。其引入使得所有链表 (即使是空表)的值都不为NULL。)如下图所示:
head
a b c d^
//检测第一个结点 for (p=L.head;p->next&&p->next!=e;p=p->next); if (p->next==e) return p; esle return NULL; }
15
9. 返回链表L中结点e的直接后继结点 LNode *NextElem(LinkList L,LNode* e) { LNode *p; for(p=L.head->next;p&&p!=e;p=p->next); if (p) p=p->next; return p; }
//让next域指向它自身 else return ERROR ; }
20
2. 在循环链表CL中检索值为e的数据元素 LNode *LocateELem(LinkList CL,EntryType e) {//如果所有结点都没有满足条件,则P停在头结点;而单链表中,
P停在最末一个结点。 LNode *p; for (p=CL.head->next;(p!=CL.head)&&(p->data!=e);p=p->next); if (p!=CL.head) return p; else return NULL ; }
10
4. 求链表L的长度
int ListLength(LinkList L)
{
LNode *p;
int len;
for(p=L.head, len=0;p->next==NULL; p=p->next,len++);
return(len);
循环条件表达式 重复执行的语句
}
11
5. 判断链表L是否为空。 int ListEmpty(LinkList L) {
21
**与单链表对比其他操作如下:
1、关于销毁链表、清空链表、求链表长度、返回第i个 元素的值、插入元素、删除元素等操作,与单链表基 本相似。
2、判断链表是否为空: if (cl.head->next==cl.head) return TRUE;
else return FALSE; 3、返回结点e的直接前驱结点与直接后继结点:
假线有一线性表线性表线式存线线示意线表示每据元素的部分信息线合个数其中表示据元素容的部分被线线表线化线下线描述形式head其中headnull线在线示中常用特殊线理
2.3 线性表的链式存储结构
线性表顺序存储结构的特点 它是一种简单、方便的存储方式。它要求线性表 的数据元素依次存放在连续的存储单元中,从而利用 数据元素的存储顺序表示相应的逻辑顺序,这种存储 方式属于静态存储形式。 暴露的问题 l 在做插入或删除元素的操作时,会产生大 量的数据元素移动; l 对于长度变化较大的线性表,要一次性地 分配足够的存储空间,但这些空间常常又得不到充分 的利用; l 线性表的容量难以扩充。
带头结点的循环链表示意图 实现循环链表的类型定义与单链表完全相同,它的所 有操作也都与单链表类似。只是判断链表结束的条件有所 不同。下面我们就列举两个循环链表操作的算法示例。
19
1. 初始化链表CL:即只有一个头结点,并且头结点的 next域指向它自身。
int InitList(LinkList *CL) { CL->head=(*LNode)malloc(sizeof(LNode)); if (CL->head) {CL->head->next=CL->head; return OK;}
6
在C语言中,实现线性表的链式存储结构的类型定义 typedef strcut LNode{ //结点类型 ElemType data; struct LNode *next; }LNode; typedef struct{ //链表类型 LNode *head; }LinkList;
7
2.3.2 典型操作的算法实现 1. 初始化链表L:创建带头结点的空链表。 int InitList(LinkList *L) { L->head=(*LNode)malloc(sizeof(LNode)); //为头结点分配存储单元 if (L->head) {L->head->next=NULL; return OK;}
1
线性表的链式存储结构
线性表的链式存储结构是指用一组任意的存储单 元(可以连续,也可以不连续)存储线性表中的数据 元素。为了反映数据元素之间的逻辑关系,对于每个 数据元素不仅要表示它的具体内容,还要附加一个表 示它的直接后继元素存储位置的信息。假设有一个线 性表(a,b,c,d),可用下图2所示的形式存储:
if (L.head->next==NULL) return TRUE; else return FALSE; }
12
6. 通过e返回链表L中第i个数据元素的内容 void GetElem(LinkList L,int i,EntryType *e) {
LNode *p; int j; //j为计数器,记载所经过的结点数目 if (i<1||i>ListLength(L)) exit ERROR; //检测i值的合理性 for (p=L.head,j=0; j!=i;p=p->next,j++); //找到第i个结点 *e=p->data; //将第i个结点的内容赋给e指针所指向的存储单元中 }
26
(2)在双向循环链表DL中,第i个数据元素之前 插入数据元素e
在一个结点之前插入一个新结点的过程。 在双向循环链表中的p结点之前插入s结点应使用 下列语句序列:P30图2.16 s->next=p;//s的next域指向p结点 s->prior=p->prior;// p->prior赋给 s->prior p->prior->next=s;//p的前驱结点的next域指向s p->prior=s;// p->prior域指向s
其中表示数据元素内容的部分被称为数据域 (data);
表示直接后继元素存储地址的部分被称为指针或 指针域(next)。
单链表简化为下图描述形式
head a
b
c
d ^^
单链表结构示意图
4
其中,head是头指针,它指向单链表中的第一个结点,这是 单链表操作的入口点。由于最后一个结点没有直接后继结点,所 以,它的指针域放入一个特殊的值NULL。NULL值在图示中常 用(^)符号表示。
else return ERROR ; }
8
2. 销毁链表L:删除链表中包括头结点在内所有结点。
void DestoryList(LinkList *L)
{
LNode *p;
while (L->head){
//依次删除链表中的所有结点
p=L->head; L->head=L->head->next;
16
10. 在链表L中第i个数据元素之前插入数据元素e
int ListInsert(LinkList *L,int i,EntryType e)
{
LNode *p,*s;
int j;
if (i<1||i>ListLength(L)+1) return ERROR;
s=(LNode*)malloc(sizeof(LNode));//s为存放e的结点
free(p);
}
}//从头结点开始删,直到删完为止。
9
3. 清空链表L :删除链表中除头结点外的所有结点。 void ClearList(LinkList *L) {
LNode *p; while (L->head->next){
p=L->head->next; //p指向链表中头结点后面的第一个结点 L->head->next=p->next; //删除p结点 free(p); //释放p结点占据的存储空间 } }//从头结点后的第一个结点开始删,直到删完为止。