目录
在上一篇文章中,我们了解了顺序表的结构以及他的接口的实现。但同时我们也发现了他的一些缺陷
问题:
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
链表一共有八种类型,他们可由是单向还是双向,是循环还是非循环,是带头结点还是不带头结点进行排列组合出八种结构
虽然有很多种结构,但是只有两种最为常用
无头单向非循环链表和带头双向循环链表
这里我们先只需要了解无头单向非循环链表,其他链表后续了解
如下图所示,是链表的实际的物理结构与逻辑结构。物理结构就是实实在在数据在内存中的变化,逻辑结构就是为了方便理解,形象化出来的
typedef int SLTDateType; typedef struct SListNode { SLTDateType data; struct SListNode* next; }SListNode;如上代码所示,单链表有数据域和指针域两部分组成,指针是用于指向下一个结点的指针
//单链表的打印 void SListPrint(SListNode* plist); //单链表的尾插 void SListPushBack(SListNode** pplist, SLTDateType x); //单链表的头插 void SListPushFront(SListNode** pplist, SLTDateType x); //单链表的尾删 void SListPopBack(SListNode** pplist); //单链表的头删 void SListPopFront(SListNode** pplist); //单链表的查找 SListNode* SListFind(SListNode* plist,SLTDateType x); //单链表在pos位置之后插入 void SListInsertAfter(SListNode* pos, SLTDateType x); //单链表在pos位置之前插入 void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x); //单链表在pos之后删除 void SListEraseAfter(SListNode* pos); //单链表在pos之前删除 void SListErasePrev(SListNode** pplist, SListNode* pos); //单链表在pos位置删除 void SListErase(SListNode** pplist, SListNode* pos); //单链表的销毁 void SListDestroy(SListNode** pplist);如上代码所示,是我们的单链表需要实现的接口,对于链表和顺序表一样都是为了实现数据的管理,区别就是前者是离散的,后者是连续的。我们的目的还是增删查改
1.单链表的打印
//单链表的打印 void SListPrint(SListNode* plist) { SListNode* cur = plist; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); }我们先来完成单链表的打印,对于单链表的打印,还是比较简单的,只需要先将头结点的指针给保存下来,然后依次去遍历单链表即可
2.单链表的尾插以及获取结点函数
//获取一个结点 SListNode* BuySListNode(SLTDateType x) { SListNode* tmp = (SListNode*)malloc(sizeof(SListNode)); if (tmp == NULL) { perror("malloc fail"); return; } tmp->next = NULL; tmp->data = x; return tmp; } //单链表的尾插 void SListPushBack(SListNode** pplist, SLTDateType x) { assert(pplist); SListNode* newnode = BuySListNode(x); if (*pplist == NULL) { *pplist = newnode; return; } SListNode* tail = (*pplist); while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; }对于单链表的尾插,我们要特别注意了。pplist是单链表头结点的地址,所以一定不为空
首先是获取结点,我们为了方便,先直接将其封装为一个函数。
有了结点了,那么我们还要思考如何尾插,那么我们先完成一般情况,假设已经有了一个很长的单链表了,我们还想要继续尾插一个值,那么只需要先找到原来的尾结点,然后将尾结点和新结点进行链接即可
当然还是存在一些特殊情况的,比如说原来的单链表压根就没有尾结点,也就是说链表是空的,那么上面的一般方法肯定行不通,这里其实就需要特殊处理一下,直接将新节点和链表头链接起来即可
3.单链表的头插
//单链表的头插 void SListPushFront(SListNode** pplist, SLTDateType x) { assert(pplist); SListNode* newnode = BuySListNode(x); SListNode* first = *pplist; *pplist = newnode; newnode->next = first; }对于单链表的头插,就比较简单了,我们直接创建一个新结点,然后记录下原来的第一个结点的地址,然后让链表头与新节点链接起来,然后新节点与原来的第一个结点链接起来,这里我们会发下,其实是不需要处理空链表的情况的,这里体现了单链表适合头插
4.单链表的尾删
//单链表的尾删 void SListPopBack(SListNode** pplist) { assert(pplist); assert(*pplist); if ((*pplist)->next == NULL) { free(*pplist); *pplist = NULL; } else { SListNode* tail = *pplist; while (tail->next->next != NULL) { tail = tail->next; } free(tail->next); tail->next = NULL; } }对于单链表的尾删,我们要想清楚了,首先是一般情况,当链表很长的时候,我们想要删除最后一个结点,那么得先找到前一个结点,然后释放最后一个结点,最后让前一个结点指向空。
我们还需要注意链表为空的状态,这个肯定是不可以删除的,所以我们直接断言掉。
然后是链表为一个结点的情况,如果链表只有一个结点,那么我们会发现,压根找不到前一个结点,所以我们也特殊处理,我们直接释放掉第一个结点,然后置空即可。
5.单链表的头删
//单链表的头删 void SListPopFront(SListNode** pplist) { assert(pplist); assert(*pplist); SListNode* second = (*pplist)->next; free(*pplist); *pplist = second; }对于单链表的头删,我们同样断言掉链表为空的状态
然后我们需要做的就是记录第二个结点,然后释放原来的第一个结点,最后连接链表头和第二个结点。这样我们就实现了我们的目的。值得注意的是,我们发现链表的头删也是比较有优势的。
6.单链表的查找
//单链表的查找 SListNode* SListFind(SListNode* plist,SLTDateType x) { SListNode* cur = plist; while (cur != NULL) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }对于单链表的查找,这个也很简单,他和单链表的打印思路是一样的,不同的是只需要返回结点的地址即可。
7.单链表在pos位置之后的插入
//单链表在pos位置之后插入 void SListInsertAfter(SListNode* pos, SLTDateType x) { assert(pos); SListNode* newnode = BuySListNode(x); SListNode* next = pos->next; pos->next = newnode; newnode->next = next; }单链表在pos位置之后的插入也是比较简单的,我们只需要先申请一个结点,然后记录pos位置的下一个结点,然后连接就可以了。值得注意的是,pos的位置不可能为空。因为他不是一个有效的结点地址
8.单链表在pos位置之前的插入
//单链表在pos位置之前插入 void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x) { assert(pplist); assert(pos); assert(*pplist); SListNode* newnode = BuySListNode(x); if (*pplist == pos) { *pplist = newnode; newnode->next = pos; } else { SListNode* prev = *pplist; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } }对于在pos位置之前的插入,确实是比较繁琐了。我们要思考,首先pos和pplist不可能为空,然后这个链表也是不可能为空链表的,至少也要有一个值。否则如果存在pos这个结点呢?
然后我们在来考虑一般情况,我们假设链表很长,在中间位置pos之前插入一个结点,那么毫无疑问的是,我们需要先申请一个结点,然后在通过遍历的方式要找到pos之前的那个结点。
有了这两个结点,那么我们就可以进行连接了。
然后是特殊情况,假如这个链表只有一个结点呢,这个结点正好就是pos,我们发现pos就没有前一个结点,其实这个就等效于头插。我们采用头插的方式即可
9.单链表在pos之后删除
//单链表在pos之后删除 void SListEraseAfter(SListNode* pos) { assert(pos); assert(pos->next); SListNode* next = pos->next; pos->next = next->next; free(next); next = NULL; }单链表在pos之后删除的话,首先pos和pos的下一个结点不会为空,否则题目就矛盾了。所以我们得断言,然后我们直接记录pos 的下一个结点,然后连接pos和pos之后的结点。最后释放掉pos的下一个结点即可。
10.单链表在pos之前删除
//单链表在pos之前删除 void SListErasePrev(SListNode** pplist, SListNode* pos) { assert(pos); assert(pplist); assert(pos != *pplist); assert(*pplist); if ((*pplist)->next == pos) { SListNode* del = *pplist; *pplist = pos; free(del); del = NULL; } else { SListNode* prev = *pplist; while (prev->next->next != pos) { prev = prev->next; } SListNode* del = prev->next; prev->next = pos; free(del); del = NULL; } }对于单链表在pos之前删除确实就比较复杂了,首先pos,pplist,*pplist肯定不可能为空,然后pos也绝不可以是头节点,所以pos!=*pplist
我们现在来思考,假设一般情况,链表很长,在中间位置是pos,删除pos的前一个结点,那么我们就需要找到pos 的前一个的前一个结点。然后记录pos的前一个结点。释放pos 的前一个结点,然后进行连接即可
对于特殊情况,也就是,pos在第二个结点上,这样我们无法找到pos的前一个的前一个结点,但是这个就是头删,我们直接采用类似的思路即可
11.单链表在pos位置的删除
//单链表在pos位置删除 void SListErase(SListNode** pplist, SListNode* pos) { assert(pplist); assert(pos); assert(*pplist); if (*pplist == pos) { free(pos); *pplist = pos = NULL; } else { SListNode* prev = *pplist; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } }这个与上一个接口是基本一致的思路。不同的是,pos可以在头结点了,如果是头节点就是头删。
如果不是,就是先找到前一个结点,然后进行删除连接即可
12.单链表的销毁
//单链表的销毁 void SListDestroy(SListNode** pplist) { SListNode* cur = *pplist; while (cur != NULL) { SListNode* next = cur->next; free(cur); cur = next; } }对于单链表的销毁,这个也很简单,就直接遍历销毁即可,与打印和查找的思路是一致的
SList.h文件
#pragma once #include<stdio.h> #include<stdlib.h> #include<malloc.h> #include<assert.h> typedef int SLTDateType; typedef struct SListNode { SLTDateType data; struct SListNode* next; }SListNode; //单链表的打印 void SListPrint(SListNode* plist); //单链表的尾插 void SListPushBack(SListNode** pplist, SLTDateType x); //单链表的头插 void SListPushFront(SListNode** pplist, SLTDateType x); //单链表的尾删 void SListPopBack(SListNode** pplist); //单链表的头删 void SListPopFront(SListNode** pplist); //单链表的查找 SListNode* SListFind(SListNode* plist,SLTDateType x); //单链表在pos位置之后插入 void SListInsertAfter(SListNode* pos, SLTDateType x); //单链表在pos位置之前插入 void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x); //单链表在pos之后删除 void SListEraseAfter(SListNode* pos); //单链表在pos之前删除 void SListErasePrev(SListNode** pplist, SListNode* pos); //单链表在pos位置删除 void SListErase(SListNode** pplist, SListNode* pos); //单链表的销毁 void SListDestroy(SListNode** pplist);
SList.c文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"SList.h" //单链表的打印 void SListPrint(SListNode* plist) { SListNode* cur = plist; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); } //获取一个结点 SListNode* BuySListNode(SLTDateType x) { SListNode* tmp = (SListNode*)malloc(sizeof(SListNode)); if (tmp == NULL) { perror("malloc fail"); return; } tmp->next = NULL; tmp->data = x; return tmp; } //单链表的尾插 void SListPushBack(SListNode** pplist, SLTDateType x) { assert(pplist); SListNode* newnode = BuySListNode(x); if (*pplist == NULL) { *pplist = newnode; return; } SListNode* tail = (*pplist); while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } //单链表的头插 void SListPushFront(SListNode** pplist, SLTDateType x) { assert(pplist); SListNode* newnode = BuySListNode(x); SListNode* first = *pplist; *pplist = newnode; newnode->next = first; } //单链表的尾删 void SListPopBack(SListNode** pplist) { assert(pplist); assert(*pplist); if ((*pplist)->next == NULL) { free(*pplist); *pplist = NULL; } else { SListNode* tail = *pplist; while (tail->next->next != NULL) { tail = tail->next; } free(tail->next); tail->next = NULL; } } //单链表的头删 void SListPopFront(SListNode** pplist) { assert(pplist); assert(*pplist); SListNode* second = (*pplist)->next; free(*pplist); *pplist = second; } //单链表的查找 SListNode* SListFind(SListNode* plist,SLTDateType x) { SListNode* cur = plist; while (cur != NULL) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } //单链表在pos位置之后插入 void SListInsertAfter(SListNode* pos, SLTDateType x) { assert(pos); SListNode* newnode = BuySListNode(x); SListNode* next = pos->next; pos->next = newnode; newnode->next = next; } //单链表在pos位置之前插入 void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x) { assert(pplist); assert(pos); assert(*pplist); SListNode* newnode = BuySListNode(x); if (*pplist == pos) { *pplist = newnode; newnode->next = pos; } else { SListNode* prev = *pplist; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } } //单链表在pos之后删除 void SListEraseAfter(SListNode* pos) { assert(pos); assert(pos->next); SListNode* next = pos->next; pos->next = next->next; free(next); next = NULL; } //单链表在pos之前删除 void SListErasePrev(SListNode** pplist, SListNode* pos) { assert(pos); assert(pplist); assert(pos != *pplist); assert(*pplist); if ((*pplist)->next == pos) { SListNode* del = *pplist; *pplist = pos; free(del); del = NULL; } else { SListNode* prev = *pplist; while (prev->next->next != pos) { prev = prev->next; } SListNode* del = prev->next; prev->next = pos; free(del); del = NULL; } } //单链表在pos位置删除 void SListErase(SListNode** pplist, SListNode* pos) { assert(pplist); assert(pos); assert(*pplist); if (*pplist == pos) { free(pos); *pplist = pos = NULL; } else { SListNode* prev = *pplist; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } } //单链表的销毁 void SListDestroy(SListNode** pplist) { SListNode* cur = *pplist; while (cur != NULL) { SListNode* next = cur->next; free(cur); cur = next; } }
Test.c文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Slist.h" void TestSList1() { SListNode* phead = NULL; SListPushBack(&phead, 1); SListPushBack(&phead, 2); SListPushBack(&phead, 3); SListPushBack(&phead, 4); SListPushBack(&phead, 5); SListPrint(phead); SListPushFront(&phead, 6); SListPushFront(&phead, 7); SListPushFront(&phead, 8); SListPushFront(&phead, 9); SListPushFront(&phead, 10); SListPrint(phead); SListPopBack(&phead); SListPopBack(&phead); SListPopBack(&phead); SListPopBack(&phead); SListPrint(phead); SListPopBack(&phead); SListPrint(phead); } void TestSList2() { SListNode* phead = NULL; SListPushBack(&phead, 1); SListPushBack(&phead, 2); SListPushBack(&phead, 3); SListPushBack(&phead, 4); SListPushBack(&phead, 5); SListPrint(phead); SListPopFront(&phead); SListPopFront(&phead); SListPopFront(&phead); SListPopFront(&phead); SListPrint(phead); SListPopFront(&phead); SListPrint(phead); } void TestSList3() { SListNode* phead = NULL; SListPushBack(&phead, 1); SListPushBack(&phead, 2); SListPushBack(&phead, 3); SListPushBack(&phead, 4); SListPushBack(&phead, 5); SListPrint(phead); SListNode* pos = SListFind(phead, 3); pos->data = 100; SListPrint(phead); SListInsertAfter(pos, 200); SListInsertAfter(pos, 300); SListInsertAfter(pos, 400); SListInsertAfter(pos, 500); SListPrint(phead); SListNode* pos2 = SListFind(phead, 1); SListInsertPrev(&phead, pos2, 101); SListInsertPrev(&phead, pos2, 102); SListInsertPrev(&phead, pos2, 103); SListInsertPrev(&phead, pos2, 104); SListPrint(phead); SListEraseAfter(pos2); SListEraseAfter(pos2); SListEraseAfter(pos2); SListPrint(phead); } void TestSList4() { SListNode* phead = NULL; SListPushBack(&phead, 1); SListPushBack(&phead, 2); SListPushBack(&phead, 3); SListPushBack(&phead, 4); SListPushBack(&phead, 5); SListPrint(phead); SListNode* pos = SListFind(phead, 5); SListErasePrev(&phead, pos); SListPrint(phead); SListErasePrev(&phead, pos); SListPrint(phead); SListErase(&phead, pos); SListPrint(phead); SListDestroy(&phead); } int main() { //TestSList1(); //TestSList2(); //TestSList3(); TestSList4(); return 0; }
本节内容到此位置,感谢您的阅读
如果对你有帮助的话,不要忘记点赞加收藏哦!!!
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,