目录
图解:
前几日,我写了一篇关于单链表的博客,想要了解的可以看一看: 数据结构之链表详解(1)
单链表的特征就是:单向、不带头、非循环,你们可以叫它三无链表hhh,单向就是说链表的所有节点都是只有一个指针next,它只能链接一个节点的地址或者NULL;不带头就是没有哨兵位头节点,这个之后介绍;非循环就是链表中节点之间没有环相连。其次在上篇博客里还实现了单链表的初始化、申请新节点、头插头删、尾插尾删、在某个指定位置的插入删除等功能实现。
而今天实现的双向链表与单链表完全相反,接下来就带着大家来看一看。
双向链表:Double List Node,它的特征为:带头+双向+循环。
带头:就是哨兵位头节点,哨兵位头节点是用动态申请(malloc、calloc、realloc)下的一个节点,它的里面绝不存储有效数据,即它不可以作为节点进行存值,但可以存储节点的地址 ,它最大的优势就是让链表的起始指针永远指向哨兵位头节点,由哨兵位头节点去插入或删除节点,这样做就不会修改链表起始指针,也就用不到二级指针;而单链表中没有哨兵头节点,所以常常要改变起始指针的指向,使用二级指针接收实参,才能去改变实参!!!
双向:单链表只有一个指针,所以一个节点只能链接一个地址;而双链表的节点中有两个指针地址,既可以链接它前面的节点地址,也可以链接它后面的节点地址,十分方便。
循环:便是双链表中头尾使用环去链接,链表的最后一个节点不会指向NULL,而是与头节点相互链接,便组成了循环。

如上图 :双向链表的head结点就是哨兵位头节点,它用于指向第一个结点地址,而每个结点的后指针都相互指向下一个结点的前指针。
注:这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了。
共有三部分:一个数据域,两个指针域
typedef int DLTDataType;//重命名int结构用于双向链表,使用双向链表就用这种新结构,使用普通变量还是
//使用int类型,重定义只是做一个区分而已。
typedef struct DListNode {
DLTDataType data; //数据域
struct DListNode* prev; //前指针
struct DListNode* next; //后指针
}DLNode;//重定义结构体类型名称

