复习数据结构No.2

简介: 复习数据结构No.2

复习链表

使用部分C++的语法,写的单链表,发现了一些以前没有真正明白的知识,总的来说各种数据结构难的不是增删查改,难的是数据的存储问题有没有搞懂,怎么存?存在哪?


如果有链表某些方面没搞懂的问题,相信我注释肯定可以帮到你,因为你的坑,我肯定踩过!

人送外号踩坑小王子

//不管是昨天的顺序表,还是今天的链表,我发现一开始难住我的都不是增删查改,而是空间开辟问题,搞懂了空间问题,别的都是水到渠成
#include<iostream>
#include<stdlib.h>
#include<assert.h>
using namespace std;
typedef int SLLDataType;
typedef struct SLinkedListNode
{
  struct SLinkedListNode* next;
  SLLDataType data;
}SLLNode;
SLLNode* GreatSLLNode(SLLDataType x)//此时的这个传参有顾虑(意思:就是开辟了结点之后,给一个数据就行)
{
  SLLNode* newnode = new SLLNode;
  if (newnode == nullptr)
  {
    cout << "new fail" << endl;//此时可以使用perror
    exit(-1);//这个退出别忘了
  }
  else
  {
    newnode->data = x;
    newnode->next = nullptr;
  }
  return newnode;//返回的是new出来的空间的地址(所以返回结构体的地址就要用结构体指针),记住地址和指针是永远挂钩的就行
}
SLLNode* GreatSLL1()//下述的代码就是一个链表的最本质的写法(经典,但是有点笨)
{
  SLLNode* n1 = GreatSLLNode(1);
  SLLNode* n2 = GreatSLLNode(2);
  SLLNode* n3 = GreatSLLNode(3);
  SLLNode* n4 = GreatSLLNode(4);
  n1->next = n2;
  n2->next = n3;
  n3->next = n4;
  n4->next = nullptr;
  return n1;//搞定链表结构,只要拿到了头结点,就等于拿到了整个链表(因为本质都是通过一个一个指针相连的)
}
SLLNode* GreatSLL2(size_t n)//此时这个接口就是一个更聪明的写法,连续开辟结点和循环链接
{
  SLLNode* phead = nullptr;
  SLLNode* ptail = nullptr;//此时多定义一个ptail的目的就是为了:让phead不同,一直当老大(不会找不到头),这也就是链表结构的特殊性
  for (int i = 0; i < n; ++i)
  {
    SLLNode* newnode = GreatSLLNode(i);//反正不管怎样,想要存数据就要存在堆区上,就要调用malloc(new)
    if (phead == nullptr)
    {
      phead = ptail = newnode;//替死鬼登场
    }
    else
    {
      ptail->next = newnode;//此时newnode->next=nullptr在结点创建的过程就搞定了,这里不需要麻烦
      ptail = newnode;
    }
  }
  return phead;//返回头结点就等于返回了整个链表
}
void SLListPrint(SLLNode*& plist)//这个位置本质因为不需要修改,所以传什么都是一样的
{
  //SLLNode* cur = plist;
  //while (cur != nullptr)
  //{
  //  cout << cur->data << "->";
  //  cur = cur->next;
  //}//上述这个是while写法(有while写法就要for写法)
  for (SLLNode* cur = plist; cur; cur = cur->next)
  {
    cout << cur->data << "->";
  }
  cout << "nullptr" << endl;
}
void SLListPushBack(SLLNode*& plist, SLLDataType x)
{
  SLLNode* newnode = GreatSLLNode(x);//插入数据第一步:把结点构建出来
  SLLNode* tail = plist;
  if (plist == nullptr)
  {
    //plist->next = newnode;//这步写了一点小问题出来(空指针解引用,人才了)
    plist = newnode;//空指针就直接赋值就好
  }
  else
  {
    while (tail->next != nullptr)
    {
      tail = tail->next;
    }
    tail->next = newnode;
    tail = newnode;
  }//有while循环的写法就一定有for循环的写法
  //for (tail; tail->next; tail = tail->next)//if else省略
  //tail->next = newnode;
  //tail = newnode;
}
void SLListPushFront(SLLNode*& plist, SLLDataType x)
{
  SLLNode* newnode = GreatSLLNode(x);
  newnode->next = plist;
  plist = newnode;
}
void SLListPoptBack(SLLNode*& plist)
{
  //for (SLLNode* tail = plist; tail->next; tail = tail->next)
  //delete tail;//链表的一个大坑(刚好踩到)原因:就是导致野指针问题(最后一个结点的next)
  SLLNode* tail = plist;
  SLLNode* prev = nullptr;
  if (plist == nullptr)
  {
    return;
  }
  else if (plist->next == nullptr)
  {
    delete plist;
    plist = nullptr;
  }
  else
  {//1.
    for (tail; tail->next->next; tail = tail->next);
    delete tail->next;
    tail->next = nullptr;
   //2.
    //for (tail; tail->next; tail = tail->next)
    //{
    //  prev = tail;
    //}
    //delete tail;
    //prev->next = nullptr;
  }
}
void SLListPoptFront(SLLNode*& plist)
{
  assert(plist != nullptr);
  //delete plist;
  //plist = nullptr;//恭喜你,链表的第二个大坑你又踩到了
  SLLNode* next = plist->next;
  delete plist;
  plist = next;
}
SLLNode* SLListFind(SLLNode*& plist,SLLDataType x)
{
  for (SLLNode* cur = plist; cur->next; cur = cur->next)
  {
    if (x == cur->data)
    {
      return cur;
    }
  }
}
void SLListDestory(SLLNode*& plist)
{
  for (plist; plist; plist = plist->next)
  {
    SLLNode* next = plist->next;
    delete plist;
    plist = next;
  }
}
void SLListInsert(SLLNode*& plist, SLLNode* pos, SLLDataType x)//此时pos代表的不仅仅是一个位置(本质上代表的是一个结构体的位置),和指针也有很大的关系
{                                                              //此时千万不敢和数组下标搞混(链表的第三个大坑)(链表中没有下标的概念,那个是data不是下标)
  SLLNode* newnode = GreatSLLNode(x);
  SLLNode* cur = plist;
  if (pos == plist)
  {
    newnode->next = plist;
    plist = newnode;
  }
  else
  {
    for (cur; cur->next != pos; cur = cur->next);
    cur->next = newnode;
    newnode->next = pos;
  }
}
void SLListErase(SLLNode*& plist, SLLNode* pos)
{
  if (plist == pos)
  {
    SLLNode* next = plist->next;
    delete pos;
    plist = next;
  }
  else
  {
    SLLNode* cur = plist;
    for (cur; cur->next != pos; cur = cur->next);
    cur->next = pos->next;
    delete pos;
  }
}
void TestSLList1(SLLNode*& plist)//这个位置因为会涉及到增删查改,所以必须要给二级指针或者传引用
{
  SLListPushBack(plist, 1);
  SLListPushBack(plist, 2);
  SLListPushBack(plist, 3);
  SLListPushBack(plist, 4);
  SLListPushBack(plist, 5);
  SLListPushFront(plist, 1);
  SLListPushFront(plist, 2);
  SLListPushFront(plist, 3);
  SLListPushFront(plist, 4);
  SLListPushFront(plist, 5);
  SLListPoptBack(plist);
  SLListPoptBack(plist);
  SLListPoptBack(plist);
  SLListPoptBack(plist);
  SLListPoptBack(plist);
  SLListPoptFront(plist);
  SLListPoptFront(plist);
  SLListPoptFront(plist);
  SLListPoptFront(plist);
  SLListPoptFront(plist);
  SLListPrint(plist);
  SLListDestory(plist);
}
void TestSLList2(SLLNode*& plist)
{
  SLLNode* pos = SLListFind(plist, 5);
  cout << pos->data << endl;//这个位置傻傻的不会打印(接收一下就可以打印都想不到吗?)
}
void TestSLList3(SLLNode*& plist)
{
  SLLNode* pos = SLListFind(plist, 5);
  SLListInsert(plist, pos, 50);
  SLListPrint(plist);
  SLListErase(plist, pos);
  SLListPrint(plist);
}
int main() 
{
  //SLLNode plist;//首先第一个点就把我自己搞懵了(这里是使用SSL指针类型,还是SSL结构体变量类型,可以看出当时确实是没怎么学明白)
  //百度之后,没有找到答案,可以看出这个问题确实是比较容易被忽视,但是航哥给了我答案:
  //(与栈帧有关)就是如果我使用单链表结构体定义一个结构体变量,那么此时这个变量的空间就是在栈上申请的(如果使用的是一个指针变量,那么此时这个指针变量就可以指向堆上的空间,这个就是本质原因)
  //本质的改变就是在结点中的数据不会因为栈帧的销毁而销毁
  //并且我们的顺序表虽然没有使用指针,但是它的空间都是通过扩容扩出来的(realloc),本质上还是在堆上,所以我们也要把单链表中的数据存到堆区上
  //但是由于链表的特殊结构,此时我们无法直接在堆区上扩容,所以就要使用结构体指针
  //所以本质上我们的头结点应该是如下这样写的
  //SLLNode* plist = (SLLNode*)malloc(sizeof(SLLNode));//但是这样写比较麻烦,所以一般我们都是直接写成一个函数,每次要用就去调用那个函数就行
  //并且上述是一个C语言的写法,下面的是一个C++的写法
  //SLLNode* plist = new SLLNode;//会自动调用构造函数,所以不怕
  SLLNode* plist1 = GreatSLLNode(1);
  SLLNode* plist2 = GreatSLL1();
  SLLNode* plist3 = GreatSLL2(10);//这个参数此时代表的是个数
  SLListPrint(plist1);//开结点
  SLListPrint(plist2);//傻傻开
  SLListPrint(plist3);//循环开
  //搞懂了上述,我敢包票你就搞懂了链表
  //因为还是如最上面的那句话,最难的只是开空间问题而已
  //所以此时我们开始增删查改(外包函数)
  //TestSLList1(plist3);
  //TestSLList2(plist3);
  TestSLList3(plist3);
  return 0;
}


