【双向链表】

简介: 【双向链表】

带头双向循环链表的实现

今天我们来实现一下带头双向循环链表,顾名思义,带头就是有哨兵位,哨兵位不是链表的头,它是连接头节点的一个节点,方便操作链表而已;双向就是一个节点可以找到下一个节点,也可以找到上一个节点,所以每个节点应该有两个结构体指针;循环就是没有空指针,哨兵位的上一个节点是尾,尾的下一个节点是哨兵位;如下图为带头双向循环链表:

1. 函数的声明

以下是函数的声明部分,我们主要实现双向链表的增删查改功能;

#pragma once
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <stdbool.h>
    //类型的命名
    typedef int LTDataType;
    //定义节点
    typedef struct ListNode
    {
      struct ListNode* prev;
      struct ListNode* next;
      LTDataType data;
    }LTNode;
    //初始化链表
    LTNode* ListInit();
    //打印链表
    void ListPrint(LTNode* phead);
    //判断是否是空链表
    bool IsEmpty(LTNode* phead);
    //尾插、头插、头删、尾删
    void ListPushBack(LTNode* phead, LTDataType x);
    void ListPushFront(LTNode* phead, LTDataType x);
    void ListPopFront(LTNode* phead);
    void ListPopBack(LTNode* phead);
    //查找
    LTNode* LTFindPos(LTNode* phead, LTDataType x);
    //在pos位置之前插入
    void LTInsertPos(LTNode* pos, LTDataType x);
    //删除pos位置
    void LTErasePos(LTNode* pos);
    //销毁
    void LTDestroy(LTNode* phead);

2. 函数的实现

为了防止创建新的节点的时候重复出现,我们将创建节点写成一个函数:

LTNode* BuyListNode(int x)
    {
      LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
      assert(newnode);
      newnode->data = x;
      newnode->next = NULL;
      newnode->prev = NULL;
      return newnode;
    }

初始化节点,只需要一个哨兵位,它的next和prev都指向自己;

LTNode* ListInit()
    {
      LTNode* phead = BuyListNode(-1);
      phead->next = phead;
      phead->prev = phead;
      return phead;
    }

打印链表的函数:

void ListPrint(LTNode* phead)
    {
      assert(phead);
      LTNode* cur = phead->next;
      printf("guard<==>");
      while (cur != phead)
      {
        printf("%d<==>",cur->data);
        cur = cur->next;
      }
      printf("\n");
    }

由于头删尾删的时候不能是空链表,带头的链表的空链表相当于只有一个哨兵位,所以头删尾删的时候链表中不能只剩下哨兵位;

bool IsEmpty(LTNode* phead)
    {
      assert(phead);
      return phead->next == phead;
    }

尾插函数:

void ListPushBack(LTNode* phead,LTDataType x)
    {
      assert(phead);
      LTNode* newnode = BuyListNode(x);
      LTNode* tail = phead->prev;
      newnode->next = phead;
      tail->next = newnode;
      newnode->prev = tail;
      phead->prev = newnode;
    }

头插函数:

void ListPushFront(LTNode* phead, LTDataType x)
    {
      assert(phead);
      LTNode* newnode = BuyListNode(x);
      LTNode* next = phead->next;
      newnode->next = next;
      newnode->prev = phead;
      next->prev = newnode;
      phead->next = newnode;
    }

头删函数:

void ListPopFront(LTNode* phead)
    {
      assert(phead);
      assert(!IsEmpty(phead));
      LTNode* del = phead->next;
      LTNode* next = del->next;
      phead->next = next;
      next->prev = phead;
      free(del);
    }

尾删函数:

void ListPopBack(LTNode* phead)
    {
      assert(phead);
      assert(!IsEmpty(phead));
      LTNode* tail = phead->prev;
      LTNode* tailprev = tail->prev;
      tailprev->next = phead;
      phead->prev = tailprev;
      free(tail);
    }

查找某个节点的函数,返回找到的节点,否则返回空;

LTNode* LTFindPos(LTNode* phead, LTDataType x)
    {
      assert(phead);
      LTNode* cur = phead->next;
      while (cur)
      {
        if (cur->data == x)
        {
          return cur;
        }
        cur = cur->next;
      }
      return NULL;
    }

在pos位置之前插入的插入函数:

void LTInsertPos(LTNode* pos, LTDataType x)
    {
      assert(pos);
      LTNode* newnode = BuyListNode(x);
      LTNode* ptr = pos->prev;
      ptr->next = newnode;
      newnode->next = pos;
      pos->prev = newnode;
      newnode->prev = ptr;
    }

删除pos位置的函数:

void LTErasePos(LTNode* pos)
    {
      assert(pos);
      assert(!IsEmpty(pos));
      LTNode* ptr = pos->prev;
      LTNode* next = pos->next;
      ptr->next = next;
      next->prev = ptr;
      free(pos);
    }

销毁链表的函数:

void LTDestroy(LTNode* phead)
    {
      assert(phead);
      LTNode* cur = phead->next;
      while (cur != phead)
      {
        LTNode* next = cur->next;
        free(cur);
        cur = next;
      }
      free(phead);
    }

3. 主函数测试

#include "List.h"
    int main()
    {
      LTNode* phead = ListInit();
      ListPushBack(phead, 1);
      ListPushBack(phead, 2);
      ListPushBack(phead, 3);
      ListPushBack(phead, 4);
      ListPushFront(phead, 10);
      ListPopBack(phead);
      ListPopFront(phead);
      LTNode* pos = LTFindPos(phead, 3);
      LTInsertPos(pos, 20);
      LTErasePos(pos);
      ListPrint(phead);
      LTDestroy(phead);
      return 0;
    }

以上就是带头双向循环链表的基本实现,感兴趣的伙伴可以自行尝试噢!!!

目录
相关文章
|
2天前
|
存储
双向链表
单链表每个结点有一个指针域和一个数据域,要访问任何结点都需知道头结点,不能逆着进行。双向链表则添加了一个指针域,通过两个指针域分别指向结点的前一个结点和后一个结点。这样的话,可以通过双链表的任何结点访问到它的前一个结点和后一个结点。 两个指针域一个存储直接后继结点的地址,一般称为右链域,另一个存储直接前驱结点,一般称为左链域。
|
1月前
|
存储
双向链表(详解)
双向链表(详解)
24 1
|
5月前
|
存储
双向链表专题
双向链表专题
41 6
|
5月前
双向链表的实现
双向链表的实现
21 0
|
6月前
秒懂双向链表
秒懂双向链表
27 0
|
6月前
|
存储
双向链表介绍
带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨的”。哨兵位存在的意义:避免链表出现死循环。
37 0
|
6月前
|
Java
7.双向链表最佳实现
7.双向链表最佳实现
57 1
|
6月前
|
存储
【双向链表】数据结构双向链表的实现
【双向链表】数据结构双向链表的实现
|
存储 算法 搜索推荐
双向链表
双向链表是一种链式存储结构,每个节点包含两个指针,分别指向其前驱和后继。相比于单向链表,双向链表可以在常数时间内向前或向后遍历整个链表。因此,双向链表在需要频繁遍历链表的场景中具有优势。
56 7
|
6月前
|
存储
双向链表的操作
双向链表的操作