五种基本数据类型主要包括常见的5种数据类型，分别是：String、List、Set、Zset、Hash。结构类型结构存储的值结构的读写能力String 字符串可以是字符串、整数或浮点数对整个字符串或字符串的一部分进行操作；对整数或浮点数进行自增或自减操作；List 列表一个链表，链表上的每个节点都包含一个字符串对链表的两端进行push和pop操作，读取单个或多个元素；根据值查找或删除元素；Set 集合包含字符串的无序集合字符串的集合，包含基础的方法有看是否存在添加、获取、删除；还包含计算交集、并集、差集等Hash 散列包含键值对的无序散列表包含方法有添加、获取、删除单个元素Zset 有序集合和散列一样，用于存储键值对字符串成员与浮点数分数之间的有序映射；元素的排列顺序由分数的大小决定；包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素String字符串String是redis最基本的类型，一个key对应一个value。String类型是二进制安全的，意思是redis的string可以包含任何数据，比如jpg图片或者序列化的对象。String类型是redis最基本的数据类型，一个redis中字符串value最多可以是512Mset (key) (value) # 设置键值对 setnx (key) (value) # 防止覆盖，设置键值对， #如果key不存在就设置，返回1 #如果key已经存在就不设置，返回0 get(key) # 获取key对应的value getset (key) (value)# 先get再set，返回旧值，如果没有旧值返回nil append (key) (value)# 向指定的key的value后追加字符串 del (key) # 删除key strlen (key) # 获取key对应值的字符串长度数字value的加减incr (key) `：value + 1` decr (key) `：value - 1` incrby (key) (number)`：value + number` decrby (key) (number)`：value - number`获取或者设置指定范围内的值getrange (key) (begin) (end) # 获取[begin,end]下标范围内的值，如果是(0,1)就是获取所有值 setrange (key) (begin) (xxxx) # 从begin下标开始设置xxx值，将原有的替换掉设置键值过期时间setex (key) (seconds) expire # 设置键过期时间 ttl (key) # 查看key剩余存活时间https://blog.csdn.net/qq_50596778/article/details/124554777常见面试问题
内网穿透概念内网穿透简单来说就是将你电脑访问和别人电脑访问通过natapp隧道打通，让内网的数据能在别人的电脑上可以获取。换个说法就是将原本只能在本机上访问的项目网站等，通过内网穿透使得你可以在其他人的网上进行访问。为什么要使用内网穿透？要想让你的项目可以实现在网络上进行访问和使用。要么将你的项目部署到云服务器上，要么通过内网穿透使得他人可以访问原本只在你的计算机上项目接口。但是如果使用第一种方法，如果项目出现问题后，就必须得将项目重新部署到云服务器上，而且还需要公网IP和服务器。但使用内网穿透可以直接实现本地调试，比较方便。那什么时候使用？公司交付 甲方！内网穿透工具花生壳、Sunny-Ngrok内网穿透服务
如果对于容器和镜像还无法区分：https://blog.csdn.net/yao694899744/article/details/105307150?spm=1001.2014.3001.5502Docker底层原理Docker是怎么工作的？Docker 是一个 Client - Server（C/S） 结构的系统。Dokcer的守护进程运行在主机上。通过Socket从客户端访问！DockerServer 接受到 Docker-Client 的指令，就会执行这个命令！Dokcer 为什么比 VM 快？1、Dokcer 有着比虚拟机更少的抽象层2、Dokcer 利用的是宿主机的内核， VM 需要 Guest OS。所以说，新建一个容器的时候，docker不需要像虚拟机一样重新加载一个操作系统内核，避免引导。虚拟机是加载 Guest OS，分钟级别的，而docker 是利用宿主机的操作系统，省略了这个复杂的过程，秒级！Docker常用命令帮助命令docker version #显示docker的版本信息 docker info #显示docker的系统信息，包括镜像和容器的数量 docker 命令 --help #帮助命令帮助文档的地址：https://docs.docker.com/engine/reference/commandline/镜像命令C:\Users\lookchem>docker images REPOSITORY TAG IMAGE ID CREATED SIZE casimg dev 75fda16afbb3 6 weeks ago 186MB mcr.microsoft.com/dotnet/runtime 5.0 da195286baf8 4 months ago 186MB mcr.microsoft.com/dotnet/aspnet 5.0 99323686c908 5 months ago 205MB #解释 REPOSITORY 镜像的仓库源 TAG 镜像的标签 IMAGE ID 镜像的id CREATED 镜像的创建时间 SIZE 镜像的大小 #可选项 -a, --all Show all images (default hides intermediate images)#列出所有镜像 --digests Show digests -f, --filter filter Filter output based on conditions provided --format string Pretty-print images using a Go template --no-trunc Don't truncate output -q, --quiet Only show image IDs(只显示镜像的id) docker search 搜索镜像C:\Users\lookchem>docker search mysql NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a widely used, open-source relation… 13234 [OK] mariadb MariaDB Server is a high performing open sou… 5062 [OK] #可选项 通过搜索来过滤 --filter=STARS=3000 #搜索出来的镜像就是STARS大于3000的 C:\Users\lookchem>docker search mysql --filter=stars=3000 NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a widely used, open-source relation… 13234 [OK] mariadb MariaDB Server is a high performing open sou… 5062 [OK] -f, --filter filter Filter output based on conditions provided --format string Pretty-print search using a Go template --limit int Max number of search results (default 25) --no-trunc Don't truncate output docker pull 下载镜像# 下载镜像 docker pull 镜像名[:tag] C:\Users\lookchem>docker pull mysql Using default tag: latest #如果不写 tag，默认就是 latest latest: Pulling from library/mysql 295ca2342728: Pull complete #分层下载，docker images的核心 联合文件系统 79af4312a7e0: Pull complete 48d3d73d1704: Pull complete 521b8724b397: Pull complete b8cf360b4a14: Pull complete 0115482cc006: Pull complete a360b08917ea: Pull complete 12deeb3c1323: Pull complete ee1dc10db1e9: Pull complete 64be404ad29c: Pull complete 1921fe8879a2: Pull complete Digest: sha256:3c1aab708f6e57fc4dccafe36baccc7219acfb4ec450f3fb6d370ea89409e906 #签名 Status: Downloaded newer image for mysql:latest docker.io/library/mysql:latest#真实地址 # 等价于它 docker pull mysql docker pull docker.io/library/mysql:latest # 指定版本下载 C:\Users\lookchem>docker pull mysql:5.7docker rmi 删除镜像# docker rmi -f 镜像id #删除指定镜像id # docker rmi -f 镜像id 镜像id 镜像id 镜像id #删除多个镜像id # docker rmi -f $(docker images -aq) #删除全部镜像 C:\Users\lookchem>docker rmi -f dbaea59d1b41 # 删除指定镜像id Untagged: mysql:latest Untagged: mysql@sha256:3c1aab708f6e57fc4dccafe36baccc7219acfb4ec450f3fb6d370ea89409e906 Deleted: sha256:dbaea59d1b4194257138a6434ec0820a13097dd7e900135e78daf9759fe2407a Deleted: sha256:e507fd966e8388339d4024e698d2d9000eece41c497a8857774c9f0f1f17c3de Deleted: sha256:239ac15e65c3efa92872682fdfcbf177735fba69a3bb236f7acfb230319f8b9f Deleted: sha256:944a10fce07786f698ca86bb6781f004e9f1fa22fad021ec57132d77e231a165 Deleted: sha256:6acb95708216880feed67b390e3855ec9c48947904a1f97900584c9bedafca14 Deleted: sha256:28945f2a92f5ff073346724641d6e82464dd61dc903ab71f18eb975503a1a5d1 Deleted: sha256:a1e4e03c3de2361aedf9cea34b0477dc80a7de3dce128ac450a1aa8b4202ea7f Deleted: sha256:8f063e9b29c9e6fca010e488a7972826483aa87364fee8005d30ac5809d539df Deleted: sha256:714888f3d7e50ce4819618162cf60f0d6899e556b4cfc83edce10750117bcbaf Deleted: sha256:3b0f259c919a98067a2debaa2bc9c405250fc9c468ceabc9a0ebe51eb36d2e7e Deleted: sha256:2047a833bd7438a2271051138d5d599d01374b615a7f77f6ef2b199ff917f6cb Deleted: sha256:553b196ea5e8358e8b819253dc8fb9af6cf5711df6c83e594b4c2109c61eda18 容器命令说明：我们有了镜像才可以创建容器。linux下载一个centos镜像来测试学习docker pull centos新建容器并启动docker run [可选参数] image # 参数说明 --name="Name" 容器名称 tomcat01 tomcat02，用来区分容器 -d 后台方式运行 -it 使用交互方式运行，进入容器查看内容 -p 指定容器的端口 -p 8080:8080 -p ip:容器端口:主机端口 -p 容器端口:主机端口(常用) -p 容器端口 -p 随机指定端口 #测试，启动并进入容器 C:\Users\lookchem>docker run -it centos [root@e543898b573a /]# ls #查看容器内的centos，基础版本，很多命令都是不完善的！ bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var列出所有运行的容器#docker ps 命令 # 列出正在运行的容器 -a # 列出正在运行的容器+未运行的容器 -n=? # 显示最近创建的容器 ？=显示几个 -q # 只显示容器的编号 C:\Users\lookchem>docker ps #列出正在运行的容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e543898b573a centos "/bin/bash" 12 minutes ago Up 7 seconds awesome_elbakyan f1752205028a ccr.ccs.tencentyun.com/mssql/sqlserver:2019-CU6-ubuntu-16.04 "/opt/mssql/bin/perm…" 19 months ago Up 6 hours 0.0.0.0:1433->1433/tcp mssql C:\Users\lookchem>docker ps -a #列出所有容器，包括未运行的 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e543898b573a centos "/bin/bash" 7 minutes ago Exited (0) 18 seconds ago awesome_elbakyan f83772c4d175 redis "docker-entrypoint.s…" 9 months ago Exited (255) 6 weeks ago 0.0.0.0:6379->6379/tcp redis 5c2c179e2ca8 mongo:latest "docker-entrypoint.s…" 15 months ago Exited (255) 7 weeks ago 0.0.0.0:27017->27017/tcp log-mongo f1752205028a ccr.ccs.tencentyun.com/mssql/sqlserver:2019-CU6-ubuntu-16.04 "/opt/mssql/bin/perm…" 19 months ago Up 6 hours 0.0.0.0:1433->1433/tcp mssql 退出容器exit #停止并退出容器 ctrl + p + Q #容器不停止 退出 启动和停止容器docker start 容器ID #启动指定现有容器 docker restart 容器id #重启容器 docker stop 容器id #停止容器 优雅退出 docker kill 容器id #停止容器 应用程序直接退出（强制）删除容器docker rm 容器id #删除指定现有容器，不能删除正在运行的容器。如果强制删除 rm -f 全称是force：强制 docker rm -f $(docker ps -aq) #删除所有容器 docker ps -a -q|xargs docker rm #删除所有容器rm删除容器 rmi删除镜像常用其他命令后台启动容器# 命令 docker run -d 容器名称 C:\Users\lookchem>docker run -d centos # 问题docker ps 发现 centos停止了 # 常见的坑：docker 容器使用后台运行，就必须要有一个前台进程，docker发现没有应用，就会自动停止 # nginx，容器启动后，发现自己没有提供服务，就会立刻停止，就是没有程序了 查看日志docker logs --help 容器数据卷什么是容器数据卷docker的理念回顾将应用和环境打包成一个镜像！数据？如果数据都在容器中，那么我们容器删除，数据就会丢失！ 需求：数据持久化MySQL，容器删了，就等于删库跑路 需求：MySQL可以存储在本地！(数据持久化)容器之间可以有一个数据共享的技术！Docker容器中产生的数据，同步到本地！这就是卷技术！目录的挂载，将我们容器内的目录，挂载到Linux上面！总结一句话：容器的持久化和同步操作！容器间也是可以数据共享的！使用数据卷分十一：直接使用命令来挂载 -vdocker run -it -v -p 主机端口:容器内端口
依赖注入基本类库最大的引用场景：项目的可拓展性、灵活性管理复杂的依赖关系消除new依赖注入（本身是一个核心对象）：IOC容器(控制反转)映射依赖、注册类型、注册服务实例解析.Net Core 是由 基础类库+拓展类库(依赖注入框架....).Net Core 原生DI（内置依赖注入框架）而依赖注入是基石Microsoft.Extesions.Dependencyinjection //具体实现 Microsoft.Extesions.Dependencyinjection.Abstractopms //抽象、基础类型、 ServiceProVider//根容器服务范围3中生命周期：单例、作用域、瞬时单例模式 —— 根容器（保存在根容器上，下次就可以直接获取）作用域模式 —— 当前容器（子容器）瞬时模式 —— 一次性不保存​ ASP.NET Core应用 —— 全局根容器(自带的)​ 只有子容器，没用孙子容器。不管有多少孙子都是和子容器同级。​ 子容器只关心根容器，不会在意谁是父容器，也不会知道父容器是谁。​ 每一个 作用域对象 都是有明确的边界，针对每个 HTTP请求上下文​ 一个请求上下文就是一个作用域 (绑定).NET Core 用于提供服务实例的容器有两种Application ServiceProvider（程序本身）Request SericeProvider（请求所创建）请求 —— 创建服务范围 —— 子容器(瞬时、作用域) —— 处理请求所需的服务容器可以提供构造函数所需的所有参数如果某个构造函数的参数类型集合能够成为所有合法构造函数参数类型的超集，那么这个构造函数就会被容器选择扩展类库Autofac: 扩展类库 第三方依赖注入框架（里面有非常丰富的注册功能。例如：属性注入、程序集注入）Scrutor: .Net内置DI的扩展包，弥补内置DI服务注册方式​
1、排序的概念及其运用1.1、排序的概念排序：所谓排序，就是使一串记录，按照其中的某个或某些关键字的大小，递增或递减的排列起来的操作。稳定性：假定在待排序的记录序列中，存在多个既有相同的关键字的记录。若经过排序，这些记录的相对次序保持不变，即在原序列中，r[i] = r[j] ,且r[i]在r[j]之前，而在排序后的序列中，r[i]仍在r[j]之前。则称这种排序算法是稳定的；否则称为不稳定。内部排序：数据元素全部放在内存中的排序。外部排序：数据元素太多不能同时放在一个内存中，根据排序过程的要求不能在内外存之间移动数据的排序。1.2、排序的运用1.3、常见的排序算法2、插入排序2.1、基本思想直接插入排序是一种简单的插入排序法，其基本思想是：把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中，直到所有的记录插入完为止，得到一个新的有序序列。实际中我们完扑克牌时，就是用了插入排序的思想2.2、直接插入排序当插入第i(i>=1)个元素时，前面的array[0]，array[1]，......array[i-1]已经排好序，此时用array[i]的排序码与array[i-1]，array[i-2]，.....的排序码顺序进行比较，找到插入位置即将array[i]插入，原来位置上的元素顺序后移。//插入排序 void InsertSort(int*a,int n) { // [0,end] 有序 end+1位置的值插入进去，让[0,end+1]有序 for (int i = 0; i < n-1; ++i) { int end= i; int tmp = a[end + 1]; while (end >= 0) { if (a[end] > tmp) { a[end + 1] = a[end]; --end; } else { break; } a[end + 1] = tmp; } } }直接插入排序的特性总结：元素集合越接近有序，直接插入排序算法的时间效率越高。时间复杂度：O(N^2^)空间复杂度：O(1)，它是一种稳定的排序算法稳定性：稳定2.3、希尔排序(缩小增量排序)希尔排序法又称缩小增量法。基本思想是：先选定一个整数，把待排序文件中的所有记录分成个组，所有距离为gap的记录分在同一组内，并对每一组内的记录进行排序。然后，取，重复上述分组和排序的工作。当到达gap=1时，所有记录在统一组内排好序。void ShellSort(int* a, int n) { //int gap = 1; //////把间隔为gap的多组数据同时排 //for (int i = 0; i < n - gap; ++i) //{ // int end = i; // int tmp = a[end + gap]; // while (end >= 0) // { // if (a[end] > tmp) // { // a[end + gap] = a[end]; // --end; // } // else { // break; // } // } // a[end + gap] = tmp; //} //那么问题来了 //gap 到底给多少呢？ int gap = n; while (gap > 1) { //gap = gap / 2; // logN gap = gap / 3 + 1; // log3N 以3为底数的对数 // gap > 1时都是预排序 接近有序 // gap == 1时就是直接插入排序 有序 // gap很大时，下面预排序时间复杂度O(N) // gap很小时，数组已经很接近有序了，这时差不多也是(N) // 把间隔为gap的多组数据同时排 for (int i = 0; i < n - gap; ++i) { int end = i; int tmp = a[end + gap]; while (end >= 0) { if (a[end] > tmp) { a[end + gap] = a[end]; end-=gap; } else { break; } } a[end + gap] = tmp; } } }希尔排序的特征总结 ：希尔排序是对直接插入排序的优化。当 gap > 1 时都是预排序，目的是为了让数组跟接近与有序。当 gap==1 时，数组已经接近有序，这样就会很快，这样整体而言，可以达到优化的效果。我们实现后可以进行性能测试和对比。希尔排序的时间复杂度不好计算，推导出来平均时间复杂度：O(N^1.3^至N^2^)稳定性：不稳定3、选择排序3.1、基本思想每一次从待排序的数据元素中选出最小（或最大）的一个元素，存放在序列的起始位置，直到全部待排序的数据元素排完。3.2、直接选择排序在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素若它不是这组元素中的最后一个（第一个）元素，则将它与这组元素中的最后一个（第一个）元素交换在剩余的array[i]--array[n-2]（array[i+1]--array[n-1]）集合中，重复上述步骤，直到集合剩余1个元素排序（冒泡排序，选择排序，插入排序，归并排序，快速排序，计数排序，基数排序） - VisuAlgo直接选择排序的特性总结：直接选择排序思考非常好理解，但是效率不是很好。实际中很少使用时间复杂度：O(N^2)空间复杂度：O(1)稳定性：不稳定4、堆排序​ 向下调整算法?（前提：左右子树都是小堆（大堆））​ 2.自底向上的建堆方式​ 该建堆方式是从倒数第二层的节点（叶子节点的上一层）开始，从右向左，从上到下的向下进行调整。 同样的，假设该堆为满二叉树，堆高为。同样的，假设每层高度为,每层节点数为，则 建堆的复杂度为，则有​ 同样的，该数列和差比数列，因此，可以用错位相减法，得到时间复杂度为​ 即，空间复杂度为堆排序(HeapSort)是指利用堆积树（堆）这种数据结构所设计的一种排序算法，它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆，排降序建小堆。堆排序的特征总结：堆排序使用堆来选数，效率就高了很多。时间复杂度：O(N*LogN)空间复杂度：O(1)稳定性：不稳定5.交换排序 (冒泡、快速)基本思想：所谓交换，就是 根据序列中两个记录键值的比较结果来对换着两个记录在序列中的位置，交换排序的特点是：将键值较大的记录向序列的尾巴移动，键值较小的记录向序列的前部移动。5.1 冒牌排序冒泡排序的特征总结：冒泡排序是一种非常容易理解的排序时间复杂度：O(N^2)空间复杂度：O(1)稳定性：稳定5.2 快速排序快速排序是Hoare与1962年提出的一种二叉树结构的交换排序方法，其基本思想未：任取待排序元素序列中的某元素作为基准值，按照该排序码将待排序集合分割成两个序列，左子序列中所有元素均小于基准值，右子序列中所有元素均大于基准数，然后最左右子序列重复该过程，直到所有元素都排列在相对应的位置上为止。将区间按照基准值划分为左右两半部分的常见方法有：5.2.1、挖坑法整体实现思想：快速排序的特征性总结：快速排序整体的综合性能和使用场景都是比较好的，所以才敢叫快速排序时间复杂度：O(N*LogN)空间复杂度：O(1)快排什么情况最坏？（有序）时间复杂度是多少？（O（N^2^））快排的致命缺陷：在有序时效率差不多等于插入排序，那么我们有什么办法去解决呢？三数取中int GetMidIndex(int* a, int left, int right){ int mid = (left + right) >> 1; if (a[left] < a[mid]) { if (a[mid] < a[right]) { return mid; } else if (a[left] > a[right]) { return left; } else { return right; } } else { if (a[mid] > a[right]) { return mid; } else if(a[left] < a[right]) { return left; } else { return right; } } return 0; }这样 咱们快排就基本可以算是一个综合能力非常强的一种排序了。那么我们再思考，比如最后一层调用为100万次，倒数第二层为50万次，倒数第三层为25万次。我们能不能把这些消除掉呢？小区间优化if (prvot - 1 - left >10) { QuickSort(a, left, prvot - 1); } else { InsertSort(a + left, prvot - 1 - left + 1); } if (right - (prvot + 1) > 10) { QuickSort(a, prvot + 1, right); } else { InsertSort(a + prvot + 1, right - (prvot + 1) + 1); }整体速率提升了但优化的时间也就几十毫秒，但可以想象一下 几十毫秒 计算机就执行了这么多万次使用总结：大概在小于500左右开始使用插入排序 效率比较高（10000000）官方示例为：10-13总结：小区间优化的区间取值需看整体数量来定，官方所给数量为10-13差不多为最优。5.2.2、左右指针法5.2.3、前后指针法6、归并排序基本思想：归并排序（MERGE-SORT）是建立在归并操作上的一种有效的排序算法，该算法是采用了分治法（Divide and Conquer）的一个非常典型的应用。将已有序的子序列合并，得到完全有序的序列；即先使每个子序列有序，再使子序列段间有序。若将两个有序表合并成一个有序表，称为二路归并。归并排序核心步骤： 归并排序是稳定排序，它也是一种十分高效的排序，能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法，就是归并排序的优化版本。从上文的图中可看出，每次合并操作的平均时间复杂度为O(n)，而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且，归并排序的最好，最坏，平均时间复杂度均为O(nlogn)。总结：迷宫问题迷宫问题__牛客网 (nowcoder.com)
树与二叉树1.树的概念及结构（了解）2.二叉树概念及结构3.二叉树常见OJ题练习1.树的概念及结构（了解）1.1树的概念树是一种非线性的数据结构，它是由n（n>=0）个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像以可倒挂的树，也就是说它是根朝上，而叶朝下的。有一个特殊的结点，称为根节点，根节点没有前驱结点除根结点外，其余结点被分为M（M>0）个互不相交的集合T1、T2、T3、......、Tm，其中每一个集合Ti（1<=i<=M）又是一颗结构与树类似的子树。每棵子树的根结点有且只有一个前驱，可以有0个或多个后继因此，树是递归定义的。节点的度：一个节点含有的子树的个数称为该节点的度；如上图：A的为6叶节点或终端节点：度为0的节点称为叶节点；如上图：B、C、H、I...等节点为叶节点非终端节点或分支节点：度不为0的节点：如上图：D、E、F、G...等节点为分支节点双亲结点或父节点：若一个节点含有子节点，则这个节点称为其子节点的父节点；如上图：A是B的父节点孩子节点或子节点：一个节点含有的子树的根节点称为该节点的子节点；如上图B是A的孩子节点兄弟节点：具有相同父节点的节点互称为兄弟节点；如上图：B、C是兄弟节点树的度：一棵树中，最大的节点的度称为树的度；如上图：树的度为6节点的层次：从根开始定义起，根为第1层，根的子节点为第2层，以此类推；树的高度或深度：树中节点的最大层次；如上图：树的深度为4节点的祖先：从根到该节点所经分支上的所有节点；如上图：A是所有节点的祖先子孙：以某节点为根的子树中任一节点都称为该节点的子孙。如上图：所有节点都是A的子孙森林：由m（m>0）颗互不相交的多棵树的集合称为森林；（数据结构中的学习并查集本质就是一个森林）1.2树的表示树结构相对线性表就比较复杂了，要存储表示起来就非常麻烦了，实际中树有很多种表示方法，如：双亲表示法，孩子表示法，孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法一种表示方式： struct TreeNode { int data; vector<struct TreeNode*> childs;//C++ } 另一种发送：左孩子右兄弟 如下图所示typeof int DataType; struct Node { struct Node* _firstChilds; //第一个孩子结点 struct Node* _pNextBrother; //指向其下一个兄弟结点 DataType _data; //结点中的数据域 }第三种表示法：双亲表示法双亲表示法原理：就是存双亲的下标；例如上图：A-0,B-0,C-0、D-1(A)1.3树在实际中的运用（表示文件系统的目录树结构）2.二叉树概念及结构2.1概念一颗二叉树是结点的一个有限集合，该集合或者为空，或者是由一个根结点加上两颗别称为左子树和右子树的二叉树组成。二叉树的特点：每个结点最多有两颗子树，即二叉树不存在度大于2的结点。二叉树的子树有左右之分，其子树的次序不能颠倒。2.2现实中的二叉树2.3数据结构中的二叉树：2.4特殊的二叉树：满二叉树：一个二叉树，如果每一层的结点都达到最大值，则这个二叉树就是满二叉树。也就是说，如果一个二叉树的层数为K，目结点总数是(2^K^)-1=N，层数则等于log2^N+1^。则它就是满二叉树。完全二叉树：完全二叉树是效率很高的数据结构，完全二叉树是由满二叉树而引出来的。对于深度为K的，有n个结点的二叉树，当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点——对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。​ 目结点总数是(2^K^)-1-x=N。K=log2^N+1+x^​ 搜索二叉树(特别适合搜索)：任何一颗数，左子树都比根小，右子树都比根大；(这样的增删查改就有意义了)堆：堆的逻辑结构（想象出来的结构）是一个完全二叉树，而物理结构是一个数组。堆的两大特征结构性：用数组表示的完全二叉树。有序性：任一结点的关键字是其子树所有结点的最大值（或最小值）最大堆(MaxHeap)，也称大顶堆：最大值要求：树中所有的父亲都要大于等于孩子最小堆(MinHeap)，也称小顶堆：最小值要求：树中所有父亲都要小于等于孩子那么既然是用数组来表示怎么来确定左右孩子呢？​ 左孩子：LeftChild = Parent*2+1​ 右孩子：RightChild = Parent*2+2​ 父亲：Parent = (Child - 1) / 2 （Parent单数减一，双数减二，但在程序中减一即可） for (int i = (n-1-1)/2; i >= 0; --i) { AdjustDwon(); }2.5二叉树的存储结构二叉树一般可以使用两种结构存储，一种顺序结构，一种链式结构（普通二叉树：不讲普通二叉树的增删查改，在实际中普通二叉树增删查改没有意义。有意义的是搜索二叉树）。二叉树的性质若规定根节点的层数为1，则一颗非空二叉树的第i层上最多有2^(i-1)个结点。若规定根节点的层数为1，则深度为h的二叉树的最大结点数是2^h-1。对任何一颗二叉树，如果度为0其叶结点个数为n0，度为2的分支结点个数为n2，则有n0 = n2 + 1若规定根节点的层数为1，具有n个结点的满二叉树的深度，h=logN2.5.1顺序存储：循序结构存储就是使用数组来存储，一般使用数组只适合表示完全二叉树，因为不是完全二叉树会有空间的浪费。而实现中使用中只有堆才会使用数组来存储，关于堆我们后面再讲。二叉树顺序存储在物理上是一个数组，在逻辑上是一颗二叉树。2.5.2链式存储：二叉树的链式存储结构是指，用链表来表示一棵二叉树，即用链来指示元素的逻辑关系。通常的方法是链表中每一个结点由三个域组成，数据域和左右指针域，左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链表结构又分为二叉链和三叉链，当前我们学习中一般都是二叉链，后面课程学到高阶数据结构如红黑树等会用到三叉树。//二叉链 struct BinaryTreeNode { struct BinTreeNode* pLeft; //指向当前节点左孩子 struct BinTreeNode* pRight; //指向当前节点右孩子 BTDataType _data;//当前节点值域 } //三叉链 struct BinaryTreeNode { struct BinTreeNode* pParent; //指向当前节点的双亲 struct BinTreeNode* pLeft; //指向当前节点左孩子 struct BinTreeNode* pRight; //指向当前节点右孩子 BTDataType _data;//当前节点值域 }4.二叉树链式结构的实现4.1二叉树链式结构的遍历所谓遍历(Traversal)是指沿着某条搜索路线。依次对树种每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一，是二叉树上进行其他运算的基础。前序/中序/后序的递归结构遍历：是根据访问结点操作发生位置命名​ 1.NLR：前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。​ 2.LNR：中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中（间）。​ 3.LRN：后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。​ 4.前序/中序/后序遍历也称：深度优先遍历​ 5.层序遍历也称：广度优先遍历由于被访问的结点必是某子树的根，所有N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。层序遍历：除了先序遍历、中序遍历、后序遍历外、还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1，层序遍历就是从所在二叉树的根节点出发，首先访问第一层的树根节点，然后从左到右访问第2层上的节点，接着是第三层的节点。以此类推，自上而下。自左至右逐层访问树的结点的过程就是层序遍历。分治算法：分而治之，大问题分成类似子问题，子问题再分成子问题...直到子问题不可再分割前序：A B D NULL NULL E NULL NULL C NULL NULL中序：NULL D NULL B NULL E NULL A NULL C NULL后序：NULL NULL D NULL NULL E B NULL NULL C AA B NULL D F NULL NULL NULL C E NULL NULL NULL中序：NULL B NULL F NULL D NULL A NULL E NULL C NULL4.1 AVL树4.1.1 AVL树的概念二叉搜索树虽可以缩短查找的效率，但如果数据有序或接近有序二叉搜索树将退化为单支树，查找元素相当于在顺序表中搜索元素，效率低下。因此，两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法：当向二叉搜索树中插入新结点后，如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整)，即可降低树的高度，从而减少平均搜索长度。高度平衡搜索二叉树​ 一颗AVL树或者是空树，或者是具有以下性质的二叉搜索树：它的左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)4.2 哈夫曼树4.2.1 哈夫曼树的概念当用 n 个结点（都做叶子结点且都有各自的权值）试图构建一棵树时，如果构建的这棵树的带权路径长度最小，称这棵树为“最优二叉树”，有时也叫“赫夫曼树”或者“哈夫曼树”。在构建哈弗曼树时，要使树的带权路径长度最小，只需要遵循一个原则，那就是：权重越大的结点离树根越近。在图 1 中，因为结点 a 的权值最大，所以理应直接作为根结点的孩子结点。哈夫曼树又称最优二叉树，是一种带权路径长度最短的二叉树。所谓树的带权路径长度，就是树中所有的叶结点的权值乘上其到根结点的路径长度（若根结点为0层，叶结点到根结点的路径长度为叶结点的层数）。树的路径长度是从树根到每一结点的路径长度之和，记为WPL=（W1L1+W2L2+W3L3+...+WnLn），N个权值Wi（i=1,2,...n）构成一棵有N个叶结点的二叉树，相应的叶结点的路径长度为Li（i=1,2,...n）。可以证明哈夫曼树的WPL是最小的。1、路径和路径长度在一棵树中，从一个结点往下可以达到的孩子或孙子结点之间的通路，称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1，则从根结点到第L层结点的路径长度为L-1。2、结点的权及带权路径长度若将树中结点赋给一个有着某种含义的数值，则这个数值称为该结点的权。结点的带权路径长度为：从根结点到该结点之间的路径长度与该结点的权的乘积。3、树的带权路径长度树的带权路径长度规定为所有叶子结点的带权路径长度之和，记为WPL。哈夫曼编码：编码是为了给权值列表中的权值数值分别用0，1编码。在哈夫曼树上，左分支边是0， 右分支边是1。 由此构成编码。如下图，依旧采用上面举例的哈夫曼树：编码后，1： 000，2：001， 3：01， 4：10， 5：11这样有什么用处呢？ 请看这一穿字符：[ABCABDBBDBEDEED]A出现2次，B出现5次，C出现1次，D出现4次，E出现3次。这也正好是上面举例的权值列表。 那编了这些码有什么用呢？ 用来压缩。 是哈夫曼树的作用。没有压缩前一个字符用定长的3位表示， 总共需要45位。压缩后是不定长的，而且你会发现出现次数越多的编码长度越短。比如B出现5次，编码是11， 而C只出现一次编码是000。压缩后是：001 11 000 001 11 10 11 11 10 11 01 10 01 01 10 总共是33位。压缩了12位。4.3 红黑树(R-B Tree)4.3.1 红黑树的概念1. 红黑树的规则特性：节点分为红色或者黑色；根节点必为黑色；叶子节点都为黑色，且为null；连接红色节点的两个子节点都为黑色（红黑树不会出现相邻的红色节点）；从任意节点出发，到其每个叶子节点的路径中包含相同数量的黑色节点；新加入到红黑树的节点为红色节点；(其实这个是推断出来的，下面会说)上面的6条就是红黑树给出的自动维持平衡所需要具备的规则我们需要注意的是第3条：叶子节点都为黑色，且都为null，我们在java中空置是null，所以我们能看到的都是红色的非叶子节点，但要知道它并不是叶子节点。我们看一看一个典型的红黑树到底是什么样儿？2、红黑树的规则特点能够推断出什么东西？从根节点到叶子节点的最长路径不大于最短路径的2倍 怎么样的路径算最短路径？ 从规则5中，我们知道从根节点到每个叶子节点的黑色节点数量是一样的，那么纯由黑色节点组成的路径就是最短路径； 什么样的路径算是最长路径？ 根据规则4和规则3，红黑树不可能出现连续的红色节点，若有红色节点，则必然有一个连接的黑色节点，当红色节点和黑色节点数量相同时，就是最长路径，也就是黑色节点（或红色节点）* 23、对于第6条：新加入的节点为红色节点，为什么呢？从规则4中知道，当前红黑树中从根节点到每个叶子节点的黑色节点数量是一样的，此时假如新插入的是黑色节点的话，必然破坏规则(它所在的路径上会多出一个黑色节点)，但加入红色节点却不一定，除非其父节点就是红色节点才会破坏规则，因此加入红色节点，破坏规则的可能性小一些，下面我们也会举例来说明。三、红黑树的插入操作我们还是以这个红黑树为例：插入步骤：根据二叉搜索树的特性，找到新的节点合适的插入位置，也就是找到它的爸爸决定将它作为它爸爸的左孩子还是右孩子(还是二叉搜树的特点)把它标记为红色节点，因为它可能破坏了原红黑树的规则，所以需要变色+旋转来进行调整情况一：插入之后不破坏规则，不需要旋转，也不需要变色：我们来看下面这种情况当我们插入值为【66】的节点时，红黑树变成了这样题目：1.某二叉树共有个节点399个结点，其中有199个度为2的结点，则该二叉树中的叶子结点数为（B）A：不存在这样的二叉树B：200C：198D：1992.在具有2n个结点的完全二叉树中，叶子结点个数为（A）A：nB：n+1C：n-1D：n/23.一颗完全二叉树的节点数位为531，那么这颗树的高度为（B）A：11B：10C：8D：124.清华大学计算机历年考研复试上机题//题解 #include<stdio.h> typedef struct TreeNode { struct TreeNode* left; struct TreeNode* right; char val; }TNode; void InOrder(TNode* root) { if (root == NULL) { //printf("NULL "); return; } InOrder(root->left); printf("%c ", root->val); InOrder(root->right); } TNode* CreateTree(char* a, int* p1) { if (a[*p1] == '#') { ++(*p1); return NULL; } TNode* root = (TNode*)malloc(sizeof(TNode)); if (root == NULL) { printf("malloc fail\n"); exit(-1); } root->val = a[*p1]; ++(*p1); root->left = CreateTree(a, p1); root->right = CreateTree(a, p1); return root; } int main (){ char str[100]; scanf("%s", str); int i = 0; TNode* root = CreateTree(str, &i); InOrder(root); return 0; }
栈1.栈的表示和实现1.1栈的概念及结构栈：一种特殊的线性表，其只允许在固定的一段进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶，另一端称为栈底。栈中的数据元素遵循后进先出LIFO（Last In First Out）的原则压栈：栈的插入操作叫做进栈/压栈/入栈，入数据在栈顶。出栈：栈的删除操作叫做出栈。入数据也在栈顶。栈有两种实现方式：数组、链表数组：相当于之前顺序表的尾插，尾插用尾去做了栈顶，非常合适。唯一缺陷就是：空间不够需要增容链表单链表：使用单链表就优点不合适了，除非将链表的链尾当作栈底、链头当栈顶。栈顶插入就等于链表头插入O(1)双链表：如果用尾做栈顶，那么就要用双向链表更好。效率O(1) **但一般栈的实现都会使用数组来实现**（数组的cpu缓存命中要略高一些(**预加载**)） 声名：#define _CRT_SECURE_NO_WARNINGS 1 #ifndef _TEST_H #define _TEST_H //栈的链式存储结构； typedef struct Node //创建一种类型的节点； { int data; struct Node * pnext; }NODE, *PNODE; typedef struct Stack //创建一个栈类型，里面含有两个指针，一个指向栈顶，一个指向栈底 { PNODE ptop; PNODE pBottom; }STACK, *PSTACK; void init(PSTACK ps);//初始化栈 void push(PSTACK ps, int val);//压栈 void traverse(PSTACK ps);//遍历栈 bool empty(PSTACK ps);//判断栈是否为空 bool pop(PSTACK ps, int * pval);//出栈 void clear(PSTACK ps);//清空栈 #endif //_TEST_H具体实现：#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <malloc.h> #include <stdlib.h> #include <stdbool.h> #include "test.h" int main() { //int val;//保存出栈的元素 STACK S;//定义一个栈变量 init(&S);//初始化这个栈 push(&S, 1);//压栈 push(&S, 2); push(&S, 3); push(&S, 4); push(&S, 5); push(&S, 6); traverse(&S);//遍历 /*if (pop(&S, &val))//出栈 { printf("出栈成功!出栈元素为%d \n", val); printf("-------------------------------------------------------\n"); } else { printf("出栈失败！\n"); }*/ //traverse(&S); clear(&S);//清空 printf("-------------------------------------------------------\n"); printf("清空后栈中元素为："); printf("栈中元素为：\n"); traverse(&S); //free（S）;//销毁栈 system("pause"); return 0; } void init(PSTACK ps) { ps->ptop = (PNODE)malloc(sizeof(NODE));//创建一个空栈 if (NULL == ps->ptop) { printf("success\n"); exit(-1); } else { ps->pBottom = ps->ptop;//栈底指针跟栈顶指针都指向这个新建的空节点 ps->ptop->pnext = NULL; } } void push(PSTACK ps, int val)//压栈 { PNODE pNew = (PNODE)malloc(sizeof(NODE));//新建一个节点 pNew->data = val;//新节点数据域等于val pNew->pnext = ps->ptop;//新节点指针域指向栈顶指针所指向的节点，就是前面栈底跟栈顶同时指向的空节点，这样就实现压栈了 ps->ptop = pNew;//然后栈顶元素上移，始终指向就是最后入栈的节点 } void traverse(PSTACK ps)//遍历 { PNODE p = ps->ptop;//遍历是从栈顶依次输出，所以创建一个指针，指向栈顶指针所指向的节点 while (p!=ps->pBottom)//当p所指向的节点不是栈底指针指向的节点时 { printf("%d\n", p->data);//输出p所指向的节点的数据域的值 p = p->pnext;//然后指针p重新指向下一个节点 } } //bool pop(PSTACK ps, int * pval)//出栈一个元素 //{ // if (empty(ps))//先判断栈是否为空 // { // return false; // } // else//不为空时 // { // PNODE r = ps->ptop;//创建一个新的指针r，指向栈顶指针所指向的节点 // *pval = r->data;//将该节点的数据域赋值给val // ps->ptop = r->pnext;//栈顶指针重新指向r所指向的下一个节点， // free(r);//释放r所指向的节点，就是刚才的栈顶节点 // r = NULL;//清空 // return true; // } //} bool empty(PSTACK ps)//判断栈是否为空 { if (ps->ptop == ps->pBottom) return true; else return false; } void clear(PSTACK ps)//清空栈内元素 { if (empty(ps)) { return ; } else { PNODE p = ps->ptop;//创建一个指针，指向当前栈顶指针指向的节点 PNODE q = NULL;//创建一个中间指针 while (p!=ps->pBottom)//当p所指向的节点不是栈底指针所指向的节点时 { q = p->pnext;//先将p所指向的下一个节点赋给q free(p);//然后删除该节点 p = q;//然后将q指向的节点赋给p,也就是p重新指向了p的下一个节点 } ps->ptop = ps->pBottom;//循环完了之后，栈底指针跟栈底指针指向同一个节点 } }2.队列2.1队列的概率及结构队列：只允许在一端进行插入数据操作，在另一端进行删除数据操作的特殊线性表，队列具有先进先出FIFO（First in First Out）入队列：进行插入操作的一端称为队尾出队列：进行删除操作的一端称为队头2.2队列的实现队列也可以数组和链表的结构实现，使用链表的结构实现更优一些，因为如果使用数据的结构，出队列在数组头上出数据，效率会比较低。记住两个公式：队列中，队列满的条件是：（rear+1）%QueueSize=front；​ 队列长度公式是：（rear-front+QueueSize）%QueueSize。#pragma once #include <stdio.h> #include <stdbool.h> #include<assert.h> #include<stdlib.h> typedef int QDataType; typedef struct QueueNode { struct QueueNode* next; QDataType data; }QNode; typedef struct Queue { //这样的好处就是消除二级指针 QNode* tail; QNode* head; }Queue; void QueueInit(Queue* pq); void QueueDestory(Queue* pq); //规定：队尾入 void QueuePush(Queue* pq, QDataType x); //队头出 void QueuePop(Queue* pq); QDataType QueueFront(Queue* pq); QDataType QueueBack(Queue* pq); int QueueSize(Queue* pq); bool QueueEmpty(Queue* pq); 3.栈和队列OJ题1.括号匹配问题。（力扣搜索）2.用队列实现栈。3.用栈实现队列。
什么是Webpack​ 本质上，Webpack 是一个现代 JavaScript 应用程序的静态模块打包器（module bundler）。当webpack 处理应用程序是，它会递归地构建一个依赖关系图（dependency graph），其中包含应用程序需要的每个模块，然后将所有这些模块打包成一个或多个bundle。​ Webpack是当下最热门的前端资源模块化管理和打包工具，它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离，等到实际需要再异步加载。通过loader转换，任何形式的资源都可以当做模块，比如 CommonsJS、AMD、ES6、CSS、JSON、Coffeescript、LESS等；​ 伴随着移动互联网的大潮，当今越来越多的网站已经从网页模式进化到了 WebApp模式。它们运行在现代浏览器里，使用 HTML、CSS3、ES6 等新的技术来开发丰富的功能，网页已经不仅仅是完成浏览器的基本需求；WebApp 通常是一个 SPA（单页面应用），每一个视图通过异步的方式加载，这导致页面初始化和使用过程中会加载越来越多的 JS 代码，这给前端的开发流程和资源组织带来了巨大挑战。​ 前端开发和其他开发工作的主要区别，首先是前端基于多语言、多层次的编码和组织工作，其次前端产品的交付是基于浏览器的，这些资源是通过增量加载的方式运行到浏览器端，如何在开发环境组织好这些碎片化的代码和资源，并且保证他们在浏览器端快速、优雅的加载和更新，就需要一个模块化系统，这个理想中的模块化系统是前端工程师多年来一直探索的难题。模块化的演进Script 标签 ​ 这是最原始的 JavaScript 为文件加载方式，如果把每一个文件看做是一个模块，那么他们的接口通常是暴露在全局作用域下，也就是定义在 window 对象中，不同模块的条用都是一个作用域。​ 这种元素的加载方式暴露了一些显而易见的弊端：全局作用域下容易造成变量冲突文件只能按照 7. 在IDEA 控制台中直接执行webpack；如果失败的话，就使用管理员权限运行即可！8.运行 HTML 看效果 说明：webpack --w
ajax H5 游客姓名 入境时间 出境时间 国籍名称 ajax主要使用XMLHttpRequest，也可以将ajax看做一个方法后台//首先需引用 using Newtonsoft.Json; public string GetTourists()//ajax调用GetTourists 返回一个json数组 在前端 进行数值绑定 { var tours = db.Tourists.Select(p => new { p.TouristID, p.TouristName, p.StartTime, p.EndTime, p.Nationality.NationalityName }); return JsonConvert.SerializeObject(tours); } 可实用的方法Model封装 数据库对象 用户姓名 证件号 入境时间 出境时间 头像 国籍 中国 美国 条件:前端name必须和数据库列名相同//数据封装 装箱 Tourist tourist = new Tourist(); tourist.TouristName = Request["TouristName"]; tourist.CardID = Request["CardID"]; tourist.StartTime = Convert.ToDateTime(Request["StartTime"]); tourist.EndTime = Convert.ToDateTime(Request["EndTime"]); tourist.HeadPic = Request["HeadPic"]; tourist.NationalityID = Convert.ToInt32(Request["NationalityID"]); Tourist tourist = RequesModel<Tourist>(); //和下面方法等同Tourist tourist = RequesModel<Tourist>(); public T RequesModel<T>() where T : new() //where T:new()表示泛型约束,T类型 { T model = new T(); //获取对象的所有属性 Type modelType = model.GetType(); //从类型对象中获取该对象的所有属性 PropertyInfo[] props = modelType.GetProperties(); //遍历所有的属性 foreach (var prop in props) { //取出属性的名称 string propName = prop.Name; //按属性名称获取表单数据 string propVal = Request[propName]; //判断获取的值是否存在 if (!string.IsNullOrEmpty(propVal)) { //值获取到 就可以给model赋值了 //获取属性的数据类型 Type propType = prop.PropertyType;//可能存在可空类型 所有需要判断 //获取可空类型的基础类型 Type underType = Nullable.GetUnderlyingType(propType); prop.SetValue(model,Convert.ChangeType(propVal,underType??propType));//给model对象的prop对应的属性赋值 //??的意思是如果前方为空就获取后方 前方不为空就获取前方 } } return model; }
简介[scode type="blue"]DOM：文档对象模型[/scode][scode type="blue"]BOM：浏览器对象模型[/scode]核心浏览器网页就是一个DOM树形结构！更新：更新DOM节点遍历DOM节点：得到DOM节点删除：删除一个DOM节点添加：添加一个新的节点那么要操作一个DOM节点，就必须要先获得这个DOM节点获得DOM节点 java 陈捷 李泽群 链接1 链接2 链接3 链接4 链接5 123 这是原生代码，之后尽量都是用jQuery();1、更新节点var a = document.getElementById('id1') undefined a.innerText= 666 //插入修改节点 666 a.innerHTML = '666' "666"操作文本a.innerText= 666 修改文本值（但会覆盖节点）a.innerHTML = 666 可以解析HTML文本标签操作JS（JavaScript能让网页上变动）样式id1.style.color = 'red' //属性使用 字符串 包裹 id1.style.color = 'yellow' id1.style.fontSize = '200px' // - 转 驼峰命名问题2、删除节点删除节点的步骤：先获取父节点，再通过父节点删除自己。 java 傻逼陈捷 傻逼李泽群 注意：删除多个节点的时候，children是时刻在变化的，删除节点的时候一定要注意~3、插入节点先获得某个DOM节点，假设这个DOM节点是空的，我们通过innerHTML，就可以增加一个元素。但如果这个DOM节点本身存在元素，那么innerHTML会将原本的元素覆盖！ 那么最好是在后面追加元素appendappendChild() //追加到后面 JavaScript C# Java C++ 创建新标签，实现插入document.createElement('p')//通过js 创建一个新的节点 var newP = document.createElement('p');//创建一个p标签 newP.id = 'newP'; newP.innerText = '666我的宝贝'; salf.appendChild(newP); //将p标签追加在salf后面 包括也能添加Script、cssvar dd = document.getElementById('id2'); 'use strict' //创建一个标签节点 var myScript = document.createElement('script'); myScript.setAttribute('type','text/javascript')//非常多的玩法，只有你想不到，没有他做不到，所以为什么javascript这么随意 var body = document.getElementsByTagName('body') body[0].style.backgroundColor = '#000';//第一种增加方式 body[0].setAttribute('style','background-color: rgb(0, 0, 0);')//第二种方式，建议使用第二种，因为后端都是通过这种方式去增加 //需要注意的是像这种取值，取到的是一个集合第三种方式//可以创建一个Style标签 var myStyle = document.createElement('style');//创建一个空style标签 myStyle.setAttribute('type','text/css'); myStyle.innerHTML = 'boby{background-color:chartreuser}';//设置标签内容 document.getElementByTagName('head')[0].appendChild(myStyle);JavaScript方法方式许许多多，只要逻辑行的通都可以。insertinsertBefore方法可在已有的子节点前插入一个新的子节点 JavaScript C# Java C++ replaceChild(newNode,oldNode) 替换节点··removeChild(Node) 删除子节点