手撕双链表

简介: 手撕双链表

> 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

🌟前言      

       前面我们已经学习了顺序表和单链表,顺序表可以存储动态的数据,但是一旦元素过少,而又要开辟空间,这样就造成空间的浪费,而单链表以节点为单位存储,不支持随机访问,只能从头到尾遍历访问,为了解决上面两个问题,人们发现了双链表,把一个一个元素以链子的形式存储,可以存储相互的地址,那双链表如何实现呢,今天咱们就实现一下--《双链表》。

🌙主体

咱们从三个方面实现双链表,动态管理,头插头删尾插尾删,增删查改。

在程序中为了实现双链表,需要创建头文件List.h ,创建源文件Test.c,List.c。

🌠动态管理

💤初始化动态双链表

既然实现双链表,初始化动态的双链表必不可少,从两个方面实现初始化动态的双链表。

1.首先我们在List.h定义动态的双链表,省得我们再定义节点(双链表)。

//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
  //上一个
  struct ListNode* next;
  //下一个
  struct ListNode* prev;
  LTDataType data;
}LTNode;

2.对双链表进行初始化

我们要明白,这里不像单链表一样,形成节点就行,还需要初始化。

💦这里采用malloc开辟空间

💦采用预指令判断空间是否开辟完成(没有开辟空间返回-1)

💦之后就是简单的初始数据

💦记得返回值

//初始化
LTNode* BuyLTNode(LTDataType x)
{
  //开辟空间
  LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  //判断开辟的空间是否为空
  if (node == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  //初始化
  node->data = x;
  node->next = NULL;
  node->prev = NULL;
  return node;
}
//形成双链表
LTNode* LTInit()
{
  //使头为0
  LTNode* phead = BuyLTNode(0);
  //构成循环
  phead->next = phead;
  phead->prev = phead;
  return phead;
}

💤释放双链表内存

双链表的内存释放与单链表的内存释放有一定的区别,这里我们分开两类,清理与销毁

清理的代码如下:

//清理
void ListClear(LTNode* phead)
{
  //断言
  assert(phead);
  //清理全部数据,保留头结点
  LTNode* cur = phead->next;
  //循环销毁
  while (cur != phead)
  {
    LTNode* next = cur->next;
    free(cur);
    cur = NULL;
    cur = next;
  }
}

销毁的代码如下:

//销毁
void ListDestory(LTNode** pphead)
{
  //断言
  assert(*pphead);
  //调用清理(函数)
  ListClear(*pphead);
  //释放内存
  free(*pphead);
  *pphead = NULL;
}

🌠头插头删尾插尾删

💤打印元素

打印元素就太简单了,直接上代码

//打印数据
void LTPrint(LTNode* phead)
{
  //断言
  assert(phead);
  printf("phead<=>");
  LTNode* cur = phead->next;
  //注意循环的结束的语句
  while (cur != phead)
  {
    printf("%d<=>", cur->data);
    cur = cur->next;
  }
  printf("\n");
}

💤尾插(重点)

有了这个图就对指向的改变就轻轻松松

//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
  //断言
  assert(phead);
  LTNode* tail = phead->prev;
  //给添加的元素创建节点
  LTNode* newnode = BuyLTNode(x);
    //新开辟的元素下一个节点指向尾
  newnode->prev = tail;
  //尾的上一个节点指向新的元素
    tail->next = newnode;
    //新元素的上一个节点指向头
  newnode->next = phead;
    //头的下一节点指向新的元素节点
  phead->prev = newnode;
  //本质上与尾插相似 (双向链表在pos的前面进行插入)
  //LTInsert(phead, x);
}

💤尾删(重点)

双链表重在画图,希望小伙伴们能看得懂。

//尾删
void LTPopBack(LTNode* phead)
{
  //断言
  assert(phead);
  //防止没有头指向没有元素
  assert(phead->next != phead);
  //找到尾
  LTNode* tail = phead->prev;
  //找到尾前面一个元素
  LTNode* tailPrev = tail->prev;
  //释放内存
  free(tail);
  //(把尾的上一个元素)的上一个指针 指向头
  tailPrev->next = phead;
  //头的下一个指针指向尾的前一个元素
  phead->prev = tailPrev;
  // 双向链表删除pos位置的结点(本质上与尾删相似)
  //LTErase(phead->prev);
}

💤头插(重点)

博主在这里采用三种方法,希望大家至少学会一种

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
  assert(phead);
  //方法一
  //初始化
  //LTNode* newnode = BuyLTNode(x);
  //newnode->next = phead->next;
  //phead->next->prev = newnode;
  //phead->next = newnode;
  //newnode->prev = phead;
  //方法二
  //初始化
  LTNode* newnode = BuyLTNode(x);
  LTNode* first = phead->next;
  phead->next = newnode;
  newnode->prev = phead;
  newnode->next = first;
  first->prev = newnode;
  //方法三
  // 双向链表删除pos位置的结点(本质上就是头插)
  //LTInsert(phead->next, x);
}

💤头删(重点)

