双链表-数据结构
- C语言
- 2024-04-07
- 5077热度
- 0评论
双链表
双链表(doubly linked list)和单链表(singly linked list)是两种常见的链表数据结构,它们在内存中存储数据的方式以及对数据的访问方式上有所不同。以下是它们之间的对比:
-
存储方式:
- 单链表:每个节点包含一个数据元素和一个指向下一个节点的指针(通常称为“next”指针)。
- 双链表:每个节点包含一个数据元素,一个指向下一个节点的指针和一个指向前一个节点的指针(通常称为“prev”指针)。
-
访问方式:
- 单链表:只能从头节点开始,沿着每个节点的“next”指针顺序地访问每个节点。
- 双链表:可以从头节点或尾节点开始,沿着每个节点的“next”或“prev”指针来访问每个节点,这使得在某些情况下,双链表的遍历和操作更加灵活。
-
插入和删除操作:
- 单链表:在已知位置插入或删除节点时,需要通过修改指针来重新链接节点,因为单链表中只有“next”指针。
- 双链表:由于双链表有“prev”指针,因此在已知位置插入或删除节点时,可以更轻松地更新相邻节点的链接。
-
空间复杂度:
- 单链表和双链表在空间上的复杂度都是O(n),其中n是链表中的节点数。但是,双链表需要额外的空间来存储每个节点的“prev”指针。
-
优势和劣势:
- 单链表的优势在于实现简单,每个节点占用的空间较小,但在某些操作(如查找前一个节点)时效率较低。
- 双链表的优势在于可以在O(1)时间内查找前一个节点,因此在某些情况下操作更加高效,但它需要额外的空间来存储“prev”指针。
总的来说,选择单链表还是双链表取决于具体的应用场景和需求。如果需要频繁地在中间位置插入或删除节点,或者需要反向遍历链表,则双链表可能更适合。而如果对空间有较高的要求,或者只需要单向遍历链表,则可以选择单链表。
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct DNode{ //定义双链表结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinklist;
int main() {
return 0;
}
双链表初始化(带头结点)
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
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;
}
//判断双链表是否为空
bool Empty(DLinklist L){
if(L->next=NULL)
{
return true;
} else
{
return false;
}
}
int main() {
//初始化双链表
DLinklist L;
InitDLinkList(L);
return 0;
}
双链表的插入
下面给出的代码是用于在双链表中节点p的后面插入一个新节点s的操作。下面是对该函数的解释:
- s->next = p->next:将新节点s的next指针指向p节点的下一个节点,这样s就插入到了p节点的后面,与p的下一个节点相连。
- p->next->prior = s:将p节点原来的下一个节点的prior指针指向新节点s,这样新节点s就正确地与p节点的下一个节点相连。
- s->prior = p:将新节点s的prior指针指向节点p,这样新节点s就正确地与p节点相连。
- p->next = s:最后将p节点的next指针指向新节点s,这样就完成了在p节点后面插入新节点s的操作。
需要注意的是,这段代码假设了p节点的next指针不是NULL,否则可能会导致空指针异常。在实际使用中,可能需要进行额外的判断以确保程序的健壮性。
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct DNode{ //定义双链表结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinklist;
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
}
int main() {
return 0;
}
但是该代码存在问题,如果p是最后一个结点,p->next=NULL,则最后结点并没有前驱指针,此时p->next->prior=s语句出现问题。
需要对代码进行修改,判断p、s的值是否合法,p结点是否还有后继结点。
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct DNode{ //定义双链表结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinklist;
//在p结点之后插入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;
}
int main() {
return 0;
}
双链表的删除
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct DNode{ //定义双链表结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinklist;
//删除p结点的后继节点
bool DeleteNextDNode(DNode *p){
if(p==NULL)
{
return false;
}
DNode *q=p->next;
if(q==NULL)
{
return false;
}
p->next=q->next;
if(q->next!=NULL)
{
q->next->prior=p;
}
free(q);
return true;
}
int main() {
DLinklist L = NULL; // 初始化头指针为NULL
// 创建双链表,假设已经创建并赋值了
// 循环释放各个数据结点
while (L != NULL && L->next != NULL) {
DeleteNextDNode(L);
}
// 释放头结点
free(L);
L = NULL; // 头指针指向NULL
return 0;
}
双链表的遍历
双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。时间复杂度为O(n)。
双链表的遍历可分为前向遍历、后向遍历和前向遍历(跳过头结点),下面给出代码。
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct DNode {
ElemType data;
struct DNode *prior, *next;
} DNode, *DLinklist;
// 前向遍历双链表
void ForwardTraversal(DLinklist L) {
DNode *p = L->next; // 从第一个节点开始
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 后向遍历双链表
void BackwardTraversal(DLinklist L) {
DNode *p = L;
while (p->next != NULL) {
p = p->next; // 移动到链表的最后一个节点
}
while (p != L) {
printf("%d ", p->data);
p = p->prior; // 逆序输出节点
}
printf("\n");
}
// 前向遍历双链表(跳过头结点)
void ForwardTraversalWithoutHead(DLinklist L) {
DNode *p = L;
while (p->next != NULL) {
p = p->next; // 从头结点的下一个节点开始
printf("%d ", p->data);
}
printf("\n");
}
// 示例函数
int main() {
// 创建并初始化双链表
DNode *L = (DNode *)malloc(sizeof(DNode));
L->next = NULL;
L->prior = NULL;
// 假设双链表已经被创建并赋值
// 此处省略赋值过程
// 调用函数进行遍历
printf("Forward traversal: ");
ForwardTraversal(L);
printf("Backward traversal: ");
BackwardTraversal(L);
printf("Forward traversal without head: ");
ForwardTraversalWithoutHead(L);
// 释放双链表的内存
// 此处省略释放过程
return 0;
}