文章目录
前言
链表有单链表和双链表两种实现形式。
它通指针将表内各个节点相连,不要求逻辑上的相邻元素在内存上也相邻。因此,链表可以灵活地在内存中“穿梭”,不需要一整片连续的内存空间。
同时,这也决定了链表不能随机存取表内元素。
一、单链表
1.单链表的结构体定义
单链表的结构包含数据域和指针域。
其中,数据域存放数据元素;指针域存放指向下一个结点地址的指针。
【注】本贴只记录带头结点的链表。
图示如下:
结构体定义如下,写入
LinkList.h
文件:
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct LNode{
ElemType data; // 存储数据
struct LNode *next; // 指向下一个结点
}LNode, *LinkList;
2.单链表的创建
1)头插法创建单链表
#include "LinkList.h"
void CreateList_HeadInsert(LinkList &L) {
LNode *t; // 临时结点 t,存储要插入的数据及其指针域
ElemType x; // 要插入的数据
L = (LNode*)malloc(sizeof(LNode)); // 分配头节点的内存空间
L->next = NULL; // 头节点(头插创建过程中相当于链表尾)初始化
cin>>x;
while (x!=9999) {
t = (LNode*)malloc(sizeof(LNode)); // 分配临时结点内存空间
t->data = x; // 存入数据
t->next = L->next;
L->next = t; // 设置指针
cin>>x;
}
}
图解如下:
2)尾插法创建单链表
#include "LinkList.h"
void CreateList_TailInsert(LinkList &L) {
L = (LNode*)malloc(sizeof(LNode));
LNode *t, *r = L; // 指针 r 一直指向链表 L 的尾部
ElemType x;
cin>>x;
while (x!=9999) {
t = (LNode*)malloc(sizeof(LNode));
t->data = x;
r->next = t;
r = t;
cin>>x;
}
r->next = NULL; // 链表尾部之后为 NULL
}
图解如下:
3.单链表的基本操作
3.1 输出操作 - PrintList
函数名:
PrintList(LinkList L)
输出格式:
output: 1 2 3 4 5 6
----------------
#include "LinkList.h"
void PrintList(LinkList L) {
cout<<"output: ";
while (L->next!=NULL) {
L = L->next;
cout<<L->data<<" ";
}
cout<<'\n'<<"----------------"<<'\n';
}
3.2 按位查找 - GetElem
函数名:
GetElem(LinkList L, int i)
#include "LinkList.h"
LNode* GetElem(LinkList L, int i) {
int j = 1; // 位序,从 1 开始
LNode *p = L->next; // p 指向第一个存有数据的结点
if (i==0) return L; // i 是位序,从 1 开始;0 表示头结点
if (i<1) return NULL; // 位序不合法
while (p&&j<i) {
p = p->next;
j++; // 遍历,直到位置 i
}
return p; // 返回指向位置 i 处结点的指针;无匹配返回 NULL
}
3.3 按值查找 - LocateElem
函数名:
LocateElem(LinkList L, ElemType e)
#include "LinkList.h"
LNode* LocateElem(LinkList L, ElemType e) {
LNode *p = L->next;
while (p&&p->data!=e) // 从第一个存储数据的结点开始往后遍历
p = p->next;
return p; // 若表内无匹配元素,返回表尾的 next: NULL
}
3.4 插入操作 - ListInsert
函数名:
ListInsert(LinkList &L, int i, ElemType e)
#include "LinkList.h"
bool ListInsert(LinkList &L, int i, ElemType e) {
LNode *p = GetElem(L, i-1); // 前驱结点
if (p==NULL) return false; // 位序不合法,插入失败
LNode *t = (LNode*)malloc(sizeof(LNode));
t->data = e;
t->next = p->next;
p->next = t;
return true;
}
示例如下:
3.5 删除操作 - ListDelete
函数名:
ListDelete(LinkList &L, int i, ElemType &e)
#include "LinkList.h"
bool ListDelete(LinkList &L, int i, ElemType &e) {
LNode *p = GetElem(L, i-1); // 前驱结点
if (p==NULL) return false;
LNode *s = p->next;
e = s->data; // 带出数值
p->next = s->next;
free(s); // 释放删除的结点
return true;
}
示例如下:
二、双链表
1.双链表的结构体
单链表结点只有一个 next
指针指向它的后继,因此单链表在搜索查询、插入删除时,只能从前往后依次遍历。若要访问某处结点的前驱,则需要从头开始重新遍历。
因此双链表结点采用两个指针:prior
指向前驱;next
指向后继。
示意图如下:
结构体定义如下,写入
DLinkList.h
文件:
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct DNode {
ElemType data;
struct DNode *prior, *next; // 分别指向前驱、后继
}DNode, *DLinkList;
由于双链表结点有两个指针,其删除、插入操作与单链表稍有区别。
2.双链表的创建
只记录尾插法:(头插可根据前文讲解自己练习)
#include "DLinkList.h"
void CreateDLinkList_TailInsert(DLinkList &DL) {
DL = (DNode*)malloc(sizeof(DNode));
DNode *t, *r = DL;
DL->prior = NULL;
ElemType x;
cin>>x;
while (x!=9999) {
t = (DNode*)malloc(sizeof(DNode));
t->data = x;
t->prior = r; // 设置插入结点前驱
r->next = t; // 设置前驱的后继
r = t; // 更新表尾指针
cin>>x;
}
r->next = NULL; // 表尾之后为 NULL
}
图解如下:
三、总结
链表不同于顺序表,不能随机存取。
链表的指针域会占用存储资源,存储效率不及顺序表(只存数据元素)。
但同时链表能够更加灵活的进行存储,不受限于正片完整的内存空间,因此链表的长度可以远远大于顺序表。
本次记录了单链表的定义、结构体和基本操作;以及双链表的定义和创建。
还有双链表的基本操作,以及循环链表、静态链表没有更新。
其实可以类比推出代码的实现方法。
——2023/2/28