当前位置: 首页 > news >正文

工程建筑公司网站设计网站推荐按钮的作用

工程建筑公司网站,设计网站推荐按钮的作用,申请注册自媒体平台,遵义公司做网站文章目录 🍉前言🍉基本概念🍉链表的分类🍌单链表节点的结构🍌创建节点🍌打印链表🍌插入和删除🥝尾插🥝头插🥝尾删🥝头删🥝指定位置之前…

文章目录

  • 🍉前言
  • 🍉基本概念
  • 🍉链表的分类
    • 🍌单链表节点的结构
    • 🍌创建节点
    • 🍌打印链表
    • 🍌插入和删除
      • 🥝尾插
      • 🥝头插
      • 🥝尾删
      • 🥝头删
      • 🥝指定位置之前插入
      • 🥝指定位置之后插入
      • 🥝删除指定节点
    • 🍌销毁
  • 🍉源码
    • 🍌头文件:声明部分
    • 🍌源文件:功能实现部分

🍉前言

喜茶的果汁茶有这样的一句宣传语:一半果汁一半茶。用这个来形容单链表那可是再合适不过了一一 一半数据一半指针。
在这里插入图片描述

🍉基本概念

链表是一种数据结构,采用链式存储一一在内存中不是连续存储的,各元素的逻辑顺序是通过链表中的指针链接次序实现的。它包含数据域指针域,分别保存数据和下一个节点的地址。
在这里插入图片描述
如果要创建节点,一般是在堆上申请。


🍉链表的分类

链表结构多种多样,有三种分类,这些分类进行排列组合共有8种:

①单向或双向
区分单双向就看各节点之间的指向是否是双向的,比如下面这个就是双向链表。
在这里插入图片描述

②带头或不带头
就是有没有带头节点,头节点的数据域一般是存放这个链表的基本信息,其指针域指向第一个节点。在这里插入图片描述>>③循环或非循环
尾节点的指针指向头节点就可以形成一个循环。在这里插入图片描述

我们最常用的是这两种:无头单向非循环链表和带头双向循环链表,本文要讲的是单链表。

🍌单链表节点的结构

typedef int SLTypeDate;
typedef struct SListNode {SLTypeDate data;struct SListNode* next;
}SLNode;

这个结构体里面有一个同类型的结构体指针,这种现象叫做结构体的自引用,往下看你就会知道这个指针的妙处了。

Q:使用 typedef 对结构体重命名之后,可以在结构体内部使用新的名字吗?
A:不可以,因为编译器是向下编译的,上面的语句相当于:

struct SListNode {SLTypeDate data;struct SListNode* next;
};
typedef int SLTypeDate SLNode;

🍌创建节点

刚才已经在头文件里面定义了一个结构体类型SLNode(链表节点),那么现在来建立一些节点,用于后面测试相应的函数。
(原文件 test.c)

void SLTest() {SLNode* Node1 = (SLNode*)malloc(sizeof(SLNode));Node1->data = 1;SLNode* Node2 = (SLNode*)malloc(sizeof(SLNode));Node1->data = 2;SLNode* Node3 = (SLNode*)malloc(sizeof(SLNode));Node1->data = 3;SLNode* Node4 = (SLNode*)malloc(sizeof(SLNode));Node1->data = 4;
}
	Node1->next = Node2;Node2->next = Node3;Node3->next = Node4;Node4->next = NULL;

建立了四个节点,但是它们现在彼此之间还没有联系,就是孤零零的四个节点,此时我们用next指针把它们给串联起来。整体效果图就是这样:
在这里插入图片描述
某个节点的指针域保存的是下一个节点的地址


🍌打印链表

我们来写一个函数打印链表中存放的数据。

void SLPrint(SLNode* phead) {SLNode* pcur = phead;while (pcur) {printf("%d ->", pcur->data);pcur = pcur->next;  //pcur原先指向某个节点,现在让它指向下一个节点}printf("NULL\n");
}

这里解释下为什么要用一个pcur保存第一个节点的地址:因为你如果用phead的话,那到时phead在循环中就会不断往前走,直到它为NULL,也就是说,循环结束以后phead就是空指针了,那你就再也找不到链表中各个节点的地址了。

所以我们不难看出链表是通过地址的赋值,从上一个节点走到下一个节点

接下来也是和顺序表差不多,有头插、尾插的操作,执行插入操作时我们需要申请新节点,为了避免重复,让代码简洁一点,我们可以写一个申请新节点的函数。

SLNode* SLBuyNode(SLTypeDate x) {  //申请一个节点,并将想要存储的数据存进去SLNode* node = (SLNode*)malloc(sizeof(SLNode));node->data = x;node->next = NULL;return node;
}

🍌插入和删除

老规矩,还是分为头插和尾插还有随便插,先来写个尾插

🥝尾插

在这里插入图片描述

我们首先得找到最后一个节点,然后把它的next指针改为node的地址,代码如下,该说的基本都在注释讲了,主要来说一下 while 循环的终止条件。
在这里插入图片描述
如果循环终止条件是pcur == NULL 的话,那就跑过头了,此时pcur就成空指针。

void SLPushBack(SLNode** pphead,SLTypeDate x) {assert(pphead);SLNode* node = SLBuyNode(x);if (*pphead == NULL) {  //phead为空说明此时链表为空,那就直接插入*pphead = node;  //让node是第一个节点的地址return;}//若不为空,则先找到链表的最后一个节点,再插入SLNode* pcur = *pphead;  //老样子,用临时变量pcur去走循环while (pcur->next) {pcur = pcur->next;}//此时pcur指向最后一个节点pcur->next = node;
}

如果你指针部分的知识学得很扎实,那你一眼就可以看出这段代码有问题了。

这样写问题出在哪里呢?问题在于你传的参数,你觉得把实参传给这个phead是传值调用还是传址调用?显然是传值,因为实参虽然是节点的地址,但是它本质上是一个值,你传过来给 phead 的只是一个值而已,任你怎么改变phead,都与实参无关。而在这个函数中,如果你进行尾插时链表为空,那node的值只给到了phead,无法影响到实参。
要解决问题的话,改为传二级指针就ok了。

而如果链表不为空,那就没啥影响了,因为此时你只需改变 next ,无需改变实参。传值确实没问题,但是为了形式上的统一(避免一下子是一级指针,一下子又是二级指针),所以也传二级指针。
phead 是一级指针,那么二级指针我们就记为pphead,把原本的phead改为 *pphead就ok了。同时我们需要对 pphead 进行断言,因为它为空的话那就不能解引用。
所以正确的代码如下:

void SLPushBack(SLNode** pphead,SLTypeDate x) {assert(pphead);  //进行断言,防止传过来的指针为空SLNode* node = SLBuyNode(x);if (*pphead == NULL) {  //phead为空说明此时链表为空,那就直接插入*pphead = node;  //让node是第一个节点的地址return;}//若不为空,则先找到链表的最后一个节点,再插入SLNode* pcur = *pphead;  //老样子,用临时变量pcur去走循环while (pcur->next) {pcur = pcur->next;}//此时pcur指向最后一个节点pcur->next = node;
}

🥝头插

头插就很简单了,不用考虑顺序表为不为空。

void SLPushFront(SLNode** pphead, SLTypeDate x) {assert(pphead);SLNode* node = SLBuyNode(x);node->next = *pphead;*pphead = node;
}

🥝尾删

尾删需要完成2个任务:①释放掉最后一个节点的空间;②将倒数第二个节点的 next 指针置为空。
最后一个节点这个好找,那倒数第二个呢?请看下面代码:

void SLPopBack(SLNode** pphead,SLTypeDate x) {assert(pphead);assert(*pphead);  //如果节点地址为空,那么说明节点为空(为空说明没有指向),而节点为空时显然不能删除SLNode* prev = NULL;SLNode* ptail = *pphead;if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;return;}while (ptail->next) {prev = ptail;ptail = ptail->next;}prev->next = ptail->next;free(ptail);ptail = NULL;
}
}

我这次弄了两个指针:prevptailptail经过循环最终指向最后一个节点;而prev则是指向倒数第二个节点。可以看到,每次循环我在把下一个节点地址赋给 ptail之前,就把当下ptail 保存到prev,这样在最后一次循环时 prev 就在倒二了。
然后 if 语句里面的是只有一个节点的情况,为什么要单独把它拿出来讨论呢?因为如果没有这个 if 语句,那么此时 prev 就是NULL,它不能解引用去访问节点里面的 next,更何况此时还是一个空节点。

释放最后一个节点之后,一定要把它的地址置为空,你如果不置空,在尾删这个函数里面确实没问题,但是在打印链表的函数中,循环的终止条件是pcur为空,当pcur走到被删掉的节点时,因为地址不为空,所以会把这里的东西打印出来。
释放掉某块空间,只是把里面的数据给释放掉,但是那块空间仍然存在


🥝头删

头删的思路就是:把第一个节点释放掉,把 phead(*pphead) 给到原先的第二个节点

void SLPopFront(SLNode** pphead) {assert(pphead);assert(*pphead);SLNode* pcur = *pphead;*pphead = (*pphead)->next;free(pcur);pcur = NULL;
}

这里 pcur 可以不置为空,但出于代码规范,所以置空。


🥝指定位置之前插入

既然要在某位置前插入,那就得先找到这个位置,怎么找呢?当然是循环遍历了,先来写下找到我们想要的节点的函数:

SLNode* SLFindNode(SLNode** pphead, SLTypeDate x) {assert(pphead);SLNode* pcur = *pphead;while (pcur) {if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;  //找不到就返回空指针
}

然后可以写插入的函数了:

void SLInsert(SLNode** pphead, SLNode* pos, SLTypeDate x) {assert(pphead);assert(*pphead);assert(pos);SLNode* node = SLFindNode(*pphead,pos);SLNode* prev = *pphead;if (*pphead = pos) {  //只有一个节点或插入位置位于第一个节点之前的情况node->next = *pphead;*pphead = node;return;}//多个节点的情况while (prev->next != pos) {prev = prev->next;}node->next = pos;prev->next = node;
}

注:你画图就会发现只有一个节点or插入第一个节点之前的位置,这两种情况下 pos 和 *pphead 都指向第一个节点。


🥝指定位置之后插入

这个就比上面那个简单多了,因为现在pos节点已知,那就少了遍历的过程。(pos后面的节点可以通过 next 找到,但是pos之前的节点只能遍历得到)
不过这次需要注意的插入的次序,比如现在节点node和pos已知,插入时,应该先让node的next指向第三个节点,然后再让 pos 的 next 指向 node,如果反过来的话,那第三个节点可就找不到了(即pos->next)
在这里插入图片描述
这个函数的实现很简单的,你自己尝试一下。


🥝删除指定节点

如图,要删除pos这个节点
在这里插入图片描述
首先我们得找到pos前面的节点 prev,然后让prev->next = pos->next,完事之后这个pos就没啥“利用价值”了,把它 free 掉。
不过前面做了这么多个接口之后,你应该也会考虑到一些特殊情况,比如要删除的pos就是第一个节点。
在这里插入图片描述

这种情况下我们就先弄一个pcur 保存第一个节点的地址,然后*pphead = (*pphead)->next,再把pcur指向的空间free掉并置空。
这里有一个要注意的点:解引用操作符优先度比“->”低,所以要用括号给括起来,不然会报错。

void SLErase(SLNode** pphead, SLNode* pos) {assert(*pphead);assert(pphead);assert(pos);if (*pphead == pos) {*pphead = (*pphead)->next;free(pos);pos = NULL;return;}SLNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;
}

🍌销毁

使用完之后,就要把链表给销毁了。

void SLDestroy(SLNode** pphead) {assert(pphead);SLNode* pcur = *pphead;while (*pphead) {pcur = *pphead;*pphead = (*pphead)->next;free(pcur);pcur = NULL;}
}

🍉源码

🍌头文件:声明部分

#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>typedef int SLTypeDate;
typedef struct SListNode {SLTypeDate data;struct SListNode* next;
}SLNode;void SLPrint(SLNode* phead);void SLPushBack(SLNode** pphead, SLTypeDate x);void SLPushFront(SLNode** pphead, SLTypeDate x);void SLPopFront(SLNode** pphead);void SLPopBack(SLNode** pphead);void SLInsert(SLNode** pphead, SLNode* pos, SLTypeDate x);SLNode* SLFindNode(SLNode** pphead, SLTypeDate x);void SLInsertAfter(SLNode* pos, SLTypeDate x);void SLErase(SLNode** pphead, SLNode* pos);void SLDestroy(SLNode** pphead);

🍌源文件:功能实现部分

#include"SList.h"void SLPrint(SLNode* phead) {SLNode* pcur = phead;while (pcur) {printf("%d ->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}SLNode* SLBuyNode(SLTypeDate x) {  //申请一个节点,并将想要存储的数据存进去SLNode* node = (SLNode*)malloc(sizeof(SLNode));node->data = x;node->next = NULL;return node;
}void SLPushBack(SLNode** pphead,SLTypeDate x) {assert(pphead);SLNode* node = SLBuyNode(x);if (*pphead == NULL) {  //phead为空说明此时链表为空,那就直接插入*pphead = node;  //让node是第一个节点的地址return;}//若不为空,则先找到链表的最后一个节点,再插入SLNode* pcur = *pphead;  //老样子,用临时变量pcur去走循环while (pcur->next) {pcur = pcur->next;}//此时pcur指向最后一个节点pcur->next = node;
}void SLPushFront(SLNode** pphead, SLTypeDate x) {assert(pphead);SLNode* node = SLBuyNode(x);node->next = *pphead;*pphead = node;
}void SLPopBack(SLNode** pphead) {assert(pphead);assert(*pphead);  //如果节点地址为空,那么说明节点为空(为空说明没有指向),而节点为空时显然不能删除SLNode* prev = NULL;SLNode* ptail = *pphead;if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;return;}while (ptail->next) {prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;
}void SLPopFront(SLNode** pphead) {assert(pphead);assert(*pphead);SLNode* pcur = *pphead;*pphead = (*pphead)->next;free(pcur);//pcur = (*pphead)->next;//free(*pphead);//*pphead = pcur;pcur = NULL;
}SLNode* SLFindNode(SLNode** pphead, SLTypeDate x) {assert(pphead);SLNode* pcur = *pphead;while (pcur) {if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;  //找不到就返回空指针
}//在指定位置前插入
void SLInsert(SLNode** pphead, SLNode* pos, SLTypeDate x) {assert(pphead);assert(*pphead);assert(pos);SLNode* node = SLFindNode(*pphead,pos);SLNode* prev = *pphead;if (*pphead = pos) {  //只有一个节点或插入位置位于第一个节点之前的情况node->next = *pphead;*pphead = node;return;}//多个节点的情况while (prev->next != pos) {prev = prev->next;}node->next = pos;prev->next = node;
}void SLInsertAfter(SLNode* pos, SLTypeDate x) {assert(pos);SLNode* node = SLBuyNode(x);node->next = pos->next;pos->next = node;
}void SLErase(SLNode** pphead, SLNode* pos) {assert(*pphead);assert(pphead);assert(pos);if (*pphead == pos) {*pphead = (*pphead)->next;free(pos);pos = NULL;return;}SLNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}void SLDestroy(SLNode** pphead) {assert(pphead);SLNode* pcur = *pphead;while (*pphead) {pcur = *pphead;*pphead = (*pphead)->next;free(pcur);pcur = NULL;}
}
http://www.yayakq.cn/news/892384/

相关文章:

  • 网站数据库如何导入数据库文件公司做网站都需要什么材料
  • 什么语言做网站最好安阳网站自然优化
  • 外贸网站建设和seo外贸网络营销推广
  • 荣县规划和建设局网站河南省建设科技网站
  • 网站优化推广 site软件开发设计流程图
  • 搜索引擎网站推广可以自己做吗有什么做数学题的网站
  • 信息手机网站模板下载软件企业网站建设推广公司
  • 万网备案网站名称佛山搜索引擎推广服务好
  • 推广型网站开发软件在线建设网站 源代码
  • 绍兴网站建设哪家专业竞价账户托管哪家好
  • 怎么做公司网站文案google怎么推广
  • 网站访客qq系统天津市建设行业联合会网站
  • 做企业内刊有哪些网站推荐对网站开发实训的建议
  • 网站诊断seo当前数据是指wordpress定时发布功能
  • 长沙网站建设推荐门户网站开发投标文件.doc
  • p2p商城网站建设方案网站建设需要准备什么
  • 龙岩网站建设方案仿同程网 连锁酒店 网站模板
  • 网站定制开发公司推荐做交通锁具网站碟刹锁
  • 无锡高端网站设计移动商城网站建设
  • 外网有趣的网站建站之星和凡科建站哪个系统好
  • 企信宝企业信息查询平台西安seo服务商
  • 网站开发设置用户恒美广告公司
  • 简单网站系统企业网站建设数据现状分析
  • 网站开发英语词汇加工平台调平装置
  • 成都高端网站建设触屏网站模板
  • 个人电脑建立网站一般通过头发就能察觉到
  • 响应式网站切图企业网站可以自己做吗
  • seo企业网站源码怎么建设菠菜网站
  • 网站系统分类南昌网站建设志博
  • 网站建设化妆品的目录做网站的一个专题