//链表初始化
void DListInit(DLNode* phead) {
DLNode* guard = (DLNode*)malloc(sizeof(DLNode));//哨兵位头节点
if (guard == NULL) {
prerror("malloc fail\n");
return -1;
}
guard->prev = guard;
guard->next = guard;
return guard;
}
初始化便是要创建哨兵位头节点,因为哨兵位头节点不存储有效数据,且该开始两指针并不知道指向谁,所以根据双向链表循环的特性,就让该结点的两个指针自己指向自己。
//动态申请节点函数
DLNode* BuyDLTNode( DLTDataType x) {
DLNode* node = (DLNode*)malloc(sizeof(DLNode));
//刚申请下的堆区空间有可能开辟失败,所以要进行检查
if (node == NULL) {
perror("malloc fail\n");
return -1;
}
//开辟好后就赋值
node->data = x;
node->prev = NULL;
node->next = NULL;
return node;
}
因为刚开辟下节点,还没有进行链接,所以先暂时让两指针存空。
打印链表就是遍历每一个节点,因为双向链表为循环,尾节点不指向NULL,头尾相连,所以需要新的限制条件——哨兵位头节点,哨兵位头节点不存储有效数据,所以遍历指针只要遇到哨兵节点就会停止,即打印完毕!
//打印
void DListPrint(DLNode* phead) {
assert(phead);
DLNode* cur = phead->next;
printf("guard<=>\n");
while (cur != phead) {
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
情况一:


情况一讲解:之前对于单链表的尾插实现时需要进行找尾才能插入节点(需要遍历cur!=NULL才行) ,但是双向链表的尾插不同,它不需要遍历寻找,如图1,phead作为头指针,永远指向哨兵头,而哨兵头的prev指针却是与尾部节点d3相连(头尾组成循环相连),那么尾节点就一定是哨兵头的prev指向地址,所以不需要遍历,可以轻易的找到!
情况二:
情况一与情况二原理相同。
//链表尾插
void DListPushBack(DLNode* phead, DLTDataType x) {
assert(phead);
DLNode* tail = phead->prev;//找到尾 尾节点指针处在哨兵位头节点的prev
DLNode* newnode = BuyDLTNode(x);//新节点
//改变顺序
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
void TestDlist1() {
DLNode* plist1=DListInit();
//传参时,不需要传plist1的地址,因为函数不会改变plist1的指向,因为plist1永远指向哨兵位头节点,
//使用哨兵位头节点进行改变,所以用plist传递,用一级指针接收就行
DListPushBack(plist1, 1);
DListPushBack(plist1, 2);
DListPushBack(plist1, 3);
DListPushBack(plist1, 4);
DListPrint(plist1);
}
int main() {
TestDlist1();
return 0;
}
结果:


注:phead就等价于哨兵位头节点!

//头插
void DListPushFront(DLNode* phead,DLTDataType x) {
assert(phead);
DLNode* first = phead->next;
DLNode* newnode = BuyDLTNode(x);
newnode->next = first;
newnode->prev = phead;
phead->next = newnode;
first->prev = newnode;
}
情况一:当链表有多个节点时
情况二:当链表只有一个节点时 ,尾删后,哨兵位头节点的两个指针就会自己指向自己

关于头尾部的删除,需要进行检查链表是否为空,所以,我封装了一个检查函数:
//暴力检查
bool DListEmpty(DLNode* phead) {
assert(phead);
return phead->next == phead;
}
注:bool类型的返回值为true、false两种,若是phead->next==phead说明链表为空,那么assert(!DListEmpty(phead)==assert(NULL);函数就报错,且停止下面语句的执行;若是 phead->next !=phead说明链表不为空,那么assert(!DListEmpty(phead)这条语句就不起作用,检查通过,继续下面语句的执行。
//尾删
void DListPopBack(DLNode* phead) {
assert(phead);
//暴力检查
assert(!DListEmpty(phead));
DLNode* tail = phead->prev;
DLNode* last = tail->prev;
//改变顺序
last->next = phead;
phead->prev = last;
//释放尾节点
free(tail);
tail = NULL;
}



//头删
void DListPopFront(DLNode* phead) {
assert(phead);
//暴力检查
assert(!DListEmpty(phead));
DLNode* first = phead->next;
DLNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}

查找函数可以用来查找某个节点,还可以通过对查找到的节点进行修改、对指定位置的增删功能实现。
//链表查找
DLNode* DListFind(DLNode* phead, DLTDataType x) {
assert(phead);
DLNode* cur = phead->next;
//遍历查找
while (cur != phead){
if (cur->data == x) {
return cur;//找到了,返回
}
cur = cur->next;//继续往后找
}
return NULL;//找不到,返回空
}


//链表在指定位置pos前插入函数
void DListInsert(DLNode* pos, DLTDataType x) {
//指定的位置不可以为空
assert(pos);
DLNode* last = pos->prev;
DLNode* newnode = BuyDLTNode(x);
last->next = newnode;
newnode->prev = last;
newnode->next = pos;
pos->prev = newnode;
}


注:在某个位置前插入节点时,总要先找到pos的位置,然后才可以使用Insert函数!

//链表在指定位置pos处删除节点
void DListErase(DLNode* pos) {
assert(pos);
DLNode* later = pos->next;
DLNode* last = pos->prev;
last->next = later;
later->prev = last;
free(pos);
pos = NULL;
}

这个函数与打印函数原理相同,都是通过指针遍历每一个节点去统计节点个数。
//求链表的长度函数
size_t DListSize(DLNode* phead) {
assert(phead);
size_t n = 0;
DLNode* cur = phead->next;
while (cur != phead) {
n++;
cur = cur->next;
}
return n;
}

//释放空间
void DListDestory(DLNode* phead) {
assert(phead);
DLNode* cur = phead->next;
//以遍历节点的方式,一个一个的释放
while (cur != phead) {
DLNode* later = cur->next;
free(cur);
cur = NULL;
}
//释放完后,再把哨兵位头节点也释放掉
free(phead);
phead = NULL;
}
完整代码:
Test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"DList.h"
//尾插
void TestDlist1() {
DLNode* plist1=DListInit();
//传参时,不需要传plist1的地址,因为函数不会改变plist1的指向,因为plist1永远指向哨兵位头节点,
//使用哨兵位头节点进行改变,所以用plist传递,用一级指针接收就行
printf("尾插\n");
DListPushBack(plist1, 1);
DListPushBack(plist1, 2);
DListPushBack(plist1, 3);
DListPushBack(plist1, 4);
DListPrint(plist1);
//尾删
printf("\n尾删\n");
printf("尾删第一次:");
DListPopBack(plist1);
DListPrint(plist1);
printf("尾删第二次:");
DListPopBack(plist1);
DListPrint(plist1);
printf("尾删第三次:");
DListPopBack(plist1);
DListPrint(plist1);
printf("尾删第四次:");
DListPopBack(plist1);
DListPrint(plist1);
}
//头插
void TestDlist2() {
DLNode* plist2 = DListInit();
printf("头插\n");
DListPushFront(plist2, 10);
DListPushFront(plist2, 20);
DListPushFront(plist2, 30);
DListPushFront(plist2, 40);
DListPrint(plist2);
//头删
printf("\n头删\n");
printf("头删第一次:");
DListPopFront(plist2);
DListPrint(plist2);
printf("头删第二次:");
DListPopFront(plist2);
DListPrint(plist2);
printf("头删第三次:");
DListPopFront(plist2);
DListPrint(plist2);
printf("头删第四次:");
DListPopFront(plist2);
DListPrint(plist2);
}
//查找,修改
void TestDlist3() {
DLNode* plist3 = DListInit();
printf("头插\n");
DListPushFront(plist3, 10);
DListPushFront(plist3, 20);
DListPushFront(plist3, 30);
DListPushFront(plist3, 40);
DListPrint(plist3);
DLNode* find = DListFind(plist3, 20);
if (find) {
printf("找到了\n");
find->data = 999;
printf("修改节点数值成功\n");
DListPrint(plist3);
}
else {
printf("没找到\n");
return -1;
}
}
//在某个位置pos前插入
void TestDlist4() {
DLNode* plist4 = DListInit();
printf("尾插\n");
DListPushBack(plist4, 100);
DListPushBack(plist4, 200);
DListPushBack(plist4, 300);
DListPushBack(plist4, 400);
DListPrint(plist4);
//在某个位置前插入
//DLNode* find = DListFind(plist4, 100);//找个pos位置
//if (find) {
// printf("找到了\n");
// DListInsert(find, 2999);
// DListPrint(plist4);
//}
//else {
// return -1;
//}
DLNode* find2 = DListFind(plist4, 400);//找个pos位置
if (find2) {
printf("找到了\n");
DListInsert(find2, 6999);
DListPrint(plist4);
}
else {
return -1;
}
}
//在某个位置pos删除节点
void TestDlist5() {
DLNode* plist5 = DListInit();
printf("尾插\n");
DListPushBack(plist5, 100);
DListPushBack(plist5, 200);
DListPushBack(plist5, 300);
DListPushBack(plist5, 400);
DListPrint(plist5);
//在某个位置删除
DLNode* find = DListFind(plist5, 100);//找个pos位置
if (find) {
printf("找到了\n");
DListErase(find);//删除值为100的节点
DListPrint(plist5);
}
else {
return -1;
}
//DLNode* find2 = DListFind(plist5, 400);//找个pos位置
//if (find2) {
// printf("找到了\n");
// DListInsert(find2, 6999);
// DListPrint(plist5);
//}
//else {
// return -1;
//}
}
void TestDlist6() {
DLNode* plist6 = DListInit();
size_t count = DListSize(plist6);//刚开始链表的长度为0
printf("当前链表的长度为:%zu\n",count);
DListPushBack(plist6, 100);
DListPushBack(plist6, 200);
DListPushBack(plist6, 300);
DListPushBack(plist6, 400);
DListPrint(plist6);
count= DListSize(plist6);
printf("当前链表的长度为:%zu", count);
}
int main() {
//TestDlist1();
//TestDlist2();
//TestDlist3();
//TestDlist4();
//TestDlist5();
TestDlist6();
return 0;
}
DList.c代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"DList.h"
//链表初始化
DLNode* DListInit() {
DLNode* guard = (DLNode*)malloc(sizeof(DLNode));//哨兵位头节点
if (guard == NULL) {
perror("malloc fail\n");
return -1;
}
guard->prev = guard;
guard->next = guard;
return guard;
}
//动态申请节点函数
DLNode* BuyDLTNode( DLTDataType x) {
DLNode* node = (DLNode*)malloc(sizeof(DLNode));
if (node == NULL) {
perror("malloc fail\n");
return -1;
}
node->data = x;
node->prev = NULL;
node->next = NULL;
return node;
}
//链表尾插
void DListPushBack(DLNode* phead, DLTDataType x) {
assert(phead);
DLNode* newnode = BuyDLTNode(x);//新节点
DLNode* tail=phead->prev;//找到尾节点指针处在哨兵位头节点的prev
//改变顺序
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//打印
void DListPrint(DLNode* phead) {
assert(phead);
DLNode* cur = phead->next;
printf("guard<=>");
while (cur != phead) { //遍历
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
//头插
void DListPushFront(DLNode* phead,DLTDataType x) {
assert(phead);
//找到头节点
DLNode* first = phead->next;
DLNode* newnode = BuyDLTNode(x);
//改变顺序
newnode->next = first;
newnode->prev = phead;
phead->next = newnode;
first->prev = newnode;
}
//暴力检查
bool DListEmpty(DLNode* phead) {
assert(phead);
return phead->next == phead;
}
//尾删
void DListPopBack(DLNode* phead) {
assert(phead);
//暴力检查
assert(!DListEmpty(phead));
DLNode* tail = phead->prev;
DLNode* last = tail->prev;
//改变顺序
last->next = phead;
phead->prev = last;
//释放尾节点
free(tail);
tail = NULL;
}
//头删
void DListPopFront(DLNode* phead) {
assert(phead);
//暴力检查
assert(!DListEmpty(phead));
DLNode* first = phead->next;
DLNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
//链表查找
DLNode* DListFind(DLNode* phead, DLTDataType x) {
assert(phead);
DLNode* cur = phead->next;
//遍历查找
while (cur != phead){
if (cur->data == x) {
return cur;//找到了,返回
}
cur = cur->next;//继续往后找
}
return NULL;//找不到,返回空
}
//链表在指定位置pos前插入函数
void DListInsert(DLNode* pos, DLTDataType x) {
//指定的位置不可以为空
assert(pos);
DLNode* last = pos->prev;
DLNode* newnode = BuyDLTNode(x);
last->next = newnode;
newnode->prev = last;
newnode->next = pos;
pos->prev = newnode;
}
//链表在指定位置pos处删除节点
void DListErase(DLNode* pos) {
assert(pos);
DLNode* later = pos->next;
DLNode* last = pos->prev;
last->next = later;
later->prev = last;
free(pos);
pos = NULL;
}
//求链表的长度函数
size_t DListSize(DLNode* phead) {
assert(phead);
size_t n = 0;
DLNode* cur = phead->next;
while (cur != phead) {
n++;
cur = cur->next;
}
return n;
}
//释放空间
void DListDestory(DLNode* phead) {
assert(phead);
DLNode* cur = phead->next;
//以遍历节点的方式,一个一个的释放
while (cur != phead) {
DLNode* later = cur->next;
free(cur);
cur = NULL;
}
//释放完后,再把哨兵位头节点也释放掉
free(phead);
phead = NULL;
}
DList.h头文件代码:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int DLTDataType;//重命名int结构用于双向链表,使用双向链表就用这种新结构,使用普通变量还是使用
//int类型,重定义只是做一个区分而已。
typedef struct DListNode {
DLTDataType data; //数据域
struct DListNode* prev; //前指针
struct DListNode* next; //后指针
}DLNode;//重定义结构体类型名称
//链表初始化
DLNode* DListInit();
//链表尾插
void DListPushBack(DLNode* phead, DLTDataType x);
//打印
void DListPrint(DLNode* phead);
//头插
void DListPushFront(DLNode* phead, DLTDataType x);
//尾删
void DListPopBack(DLNode* phead);
//头删
void DListPopFront(DLNode* phead);
//链表查找
DLNode* DListFind(DLNode* phead, DLTDataType x);
//链表在指定位置pos前插入函数
void DListInsert(DLNode* pos, DLTDataType x);
//链表在指定位置pos处删除节点
void DListErase(DLNode* pos);
//求链表的长度函数
size_t DListSize(DLNode* phead);
//释放空间
void DListDestory(DLNode* phead);
以上就是对双向链表的讲解和功能实现了。
我想将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.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,