24考研数据结构-线性表5

数据结构:双链表与循环链表

在计算机科学中,数据结构是一种组织和存储数据的方式,能够高效地进行数据的增删查改等操作。双链表和循环链表是两种常见的链表数据结构,它们在实际应用中有着各自的优势和特点。本文将对双链表和循环链表进行介绍和比较。

双链表

双链表是一种链表数据结构,与单链表相比,它在每个节点中有两个指针,分别指向前驱节点和后继节点。这使得双链表可以从任意节点开始向前或向后遍历,提高了对链表的操作灵活性。

双链表的结构

一个双链表节点通常包含三部分内容:

  • 数据域:用于存储节点所包含的数据。
  • 前驱指针(prev):指向当前节点的前一个节点。
  • 后继指针(next):指向当前节点的后一个节点。
struct Node {
    int data;
    Node* prev;
    Node* next;
};
双链表的操作

双链表支持多种操作,包括节点的插入、删除、查找等。由于它有两个指针,操作起来可能比较复杂,但它在某些场景下能够提供更高效的操作。例如,在需要频繁地在链表中间插入或删除节点时,双链表可以比单链表更加高效。

循环链表

循环链表是一种特殊的链表数据结构,它与单链表类似,但是最后一个节点的后继指针指向头节点,形成了一个闭环。这样,循环链表中的每个节点都有一个后继节点,即使在最后一个节点之后也有一个节点,使得链表形成了一个循环。

循环链表的结构

循环链表的结构与单链表类似,也包含数据域和一个指向后继节点的指针。但是最后一个节点的指针将指向链表的头节点。

struct Node {
    int data;
    Node* next;
};
循环链表的操作

循环链表的操作与单链表类似,包括节点的插入、删除、查找等。由于链表是循环的,因此可以从任意节点开始遍历整个链表。

双链表与循环链表的比较

双链表和循环链表是两种不同的链表数据结构,在不同场景下有着各自的优势。

双链表的优势:

  • 支持双向遍历:双链表可以从任意节点开始向前或向后遍历,这在某些场景下十分有用。
  • 更高效的插入和删除操作:在链表中间插入或删除节点时,双链表可以比单链表更高效。

循环链表的优势:

  • 循环性质:循环链表中的每个节点都有后继节点,可以形成一个循环。这在某些场景下可以简化代码的编写和操作的实现。
  • 循环遍历:由于链表是循环的,可以从任意节点开始遍历整个链表,无需考虑尾节点的后继指针为空。

结论

双链表和循环链表是两种常见的链表数据结构,它们在实际应用中有着各自的优势和特点。在选择使用哪种链表数据结构时,需要根据具体的应用场景和操作需求来进行判断。无论是双链表还是循环链表,都为我们提供了在动态数据结构中高效存储和操作数据的方法。

详细介绍

2.4.6 双链表

2.4.6.1 双链表中节点类型的描述:
typedef struct DNode{            //定义双链表结点类型
    ElemType data;               //数据域
    struct DNode *prior, *next;  //前驱和后继指针
}DNode, *DLinklist;

存储密度更低,因为需要额外空间存储前驱指针
2.4.6.2 双链表的初始化
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;
}

void testDLinkList(){
    //初始化双链表
    DLinklist L;         // 定义指向头结点的指针L
    InitDLinkList(L);    //申请一片空间用于存放头结点,指针L指向这个头结点
    //...
}

//判断双链表是否为空
bool Empty(DLinklist L){
    if(L->next == NULL)    //判断头结点的next指针是否为空
        return true;
    else
        return false;
}


2.4.6.3 双链表的插入操作
  1. 后插操作
    InsertNextDNode(p, s): 在p结点后插入s结点
  2. 按位序插入操作:
    思路:从头结点开始,找到某个位序的前驱结点,对该前驱结点执行后插操作
  3. 前插操作:
    思路:找到给定结点的前驱结点,再对该前驱结点执行后插操作
bool InsertNextDNode(DNode *p, DNode *s){ //将结点 *s 插入到结点 *p之后
    if(p==NULL || s==NULL) //非法参数
        return false;
    
    s->next = p->next; //1
    if (p->next != NULL)   //p不是最后一个结点=p有后继结点  
        p->next->prior = s; //2
    s->prior = p;  //3
    p->next = s;  //4
    
    return true;
}

在这里插入图片描述

2.4.6.4 双链表的删除操作

删除p节点的后继节点 (遍历实现)

//删除p结点的后继结点
bool DeletNextDNode(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;
}

//销毁一个双链表
bool DestoryList(DLinklist &L){
    //循环释放各个数据结点
    while(L->next != NULL){
        DeletNextDNode(L);  //删除头结点的后继结点
    free(L); //释放头结点
    L=NULL;  //头指针指向NULL

    }
}


2.4.6.5 双链表的遍历操作

前向遍历

while(p!=NULL){
    //对结点p做相应处理,eg打印
    p = p->prior;
}

后向遍历

while(p!=NULL){
    //对结点p做相应处理,eg打印
    p = p->next;
}

2.4.6.6 知识回顾与重要考点

在这里插入图片描述

2.4.7 循环链表

2.4.7.1 循环单链表

最后一个结点的指针不是NULL,而是指向头结点

L->next = L;            //头结点next指针指向头结点

L->next == L ;     //判空
p->next == L ;   //判断是否是尾节点
typedef struct LNode{            
    ElemType data;               
    struct LNode *next;  
}DNode, *Linklist;

/初始化一个循环单链表
bool InitList(LinkList &L){
    L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点
    if(L==NULL)             //内存不足,分配失败
        return false;
    L->next = L;            //头结点next指针指向头结点
    return true;
}

//判断循环单链表是否为空(终止条件为p或p->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;
}


2.4.7.2 单链表和循环单链表的比较:

单链表:从一个结点出发只能找到该结点后续的各个结点;对链表的操作大多都在头部或者尾部;设立头指针,从头结点找到尾部的时间复杂度O(n),即对表尾进行操作需要O(n)的时间复杂度;
循环单链表:从一个结点出发,可以找到其他任何一个结点;设立尾指针,从尾部找到头部的时间复杂度为O(1),即对表头和表尾进行操作都只需要O(1)的时间复杂度;

优点:从表中任一节点出发均可找到表中其他结点。

2.4.7.3 循环双链表

表头结点的prior指向表尾结点,表尾结点的next指向头结点

L->prior = L;          //头结点的prior指向头结点
L->next = L;           //头结点的next指向头结点

L->next == L			//判空
p->next == L			//判断是否为表尾
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 = L;          //头结点的prior指向头结点
    L->next = L;           //头结点的next指向头结点
}

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->next->prior = s;这一步的 p->next->prior 是否存在,即p的下一个节点是不是null。

bool InsertNextDNode(DNode *p, DNode *s){ 
    s->next = p->next;
    p->next->prior = s;
    s->prior = p;
    p->next = s;
双链表的删除

同理 q->next->prior

//删除p的后继结点q
p->next = q->next;
q->next->prior = p;
free(q);

2.4.7.4 知识回顾与重要考点

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VengaZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值