Day06双向链表

本文围绕双向链表展开,介绍了其定义,即节点保存前后节点地址,能找直接后继和前驱。阐述了双向链表的结构体设计,详细讲解了初始化、插入、删除等功能的实现,包括各操作中指针域的修改情况及特殊情况处理,最后留了双向循环链表的思考题。

定义

每一个节点不仅保存着下一个节点的地址,还存在一个指针域保存上一个节点的地址

意义

每一个节点既可以找到直接后继又可以找到直接前驱

fe5bb059e74e48ebaddbf247dd6c9d5c.png

 

双向链表的结构体设计

typedef int ELEM_TYPE;
typedef struct DNode{
    ELEM_TYPE data;//数据域
    struct DNode *next;//后继
    struct DNode *prior;//前驱
}DNode,*PDNode;

双向链表实现的功能

初始化

void Init_dlist(PDNode pdlist){
    assert(Pdlist!=NULL);
    pdlist->next=NULL;
    pdlist->prior=NULL;
}

初始化状态下,头结点的next和prior都是NULL;

图表解释

9211a9adae0e4cdaa6cd97f1b677f071.png

 

头插

bool Insert_head(PDNode pdlist,ELEM_TYPE val){
    assert(Pdlist!=NULL);
    struct Node*pnewnode=(struct Node*)malloc(sizeof(struct Node));
    assert(pnewnode!=NULL);
    pnewnode->data=val;
    pnewnode->next=pdlist->next;
    pnewnode->prior=pdlist;
    if(pdlist->next!=NULL){
        pdlist->next->prior=pnewnode;
    }
    pdlist->next=pnewnode;
    return true;
}

注意事项

1.因为是头插直接使用pdlist头结点即可

2.一共有4个指针域受到影响

自身的prior域,next域,头结点的next域,有效节点的prior域

图表解释

d57dd8b0472642f3898935c30c0bd776.png

 

3.修改的先后顺序(不唯一,但是头结点的next需要在最后一个修改)

first.自身的prior域,

second.自身的next域,

third.有效节点的prior域,(存在特殊情况:空链表)

forth.头结点的next域,

尾插

bool Insert_tail(PDNode pdlist,ELEM_TYPE val){
    assert(Pdlist!=NULL);
    struct Node*pnewnode=(struct Node*)malloc(sizeof(struct Node));
    assert(pnewnode!=NULL);
    pnewnode->data=val;
    struct DNode*p=pdlist;
    for(;p!=NULL;p=p->next);
    pnewnode->next=p->next;
    pnewnode->prior=p;
    p->next=pnewnode;
    free(p);
    return true;
}

不存在特例,只需要修改三个指针域:自身的next,prior,尾结点的next域

按位置插

bool Insert_pos(PDNode pdlist,int pos,ELEM_TYPE val){
    assert(Pdlist!=NULL);
    assert(pos>=0&&pos<=GetLenth(pdlist));
    if(pos==0){
       return  Insert_head(pdlist,val);
    }else if(pos==GetLength(pdlist)){
       return  Insert_tail(pdlist,val);
    }
    else{
        strcut Node *pnewnode=(strcut Node*)malloc(strcut Node);
        struct Node*p=plist;
        for(int i=0;i<pos;i++){
            p=p->next;
        }
        pnewnode->data=val;
        pnewnode->next=p->next;
        pnewnode->prior=p;
        p->next->prior=pnewnode;
        p->next=pnewnode;
        
    }
    return true;
}

图表解释

959e0c10ebc54755a66579bc77c4bcd8.png

 

pos==0:头插 有特例 修改三个指针域

pos==length:尾插 修改三个指针域

pos>0&&pos<length:中间位置插入 修改四个指针域

头删

也存在特例

bool Del_head(PDNode pdlist){
    assert(Pdlist!=NULL);
    if(IsEmpty(pdlist)){
        return false;
    }
    struct Node*p=pdlist->next;
    pdlist->next=p->next;
    if(p->next!=NULL)//判断待删除节点的下一个节点是否存在
    {
        p->next->prior=pdlist;
    }
    //因为是头删,待删除节点的上一个节点是头结点
    //存在特例只需要修改一个指针域
    free(p);
    return true;
    
}

