数据结构与算法(4)- 线性表之双向链表和双向循环链表

265 阅读6分钟

双向链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

创建链表

//定义结点
typedef struct Node{
    ElemType data;  //数据域
    struct Node *prior; //前驱指针
    struct Node *next;  //后继指针
}Node;

typedef Node * LinkList;

//5.1 创建双向链接
Status createLinkList(LinkList *L){
    // 创建头结点
    (*L) = (LinkList)malloc(sizeof(Node));
    if (!(*L)) {
        return ERROR;
    }
    (*L)->data = -1;
    (*L)->prior = NULL;
    (*L)->next = NULL;
    
    // 新增数据 (后插法)
    LinkList p = (*L);
    for (int i = 1; i<10; i++) {
        //1.创建1个临时的结点
        LinkList temp = (LinkList)malloc(sizeof(Node));
        if (!temp) {
            return ERROR;
        }
        temp->data = i;
        //2.为新增的结点建立双向链表关系
        //① temp的前驱是p
        temp->prior = p;
        temp->next = NULL;
        //② temp 是p的后继
        p->next = temp;
        //③ p 要记录最后的结点的位置,方便下一次插入
        p = temp;
    }
    return OK;
}

遍历链表

//5.2 打印循环链表的元素
void printLinkList(LinkList L){
    LinkList temp = L->next;
    if (temp == NULL) {
        printf("打印的双向链表为空!\n");
        return ;
    }
    
    while (temp) {
        printf(" %d  ",temp->data);
        temp = temp->next;
    }
    printf("\n");
}

插入结点

//5.3 双向链表插入元素
Status ListInsert(LinkList *L, int i, ElemType data){
    //1. 插入的位置不合法 为0或者为负数
    if (i<1) {
        printf("插入的位置不合法\n");
        return ERROR;
    }
    
    //2. 新建结点
    LinkList temp = (LinkList)malloc(sizeof(Node));
    temp->data = data;
    temp->next = NULL;
    temp->prior = NULL;
    
    //3. 将p指向头结点
    LinkList p = (*L);
    //4. 查找待插入位置的前一个结点
    for (int j=1; j<i && p; j++) {
        p = p->next;
    }
    //5. 如果插入的位置超过链表本身的长度
    if (p == NULL) {
        printf("插入的位置超出链表本身\n");
        return ERROR;
    }
    //6. 判断插入位置是否为链表尾部; 即判断是否尾结点
    if (p->next == NULL) {
        p->next = temp;
        temp->prior = p;
    } else {
        LinkList nextNode = p->next;
        // ①将p->next 结点的前驱prior = temp
        nextNode->prior = temp;
        // ②将temp->next 指向原来的p->next
        temp->next = nextNode;
        // ③p->next 更新成新创建的temp
        p->next = temp;
        // ④新创建的temp前驱 = p
        temp->prior = p;
    }
    
    return OK;
}

删除结点

//5.4 删除双向链表指定位置上的结点
Status ListDelete(LinkList *L, int i, ElemType *e){
    // 判断双向链表是否为空,如果为空则返回ERROR;
    if (*L == NULL) {
        printf("双向链表为空\n");
        return ERROR;
    }
    // 删除位置不合法
    if (i<1) {
        printf("删除位置不合法,需>0\n");
        return ERROR;
    }
    // 将p指向头结点
    LinkList p = (*L);
    // 找到待删除位置的结点
    for (int j = 0; j<i && p; j++) {
        p = p->next;
    }
    // 插入的位置超出链表本身
    if (p == NULL) {
        printf("插入的位置超出链表本身\n");
        return ERROR;
    }
    // 将待删除结点的data 赋值给*e,带回到main函数
    *e = p->data;
    
    // 判断待删除结点是否尾结点
    if (p->next == NULL) {
        // 删除尾结点 只需要将前一个结点的后继置为NULL即可
        p->prior->next = NULL;
    } else {
        // 待删不是尾结点,则将待删结点的前驱结点后继 指向 待删结点的后继结点
        p->prior->next = p->next;
        // 将待删结点的后继结点的前驱 指向 待删结点的前驱
        p->next->prior = p->prior;
    }
    // 释放待删除结点
    free(p);
    
    return OK;
}
//5.5 删除双向链表指定的元素
Status LinkListDeletVAL(LinkList *L, ElemType data){
    // 链表判空
    if (*L == NULL) {
        printf("双向链表为空\n");
        return ERROR;
    }
    
    // 链表遍历 找到待删结点
    LinkList p = (*L);// 头结点
    for (int i = 1; p != NULL; p = p->next, i++) {
        if (p->data == data) {
            // 待删结点 的前驱结点的后继 指向 待删结点的后继结点
            p->prior->next = p->next;
            // 判断待删结点是否尾结点
            if (p->next != NULL) {
                // 不是尾结点,则将待删结点的 后继结点的前驱 指向 戴珊结点的前驱
                p->next->prior = p->prior;
            }
            // 释放待删结点
            free(p);
            // 退出遍历
            break;
        }
    }
    return OK;
}

