【双向链表】

简介: 【双向链表】

带头双向循环链表的实现

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

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;
    }

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

目录
打赏
0
0
0
0
4
分享
相关文章
|
11月前
|
深入理解Linux虚拟内存管理(七)(上)
深入理解Linux虚拟内存管理(七)
235 1
倚天虚拟化:CPU虚拟化原理介绍
虚拟化技术中最关键的技术之一就是CPU虚拟化。在没有硬件辅助虚拟化技术出来之前,通常都是通过TCG(软件进行指令翻译)的方式实现CPU虚拟化。但是由于TCG方式的虚拟化层开销太大,性能太差,因此引入了硬件辅助虚拟化技术。
1718 1
PCIe初始化枚举和资源分配流程分析
本文主要是对PCIe的初始化枚举、资源分配流程进行分析,代码对应的是alikernel-4.19,平台是arm64 ## 1. PCIe architecture ### 1.1 pcie的拓扑结构 在分析PCIe初始化枚举流程之前,先描述下pcie的拓扑结构。 如下图所示: ![11.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs
8131 1
PCIe初始化枚举和资源分配流程分析
tmux -高效使用Linux terminal
这个利器绝对可以提升工作效率,因为你会发现日常工作中切换terminal会耗费你大量的时间,花上1-2个小时打磨一下这个利器,会事半功倍,绝对值得.
467 0
tkinter批量word转pdf
tkinter批量word转pdf
136 2
class078 树型dp-上【算法】
class078 树型dp-上【算法】
132 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问