数据结构学习笔记(六)--双链表.md
数据结构学习笔记(六)--双链表.md
因为单链表无法逆向检索,有时候会不太方便,故有双链表的数据结构。
点击进入上一篇:数据结构学习笔记(五)--单链表
双链表的定义
可理解为比单链表多了一个指向前驱结点的指针,这里不再赘述。
用代码定义一个双链表
和单链表相比多了一个指向前驱的指针,用c/c++实现,如下:
typedef struct DNode{ //定义双链表节点类型
int data; //每个节点存放一个整型元素
struct DNode *prior,*next; //前驱和后继指针
} DNode, *DLinkList;
初始化一个双链表
带头结点
用C/C++表示,如下:
//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L){
L = (DNode *) malloc(sizeof(DNode)); //分配一个头结点
if(L == NULL) //内存不足,分配失败
return false;
L->prior = NULL; //头结点的prior永远指向NULL
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
判断是否为空
用C/C++表示,如下:
//判断双链表是否为空(带头结点)
bool Empty(DLinkList L){
return (L->next == NULL);
}
不带头结点
这里是自己写的不是课上笔记,仅供参考。
用C/C++表示,如下:
//初始化双链表(不带头结点)
bool InitDLinkList(DLinkList &L){
L = NULL; //空表,暂时还没有任何节点(防止脏数据)
return true;
}
判断是否为空
用C/C++表示,如下:
//判断双链表是否为空(不带头结点)
bool Empty(DLinkList L){
return (L == NULL);
}
双链表的基本操作
以下默认为带头结点的操作。
插入
这里写一个最终要的后插操作,具体逻辑看代码,不做赘述。注意判断如果插在最后一个结点时的特殊情况。
代码实现
用C/C++表示,如下:
/**
* @brief 在p结点之后插入s结点
*
* @param p
* @param s
* @return true
* @return false
*/
bool InsertNextDNode(DNode *p,DNode *s){
if (p==NULL || s==NULL) //非法参数
return false;
s->next = p->next;
if(p->next != NULL)
p->next->prior = s; //如果p结点有后继结点
s->prior = p;
p->next = s;
return true;
}
时间复杂度
个人认为是O(1)。
除后插外其他插入的实现
由于双链表可逆向检索,所以其他插入的逻辑变得简单,核心逻辑依旧为后插,如:
- 按位序插入:遍历到指定位序后,根据指定位序的前驱指针指向前驱结点,再到前驱结点执行后插操作。
- 前插插入,根据指定结点的前驱指针指向前驱结点,再到前驱结点执行后插操作。在带头结点的情况下,表头前插并不需要考虑特殊情况。
删除
删除指定结点的后继结点
用C/C++表示,如下:
/**
* @brief 删除p结点的后继结点
*
* @param p
* @return true
* @return false
*/
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;
}
销毁整个双链表
遍历整张表,调用删除后继结点的方法即可。C/C++表示,如下:
/**
* @brief 销毁双链表
*
* @param L
*/
void DestoryList(DLinkList &L){
//循环释放各个数据结点
while(L->next != NULL)
DeleteNextDNode(L);
free(L); //释放头结点
L = NULL; //头指针指向NULL
}
时间复杂度不在赘述,自行揣摩。
遍历
分为指定结点向后或向前,过于简单所以不做赘述,如图所示。