【数据结构】双向循环带头结点链表

前面我们写过了不带头结点的单链表,由于没有头结点,在对单链表进行头插/删的时候需要传入二级指针;在需要进行尾插/删的时候,需要先根据头指针找到头结点,然后从头往后遍历找到最后一个结点再进行相应操作。而我们今天要写的双向循环带头结点链表,相对于不带头结点的单链表做增删时,将会方便许多。

  • (1)可以通过头结点找到链表首尾

  • (2)在链表中间做增删时,不需要从头遍历

  • (3)有头结点, 对带头结点的链表,在表的任何结点之前插入结点或删除表中任何结点,所要做的都是修改前一结点的指针域,因为任何元素结点都有前驱结点。若链表没有头结点,则首元素结点没有前驱结点,在其前插入结点或删除该结点时操作会复杂些,就像前面写的不带头结点的单链表,需要传入二级指针。另外由于头结点的数据域空闲,可以存放一些关于链表的属性信息。

双向循环带头结点链表的存储结构

typedef int DataType;

typedef struct Node
{
    struct Node *next;
    struct Node *prev;
    DataType data;
}Node;

创建/初始化/销毁/打印

  • 创建结点
Node *CreateNode(DataType data)
{
    Node *newNode = (Node *)malloc(sizeof(Node));
    assert(newNode);
    newNode->data = data;
    newNode->next = NULL;
    newNode->prev = NULL;

    return newNode;
}
  • 链表的初始化
void LinkListInit(Node *HNode)
{
    assert(HNode);
    HNode->data = 0;
    HNode->next = HNode;
    HNode->prev = HNode;
}
  • 链表的打印
    因为是带头结点的链表,所以应该从第二个结点开始打印
void print(const Node *HNode)
{
    Node *cur = HNode->next;
    printf("HNode--->");
    for (; cur != HNode; cur = cur->next)
    {
        printf("%d", cur->data);
        if (cur->next != HNode)
        {
            printf("--->");
        }
    }
    printf("--->HNode");
    printf("\n");
}
  • 链表的销毁
    先将链表清空,链表清空后仍存在一个头结点,然后再把头结点释放
void clear(Node *HNode)
{
    Node *cur = HNode->next;
    Node *next = NULL;

    while (cur != HNode)
    {
        next = cur->next;
        free(cur);
        cur->next = NULL;
        cur->prev = NULL;
        next->prev = HNode;
        HNode->next = next;
        cur = next;
    }
}
void destroy(Node *HNode)
{
    clear(HNode);
    free(HNode);
    HNode->next = NULL;
    HNode->prev = NULL;
}

其实双向循环带头结点链表的插入操作都是如出一辙的,都是仅需要更改插入/删除结点的next&prev指针指向,以及前一个结点的next和后一个结点的prev

下面以头删为例进行说明:

这里写图片描述

  • newNode->next

  • newNode->prev

  • HNode->next

  • node1->prev

而双向循环带头结点链表的删除操作比插入更加简单,只需要变更前一个结点的next和后一个结点的prev,然后释放掉该结点就OK

插入结点

//头插
void PushFront(Node *HNode, DataType data)
{
    Node *node = CreateNode(data);

    node->prev = HNode;
    node->next = HNode->next;
    HNode->next->prev = node;
    HNode->next = node;
}
//尾插
void PushBack(Node *HNode, DataType data)
{
    Node *node = CreateNode(data);

    node->next = HNode;
    node->prev = HNode->prev;
    HNode->prev->next = node;
    HNode->prev = node;
}
//任意位置前插入,假设pos在链表中
void Insert(Node *pos, DataType data)
{
    Node *node = CreateNode(data);
    node->next = pos;
    node->prev = pos->prev;
    pos->prev->next = node;
    pos->prev = node;
}

而因为双向循环带头结点链表操作的便利性,可以将头插/尾插通过Insert来实现

void PushFront_Simplify(Node *HNode, DataType data)
{
    Insert(HNode->next, data);
}
void PushBack_Simplify(Node *HNode, DataType data)
{
    Insert(HNode, data);
}

测试展示:

这里写图片描述

删除节点

//头删
void PopFront(Node *HNode)
{
    Node *front = HNode->next;
    if (HNode->next == HNode)
    {
        printf("链表为空,删除失败\n");
        return;
    }
    HNode->next = front->next;
    front->next->prev = HNode;
    free(front);
    front->next = NULL;
    front->prev = NULL;
}
//尾删
void PopBack(Node *HNode)
{
    Node *back = HNode->prev;
    if (HNode->next == HNode)
    {
        printf("链表为空,删除失败\n");
        return;
    }
    back->prev->next = HNode;
    HNode->prev = back->prev;

    free(back);
    back->next = NULL;
    back->prev = NULL;
}
//删除任意位置,假设pos在链表中
void Erase(Node *HNode, Node *pos)
{
    if (HNode->next == HNode)
    {
        printf("链表为空,删除失败\n");
        return;
    }
    pos->prev->next = pos->next;
    pos->next->prev = pos->prev;

    free(pos);
    pos->next = NULL;
    pos->prev = NULL;
}

同样,可以用Erase实现头删/尾删

void PopFront_Simplify(Node *HNode)
{
    Erase(HNode, HNode->next);
}
void PopBack_Simplify(Node *HNode)
{
    Erase(HNode, HNode->prev);
}

测试展示:

这里写图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值