图表解释

0faf063634af48c98f1d40bd364735c8.png

 

正常情况下进行头删,需要修改两个指针域

1.待删除节点的上一个节点的next域

2.待删除节点的下一个节点的prior域

注意事项

头删时,删除的是仅剩下的唯一一个节点

这个时候只需要修改待删除节点的上一个节点的next域即可

尾删

bool Del_tail(PDNode pdlist){
    assert(Pdlist!=NULL);
    if(IsEmpty(pdlist)){
        return false;
    }
    struct DNode*p=pdlist;
    for(;p!=NULL;q=q->next);
    struct DNode*q=pdlist;
    for(;q!=p;q=q->next);
    q->next=p->next;
    free(p);
    return true;
}

图表原理

33159db232c04b35b5cc948fd87e5e6e.png

尾删不存在特殊情况,待删除节点为尾结点,所以只有待删除节点的上一个节点存在,待删除节点的下一个节点不存在,只需要待删除节点的上一个节点的next域

按位置删

bool Del_pos(PDNode pdlist,int pos){
    assert(Pdlist!=NULL);
    assert(pos>=0&&pos<GetLength(pdlist));
    if(IsEmpty(pdlist)){
        return false;
    }
    struct DNode*q=pdlist;
    for(int i=0;i<pos;i++){
        q=q->next;
    }
    struct DNode *p=q->next;
    q->next=p->next;
    free(p);
    return true;
    
}

 

定义两个指针,一个指向待删除节点,一个指向待删除节点的上一个节点。

顺序:

按值删

bool Del_val(PDNode pdlist,ELEM_TYPE val){
    assert(Pdlist!=NULL);
     if(IsEmpty(pdlist)){
        return false;
    }
    struct DNode*p=Search(pdlist,val);
    if(p==NULL){
        return false;
    }//保证待删除节点存在且用p 指向
     struct DNode*q=pdlist;
    for(;p->next!=p;q=q->next);
    //可能存在特例,比如删除为尾结点
    if(p->next==NULL)//代表尾结点
    {
        q->next=p->next;
    }
    else//不是尾结点
    {
        q->next=p->next;
        p->next->prior=q;
    }
    free(p);
    return true;
   
}

 

查找


struct DNode *Search(PDNode pdlist,ELEM_TYPE val){
    assert(Pdlist!=NULL);
    struct DNode*p=pdlist->next;
    for(;p!=NULL;p=p->next){
        if(p->data==val){
            return p;
        }
    }
    return NULL;
}

销毁

void Destory1(PDNode pdlist){
    assert(Pdlist!=NULL);
    while(!IsEmpty(pdlist)){
        Del_head(pdlist);
    }
}//无限头删
void Destory2(PDNode pdlist){
    assert(Pdlist!=NULL);
    struct DNode*p=pdlist->next;
    pdlist->next =NULL;
    struct DNode*q;
    while(p!=NULL){
        q->next=p->next;
        free(p);
        p=q;
    }
}//利用两个指针完成

 

清空

void clean(PDNode pdlist){
    assert(Pdlist!=NULL);
    Destory (pdlist);
}

 

判空

bool IsEmpty(PDNode pdlist){
    assert(Pdlist!=NULL);
    return pdlist->next==NULL;
}

 

获取有效值个数

int GetLength(PDNode pdlist){
    assert(Pdlist!=NULL);
    int count=0;
    struct DNode*p=pdlist->next;
    for(;p!=NULL;p=p->next){
        count++;
    }
    return count;
}

 

打印

void Show(PDNode pdlist){
        assert(Pdlist!=NULL);
    int count=0;
    struct DNode*p=pdlist->next;
    for(;p!=NULL;p=p->next){
       printf("%d",p->data);
    }
​
}

从按值删后原理与Day04的单链表的原理差不多,大家有什么不明白的地方可以去Day04单链表去看看,小白在这给大家留一个思考题双向循环链表该如何设计,它与双向链表有什么区别,我们下期见。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值