双链表会自带梢兵位(这个到后期博主会讲)

//头删(重点)
void LTPopFront(LTNode* phead)
{
  //断言
  assert(phead);
  //头指向不能为空
  assert(phead->next != phead);
  //找到梢兵位的节点
  LTNode* first = phead->next;
  //找到头后面一个元素
  LTNode* second = first->next;
  //释放内存
  free(first);
  //梢兵位的节点指向头后面一个元素
  phead->next = second;
  //后面一个元素指向梢兵位的节点
  second->prev = phead;
  //双向链表删除pos位置的结点(本质上和头删一样)
  //LTErase(phead->next);
}

 🌠增删查改

💤统计双链表元素个数

这个函数还是比较简单的,注意循环的停止条件。

//统计双链表元素个数
int LTSize(LTNode* phead)
{
  //断言
  assert(phead);
  int size = 0;
  LTNode* cur = phead->next;
  while (cur != phead)
  {
    ++size;
    cur = cur->next;
  }
  return size;
}

💤双向链表在pos的前面进行插入

这里大家就看图理解就行啦

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
  //断言
  assert(pos);
  LTNode* posPrev = pos->prev;
  //为插入的元素开辟空间
  LTNode* newnode = BuyLTNode(x);
  posPrev->next = newnode;
  newnode->prev = posPrev;
  newnode->next = pos;
  pos->prev = newnode;
}

💤双向链表删除pos位置的结点

这里大家就看图理解就行啦

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
  //断言
  assert(pos);
  LTNode* posPrev = pos->prev;
  LTNode* posNext = pos->next;
  //释放内存
  free(pos);
  posPrev->next = posNext;
  posNext->prev = posPrev;
}

🌙代码总结

🌠主函数

//包含文件
#include"List.h"
void TestList1()
{
  LTNode* plist = LTInit();
  LTPushBack(plist, 1);
  LTPushBack(plist, 2);
  LTPushBack(plist, 3);
  LTPushBack(plist, 4);
  LTPushBack(plist, 5);
  LTPrint(plist);
  LTPushFront(plist, 10);
  LTPushBack(plist, 10);
  LTPrint(plist);
}
void TestList2()
{
  LTNode* plist = LTInit();
  LTPushBack(plist, 1);
  LTPushBack(plist, 2);
  LTPushBack(plist, 3);
  LTPushBack(plist, 4);
  LTPushBack(plist, 5);
  LTPrint(plist);
  LTPopBack(plist);
  //LTPopFront(plist);
  LTPrint(plist);
  //LTPopFront(plist);
  //LTPopFront(plist);
  //LTPopFront(plist);
  //LTPopFront(plist);
  LTPrint(plist);
}
//void TestList3()
//{
//  LTNode* plist = LTInit();
//  LTPushBack(plist, 1);
//  LTPushBack(plist, 2);
//  LTPushBack(plist, 3);
//  LTPushBack(plist, 4);
//  LTPushBack(plist, 5);
//  LTPrint(plist);
//
//  LTPushFront(plist, 10);
//  LTPushFront(plist, 20);
//  LTPushFront(plist, 30);
//  LTPushFront(plist, 40);
//  LTPrint(plist);
//}
//
//void TestList4()
//{
//  LTNode* plist = LTInit();
//  LTPushBack(plist, 1);
//  LTPushBack(plist, 2);
//  LTPushBack(plist, 3);
//  LTPushBack(plist, 4);
//  LTPushBack(plist, 5);
//  LTPrint(plist);
//
//  LTPopFront(plist);
//  LTPrint(plist);
//
//  LTPopBack(plist);
//  LTPrint(plist);
//}
int main()
{
  TestList1();
  return 0;
}

🌠List.h头文件

//包含头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
  //上一个
  struct ListNode* next;
  //下一个
  struct ListNode* prev;
  LTDataType data;
}LTNode;
//初始化
LTNode* BuyLTNode(LTDataType x);
//形成双链表
LTNode* LTInit();
//打印数据
void LTPrint(LTNode* phead);
//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x);
//尾删(重点)
void LTPopBack(LTNode* phead);
//头插(重点)
void LTPushFront(LTNode* phead, LTDataType x);
//头删(重点)
void LTPopFront(LTNode* phead);
//统计双链表元素个数
int LTSize(LTNode* phead);
// 双向链表查找(在双链表中不合适)
LTNode* LTFind(LTNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);
//清理
void ListClear(LTNode* phead);
//销毁
void ListDestory(LTNode** pphead);

🌠List.c源文件

