-
双链表
可进可退,存储密度更低一丢丢
-
初始化(带头结点)
typedef struct DNode{ //定义双链表结点类型 ElemType data; //数据域 strucct DNode *prior,*next; //前驱和后继指针 }DNode,*DLinklist; //初始化双链表 bool InitDLinkList(DLinklist &L){ L = (DNode *) malloc(sizeof(DNode)); //分配一个头结点 if (L == NULL) //内存不足,分配失败 return false; L -> prior = NULL; //头结点的prior永远指向NULL L -> next =NULL; //头结点之后暂时还没有结点 return true; } void testDLinkList(){ //初始化双链表 DLinklist L; InitDLinkList(L); //后续代码... } //判断双链表是否为空(带头结点) bool Empty(DLinklist L){ if(L -> next == NULL) return true; else return false; }
DLinklist ⇔等价 DNode *
-
-
插入
//在p结点之后插入s结点 bool InsertNextDNode(DNode *p,DNode *s){ if(p == NULL || s == NULL) //非法参数 return false; //修改指针时要注意顺序 s -> next = p ->next; //将结点*s插入到结点 *p之后 if(p -> next = p -> next) //如果p结点有后继节点 p -> next -> prior =s; s ->prior =p; p ->next =s; }
-
删除
//删除p结点的后继结点 bool DeleteNextDNode(DNode *p){ if(p == NULL) return false; DNode *q = p -> next; //找到p的后继节点q if(q == NULL) return false; //p没有后继 p -> next = q ->next; if(q -> next != NULL) //q结点不是最后一个结点 q -> next -> prior = p; free(q); //释放结点空间 return true; } void DestoryList(DLinklist &L){ //循环释放各个数据节点 while (L -> next != NULL) DeleteNextDNode(L); free(L); //释放头结点 L = NULL; //头指针指向NULL }
-
遍历
-
后向遍历
while (p != NULL){ //对结点p做相应处理,如打印 p = p -> next; }
-
前向遍历
while (p != NULL){ //对结点p做相应处理 p = p -> prior; }
-
前向遍历(跳过头结点)
while (p -> prior != NULL){ //对结点p做相应处理 p = p -> prior; }
-
双链表不可随机存取。
-
按位查找、按值查找操作都只能用遍历的方式实现。
-
时间复杂度:O(n)
-
总结
-
循环链表
循环单链表
typedef struct LNode{ //定义单链表结点类型
ElemType data; //每个结点村南方一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//初始化一个单链表
bool InitList(LinkList &L){
L = (LNode *) malloc(sizeif(LNode)); //分配一个头节点
if (L == NULL) //内存不足,分配失败
return false;
L -> next = L; //头节点next指向头结点
return true;
}
//判断单链表是否为空
bool Empty(LinkList L){
if (L -> next == L)
return true;
else
return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
if(p -> next == L)
return true;
else
return false;
}
循环双链表
表头结点的prior指向表尾结点;
表尾结点的next指向头结点;
typedef struct DNode{
ElemType data;
strucct DNode *prior,*next;
}DNode,*DLinklist;
//初始化空的循环双链表
bool InitDLinkList(DLinklist &L){
L = (DNode *) malloc(sizeof(DNode)); //分配一个头结点
if (L == NULL) //内存不足,分配失败
return false;
L -> prior = L; //头结点的prior指向头结点
L -> next = L; //头结点的next 指向头结点
return true;
}
void testDLinkList(){
//初始化双链表
DLinklist L;
InitDLinkList(L);
//后续代码...
}
//判断双链表是否为空
bool Empty(DLinklist L){
if(L -> next == L)
return true;
else
return false;
}
//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinklist L,DNode *p){
if(p -> next ==L)
return true;
else
return false;
}
-
插入
//在p结点之后插入s结点 bool InsertNextDNode(DNode *p,DNode *s){ s -> next = p ->next; //将结点*s插入到结点 *p之后 p -> next -> prior =s; s -> prior =p; p -> next =s; }
-
删除
//删除p结点的后继结点 bool DeleteNextDNode(DNode *p){ p -> next = q ->next; q -> next -> prior = p; free(q); //释放结点空间 return true; }
-
👍 代码问题
如何判空
如何判断结点p是否是表尾/表头结点 → 后向/前向遍历的实现核心
如何在表头、表中、表尾插入/删除一个结点
-
静态链表
-
什么是静态链表
用数组的方式实现的链表
分配一整片连续的内存空间,各个结点集中安置。
每个数据元素4B,每个游标4B(每个结点共8B)设起始地址为addr
a₁的存放地址为addr + 8*2
-
-
如何定义一个静态链表
//方法1 #define MaxSize 10 //静态链表的最大长度 struct Node{ //静态链表结构类型的定义 ElemType data; //存储数据元素 int next; //下一个元素的数组下标 } typedef struct Node SLinkList[MaxSize]; //可用SLinkList定义“一个长度为MaxSize的Node型数组” void testSlinkList(){ struct Node a[MazSize]; //数组a作为静态链表 //...后续代码 }
等价于👇
//方法2 #define MaxSize 10 //静态链表的最大长度 struct Node{ //静态链表结构类型的定义 ElemType data; //存储数据元素 int next; //下一个元素的数组下标 }SLinkList[MaxSize]; void testSlinkList(){ SLinkList a; //a是一个静态链表 //...后续代码 }
-
证明方法2
#define MaxSize 10 //静态链表的最大长度 struct Node{ //静态链表结构类型的定义 int data; //存储数据元素 int next; //下一个元素的数组下标 }; typedef struct{ //静态链表结构类型的定义 int data; //存储数据元素 int next; //下一个元素的数组下标 }SLinkList[MaxSize]; void testSlinkList(){ struct Node x; printf("sizeX = %d\\n",sizeof(x)); //sizeX = 8 struct Node a[MaxSize]; printf("sizeA = %d\\n",sizeof(a)); //sizeA = 80 SLinkList b; printf("sizeB = %d\\n",sizeof(b)); //sizeB = 80 }
结论: SLinkList b ——相当于定义了一个长度为MaxSize的Node型数组
-
-
简述基本操作的实现
-
初始化
把 a[0]的next设为-1
-
查找
从头结点出发挨个往后遍历结点 时间复杂度O(n)
-
插入位序为i的结点
1.找到一个空的结点,存入数据元素
-
Q:如何判断结点是否为空。
A:可让next为某个特殊值,如-2
2.从头结点出发找到位序为i - 1的结点
3.修改新结点的next
4.修改i - 1号结点的next
-
删除某个结点
1.从头结点出发找到前驱结点
2.修改前驱结点的游标
3.被删除结点next设为-2
-
-
总结
优点:增删操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变
使用场景:
1.不支持指针的低级语言
2.数据元素数量固定不变的场景(如操作系统的文件分配表FAT)