双链表
单链表无法逆向检索,有时候不太方便;
使用双链表,可进可退,存储密度比单链表低一些,因为一个节点除了后继指针next还多了一个前驱指针prior
typedef struct DNode{ //定义双链表节点类型
ElemType data; //数据域
struct 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;
插入
- 一般情况:插入位置在两个节点之间
Step 1:申请一个新结点存放元素e,此时其前驱后继指针都为空。
Step 2:新结点的后继指针指向其后一个节点(y);后一个节点的前驱指针改为指向新结点(s)。
Step 3:新结点的前驱指针指向其前面的一个节点(x);前面一个节点的后继指针改为指向新结点(s)。
//将节点*s插入到节点*p之后
bool InsertNextDNode(DNode *p, DNode *s) {
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
}
- 特殊情况:插入为末尾位置,无后继节点
插入后新结点后继指针为空
bool InsertNextDNode(DNode *p, DNode *s) {
if(p == NULL || s == NULL) //非法参数
return false;
s->next = p->next;
if (p->next != NULL) //判断p结点后是否有后继节点
p->next->prior = s; //如果有则处理后继节点的前驱指针
s->prior = p;
p->next = s;
}
删除
若要删除一个节点,需要对其前后节点的指针进行处理才可删除,否则会发生错误。
- 一般情况:需要删除的节点在两个节点之间
Step 1:将该节点的前驱节点(x)的后继指针指向该节点的后继节点(y);
Step 2:将该节点的后继节点(y)的前驱指针指向该节点的前驱节点(x);
Step 3:释放该节点内存,删除 - 考虑特殊情况:
删除p节点的后继节点
Step 1: 判断p节点合法,不为空;不合法,直接返回;
Step 2:取p节点的后继指针(找到p节点的后继节点);
Step 3:若p节点的后继指针指向的内存单元不为空NULL(p有后继节点),按照一般情况处理;否则,当没有后继节点时,直接返回;
Step 4:释放后继节点q,完成删除操作
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;
}
双链表的遍历
- 后向遍历
while(p != NULL) {
p = p->next;
}
- 前向遍历
while(p != NULL) {
p = p->prior;
}
- 前向遍历(跳过头结点)
while(p->prior != NULL) {
p = p->prior;
}
双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式 实现。时间复杂度为 O ( n ) O(n) O(n)
循环链表
分为循环单链表和循环双链表
- 循环单链表
单链表:表尾节点的next指针指向NULL;
从一个节点出发只能找到后续的各个节点
循环单链表:表尾节点的next指针指向头结点;
从一个节点出发可以找到其他任何一个节点;
很多时候对链表的操作都是在头部或尾部;
从头结点找到尾部的时间复杂度为 O ( n ) O(n) O(n),从尾部找到头部的时间复杂度为 O ( 1 ) O(1) O(1)
单链表的初始化:
bool InitList(LinkList &L) {
L = (LNode *)malloc(sizeof(LNode));
if (L == NULL)
return false;
L->next = L; //头结点next指针指向头结点
return true;
}
如何判断循环单链表为空:
当头结点的next指针指向头结点时
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指向NULL,表尾结点的next指向NULL
循环双链表:表头结点的prior指向表尾结点,表尾结点的next指向头结点
循环双链表的初始化:
bool InitLinkList(DLinklist &L) {
L = (DNode *)malloc(sizeof(DNode));
if (L == NULL)
return false;
L->prior = L; //头结点的prior指向头结点
L->next = L; //头结点的next指向头结点
return true;
}
如何判断循环双链表为空:
当头结点的next和prior指针都指向头结点时,在判断时可以治判断next指针是否指向头结点
与循环单链表判断方式相同
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;
p->next->prior = s;
s->prior = p;
p->next = s;
}
删除
删除p的后继节点q
p->next = q->next;
q->next->prior = p;
free(q);
静态链表
优点:增、删操作不需要大量移动元素;
缺点:不能随机存取,只能从头结点开始依次往后查找,容量固定不可变
使用场景: 不支持指针的低级语言;数据元素数量固定不变的场景(如操作系统的文件分配表FAT)
静态链表:分配一整片连续的内存空间,各个节点集中安置
游标充当指针
每个数据元素4B
,每个游标4B
(每个节点共占8B
)
设起始地址为addr
e
1
e_1
e1的存放地址为 addr + 8*2
定义静态链表
#define MaxSize 10 //静态链表的最大长度
typedef struct Node{ //静态李娜表结构类型的定义
ElemType data; //存储数据元素
int next; //下一个元素的数组下标
} SLinkList[MaxSize];
//声明
void testSLinkList() {
SLinkList a;
}
基本操作
- 初始化
把a[0]的next设为-1,不指向任何元素;
可在初始化时,让next为某个特殊值,如-2
- 查找
从头结点出发挨个往后遍历节点,时间复杂度为 O ( n ) O(n) O(n) - 插入位序为i的节点:
step 1:找到一个空的节点,存入数据元素;
step 2:从头结点出发找到位序为i-1的结点
step 3:修改新结点的next
step 4:修改i-1号结点的next
如何判断节点是否为空?可在初始化时,让next为某个特殊值,如-2