数据结构之链表详解(2)——双向链表

2023-11-04

目录

   

    前言

一.双向链表

A.双向链表的含义

B.双向链表的实现

1.双向链表的结构

2.链表的初始化

        初始化图解:

        函数代码:

 3.动态申请节点函数

        函数代码:

 4.打印双向链表函数

        函数代码:

5.尾部插入节点

        图解:

        函数代码: 

         测试:

 6.头插函数

        图解:

         函数代码:

        测试:

 7.尾删函数

        图解:

        函数代码:

        测试:

 8.头删函数

        图解

         函数代码:

        测试:

9.链表查找函数

        函数代码: 

        测试: 

10.在链表指定的位置前插入函数 

        图解:

        函数代码:

        测试:

11.在链表的pos位置处删除节点

        图解:

        函数代码:

         测试:

12.求链表的长度函数

        函数代码:

        测试:

13.释放链表空间

        函数代码:


    前言

        前几日,我写了一篇关于单链表的博客,想要了解的可以看一看: 数据结构之链表详解(1

        单链表的特征就是:单向、不带头、非循环,你们可以叫它三无链表hhh,单向就是说链表的所有节点都是只有一个指针next,它只能链接一个节点的地址或者NULL;不带头就是没有哨兵位头节点,这个之后介绍;非循环就是链表中节点之间没有环相连。其次在上篇博客里还实现了单链表的初始化、申请新节点、头插头删、尾插尾删、在某个指定位置的插入删除等功能实现。

        而今天实现的双向链表与单链表完全相反,接下来就带着大家来看一看。

一.双向链表

A.双向链表的含义

双向链表:Double List Node,它的特征为:带头+双向+循环。

        带头:就是哨兵位头节点,哨兵位头节点是用动态申请(malloc、calloc、realloc)下的一个节点,它的里面绝不存储有效数据,即它不可以作为节点进行存值,但可以存储节点的地址 ,它最大的优势就是让链表的起始指针永远指向哨兵位头节点,由哨兵位头节点去插入或删除节点,这样做就不会修改链表起始指针,也就用不到二级指针;而单链表中没有哨兵头节点,所以常常要改变起始指针的指向,使用二级指针接收实参,才能去改变实参!!!

        双向:单链表只有一个指针,所以一个节点只能链接一个地址;而双链表的节点中有两个指针地址,既可以链接它前面的节点地址,也可以链接它后面的节点地址,十分方便。

        循环:便是双链表中头尾使用环去链接,链表的最后一个节点不会指向NULL,而是与头节点相互链接,便组成了循环。

        如上图 :双向链表的head结点就是哨兵位头节点,它用于指向第一个结点地址,而每个结点的后指针都相互指向下一个结点的前指针。

注:这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了。

B.双向链表的实现

1.双向链表的结构

共有三部分:一个数据域,两个指针域

typedef int DLTDataType;//重命名int结构用于双向链表,使用双向链表就用这种新结构,使用普通变量还是            
                        //使用int类型,重定义只是做一个区分而已。

typedef struct DListNode {	
	DLTDataType data;			//数据域
	struct DListNode* prev;		//前指针
	struct DListNode* next;		//后指针
}DLNode;//重定义结构体类型名称

2.链表的初始化

初始化图解:

 

函数代码:

//链表初始化
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;
}

        初始化便是要创建哨兵位头节点,因为哨兵位头节点不存储有效数据,且该开始两指针并不知道指向谁,所以根据双向链表循环的特性,就让该结点的两个指针自己指向自己。


 3.动态申请节点函数

函数代码:

//动态申请节点函数
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;
}

因为刚开辟下节点,还没有进行链接,所以先暂时让两指针存空。

 


 

 4.打印双向链表函数

        打印链表就是遍历每一个节点,因为双向链表为循环,尾节点不指向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");
}


 

5.尾部插入节点

图解:

情况一:


 

        情况一讲解:之前对于单链表的尾插实现时需要进行找尾才能插入节点(需要遍历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就等价于哨兵位头节点!


 6.头插函数

图解:

 函数代码:

//头插
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;
}

