美文网首页
数据结构与算法-双向链表

数据结构与算法-双向链表

作者: 卡布奇诺_95d2 | 来源:发表于2020-04-07 16:27 被阅读0次

    我们在单链表中,有了next指针,这就使得我们要查找下一个结点的时间复杂度为O(1)。可是如果我们要查找上一个结点的话,那最坏的时间复杂度就是O(n)了,因为每次都需要从头开始遍历查找。为了克服这一缺点,设计出双向链表。双向链表是在单链表的每个结点中,再设置一个指向前驱结点的指针域。所以在双向链表中的所有结点都有两个指针域,一个指向直接后继,一个指向直接前驱。

    //双向链表的存储结构
    typedef struct Node{
        ElemType data;
        struct Node *prior;
        struct Node *next;
    }Node;
    

    重点:由于这是双向链表,那么对于双向链表中的某一结点p,都有以下公式,即结点p的前驱的后继是p本身,结点p后继的前驱是p本身:

    p->next->prior = p = p->prior->next
    

    由于双向链表是从单链表扩展出来的,所以很多操作与单链表相同,如计算长度,查找元素等涉及一个方向的指针操作。
    但双向链表由于增加了前驱指针,所以在插入、删除等操作与单链表不同,接下来看看双向链表的基本操作。

    双向链表的创建

    //5.1 创建双向链接
    Status createLinkList(LinkList *L){
        *L = (LinkList)malloc(sizeof(Node));//创建头结点
        if(*L == NULL) return ERROR;
        
        (*L)->data = -1;
        (*L)->prior = NULL;
        (*L)->next = NULL;
        return OK;
    }
    

    双向链表中增加数据元素

    Status addElemToLinkList(LinkList *L){
        //p 指向头结点
        LinkList p = *L;
        for(int i = 1; i < 10; i++){
            //1 创建临时结点
            LinkList temp = (LinkList)malloc(sizeof(Node));
            if(temp == NULL) return ERROR;
            temp->data = i;
            temp->next = NULL;
            temp->prior = NULL;
            
            //2 将前一个结点的后继设置为temp
            p->next = temp;
            //3 将temp结点的前驱设置为p
            temp->prior = p;
            //4 将p指向下一个结点
            p = p->next;
        }
        return OK;
    }
    

    双向链表插入数据

    image.png

    算法思路:
    1、找到需要插入位置的前一个结点A;
    2、将C结点的后继改成A->next,即B;
    3、将A->next(B)结点的前驱改成C;
    4、将C的前驱改成A;
    5、将A的后继改成C;

    Status ListInsert(LinkList *L, int i, ElemType data){
        //p指向头结点
        LinkList p = *L;
        
        //新建临时结点temp
        LinkList temp = (LinkList)malloc(sizeof(Node));
        if(temp == NULL) return ERROR;
        temp->data = data;
        temp->next = NULL;
        temp->prior = NULL;
        
        //找到需要插入位置的前一个结点
        int j = 1;
        while(j<i && p != NULL){
            j++;
            p = p->next;
        }
        if(p == NULL) return ERROR;
        
        //temp的后继是p的后继
        temp->next = p->next;
        //当增加结点的位置不是最后一个结点时,需要将p的后继的前驱改为temp
        if(p->next != NULL){
            p->next->prior = temp;
        }
        //temp的前驱是p
        temp->prior = p;
        //将p的后继改为temp
        p->next = temp;
        
        return OK;
    }
    

    双向链表的删除

    image.png

    算法思路:
    1、找到需要删除结点B;
    2、A->next = B->next;
    3、B->next->prior = B->prior;

    Status ListDelete(LinkList *L, int i, ElemType *e){
        //从第一个结点的位置开始查找
        LinkList temp = (*L)->next;
        int j = 1;
        for(j = 1; j < i && temp != NULL; j++){
            temp = temp->next;
        }
        if(j>i || temp == NULL) return ERROR;
        //将删除结点的后继设置为删除结点的后继
        temp->prior->next = temp->next;
        //若删除的不是最后一个结点,则需要将删除结点后继的前驱设置为删除结点的前驱
        if(temp->next != NULL){
            temp->next->prior = temp->prior;
        }
        *e = temp->data;
        //由于是删除操作,故需要将删除结点处的内存释放
        free(temp);
        return OK;
    }
    

    总结:双向链表相对于单链表来说,要更复杂一些,毕竟增加了prior指针,对于插入和删除操作需要格外小心。另外,由于每个结点都需要记录两份指针,所以双向链表在空间上需要多占一些。不过,由于良好的对称性,使得对某个结点的前后结点操作方便一些,可以有效提高算法的时间性能。

    相关文章

      网友评论

          本文标题:数据结构与算法-双向链表

          本文链接:https://www.haomeiwen.com/subject/pfygphtx.html