总结:不复习不知道,原来自己这么菜,哈哈哈!我发现我复习不是肉丝等着我找,是一大块老赖肉摆在我面前,我看不见啊!哈哈哈!

相关文章
|
9天前
|
存储 关系型数据库 分布式数据库
PostgreSQL 18 发布,快来 PolarDB 尝鲜!
PostgreSQL 18 发布,PolarDB for PostgreSQL 全面兼容。新版本支持异步I/O、UUIDv7、虚拟生成列、逻辑复制增强及OAuth认证,显著提升性能与安全。PolarDB-PG 18 支持存算分离架构,融合海量弹性存储与极致计算性能,搭配丰富插件生态,为企业提供高效、稳定、灵活的云数据库解决方案,助力企业数字化转型如虎添翼!
|
8天前
|
存储 人工智能 Java
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
本文讲解 Prompt 基本概念与 10 个优化技巧,结合学术分析 AI 应用的需求分析、设计方案,介绍 Spring AI 中 ChatClient 及 Advisors 的使用。
364 130
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
|
8天前
|
人工智能 Java API
AI 超级智能体全栈项目阶段一:AI大模型概述、选型、项目初始化以及基于阿里云灵积模型 Qwen-Plus实现模型接入四种方式(SDK/HTTP/SpringAI/langchain4j)
本文介绍AI大模型的核心概念、分类及开发者学习路径,重点讲解如何选择与接入大模型。项目基于Spring Boot,使用阿里云灵积模型(Qwen-Plus),对比SDK、HTTP、Spring AI和LangChain4j四种接入方式,助力开发者高效构建AI应用。
351 122
AI 超级智能体全栈项目阶段一:AI大模型概述、选型、项目初始化以及基于阿里云灵积模型 Qwen-Plus实现模型接入四种方式(SDK/HTTP/SpringAI/langchain4j)
|
20天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1340 8
|
2天前
|
存储 JSON 安全
加密和解密函数的具体实现代码
加密和解密函数的具体实现代码
190 136
|
7天前
|
监控 JavaScript Java
基于大模型技术的反欺诈知识问答系统
随着互联网与金融科技发展,网络欺诈频发,构建高效反欺诈平台成为迫切需求。本文基于Java、Vue.js、Spring Boot与MySQL技术,设计实现集欺诈识别、宣传教育、用户互动于一体的反欺诈系统,提升公众防范意识,助力企业合规与用户权益保护。
|
19天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1440 87
|
7天前
|
JavaScript Java 大数据
基于JavaWeb的销售管理系统设计系统
本系统基于Java、MySQL、Spring Boot与Vue.js技术,构建高效、可扩展的销售管理平台,实现客户、订单、数据可视化等全流程自动化管理,提升企业运营效率与决策能力。