双链表

  单链表结点中只有一个只指向后继的指针,使得单链表只能从头结点开始一次顺序的先后遍历。要访问某个结点的前驱结点(插入删除操作时),只能从头开始遍历,访问后继节点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。

  为了克服单链表的上述缺点,引入了双链表,双链表结点中有两个指针prior 和 next,分别指向其前驱结点和后继结点。

定义

  双链表中结点类型描述:

typedef struct DNode{
    ElemType data;
    struct DNode *prior, *next;
}DNode,*DLinkList;

  在双链表中,按位查找和按值查找的操作和单链表中的相同。但在插入和删除上有着较大的不同。因为双链表有prior指针,可以很方便地找到其前驱结点,因此插入,删除结点的时间复杂度为O(1)。

前插操作

  将结点s 插入到指针p所指结点b之前:

bool DLinkList_Insert(DLinkList& L,ElemTyep c){
    DNode* p;
    /* 先检查位置是否合法...  */
    DNode* s = (DNode*)malloc(sizeof(DNode));
    s->data = c;
    //第一步:结点s的prior指针指向左边的结点
    s->prior = p->prior;    
    //第二步:结点b的前驱结点(也就是左边的结点)的next指针指向结点s
    p->prior->next = s;
    //第三步:结点s的next指针指向结点b
    s->next = p;
    //第四步:结点b的prior指针指向结点s
    p->prior = s;        
    return true;
}

  注意:第一,二步必须在第四步之前,否则*p的前驱结点的指针就会丢失,导致插入失败。

后插操作

  将结点s 插入到指针p所指的节点b 之后(如上图,假设后面的结点是d):

bool DLinkList_Insert(DLinkList& L,ElemType c){
    DNode* p;
    DNode* s = (DNode*)malloc(sizeof(DNode));
    s->data = c;
    //第一步:将结点s 的next指针指向后继结点
    s->next = p->next;
    //第二步;将后继结点的prior指针指向结点s
    p->next->prior = s;
    //第三步:将结点s的prior指针指向结点b
    s->prior = p;
    //第四步:将结点b的next指针指向结点s
    p->next = s;
    return true;    
}

  同样的,前插操作和后插其实没区别,注意第四步即可,大家不清楚可以在草稿纸上面画出来,画出来了就一目了然了。

删除操作

  删除指针p指向的结点b:

bool DLinkList_Delete(DLinkList& L){
    DNode* p;
    //第一步:将结点b的前驱结点的next指针指向结点c
    p->prior->next = p->next;
    //第二步:将结点c的prior指针指向结点b的前驱结点
    p->next->prior = p->prior;
    //释放结点空间
    free(p);
}

  如果是删除指针p指向的结点的后继结点,指针q指向后继结点,也就是后删:

bool DLinkList_Delete(DLinkList& L){
    DNode *p,*q;
    p->next = q->next;
    q->next->prior = p;
    free(q);
}

Last updated