测试:

 


 7.尾删函数

图解:

情况一:当链表有多个节点时

 


 

 情况二:当链表只有一个节点时 ,尾删后,哨兵位头节点的两个指针就会自己指向自己

函数代码:

关于头尾部的删除,需要进行检查链表是否为空,所以,我封装了一个检查函数:

//暴力检查
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;
 }

测试:


 8.头删函数

图解:

 函数代码:

//头删
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;
}

 

测试:


9.链表查找函数

        查找函数可以用来查找某个节点,还可以通过对查找到的节点进行修改、对指定位置的增删功能实现。

函数代码: 

//链表查找
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;//找不到,返回空
}

测试: 


10.在链表指定的位置前插入函数 

图解:

 

函数代码:

//链表在指定位置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函数! 

 


11.在链表的pos位置处删除节点

图解:

 

函数代码:

//链表在指定位置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;
}

 测试:

 


12.求链表的长度函数

这个函数与打印函数原理相同,都是通过指针遍历每一个节点去统计节点个数。

函数代码:

//求链表的长度函数
size_t DListSize(DLNode* phead) {
	assert(phead);
	size_t n = 0;
	DLNode* cur = phead->next;
	while (cur != phead) {
		n++;
		cur = cur->next;
	}
	return n;
}

测试:

 


13.释放链表空间

函数代码:

//释放空间
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);

以上就是对双向链表的讲解和功能实现了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构之链表详解(2)——双向链表 的相关文章

