带头双向循环链表

链表的介绍

链表与顺序表一样,也属于线性表。

一个线性表是某类数据元素的一个集合,表里同时记录着元素之间的顺序关系。

线性表的数据之间有顺序关系,顺序关系分为两种,一种是物理有序,即数据物理存储的位置顺序与数据之间的顺序关系一致,另一种是逻辑有序,即数据之间的顺序关系是由某种逻辑关系(如指针)来决定的,与物理存储的位置无关。

顺序表是物理有序,而链表是逻辑有序。

链表的分类

  1. 单向链表

最简单的链表是单向链表。

单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个内容,一个用来存储数据,另一个用来存指针,指针记录下一个节点的地址,最后一个节点的指针则为空。

单向链表也分为头节点(也叫哨兵)和不带头节点两种,头节点的主要作用是简化边缘条件,让代码更容易处理或跟高效,头节点中不存数据,只保存下一个节点的地址。

  1. 双向链表

双向链表又称双面链表,相对于单向链表,双向链表除了向后的指针,还有向前的指针。

每个节点包含三个内容,一个存数据,两个存指针。一个指针指向前一个节点,当此节点为第一个节点时,指向空值,另一个指针指向下一个节点,当此节点为最后一个节点时,指向空值。

  1. 循环链表

循环链表是将单向链表“首尾相连”。

单向链表中最后一个节点的指针指向的是空,而单向循环链表中,最后一个节点的指针指向之前的节点或者链表的头节点,形成一个“环形”的链。

  • 带头双向链表是指结构体中包含两个指针,一个指向上一个节点,一个指向下一个节点,并且拥有哨兵节点的链表,吸收了所有类型链表的有点,理论上能完美解决链表的所有缺点

链表的实现

ListCreate(创建并初始化链表)

  • 创建一个结构体,因为是双向链表,所以结构体中放入了两个指针

  • 函数的返回值用结构体指针接收,避免指针出了函数就销毁。也可以传二级指针代替返回值

  • if是防止头节点创建失败,一般小内存的节点创建并不会失败,所以后面的创建并没有检查

typedef int ListDataType;
struct ListNode
{
    ListDataType data;//存放数据
    struct ListNode* next;//存下一个节点的地址
    struct ListNode* prev;//存上一个节点的地址
};

struct ListNode* ListCreate()
{
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    if (guard == NULL)//防止头节点创建失败
    {
        perror("malloc fail");
        exit(-1);
    }
    guard->next = guard;
    guard->prev = guard;
    return guard;
}

BuyListNode(创建节点)

  • 因为要多次创建节点,所以单独写一个函数来创建节点方便调用,新创建的节点的内存不需要过多处理,这样可以保证函数的简洁,节点的指针在调用函数时再做处理

struct ListNode* BuyListNode(ListDataType x)
{
    struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
    newhead->next = NULL;//创建的节点不需要在函数中赋值
    newhead->prev = NULL;
    return newnode;
}

ListInsert(在pos处前增加一个节点)

  • 写好插入以后头插和尾插不需要单独再写,直接调用函数即可

  • 这里建议用一个指针记录上一个节点的位置,防止修改指针时找不到对应位置

void ListInsert(struct ListNode* pos, ListDataType x)
{
    assert(pos);
    struct ListNode* newnode = BuyListNode(x);
    newnode->data = x;
    struct ListNode * cur = pos->prev;
    cur->next = newnode;
    newnode->prev = cur;
    newnode->next = pos;
    pos->prev = newnode;
}

ListErase(删除pos位置)

  • 用两个指针记录前后节点的位置,以免修改时找不到对应位置

  • 空链表和头节点不能删除要多一个检查

void ListErase(struct ListNode* pos)
{
    assert(pos);
    assert(pos->next != pos);
    struct ListNode* next = pos->next;
    struct ListNode* prev = pos->prev;
    prev->next = next;
    next->prev = prev;
    free(pos);
}

ListPushFront(头插)

  • 为了方便理解把头插等代码的方法单独写

  • 方法一:先对newhead->next进行修改,不然会出现找不到对应节点的情况

  • 方法二:创建一个额外的指针指向phead->next ,这样就不需要考虑顺序

  • 方法三:直接调用ListInsert函数

void ListPushFront(struct ListNode* phead, LTDataType x)
{    
    assert(phead);//防止传错
    //方法一
    struct ListNode* newnode = BuyListNode(x);
    newnode->next = phead->next;//优先修改
    phead->next->prev = newnode;
    phead->next = newnode;
    newnode->prev = phead;
    
    //方法二
    //struct ListNode* newnode = BuyListNode(x);
    //struct ListNode* first = phead->next; // 创建一个指针指向下一个节点
    //phead->next = newnode;
    //newnode->prev = phead;
    //newnode->next = first;
    //first->prev = newnode;

    //方法三
    //ListInsert(phead->next, x);
}

ListPushBack(尾插)

  • 由于是双向链表,所以尾插非常方便,直接从头节点就可以找到尾节点

  • 和头插相同,也是三种方法,这里展示一种

void ListPushBack(LTNode* phead, ListDataType x)
{
    assert(phead);//防止传错

    LTNode* tail = phead->prev;
    LTNode* newnode = BuyListNode(x);
    //将新节点插入
    tail->next = newnode;
    newnode->prev = tail;
    newnode->next = phead;
    phead->prev = newnode;
}

ListPopFront(头删)

  • 空链表和头节点时不能删除

  • 由于链表一般不需要单独写头插头删和尾插尾删,所以这里判断空列表没有单独封装函数

void ListPopFront(LTNode* phead)
{
    assert(phead);//防止传错
    assert(phead->next != phead);//判断链表不为空
    LTNode* cur = phead->next->next;
    LTNode* del = phead->next;
    phead->next = cur;
    cur->prev = phead;
    free(del);
}

ListPopBack(尾删)

  • 最后展示一下为什么说不需要单独写头插等函数,直接调用即可,非常方便

void ListPopBack(struct ListNode* phead)
{
    ListErase(phead->prev);
}

ListDestory(销毁链表)

  • 不能直接把头指针释放,这样会造成内存泄漏,应该遍历链表把每一个节点单独释放

  • 用两个指针记录位置,一个指针用来释放节点,另一个指针用来记录被释放的节点的下一个节点

  • 头指针需要在外面单独处理,防止野指针

void ListDestory(struct ListNode* phead)
{
    assert(phead);
    struct ListNode* first = phead->next;
    struct ListNode* second = first->next;
    while (first != phead)
    {
        free(first);
        first = second;
        second = second->next;
    }
    free(phead);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值