带头双向循环链表的增删改查

带头双向循环链表的节点有两个指针域,一个指针域prev存放节点前面节点的地址,另一个指针域next存放节点后面节点的地址。其中头节点的prev存放尾节点的地址,尾节点的next存放头节点的地址,达到循环的效果。

节点的定义、创建、初始化、销毁和打印

typedef int LTDataType;//重命名结构体数据类型,方便链表后续扩展

typedef struct ListNode {
    LTDataType data;
    struct ListNode* next;
    struct ListNode* prev;
}ListNode;
//创建新节点
ListNode* BuyNode(LTDataType x) {
    ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    if (newnode == NULL) {
        perror("BuyNode::malloc");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = newnode->prev = NULL;
    return newnode;
}
//销毁
void ListDestroy(ListNode* phead) {
    assert(phead);
    ListNode* cur = phead->next;
    while (cur != phead) {
        ListNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(phead);
}
//初始化
ListNode* ListInit() {
    //哨兵位的头节点
    ListNode*  phead = (ListNode*)malloc(sizeof(ListNode));
    if (phead == NULL) {
        perror("ListInit::malloc");
        exit(-1);
    }
    phead->next = phead;
    phead->prev = phead;
    return phead;
}
//打印
void ListPrint(ListNode* phead) {
    assert(phead->next != NULL);
    ListNode* cur = phead->next;
    while (cur != phead) {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

链表的增加

尾插

//尾插
void ListPushBack(ListNode* phead, LTDataType x) {
    assert(phead);
    ListInsert(phead, x);
    //ListNode* tail = phead->prev;
    //ListNode* newnode = BuyNode(x);

    //tail->next = newnode;
    //newnode->prev = tail;
    //phead->prev = newnode;
    //newnode->next = phead;
}

首先记录下尾节点地址,再创建一个新节点。

将尾节点的next指向新节点,新节点的prev指向尾节点。

头节点的prev指向新节点,新节点的next指向头节点.

头插

//头插
void ListPushFront(ListNode* phead, LTDataType x) {
    assert(phead);  
    ListInsert(phead->next, x);
    //ListNode* newNode = BuyNode(x);
    //ListNode* next = phead->next;
    // 
    //phead->next = newNode;
    //newNode->next = next;
    //next->prev = newNode;
    //newNode->prev = phead;
}

创建一个新节点,并记录下头节点的地址(也就是哨兵位的下一个节点)。

再将哨兵位的next指向新节点,新节点的next指向头节点。

头节点的prev指向新节点,新节点的prev指向哨兵位的地址。

在指定节点前面插入

//在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
    assert(pos);
    ListNode* pre = pos->prev;
    ListNode* newNode = BuyNode(x);
    pre->next = newNode;
    newNode->prev = pre;
    newNode->next = pos;
    pos->prev = newNode;
    //ListPushFront(pre, x);
}

记录下pos位置的前面一个节点的地址,创建一个新节点。

pos位置的前面一个节点的next指向新节点。

新节点的prev指向pos位置的前面一个节点,新节点的next指向pos位置。

pos位置的prev指向新节点。

节点的删除

头删

//头删
void ListPopFront(ListNode* phead) {
    assert(phead);
    assert(phead->next != phead);//判断链表是否为空
    ListErase(phead->next);
    //ListNode* newNext = phead->next->next;
    //free(phead->next);

    //phead->next = newNext;
    //newNext->prev = phead;
}

首先记录下头节点(就是哨兵位的下一个节点)的下一个节点的地址。

再释放掉头节点。哨兵位的next指向记录的头节点的下一个节点的地址。

记录下的头节点的下一个节点的prev指向哨兵位。

尾删

//尾删
void ListPopBack(ListNode* phead) {
    assert(phead);
    assert(phead->next != phead);//当phead->next == phead时,链表为空
    ListErase(phead->prev);
    //ListNode* newTail = phead->prev->prev;

    //free(phead->prev);
    //phead->prev = newTail;
    //newTail->next = phead;


}

记录下尾节点前面一个节点的位置。

释放掉尾节点。再将哨兵位的prev指向记录下的尾节点前面一个节点的位置。

记录下的尾节点的前面一个节点的next指向哨兵位。

删除指定节点

//删除pos位置的节点
void ListErase(ListNode* pos) {
    assert(pos);
    ListNode* pre = pos->prev;
    ListNode* next = pos->next;
     
    free(pos);
    pre->next = next;
    next->prev = pre;
}

首先记录下pos位置前面一个节点pre和pos位置后面一个节点next;

释放掉pos位置的节点,再将pre节点的next指向next,next节点的prev指向pre节点。

节点的查找

//查找
ListNode* ListFind(ListNode* phead, LTDataType x) {
    assert(phead);
    ListNode* cur = phead->next;
    while (cur != phead) {
        if (cur->data == x) {
            return cur;
        }
        cur = cur->next;
    }
    return NULL;
}

节点的修改

节点的修改是指修改节点中的数据域,这里可以嵌套节点的查找函数,得到对应节点的地址就能进行操作了,完成对数据域的修改。

总结

带头双向循环链表的结构比单链表的结构更加复杂,但是带头双向循环链表的删除,插入更为关键。因为单链表的删除和插入的实现,使得带头双向循环链表的头插、头删、尾插和尾删更加方便,头插、头删、尾插和尾删的操作嵌套删除和插入的函数就能完成了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值