1. 单链表
1.1 单链表的定义
通过一组任意的存储单元来存储线性表中的数据元素,是线性表的链式存储。
1.2 单链表的特点
- 解决了顺序表插删需要移动大量元素的缺点
- 引入了额外的指针域,浪费了空间
- 单链表是非随机存取的存储结构,查找需要从头遍历
- 最后一个结点的next指向“空”(通常用NULL或“^”符号表示)
1.3 单链表结点类型描述
-
因为逻辑相邻不一定物理相邻,所以需要额外的指针来存储后继信息
-
单链表结点的组成
- data:数据域,存放数据元素
- next:指针域,存放后继结点的地址
-
代码定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域,指向后继结点
}LNode,*LinkList;
1.4 单链表上基本操作的实现
1.4.1 头插法创建单链表
实现思路
1. 从空表开始
2. 新结点读取数据
3. 将新结点插入到链表表头,即头结点之后
实现代码
/* 头插法创建单链表
* L:单链表
* n:单链表结点的个数
*/
LinkList CreateList(LinkList L,int n)
{
LinkList p;
int i;
// L = InitList(L);
//循环创建新结点
for(i=0;i<n;++i)
{
//创建新结点
p = (LNode *)malloc(sizeof(LNode));
//读取输入值,设置数据域
scanf("%d",&p->data);
//p的后继结点设置为L的后继结点
p->next = L->next;
//L的后继设置为p
L->next = p;
}
//返回头结点
return L;
}
1.4.2 尾插法创建单链表
实现思路
1. 将新结点插入到表尾
2. 需增设表尾指针r,使其始终指向表尾结点
实现代码
//尾插法创建单链表
LinkList CreateList_R(LinkList L,int n)
{
LinkList p,r;
int i;
//创建头结点
L = (LNode *)malloc(sizeof(LNode));
//初始为空表
L->next = NULL;
r = L;
//循环创建新结点
for(i=0;i<n;++i)
{
//生成新结点
p = (LNode *)malloc(sizeof(LNode));
//读取输入值,设置数据域
scanf("%d",&p->data);
//设置 r 的后继结点为 p
r->next = p;
//设置尾结点为p
r = p;
}
//尾节点置空
r->next = NULL;
return L;
}
1.4.3 按值查找结点
实现思路
1. 从头开始遍历,返回其中数据域为给定e的结点的指针
实现代码
/* 从头开始遍历,返回其中数据域值为给定e的结点的指针,不存在,返回NULL
*/
LNode *locateElem(LinkList L,ElemType e)
{
//
LNode *p = L->next;
//判断表是否为空 && 还没有找到数据域的值是e的结点
while(p!=NULL && p->data != e)
{
p = p->next;
}
//没找到,p为NULL,找到了,返回结点
return p;
}
1.4.4 插入结点
实现思路
1. 将值为x的结点插入到单链表的第i个位置上
2. 先检查插入位置i(1<=i<=L.length+1)的合法性
3. 找到待插入结点的前驱结点,在其后执行插入
实现代码
/*
* 在L的第i个位置上插入元素值为x的结点
*/
void InsertNode(LinkList L,int i,ElemType x){
int *e;
LNode *p;
//创建一个新结点
LNode *s = (LNode *)malloc(sizeof(LNode));
//数据域赋值
s->data = x;
//找到第 i 个结点的前驱结点
p = GetElem(L,i-1,e);
//将s的后继结点设置为p的后继结点
s->next = p->next;
//将p的后继结点设置为s
p->next = s;
}
1.4.5 删除结点
实现思路
1. 将值单链表的第i个位置上的结点删除
2. 先检查删除位置i(1<=i<=L.length)的合法性
3. 找到单链表上的第i-1个位置上的结点,在执行删除操作
实现代码
/*
* 删除第 i 个结点,并将数据域通过e范返回
*/
void deleteNode(LinkList L,int i,ElemType *e){
//被删除指针的前驱结点和辅助指针
LNode *p,
*q;
//被删除结点的前驱结点
p = GetElem(L,i-1,e);
//q指向需要删除的结点
q = p->next;
//将需要删除的结点从链表上取下
p->next = q->next;
//将被删除结点的数据域赋值给e
*e = q->data;
//释放需要删除的结点
free(q);
}
2. 双链表
2.1 双链表与单链表的区别
- 单链表只能从前向后遍历,这对于插入是不方便的
- 双链表使得可以通过某结点访问它的直接前驱、直接后继
2.2 双链表的构成
- 数据域
- prior 前驱指针域
- next 后继指针域
2.3 双链表结点类型描述
typedef struct DNode{
ElemType data; //数据域
struct DNode *prior, //前驱指针域
*next; //后继指针域
}DNode,*DLinkList;
2.4 双链表的基本操作实现
2.4.1 双链表的创建
实现代码
/*
* 双链表的创建,返回头指针
*/
DNode *CreatList(DLinkList L,int n)
{
DNode *head,*pNode,*sNode;
int i;
head = L;
pNode = head;
//循环创建新结点
for(i=1;i<=n;i++)
{
//初始化新结点
sNode = InitList(sNode);
scanf("%d",&sNode->data);
//pNode的后继结点指向sNode
pNode->next=sNode;
// sNode 的前驱结点指向 pNode
sNode->prior=pNode;
//将 sNode 赋值给 pNode
pNode = sNode;
}
//返回头指针
return head;
}
2.4.2 双链表的按位序删除
实现代码
/*
根据位序删除双链表中的数据
*/
DLinkList deleteList1(DLinkList L,int i){
DNode *pNode,*qNode;
pNode = GetElem(L,i);
qNode = pNode->prior;
qNode->next = pNode->next;
pNode->next->prior = qNode;
free(pNode);
return L;
}
2.4.3 双链表的按值删除
实现代码
/*
根据数据域删除双链表中的数据
*/
DLinkList deleteList(DLinkList L,int i){
DNode *pNode,*qNode;
pNode = L->next;
while(pNode->data != i){
pNode = pNode->next;
}
qNode = pNode->prior;
qNode->next = pNode->next;
pNode->next->prior = qNode;
free(pNode);
return L;
}
3. 循环链表
3.1 循环单链表
3.1.1 循环单链表的定义
与单链表不同的是,循环单链表表尾结点指向了头结点,从而形成环
3.1.2 循环单链表的示意图
3.2 循环双链表
3.2.1 循环双链表的定义
与双链表不同的是,循环双链表的头结点的prior指针指向了表尾结点,表尾结点的next指向了头结点
3.2.2 循环双链表的示意图
4. 静态链表
4.1 静态链表的定义
借用数组实现的描述线性表的链式存储结构
4.2 静态链表的示意图
1. 游标0的next值为2,线性表的表头元素存放在下标为2处(a)
2. 游标2的next值为5: 线性表的直接后继元素存放在下标为5处(b)
3. 游标5的next值为6: 线性表的直接后继元素存放在下标为6处(c)
4. 游标为6的next值为3: 线性表的直接后继元素存放在下标为3处(d)
5. 游标为3的next值为-1: 表示d为表尾
4.3 静态链表的特点
- 插删不需要移动元素,只需要修改指针
- 需要一次性分配大量空间,存储结构可以反映数据之间的逻辑性
- 静态链表不允许扩容
单链表整体代码下载地址:https://download.csdn.net/download/SoloVersion/13711949
双链表整体代码下载地址:https://download.csdn.net/download/SoloVersion/13711997
关注我的技术公众号,时常会有优质的技术文章推送。
微信扫一扫下方二维码即可关注: