德州企业做网站多少钱/交换链接网站
只恨当时学的时候没有读到这本书,,,,,,
12.1 链表
有些读者可能还不熟悉链表,这里对它作一简单介绍。链表(linked list)就一些包含数据的独立数据结构(通常称为节点)的集合。链表中的每个节点通过链或指针连接在一起。程序通过指针访问链表中的节点。通常节点是动态分配的,但有时也能看到由节点数组构建的链表。即使在这种情况下,程序也是通过指针来遍历链表的。
12.2 单链表
在单链表中,每个节点包含一个指向链表下一节点的指针。链表最后一个节点的指针字段的值为NULL,提示链表后面不再有其他节点。在找到链表的第1个节点后,指针就可以带你访问剩余的所有节点。为了记住链表的起始位置,可以使用一个根指针(root pointer)。根指针指向链表的第1个节点。注意,根指针只是一个指针,它不包含任何数据。
下面是一张单链表的图。
本例中的节点是用下面的声明创建的结构。
typedef struct NODE {struct NODE *link;int value; } Node;
存储于每个节点的数据是一个整型值。这个链表包含3个节点。如果从根指针开始,随着指针到达第1个节点,可以访问存储于那个节点的数据。随着第1个节点的指针可以到达第2个节点,可以访问存储在那里的数据。最后,第2个节点的指针带你来到最后一个节点。零值提示它是一个NULL指针,在这里它表示链表中不再有更多的节点。
在上面的图中,这些节点相邻在一起,这是为了显示链表所提供的逻辑顺序。事实上,链表中的节点可能分布于内存中的各个地方。对于一个处理链表的程序而言,各节点在物理上是否相邻并没有什么区别,因为程序始终用链(指针)从一个节点移动到另一个节点。
单链表可以通过链从开始位置遍历链表直到结束位置,但链表无法从相反的方向进行遍历。换句话说,当程序到达链表的最后一个节点时,如果想回到其他任何节点,就只能从根指针从头开始。当然,程序在移动到下一个节点前可以保存一个指向当前位置的指针,甚至可以保存指向前面几个位置的指针。但是,链表是动态分配的,可能增长到几百或几千个节点,所以要保存所有指向前面位置的节点的指针是不可行的。
在这个特定的链表中,节点根据数据的值按升序链接在一起。对于有些应用程序而言,这种顺序非常重要,比如根据一天的时间安排约会。对于那些不要求排序的应用程序,当然也可以创建无序的链表。
12.2.1 在单链表中插入
怎么才能把一个新节点插入到一个有序的单链表中呢?
假定我们有一个新值,比如12,想把它插入到前面那个链表中。从概念上说,这个任务非常简单:从链表的起始位置开始,跟随指针直到找到第一个值大于12的节点,然后把这个新值插入到那个节点之前的位置。
实际的算法则比较有趣。我们按顺序访问链表,当到达内容为15的节点(第一个值大于12的节点)时就停下来。我们知道这个新值应该添加到这个节点之前,但前一个节点的指针字段必须进行修改以实现这个插入。由于我们已经越过了这个节点,所以无法返回去。解决这个问题的方法就是始终保存一个指向链表当前节点之前的那个节点的指针。
我们现在将开发一个函数,把一个节点插入到一个有序的单链表中。程序12.1是我们的第一次尝试。
/* ** 插入到一个有序的单链表。函数的参数是一个指向链表第一个节点的指针以及需要插入的值。 */ #include <stdlib.h> #include <stdio.h> #include "sll_node.h" #define FALSE 0 #define TRUE 1 int sll_insert( Node *current, int new_value ) { Node *previous; Node *new; /* ** 寻找正确的插入位置,方法是按顺序访问链表,直到到达其值大于或等于 ** 新插入值的节点。 */ while( current->value < new_value ){previous = current;current = current->link; } /* ** 为新节点分配内存,并把新值存储到新节点中,如果内存分配失败, ** 函数返回FALSE。 */ new = (Node *)malloc( sizeof( Node ) ); if( new == NULL )return FALSE; new->value = new_value; /* ** 把新节点插入到链表中,并返回TRUE。 */ new->link = current; previous->link = new; return TRUE; }
程序12.1 插入到一个有序的单链表:第1次尝试insert1.c
用下面这种方法调用这个函数:
result = sll_insert(root,12);
让我们仔细跟踪代码的执行过程,看看它是否把新值12正确地插入到链表中。首先,传递给函数的参数是root变量的值,它是指向链表第一个节点的指针。当函数刚开始执行时,链表的状态如下。
上图并没有显示root变量,因为函数不能访问它。它的值的一份副本作为形参current传递给函数,但函数不能访问root。现在current->value是5,它小于12,所以循环体再次执行。当回到循环的顶部时,current和previous指针都向前移动了一个节点。
现在,current->value的值为10,因此循环体还将继续执行,结果如下。
现在,current->value的值大于12,所以退出循环。
此时,重要的是previous指针,因为它指向我们必须加以修改以插入新值的那个节点。但首先,我们必须得到一个新节点,用于容纳新值。下面这张图显示了新值被复制到新节点之后链表的状态。
把这个新节点链接到链表中需要两个步骤。第一个步骤是执行下述语句:
new->link = current;
使新节点指向将成为链表下一个节点的节点,也就是我们所找到的第一个值大于12的那个节点。在这个步骤之后,链表的内容如下所示。
第二个步骤是让previous指针所指向的节点(也就是最后一个值小于12的那个节点)指向这个新节点。下面这条语句用于执行这项任务:
previous->link = new;
这个步骤之后,链表的状态如下。
然后函数返回,链表的最终样子如下所示。
从根指针开始,随各个节点的link字段逐个访问链表,可以发现这个新节点已被正确地插入到链表中。
问题
不幸的是,这个插入函数是不正确的。试试把20这个值插入到链表中,你就会发现一个问题:while循环越过链表的尾部,并对一个NULL指针执行间接访问操作。为了解决这个问题,必须对current的值进行测试,在执行表达式current->value之前确保它不是一个NULL指针:
while( current != NULL & current->value < value ){
下一个问题更加棘手,试试把3这个值插入到链表中,看看会发生什么?
为了在链表的起始位置插入一个节点,函数必须修改根指针。但是,函数不能访问变量root。修正这个问题最容易的方法是把root声明为全局变量,这样插入函数就能修改它。不幸的是,这是最坏的一种解决方法。因为这样一来,函数只对这个链表起作用。
稍好的解决方法是把一个指向root的指针作为参数传递给函数。然后,使用间接访问,函数不仅可以获得root(指向链表第一个节点的指针,也就是根指针)的值,也可以向它存储一个新的指针值。这个参数的类型是什么呢?root是一个指向Node的指针,所以参数的类型应该是Node **,也就是一个指向Node的指针的指针。程序12.2的函数包含了这些修改。现在,我们必须以下面这种方式调用这个函数:
result = sll_insert( &root, 12 ); /* ** 插入到一个有序单链表。函数的参数是一个指向链表根指针的指针,以及一个需要插入的新值。 */ #include <stdlib.h> #include <stdio.h> #include "sll_node.h" #define FALSE 0 #define TRUE 1 int sll_insert( Node **rootp, int new_value ) {Node *current;Node *previous;Node *new;/*** 得到指向第1个节点的指针。*/current = *rootp;previous = NULL;/*** 寻找正确的插入位置,方法是按序访问链表,直到到达一个其值大于或等于** 新值的节点。*/while( current != NULL && current->value < new_value ){previous = current;current = current->link;}/*** 为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,** 函数返回FALSE。*/new = (Node *)malloc( sizeof( Node ) );if( new == NULL )return FALSE;new->value = new_value;/*** 把新节点插入到链表中,并返回TRUE。*/new->link = current;if( previous == NULL )*rootp = new;elseprevious->link = new;return TRUE; }
程序12.2 插入到一个有序单链表:第2次尝试insert2.c
这第2个版本包含了另外一些语句。
previous = NULL;
我们需要这条语句,这样以后就可以检查新值是否应为链表的第一个节点。
current = *rootp;
这条语句对根指针参数执行间接访问操作,得到的结果是root的值,也就是指向链表第一个节点的指针。
If (previous == NULL)*rootp = new; elseprevious->link = new;
上述语句被添加到函数的最后,用于检查新值是否应该被添加到链表的起始位置。如果是,则使用间接访问修改根指针,使它指向新节点。
这个函数可以正确完成任务,而且在许多语言中,这是你能够获得的最佳方案。但是,我们还可以做得更好一些,因为C允许我们获得现存对象的地址(即指向该对象的指针)。
优化插入函数
看上去,把一个节点插入到链表的起始位置必须作为一种特殊情况进行处理。毕竟,我们此时插入新节点需要修改的指针是根指针。对于任何其他节点,对指针进行修改时实际修改的是前一个节点的link字段。这两个看上去不同的操作实际上是一样的。
消除特殊情况的关键在于:我们必须认识到,链表中的每个节点都有一个指向它的指针。对于第一个节点,这个指针是根指针;对于其他节点,这个指针是前一个节点的link字段。重点在于每个节点都有一个指针指向它。至于该指针是不是位于一个节点的内部则无关紧要。
让我们再次观察这个链表,弄清这个概念。下面是第1个节点和指向它的指针。
如果新值插入到第1个节点之前,这个指针就必须进行修改。
下面是第2个节点和指向它的指针。
如果新值需要插入到第2个节点之前,那么这个指针必须进行修改。注意,我们只考虑指向这个节点的指针,至于哪个节点包含这个指针则无关紧要。对于链表中的其他节点,都可以应用这个模式。
现在让我们看一下修改后的函数(当它开始执行时)。下面显示了第一条赋值语句之后各个变量的情况。
我们拥有一个指向当前节点的指针,以及一个“指向当前节点的link字段的”指针。除此之外,就不需要别的了!如果当前节点的值大于新值,那么rootp指针就会告诉我们哪个link字段必须进行修改,以便让新节点链接到链表中。如果在链表其他位置的插入也可以用同样的方式进行表示,就不存在前面提到的特殊情况了。关键在于我们前面看到的指针/节点关系。
当移动到下一个节点时,我们保存一个“指向下一个节点的link字段”的指针,而不是保存一个指向前一个节点的指针。我们很容易画出一张描述这种情况的图。
注意,这里rootp并不指向节点本身,而是指向节点内部的link字段。这是简化插入函数的关键所在,但我们必须能够取得当前节点的link字段的地址。在C中,这种操作是非常容易的。表达式¤t->link就可以达到这个目的。程序12.3是插入函数的最终版本。rootp参数现在称为linkp,因为它现在指向的是不同的link字段,而不仅仅是根指针。我们不再需要previous指针,因为link指针可以负责寻找需要修改的link字段。前面那个函数最后部分用于处理特殊情况的代码也不见了,因为我们始终拥有一个指向需要修改的link字段的指针——我们用一种和修改节点的link字段完全一样的方式修改root变量。最后,我们在函数的指针变量中增加了register声明,用于提高结果代码的效率。
我们在最终版本中的while循环中增加了一个窍门,它嵌入了对current的赋值。下面是一个功能相同但长度稍长的循环。
/* ** Look for the right place. */ current = *linkp; while( current !=NULL && current->value < value ){linkp = ¤t->link; current = * linkp; }
一开始,current被设置为指向链表的第一个节点。while循环测试我们是否到达了链表的尾部。如果是,它接着检查我们是否到达了正确的插入位置。如果不是,循环体继续执行,并把linkp设置为指向当前节点的link字段,并使current指向下一个节点。
循环的最后一条语句和循环之前的那条语句相同,这就促使我们对它进行“简化”,方法是把current的赋值嵌入到while表达式中。其结果是一个稍为复杂但更加紧凑的循环,因为我们消除了current的冗余赋值。
/* ** 插入到一个有序单链表。函数的参数是一个指向链表第一个节点的指针,以及一个需要插入的新值。 */ #include <stdlib.h> #include <stdio.h> #include "sll_node.h" #define FALSE 0 #define TRUE 1 int sll_insert( register Node **linkp, int new_value ) {register Node *current;register Node *new;/*** 寻找正确的插入位置,方法是按序访问链表,直到到达一个其值大于或等于** 新值的节点。*/while( ( current = *linkp ) != NULL &¤t->value < new_value )linkp = ¤t->link;/*** 为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,** 函数返回FALSE。*/new = (Node *)malloc( sizeof( Node ) );if( new == NULL )return FALSE;new->value = new_value;/*** 在链表中插入新节点,并返回TRUE。*/new->link = current;*linkp = new;return TRUE; }
程序12.3 插入到一个有序的单链表:最终版本insert3.c
消除特殊情况使这个函数更为简单。这个改进之所以可行,是由于两个因素。第一个因素是我们正确解释问题的能力。除非可以在看上去不同的操作中总结出共性,不然只能编写额外的代码来处理特殊情况。通常,这种知识只有在学习了一阵数据结构并对其有进一步的理解之后才能获得。第二个因素是C语言提供了正确的工具,可以帮助我们归纳问题的共性。
这个改进的函数依赖于C能够取得现存对象的地址这一能力。和许多C语言特性一样,这个能力既威力巨大,又暗伏凶险。例如,在Modula和Pascal中并不存在“取地址”操作符,所以指针唯一的来源就是动态内存分配。我们没有办法获得一个指向普通变量的指针或甚至是指向一个动态分配的结构的字段的指针。对指针不允许进行算术运算,也没有办法把一种类型的指针通过强制类型转换为另一种类型的指针。这些限制的优点在于它们可以防止诸如“越界引用数组元素”或“产生一种类型的指针但实际上指向另一种类型的对象”这类错误。
C的指针限制要少得多,这也是我们能改进插入函数的原因所在。另一方面,C程序员在使用指针时必须加倍小心,以避免产生错误。Pascal语言的指针哲学有点类似下面这样的说法:“使用锤子可能会伤着你自己,所以我们不给你锤子。”C语言的指针哲学则是:“给你锤子,实际上你可以使用好几种锤子。祝你好运!”有了这个能力之后,C程序员较之Pascal程序员更容易陷入麻烦,但优秀的C程序员可以比Pascal和Modula程序员产生体积更小、效率更高、可维护性更佳的代码。这也是C语言在业界为何如此流行以及经验丰富的C程序员为何如此受青睐的原因之一。
12.2.2 其他链表操作
为了让单链表更加有用,我们需要增加更多的操作,如查找和删除。但是,用于这些操作的算法非常直截了当,很容易用插入函数所说明的技巧来实现。因此,这里把这些函数留作练习。
相关文章:

肯尼斯·里科《C和指针》第12章 使用结构和指针(1)链表
只恨当时学的时候没有读到这本书,,,,,, 12.1 链表 有些读者可能还不熟悉链表,这里对它作一简单介绍。链表(linked list)就一些包含数据的独立数据结构(通常称为节点)的集…...

Xray 工具笔记
Xray 官方文档 扫描单个url(非爬虫) 并输出文件(不同文件类型) .\xray.exe webscan --url 10.0.0.6:8080 --text-output result.txt --json-output result.json --html-output report.html默认启动所以内置插件 ,指定…...

Linux环境下配置HTTP代理服务器教程
大家好,我是你们可爱的Linux小助手!今天,我将带你们一起探索如何在Linux环境下配置一个HTTP代理服务器。请注意,这不是一次火箭科学的实验,而是一次简单而有趣的冒险。 首先,我们需要明确什么是HTTP代理服…...

JavaEE作业-实验三
目录 1 实验内容 2 实验要求 3 思路 4 核心代码 5 实验结果 1 实验内容 简单的线上图书交易系统的web层 2 实验要求 ①采用SpringMVC框架,采用REST风格 ②要求具有如下功能:商品分类、订单、购物车、库存 ③独立完成,编写实验报告 …...

K8S容器挂了后重启状态正常,但应用无法访问排查处理
K8S容器挂了后重启状态正常,但应用无法访问排查处理 背景: 应用迁移K8S后因POD OOM挂了后重启,集群上POD状态正常,但应用无法访问。 排查: 查看应用日志,是启动时调用特权账号管理系统超时,…...

问题:老年人心理健康维护与促进的原则为________、________、发展原则。 #媒体#知识分享
问题:老年人心理健康维护与促进的原则为________、________、发展原则。 参考答案如图所示...

【超高效!保护隐私的新方法】针对图像到图像(l2l)生成模型遗忘学习:超高效且不需要重新训练就能从生成模型中移除特定数据
针对图像到图像生成模型遗忘学习:超高效且不需要重新训练就能从生成模型中移除特定数据 提出背景如何在不重训练模型的情况下从I2I生成模型中移除特定数据? 超高效的机器遗忘方法子问题1: 如何在图像到图像(I2I)生成模型中进行高效…...

Transformer的PyTorch实现之若干问题探讨(二)
在《Transformer的PyTorch实现之若干问题探讨(一)》中探讨了Transformer的训练整体流程,本文进一步探讨Transformer训练过程中teacher forcing的实现原理。 1.Transformer中decoder的流程 在论文《Attention is all you need》中࿰…...

解释Python中的GIL(全局解释器锁)及其影响。描述Python中的垃圾回收机制。Python中的类变量和实例变量有什么区别
解释Python中的GIL(全局解释器锁)及其影响 Python中的GIL(全局解释器锁)是CPython解释器中的一个机制,用于同步线程的执行。GIL确保任何时候只有一个线程在执行Python字节码。这意味着,即使在多核或多处理器…...

Appium使用初体验之参数配置,简单能够运行起来
一、服务器配置 Appium Server配置与Appium Server GUI(可视化客户端)中的配置对应,尤其是二者如果不在同一台机器上,那么就需要配置Appium Server GUI所在机器的IP(Appium Server GUI的HOST也需要配置本机IP…...

Java:JDK8新特性(Stream流)、File类、递归 --黑马笔记
一、JDK8新特性(Stream流) 接下来我们学习一个全新的知识,叫做Stream流(也叫Stream API)。它是从JDK8以后才有的一个新特性,是专业用于对集合或者数组进行便捷操作的。有多方便呢?我们用一个案…...

【Unity ShaderGraph】| 物体靠近时局部溶解,根据坐标控制溶解的位置【文末送书】
前言 【Unity ShaderGraph】| 物体靠近时局部溶解,根据坐标控制溶解的位置一、效果展示二、根据坐标控制溶解的位置,物体靠近局部溶解三、应用实例👑评论区抽奖送书 前言 本文将使用ShaderGraph制作一个根据坐标控制溶解的位置,物…...

测试OpenSIPS3.4.3的lua模块
这几天测试OpenSIPS3.4.3的lua模块,记录如下: 有bug,但能用 但现实世界就是这样,总是不完美的,发现之后马上提了issue 下面这段代码运行报错: function func1(msg) xlog("ERR","…...

【机器学习】数据清洗之处理缺失点
🎈个人主页:甜美的江 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步…...

Linux 命令行的世界 :2.文件系统中跳转
我们需要学习的第一件事(除了打字之外)是如何在 Linux 文件系统中跳转。在这一章节中,我们将介绍以下命令:pwd 打印出当前工作目录名 cd 更改目录 ls 列出目录内容 Linux以分层目录结构来组织所有文件。这就意味着所有文件…...

R语言:箱线图绘制(添加平均值趋势线)
箱线图绘制 1. 写在前面2.箱线图绘制2.1 相关R包导入2.2 数据导入及格式转换2.3 ggplot绘图 1. 写在前面 今天有时间把之前使用过的一些代码和大家分享,其中箱线图绘制我认为是非常有用的一个部分。之前我是比较喜欢使用origin进行绘图,但是绘制的图不太…...

Open3D 模型切片
目录 一、算法原理1、算法过程2、主要函数二、代码实现三、结果展示1、原始数据2、切片结果本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理...

KtConnect 本地连接连接K8S工具
KT Connect简介 Kt Connect (Kubernetes Developer Tool)是一个阿里开源、轻量级的面向 Kubernetes 用户的开发测试环境治理辅助工具。其核心是通过建立本地到集群以及集群到本地的双向通道。 1.阿里开源,轻量级, 2. 安装快捷简单…...

【Java万花筒】数据的安全钥匙:Java的加密与保护方法
编码的盾牌:Java开发人员的安全性武器库 前言 在当今数字化时代,保护用户数据和信息的安全已成为开发人员的首要任务。无论是在Web应用程序开发还是安全测试中,加密和安全性都是至关重要的。本文将介绍六个Java库和工具,它们为开…...

【Java多线程案例】实现阻塞队列
1. 阻塞队列简介 1.1 阻塞队列概念 阻塞队列:是一种特殊的队列,具有队列"先进先出"的特性,同时相较于普通队列,阻塞队列是线程安全的,并且带有阻塞功能,表现形式如下: 当队列满时&…...

【制作100个unity游戏之24】unity制作一个3D动物AI生态系统游戏3(附项目源码)
最终效果 文章目录 最终效果系列目录前言随着地面法线旋转在地形上随机生成动物不同部位颜色不同最终效果源码完结系列目录 前言 欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第24篇中,我们将探索如何用unity制作一…...

home work day5
第四章 堆与拷贝构造函数 一 、程序阅读题 1、给出下面程序输出结果。 #include <iostream.h> class example {int a; public: example(int b5){ab;} void print(){aa1;cout <<a<<"";} void print()const {cout<<a<<endl;} …...

c#安全-nativeAOT
文章目录 前记AOT测试反序列化Emit 前记 JIT\AOT JIT编译器(Just-in-Time Complier),AOT编译器(Ahead-of-Time Complier)。 AOT测试 首先编译一段普通代码 using System; using System.Runtime.InteropServices; namespace co…...

【Java】案例:检测MySQL是否存在某数据库,没有则创建
1.代码 package hello; import java.sql.*;public class CeShi {//定义基本数据static final String JDBC_DRIVER "com.mysql.cj.jdbc.Driver";static final String DB_URL "jdbc:mysql://localhost/";static final String USER "your_username&q…...

内网渗透靶场02----Weblogic反序列化+域渗透
网络拓扑: 攻击机: Kali: 192.168.111.129 Win10: 192.168.111.128 靶场基本配置:web服务器双网卡机器: 192.168.111.80(模拟外网)10.10.10.80(模拟内网)域成员机器 WIN7PC192.168.…...

[嵌入式系统-9]:C语言程序调用汇编语言程序的三种方式
目录 1. 使用函数声明和函数调用: 2. 使用汇编内联(Inline Assembly): 3. 使用汇编代码文件和链接器: C语言程序可以调用汇编程序的方式有多种,下面列举了几种常见的方式: 1. 使用函数声明和…...

备战蓝桥杯---搜索(完结篇)
再看一道不完全是搜索的题: 解法1:贪心并查集: 把冲突事件从大到小排,判断是否两个在同一集合,在的话就返回,不在的话就合并。 下面是AC代码: #include<bits/stdc.h> using namespace …...

深入浅出:Golang的Crypto/SHA256库实战指南
深入浅出:Golang的Crypto/SHA256库实战指南 介绍crypto/sha256库概览主要功能应用场景库结构和接口实例 基础使用教程字符串哈希化文件哈希化处理大型数据 进阶使用方法增量哈希计算使用Salt增强安全性多线程哈希计算 实际案例分析案例一:安全用户认证系…...

Unity_ShaderGraph节点问题
Unity_ShaderGraph节点问题 Unity版本:Unity2023.1.19 为什么在Unity2023.1.19的Shader Graph中找不见PBR Master节点? 以下这个PBR Maste从何而来?...

Java集合 Collection接口
这里写目录标题 集合Collection接口创建一个性表增加元素删除元素修改元素判断元素遍历集合实例判断元素是否存在 集合 Java中的Collection接口是集合类的一个顶级接口,它定义了一些基本的操作,如添加、删除、查找等。Collection接口主要有以下几个常用…...