链式存储线性表时,不需要使用地址连续的存储单元,即它不要求逻辑上相邻的两个元素在物理位置上也相邻。
一、单链表
对每个链表节点,除了存放元素自身的信息之外,嗨需要存放一个指向其后记的指针。
单链表中节点类型描述如下:
typedef struct
{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
由于单链表的元素是离散地分布在存储空间中的,所以单链表是非随机存取的存储结构,即不能直接找到表中某个特定的节点,查找某个特定的结点时,需要从表头开始遍历,一次查找。
通常用“头指针”来标识一个单链表,头指针为“NULL”时则表示一个空表。为了操作上的方便,在单链表第一个节点之前附加一个结点,称为“头结点”。头结点的数据域可以不设任何信息,也可以记录表厂等相关信息。
头结点和头指针的区分:不管带不带头结点,头指针始终指向链表的第一个结点,而头结点是带头结点链表中的第一个结点,结点内通常不存储信息。
引入头结点的优点:
1.由于开始结点的位置被存放在头结点的指针域中,所以在链表的的第一个位置上的操作和在表的其他未知上的操作一致,无需进行特殊处理。
2.无论链表是否为空,其头指针是指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理也同一。
单链表上基本操作的实现:
#include <stdio.h>
typedef struct
{
int data;
struct LNode *next;
}LNode, *LinkList;
//头插法建立单链表
LinkList CreatList1(LinkList &L)
{
LNode *s;int x;
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
scanf("%d",&x);
while(x!=9999)
{
s=(LinkList)malloc(sizeof(LNode));
s->data=x;
s-next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
//尾插法建立单链表,需要增加一个尾指针,使其始终指向当前链表的尾结点
LinkList CreatList2(LinkList &L)
{
int x;
L=(LinkList)malloc(sizeof(LNode));
LNode *s,*r;
scanf("%d",&x);
while(x!=9999)
{
s=(LinkList)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;
return L;
}
//按序号查找结点值,本算法取出单链表L(带头结点)中第i个位置的结点指针
LNode *GetElem(LinkList L,int i)
{
int j;
LNode *p=L->next; //头结点指针赋值给p
if(i==0)
return L;
if(i<0)
return NULL;
while(p&&j<i)
{
p=p->next;
j++
}
return p;
}
//按值查找表结点
LNode *LocateElem(LinkList L,int e)
{
LNode *p=L->next;
While(p!=NULL && p->data!=e){
p=p->next;
}
return p;
}
//插入结点
//步骤:1.先检查插入位置的合法性。
// 2.找到待插入位置的前驱结点
// 3.在其后插入新结点:第i-1个结点为*p,令新结点的指针域指向*p的后继结点,再令结点*p的指针域指向新插入的结点*s。
LinkList InsertNode(LinkList &L,int i,int e)
{
LNode *s;
int j=0;
s=(LinkList)malloc(sizeof(LNode));
s->data=e;
LNode *p=L->next;
while(p!=NULL && j<i-1)
{
p=p->next;
j++;
}
s->next=p->next;
p->next=s;
return L;
}
//删除结点
//步骤:1.检查删除位置的合法性
// 2.查找表中第i-1个结点
// 3.修改*p的指针域,将*p的指针域指向*q的下一结点,之后释放结点的存储空间
LinkList DeleteList(LinkList &L,int i)
{
LNode *p=L,q;
q=(LinkList)malloc(sizeof(LinkList));
int j;
while(p && j<i-1)
{
p=p->next;
j++
}
q=p->next;
p-next=q->next;
q->next=NULL;
free(q);
}
链表的表长:
单链表的长度是不包含头结点的,因此,不带头结点和带头结点的单链表在求表长操作上会有不同。对不带头结点的单链表,当表为空时,要单独处理。
二、双链表
双链表有两个指针prior和next,分别指向其前驱结点和后继结点。
双链表中结点类型的描述如下:
typedef struct
{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
双链表执行按值查找和按位查找的操作和单链表相同,但双链表在插入和删除操作的实现上,和单链表有较大不同,插入和删除算法的实践复杂度仅为O(1)。
双链表的插入操作
//将节点*s插入到结点*p之后
s->next=p-next;
p->next->prior=s;
s->prior=p;
p->next=s;
双链表的删除操作
//删除双链表中结点*p的后继结点*q
p->next=q->next;
q->next->prior=p;
free(q);
三、循环链表
1.循环单链表
循环单链表和单链表的区别在于,表中最后一个结点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环。
循环单链表中没有指针域为NULL的结点,循环单链表的判空条件不是头节点的指针是否为空,而是它是否等于头指针
2.循环双链表
头结点的prior指针还要指向表尾结点。
在循环双链表L中,某结点*p为尾结点时,p->next=L;当循环双链表为空表时,其头结点的prior域和next域都等于L。
四、静态链表
静态链表是借助数组来描述线性表的链式存储结构。结点也有数据域和指针域。但是与之前链表中的指针不同的是,这里的指针是节点的相对地址(数组下标)又称为游标。
静态链表结构类型的描述:
#define MaxSize 50
typedef struct
{
ElemType data;
int next; //下一个元素的数组下标
}SLinkList[MaxSize];
静态链表以next=-1作为其结束标志。