更新结点

//5.6.2 在双向链表中更新结点
Status replaceLinkList(LinkList *L,int index,ElemType newElem){
    // 链表判空
    if (*L == NULL) {
        printf("链表为空\n");
        return ERROR;
    }
    // 遍历
    LinkList p = (*L);
    for (int i = 1; p != NULL; i++) {
        p = p->next;
        // 注意边界条件
        if (i == index && p != NULL) {
            p->data = newElem;
            return OK;
        }
    }
    return ERROR;
}

查找结点位置

//5.6.1 在双向链表中查找元素位置
int selectElem(LinkList L,ElemType elem){
    // 链表判空
    if (L == NULL) {
        printf("双向链表为空\n");
        return -1;
    }
    // 遍历
    LinkList p = L;
    int i = 0;
    while (p) {
        if (p->data == elem) {
            return i;
        }
        p = p->next;
        i++;
    }
    return -1;
}

双向循环链表

循环链表,其最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。

创建链表 (带头结点)

注意:循环链表尾结点的后继指针 指向头结点,如果没有头结点,则指向首元结点。

// 1. 双向循环链表初始化(尾插法)
Status creatLinkList(LinkList *L){
    *L = (LinkList)malloc(sizeof(Node));

    if (*L == NULL) {
        return ERROR;
    }
    (*L)->data = -1;
    (*L)->next = *L;
    (*L)->prior = *L;

    LinkList p = (*L);// 头结点
    for (int i=1; i<10; i++) {
        // 尾插法: 将新结点 加到链表尾,变成尾结点
        LinkList temp = (LinkList)malloc(sizeof(Node));
        temp->data = i;
        // 新结点prior指向是链表尾
        temp->prior = p;
        // 新结点next 要指向链表头结点
        temp->next = (*L);
        // 新结点变成链表尾结点
        p->next = temp;
        // 将尾结点赋给p
        p = temp;
    }
    
    return OK;
}

遍历链表

// 2. 遍历打印链表
void printList(LinkList L){
    if (!L) {
        printf("链表为空\n");
        return;
    }
    
    LinkList p = L->next;//首元结点
    // 遍历链表 从首元结点到尾结点
    while (p != L) {
        printf(" %d", p->data);
        p = p->next;
    }
    printf("\n");
}

插入结点

// 3. 双向循环链表插入元素
/*当插入位置超过链表长度则插入到链表末尾*/
Status LinkListInsert(LinkList *L, int index, ElemType data) {
    
    // 双向循环链表为空,则返回error
    if(*L == NULL) return ERROR;
    
    if(index < 1) return ERROR;
    
    // 创建指针p,指向双向链表头
    LinkList p = (*L);
    int i = 1;
    
    // 找到插入位置的前一个结点
    for (; i < index && p->next != (*L); i++) {
        p = p->next;
    }
    
    // 创建新节点
    LinkList temp = (LinkList)malloc(sizeof(Node));
    if(temp == NULL) return ERROR;
    temp->data = data;
    // p后继的前驱指针 指向 temp
    p->next->prior = temp;
    // temp 后继指向 p后继
    temp->next = p->next;
    // temp前驱 指向 p
    temp->prior = p;
    // p后继 指向 temp
    p->next = temp;
    
    return OK;
}

删除结点

// 4. 双向循环链表删除结点
Status LinkListDelete(LinkList *L,int index,ElemType *e){
    *e = -1;
    // 双向循环链表为空,则返回error
    if(*L == NULL) return ERROR;
    
    if(index < 1) return ERROR;
    
    // 创建指针p,指向双向链表头
    LinkList p = (*L);
    
    // 找到待删位置的结点
    for (int i = 0; i < index && p->next != (*L); i++) {
        p = p->next;
        
        // 超出链表长度,不删除处理
        if (i<index-1 && p->next==(*L)) {
            return ERROR;
        }
    }
    p->prior->next = p->next;
    p->next->prior = p->prior;
    *e = p->data;
    free(p);
    return OK;
}

源码地址,欢迎Star github数据结构与算法