双向链表之创建插入删除

1、预前知识:

1、一个节点的前驱或者后继指向的都是节点的地址,这个地址包含当前节点的数据域和指针域。

2、对于节点p,有p->prior->next=p=p->next->prior

3、单链表的插入只需要修改两个指针,而双链表需要修改四次指针,即p的前驱、后继,s的前驱、后继。

4、双链表删除节点时比单链表方便很多。因为单链表删除时需要进行一次循环来查找它上一个节点的地址,而双链表因为有前驱,所以可以直接查找到它的上一个地址

5、虽然使用单链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定结点的前驱结点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。

6、插入元素前,首元节点的前驱和后继都指向NULL,插入后首元节点的前驱指向NULL,尾节点的后继指向NULL。

插入前:  

插入后:

 

2、实现步骤:

(1)双向链表之创建

DLNode* DList_Create(DLNode *head,int a[],int n){
    //链表初始化
    head = (DLNode*)malloc(sizeof(DLNode));//为头节点开辟一块地址,头节点是不存放数据的,只起到标杆的作用
    head->prior=NULL;
    head->next=NULL;
    DLNode *p;//创建临时头节点p,
    p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
    DLNode *s;//创建新增节点
    for(int i=0;i<n;i++){
        s = (DLNode*)malloc(sizeof(DLNode));
        s->next = s->prior = NULL;
        s->data = a[i];
        p->next = s;
        s->prior = p;
        p = p->next; //这步是核心,将原来指向head地址的p变为指向s的地址
    }
    return head;
}

 

(2)双向链表之插入

例如我们现在想要插入一个新节点s到a、b之间,首先需要定位到b的位置,假设我们现在已经定位到了b,并且b的位置我们用节点p来表示。

第一步,将s的前驱指向a的地址,即将地址a赋值给s的前驱:因为插入后s的前驱是指向a的地址,又因为a的地址为b的前驱,即 p->prior,所以有:

s->prior = p->prior

第二步,将a的后继指向s的地址,即将s的地址赋值给a的后继:因为插入后a的后继是指向s的地址,又因为a的后继为p的前驱的后继(可能有点绕),即p->prior->next,所以有:

p->prior->next = s

第三步,类似的,将s的后继指向b,即将b的地址赋值给s的后继:因为b的地址(节点)为p,所以有:

s->next = p

第四步,最后将b的前驱指向地址s,即将s的地址赋值给b的前驱,因为b的前驱为 p->prior,所以有:

p->prior = s

实现代码:

DLNode* DList_insert(DLNode *head,int i,int num){ //i为插入的位置,num为插入的元素
    DLNode *p;//创建临时头节点p,
    DLNode *s;//创建新增节点
    int j=0;
    p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
    s = (DLNode*)malloc(sizeof(DLNode));
    while(j<i && p){
         p = p->next;
         j++;
    }
    if(j==i&&i!=0)
    {
        s->data = num;
        s->prior = p->prior; //s的前驱指向p的上一个节点的地址
        p->prior->next = s;//p的上一个节点的后继指向地址s
        s->next = p; //s的后继指向地址p
        p->prior = s;//p的前驱指向地址s
        //完成一次节点的插入过程
    }
    return head;
}

 

(3)双向链表之删除:

如果前面插入的过程看明白了删除应该不用说也就懂了,过程就是将a的后继指向地址c,c的前驱指向地址a,最后释放掉b的内存。

p-prior->next = p->next    //a的后继指向c的地址,即将c的地址赋值给a的后继
p->next->prior = p->prior  //c的前驱指向a的地址,即将a的地址赋值给c的前驱
free(p) //释放掉p的内存
DLNode* DList_Delete(DLNode *head,int i) //删除的位置
{
    DLNode *p;
    p = head;
    int j=0;
    while(j<i && p != NULL){
        p = p->next;
        j++;
    }
    if(i==j && p != NULL){
        p->prior->next = p->next;//将p的前一个节点的后继指向p的下一个节点的地址
        p->next->prior = p->prior;//将p的下一个节点的前驱指向p的前一个节点的地址
        free(p);//释放p的内存空间
    }
    return head;
}

 

 

4、完整实现代码:

#include<stdio.h>
#include <stdlib.h>
typedef struct  Node
{
    struct Node *prior;//前驱
    int data;//数据域
    struct Node *next;//后继

    /* data */
}DLNode,*DLinkList; //DLNode翻译成双链表节点

DLNode* DList_Create(DLNode *head,int a[],int n){
    //链表初始化
    head = (DLNode*)malloc(sizeof(DLNode));//为头节点开辟一块地址,头节点是不存放数据的,只起到标杆的作用
    head->prior=NULL;
    head->next=NULL;
    DLNode *p;//创建临时头节点p,
    p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
    DLNode *s;//创建新增节点
    for(int i=0;i<n;i++){
        s = (DLNode*)malloc(sizeof(DLNode));
        s->data = a[i];
        p->next = s;
        s->prior = p;
        p = p->next;
    }
    return head;
}

DLNode* DList_insert(DLNode *head,int i,int num){ //i为插入的位置,num为插入的元素
    DLNode *p;//创建临时头节点p,
    DLNode *s;//创建新增节点
    int j=0;
    p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
    s = (DLNode*)malloc(sizeof(DLNode));
    while(j<i && p){
         p = p->next;
         j++;
    }
    if(j==i&&i!=0)
    {
        s->data = num;
        s->prior = p->prior; //s的前驱指向p的上一个节点的地址
        p->prior->next = s;//p的上一个节点的后继指向地址s
        s->next = p; //s的后继指向地址p
        p->prior = s;//p的前驱指向地址s
        //完成一次节点的插入过程
    }
    return head;
}

DLNode* DList_Delete(DLNode *head,int i) //删除的位置
{
    DLNode *p;
    p = head;
    int j=0;
    while(j<i && p != NULL){
        p = p->next;
        j++;
    }
    if(i==j && p != NULL){
        p->prior->next = p->next;//将p的前一个节点的后继指向p的下一个节点的地址
        p->next->prior = p->prior;//将p的下一个节点的前驱指向p的前一个节点的地址
            free(p);//释放p的内存空间
        }
    return head;
}

int main(){
    int arr[] = {1,2,3,4,5};
    int n = 5;
    DLNode *L = NULL;
    L =  DList_Create(L,arr,n);
    L = DList_insert(L,3,50); //例如将50插入到链表的第三个位置
    L = DList_Delete(L,4);//删除第四个位置的节点
    while(L->next!=NULL){
        printf(" %d ",L->next->data);//算上头节点一共六个节点,并且头节点数据域为NULL,所以输出的第一个值应该为 L->next->data
        L = L->next;
    }
}

有一处BUG,不能删除尾节点,希望有人可以帮忙解决这个问题~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值