具体实现代码见此博客:单链表的CPP实现
每个结点中只含有一个指针域
单链表的存储结构:
typedef struct LNode{
ElemType data;//数据域
struct LNode *next;//指针域
}
单链表可以包含头结点,也可以直接在头部装入第一个元素。
即,所有的链表都有一个头指针head,head中不装数据项叫做带头结点,带有数据项则称为不带头结点。如果不带头结点,每次头插法时,head都需要进行变化。
以下讨论带头结点的单链表
指向链表中第一个结点的指针(头结点或者首元结点)的指针
在链表的首元结点,数据域内只放空表标志和表长等信息
链表中存储线性表第一个数据元素的结点
先创建一个头指针,实际上就是创建一个头结点,然后头指针指向头结点
链表的定义:一个线性表由若干个结点组成,每个结点至少含有两个域:数据域和指针域,由这样的结点存储的线性表称为链表。
指针域存储的是数据的首地址。数据的宽度是由数据自己的数据类型所确定的,计算机自动截取。
指针所占的数据大小和计算机有关,与计算机的寻址能力相关,即与计算机地址线的条数相关。
单链表结构示意图:
[a1, -]–>[a2,- ]–>[a3,^]
逻辑次序和物理次序不一定相同,元素之间的逻辑关系用指针表示,需要额外的空间来存储元素之间的关系(指针)
头指针
在链式存储结构中以第一个结点的存储地址作为线性表的基地址,通常称它为头指针。线性表的最后一个元素没有后继,因此最后一个结点中的指针域是一个特殊的值NULL,常称为空指针。
头结点
在单链表上设置一个结点,本身不存放数据,它的指针域指向第一个元素的地址(不是必须有的,有些情况使用,比如分组翻转链表,主要是找头指针方便,结构也比较规整)
头结点的作用:使得对第一个元素的操作与对其他元素的操作保持一致。
单链表的结点
typedef struct LNode{
ElemType data;//数据域
struct LNode* next;//指针域
}LNode,*LinkList;
LNode* L;
LinkList L;
构造一个空的带头结点的线性表L
LinkList L;
void InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
L->next=NULL
}
//[#,^],头结点,下一个结点才开始存数据
//判断线性表L是否为空
bool ListEmpty(LinkList L){
if(L-next==NULL)
return TRUE;
else
return FALSE;
}
构造一个空的不带头结点的线性表L
LinkList L;
void(LinkList &L){
L=NULL;
}
//判断线性表L是否为空
bool ListEmpty(LinkList L){
if(L==NULL)
return TRUE;
else
return FALSE;
}
插入结点
LinkList s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
删除结点
//需要知道被删除节点的前驱,记为p,被删除的记为q
q=p->next;
p->next=q->next;
free(q);
查找结点
//查找第i个结点,将第i个结点的值存入e
GetElem_L(LinkList L,int i,ElemType &e){
p=L->next;
j=1;
while(p&&j<i){
p=p->next;
++j;
}
if(!p)
return ERROR;
e=p->data;
return OK;
}
固定位置插入节点
//在第i个结点之前插入新的元素e
ListInsert_L(LinkList L,int i,ElemType e){
p=L;
j=0;
while(p&&j<i-1){
p=p->next;
++j;
}
if(!p)
return ERROR;
else{
s=(LinkList)malloc(sizeof(LNode));
s->data=e;
s-next=p->next;
p->next=s;
return OK;
}
}
删除特定位置结点
//删除i位置的结点,值存入e
ListDelete_L(LinkList L,int i,ElemType &e){
p=L;
j=0;
while(p&&j<i-1){
p=p->next;
++j;
}//首先找到被删除节点的前驱节点
if(!(p->next))
return ERROR;//结点不存在
q=p->next;
p->next=q->next;
e->data=q->data;
free(q);
retun OK;
}
链表的优点:插入删除不需要移动数据、不需要预先分配空间
链表的缺点:指针占用存储空间,增加了内存负担,不是随机存储,是顺序存储结构(逻辑上)
头插法建立单链表
p=(LNode*)malloc(sizeof(LNode));
p->data=e;
p->next=L->next;
L->next=p;
尾插法建立单链表
r=L;
p=(LNode*)malloc(sizeof(LNode));
p->data=e;
r->next=p;
r=p;
r->next=NULL;//r跟踪链表的尾部
单链表的最后一个结点的指针域没有利用,如果使其指向头指针(头结点),则首尾构成了一个循环,称作循环链表。
从循环链表的任意结点出发都可以找到表中的其他节点。
尾指针:指向表尾的指针。循环链表通常使用尾指针,因为使用头指针的话,不容易查找尾结点。
单链表合并
合并La、Lb
//带头结点的链表合并
R=La;
while(R->next)
R=R->next;
R->next=Lb->next;
free(Lb);
循环链表合并
//带头结点头指针的循环链表合并
R1=La;
while(R1->next!=La)
R1=R1->next;
R2=Lb;
while(R2->next!=Lb)
R2=R2->next;
R1->next=Lb->next;
R2->next=La;
free(Lb);
//带头结点尾指针循环链表合并
L1=La->next;
L2=Lb->next;
La->next=L2->next;
Lb->next=L1;
free(L2);
一个链表的每一个结点都包含两个指针域,一个指针域指向其前驱节点,另一个指向其后继结点。可以对链表进行双向查找。
typedef struct DuLNode{
ElemType data;
struct DuLNode *prior;
struct DuLNode *next;
}DuLNode,*DuLinkList;
//p结点
p->next->prior;
p->prior->next;
双向链表插入节点
// 在a结点之前插入,记a结点位置是p,插入节点为s
s->prior=p->prior;
s->next=p->next;
p->prior->next=s;
p->prior=s;
双向链表删除结点
//删除a结点,位置为p
p->prior->nezt=p->next;
p->next->prior=p->prior;
free(p);
双向链表的插入算法
void ListInsert_Dul(DuLinkList &L,int i,ElemType e){
find(p);
s-data=e;
s->prior=p->prior;
s->next=p;
p->prior->next=s;
p->prior=s;
}
在没有指针的语言中构造的链表。使用前仍然需要预先分配空间。采用一个叫游标的东西实现链表的功能。使用数组来维护下标。静态链表实际上是高级的线性表。
不同的是,数据在数组中的存储是随机的。数据之间通过游标进行链接。
FAT文件存储的FAT表就是静态链表。
静态链表的结点结构:
typedef struct{
int data;
int cur;//游标
}Sta_Lis_Node;
此外,静态链表除了数据本身外,还包含备用链表,用来存储未使用的空间。
通常a[0]放备用链表的表头,a[1]放数据链表的表头。