//包含文件
#include"List.h"
//初始化
LTNode* BuyLTNode(LTDataType x)
{
  //开辟空间
  LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  //判断开辟的空间是否为空
  if (node == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  //初始化
  node->data = x;
  node->next = NULL;
  node->prev = NULL;
  return node;
}
//形成双链表
LTNode* LTInit()
{
  //使头为0
  LTNode* phead = BuyLTNode(0);
  //构成循环
  phead->next = phead;
  phead->prev = phead;
  return phead;
}
//打印数据
void LTPrint(LTNode* phead)
{
  //断言
  assert(phead);
  printf("phead<=>");
  LTNode* cur = phead->next;
  //注意循环的结束的语句
  while (cur != phead)
  {
    printf("%d<=>", cur->data);
    cur = cur->next;
  }
  printf("\n");
}
//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
  //断言
  assert(phead);
  LTNode* tail = phead->prev;
  //给添加的元素创建节点
  LTNode* newnode = BuyLTNode(x);
  //新开辟的元素下一个节点指向尾
  newnode->prev = tail;
  //尾的上一个节点指向新的元素
  tail->next = newnode;
  //新元素的上一个节点指向头
  newnode->next = phead;
  //头的下一节点指向新的元素节点
  phead->prev = newnode;
  //本质上与尾插相似 (双向链表在pos的前面进行插入)
  //LTInsert(phead, x);
}
//尾删
void LTPopBack(LTNode* phead)
{
  //断言
  assert(phead);
  //防止没有头指向没有元素
  assert(phead->next != phead);
  //找到尾
  LTNode* tail = phead->prev;
  //找到尾前面一个元素
  LTNode* tailPrev = tail->prev;
  //释放内存
  free(tail);
  //(把尾的上一个元素)的上一个指针 指向头
  tailPrev->next = phead;
  //头的下一个指针指向尾的前一个元素
  phead->prev = tailPrev;
  // 双向链表删除pos位置的结点(本质上与尾删相似)
  //LTErase(phead->prev);
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
  assert(phead);
  //方法一
  //初始化
  //LTNode* newnode = BuyLTNode(x);
  //newnode->next = phead->next;
  //phead->next->prev = newnode;
  //phead->next = newnode;
  //newnode->prev = phead;
  //方法二
  //初始化
  LTNode* newnode = BuyLTNode(x);
  LTNode* first = phead->next;
  phead->next = newnode;
  newnode->prev = phead;
  newnode->next = first;
  first->prev = newnode;
  //方法三
  // 双向链表删除pos位置的结点(本质上就是头插)
  //LTInsert(phead->next, x);
}
//头删(重点)
void LTPopFront(LTNode* phead)
{
  //断言
  assert(phead);
  //头指向不能为空
  assert(phead->next != phead);
  //找到梢兵位的节点
  LTNode* first = phead->next;
  //找到头后面一个元素
  LTNode* second = first->next;
  //释放内存
  free(first);
  //梢兵位的节点指向头后面一个元素
  phead->next = second;
  //后面一个元素指向梢兵位的节点
  second->prev = phead;
  //双向链表删除pos位置的结点(本质上和头删一样)
  //LTErase(phead->next);
}
//统计双链表元素个数
int LTSize(LTNode* phead)
{
  //断言
  assert(phead);
  int size = 0;
  LTNode* cur = phead->next;
  while (cur != phead)
  {
    ++size;
    cur = cur->next;
  }
  return size;
}
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
  //断言
  assert(pos);
  LTNode* posPrev = pos->prev;
  //为插入的元素开辟空间
  LTNode* newnode = BuyLTNode(x);
  posPrev->next = newnode;
  newnode->prev = posPrev;
  newnode->next = pos;
  pos->prev = newnode;
}
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
  //断言
  assert(pos);
  LTNode* posPrev = pos->prev;
  LTNode* posNext = pos->next;
  //释放内存
  free(pos);
  posPrev->next = posNext;
  posNext->prev = posPrev;
}
//清理
void ListClear(LTNode* phead)
{
  //断言
  assert(phead);
  //清理全部数据,保留头结点
  LTNode* cur = phead->next;
  //循环销毁
  while (cur != phead)
  {
    LTNode* next = cur->next;
    free(cur);
    cur = NULL;
    cur = next;
  }
}
//销毁
void ListDestory(LTNode** pphead)
{
  //断言
  assert(*pphead);
  //调用清理(函数)
  ListClear(*pphead);
  //释放内存
  free(*pphead);
  *pphead = NULL;
}

🌟结束语

      今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

目录
相关文章
|
6天前
|
算法 C++
【数据结构与算法】—— 手撕红黑树
【数据结构与算法】—— 手撕红黑树
|
9月前
【手撕红黑树】
【手撕红黑树】
97 0
|
10月前
手撕红黑树
手撕红黑树
56 0
|
6天前
|
存储 Java C++
手撕单链表
手撕单链表
46 0
|
6天前
|
Java C++ Python
手撕顺序表
手撕顺序表
39 0
|
6天前
|
存储 缓存 算法
手撕无头单链表
手撕无头单链表
16 0
|
6天前
|
安全 Java C语言
手撕各种排序(上)
手撕各种排序
30 0
|
6天前
|
算法 搜索推荐 索引
手撕各种排序(下)
手撕各种排序(下)
40 0
|
6天前
|
搜索推荐
手撕各种排序(中)
手撕各种排序(中)
49 0
|
5月前
|
存储
【数据结构】手撕单链表
【数据结构】手撕单链表