随机推荐

  • Python包和库

    2 3 包和库 2 3 1 包的概念 包是在模块之上的概念 为了方便管理而将多个脚本文件 模块文件 进行打包 包是一种用点式模块名构造 Python 模块命名空间的方法 例如 模块名 A B 表示包 A 中名为 B 的子模块 正如模块可以区
  • Vue生成二维码

    文章目录 概要 整体架构流程 实现过程 创建vue VsCode打开项目 打开终端 下载qrcodejs2插件 导入和使用qrcodejs2 代码展示与讲解 概要 实现输入内容后点击回车或生成按钮 生成二维码 扫描后是我们在输入框的值 在上
  • 华为OD机试 - 找到比自己强的人数(Java)

    题目描述 给定数组 2 1 3 2 每组表示师徒关系 第一个元素是第二个元素的老师 数字代表排名 现在找出比自己强的徒弟 输入描述 无 输出描述 无 用例 输入 2 1 3 2 输出 0 1 2 说明 输入 第一行数据 2 1 表示排名第
  • 立刻更新你的苹果设备!苹果被曝2大安全漏洞,无需交互就能被植入间谍软件...

    萧箫 发自 凹非寺量子位 公众号 QbitAI 不要犹豫 立刻更新你的苹果设备 就在这两天 一家安全组织发现了苹果设备的2个最新漏洞 平板 手机 电脑等都受影响 例如搭载iOS 16 6版本的iPhone手机 以及新版本的iPad平板 Ma
  • b宝塔 centos端口更改_宝塔Linux面板添加安全入口,修改管理员默认用户名与端口...

    网站安全问题是件非常容易被忽视掉的事情 有些同学安装宝塔Linux面板之后管理员账号依旧使用的是admin 使用默认的账号密码很容易被入侵 因此猫总总结了使用宝塔面Linux板必须修改的三点 宝塔Windows面板用户同样需要注意安全问题
  • IDEA 下Java获取Tomcat 项目运行路径问题

    最近在学习SpringMVC的上传文件过程中 使session getServletContext getRealPath photo 获取项目运行路径 却发现获取得到的是 C Program Files Apache Software F
  • UBT11:ubuntu安装IDEA2020.1

    11 1 简介 linux上的IDEA并不需要安装 只要解压即可运行 这就好像win上面的绿色软件 所以 我们需要把idea解压到一个合适的位置 然后创建桌面快捷方式 即可完成安装 此方法应该适用于整个JetBrains的软件 11 2 环
  • mysql8 window安装,链式复制,双主复制,数据库的负载均衡

    by xuejianxinokok 163 com 2021年3月25日 周四 15 06 43 1 下载地址 https dev mysql com downloads mysql 2 下载文件名称为 mysql 8 0 23 winx6
  • 2022年“网络安全”赛项海南省赛选拔赛 任务书

    2022年 网络安全 赛项海南省赛选拔赛 任务书 一 竞赛时间 共计6小时 二 A模块基础设施设置 安全加固 350分 一 项目和任务描述 假定你是某企业的网络安全工程师 对于企业的服务器系统 根据任务要求确保各服务正常运行 并通过综合运用
  • Linux系统安装R语言

    R语言是一款开源 免费的用于绘图和统计分析的语言和集成环境 该语言使用起来十分方便 提供了许多扩展包供下载使用 目前网上一些linux安装R语言的教程太过繁琐 其实 在ubuntu linux 系统下利用其提供的apt get命令可以方便的
  • macbook pro 散热方案,温度仅29度

    结论 Macbook Pro 13 3 寸 2017 控制住温度 性能飞起 5年前散热不好时 容易触发 CPU 降频 一 需求 长时间满载运行不降频 控制住温度 控制住散热噪音 二 尝试过的散热方案 散热方案 说明 最低温度 满载温度 一
  • 华为云云耀云服务器L实例评测

    前言 在上篇文章 华为云云耀云服务器L实例评测 快速部署MySQL使用指南 中 我们已经用 华为云云耀云服务器L实例 在命令行窗口内完成了MySQL的部署并简单使用 但是后台有小伙伴跟我留言说 能不能用 华为云云耀云服务器L实例 来实现个简
  • 联盛德W800开发板

    目录 W800 芯片介绍 W800开发板 主要接口如下 1 概述 2 准备工作 3 SDK目录结构如下 4 W800编译固件编译 4 1 安装MSYS到本地 4 2增加国内软件更新源 编辑4 3下载工具链 4 5 make工具链配置 5 M
  • 浪潮服务器不显示光驱,电脑不从光驱启动怎么办?我是浪潮品牌的机子。

    在DOS下可以装系统的 WIN98启动软盘引导系统为例在DOS下安装XP 为提高安装速度 需要在启动盘中添加smartdrv exe磁盘高速缓存 cache 程序 并且在安装之前运行该程序 smartdrv是一个磁盘高速缓存程序 称之为sm
  • React事件处理方法

    一 注意事项 1 React元素的事件处理和Dom元素很相似 但是有一点语法的不同 2 React事件的命名采用小驼峰的命名方式 而不是纯小写 camelCase 3 使用JSX语法时你需要传入一个函数作为事件处理函数 而不是一个字符串 例
  • 【CVPR 2022 多模态融合(有3D检测)】Multimodal Token Fusion for Vision Transformers

    Multimodal Token Fusion for Vision Transformers 论文简介 具体实现 Alignment agnostic fusion Alignment aware fusion Multimodal To
  • 如何快速的只取出列表中的数字

    my list a a a 1 2 3 4 5 A B C 提取出 12345 方法一 使用try方法测试 isalnum 判断是否是字母 my list a a a 1 2 3 4 5 A B C str1 for i in my lis
  • Elasticsearch 在Windows上安装和启动

    1 安装JDK 至少1 8以上 2 下载和解压缩Elasticsearch安装包 下载地址 https www elastic co cn downloads 3 启动Elasticsearch bin elasticsearch bat
  • H5存储方案——cookie、session、SessionStorage和LocalStorage

    1 简述 浏览器端存储网页中的数据有三种存储方案 cookie SessionStorage和LocalStorage 其中 SessionStorage和LocalStorage是H5新增的存储方案 而cookie经常同session一并
  • 数据结构之链表详解(2)——双向链表

    目录 前言 一 双向链表 A 双向链表的含义 B 双向链表的实现 1 双向链表的结构 2 链表的初始化 初始化图解 函数代码 3 动态申请节点函数 函数代码 4 打印双向链表函数 函数代码 5 尾部插入节点 图解 函数代码 测试 6 头插函