自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(59)
  • 收藏
  • 关注

原创 7.经典数据结构与算法常见题目(二)

1.洗衣机问题int test01(vector<int> vec) { if (vec.size() == 0) return 0; int size = vec.size(); int sum = 0; for (int i = 0; i < size; i++) { sum += vec[i]; } if (sum % size != 0) { return -1; } int avg = sum / size; int leftSum = 0; in

2022-05-06 13:23:28 720

原创 6.经典数据结构与算法题目(一)

先给一个二叉树节点结构:class Node2 {public: Node2(int val){ left = nullptr; right = nullptr; this->val = val; } Node2* left; Node2* right; int val;};1.给定一个非负整数n,代表二叉树的节点个数,返回能形成多少种不同的二叉树结构int test02(int n) { if (n < 0) return 0; if (n <=

2022-05-04 13:02:20 1164

原创 5.递归和动态规划

1.汉诺塔问题(递归)//解法1void rightTomid(int);void rightToleft(int);void leftTomid(int);void midToleft(int);void midToright(int);void leftToright(int);void rightTomid(int n) { if (n == 1) { cout << "move 1 from right to mid" << endl; return

2022-04-25 17:48:18 999

原创 4.贪心算法和并查集以及图

关于贪心算法,左神网课中总结的其实是没有定式的,也就是说,我们一般是通过尝试不同的贪心策略来获得正确的贪心策略,而通过对数器的方法来验证贪心策略的选择是否正确。其实还是要多刷相关的题目,感觉正确的贪心策略如果面试的时候遇到的话没见过的题目应该是想不出来的 - -直接上题目了1.给定一个二维数组,其中每个元素的第一个元素代表会议的开始时间,第二个元素代表会议的结束时间,会议室一个时间段只能安排一个会议,问最多能安排多少个会议int test01(std::vector<std::vector&l

2022-04-21 13:06:59 1012

原创 3.二叉树的基本算法

对上课中所涉及到的代码题做一些总结同样的首先给出以下数据结构:class TreeNode {public: TreeNode(); TreeNode(int val); int val; TreeNode* left; TreeNode* right;};TreeNode::TreeNode():val(0),left(nullptr),right(nullptr) {}TreeNode::TreeNode(int val) { this->val = val;

2022-04-14 12:36:33 981

原创 2.链表相关面试题

对于左神课上讲到的算法题,自己从头到尾基本敲了一遍,因为是先回忆了几道题后,有重新打开网课看了一下题目,因此顺序可能和讲课顺序不太一样,不过应该基本都实现了。这里先给两个链表结点数据结构,一个带有随机指针,一个不带。class ListNode {public: ListNode(); ListNode(int val); int val; ListNode* next;};//带随机指针的链表class ListNode1 {public: ListNode1(); Li

2022-04-13 17:13:17 164

原创 1.常见的排序算法及前缀树

引言在最近刷Leetcode的过程中,感觉自己为了刷题而刷题,思考太少了。虽然刷了400多了但是感觉遇到新的题还是会做的很吃力,因此打算从头扎扎实实的再过一遍数据结构与算法,所有的东西自己思考码一遍,这个专题过完之后补一些看书笔记。本专题的顺序以左神的讲课顺序来排列,算是自己的一些课后总结吧,如有错误欢迎指正。1.选择排序:时间复杂度:O(N^2) 时间复杂度:O(1)稳定性:不稳定代码:void insectSort(std::vector<int>& vec) { f

2022-04-12 18:30:10 232

原创 C++单例模式实现

单例模式:单例模式通俗的讲就是对应一个类而言,所能实例化出来的对象只能有一个。单例模式:饿汉式和懒汉式饿汉式:在单例定义的时候就进行实例化,在访问量较大的情况下,采用饿汉式可以实现更好的性能。懒汉式:尽可能的不要对单例进行实例化,适用于访问量很小的情况下。单例模式的代码书写步骤:1.首先讲构造函数全部设置为私有2.设置一个静态的本类的指针,类内声明类外初始化3.定义一个接口提供对于本类唯一的实例进行访问的接口,也设置为static函数,类内声明类外初始化。饿汉式:class Singlet

2021-12-16 20:26:38 763

原创 C++动态内存、智能指针及实现

这篇博客主要对于C++中的动态内存和智能指针。1.动态内存首先在C++中,共有三种请求堆空间的new方法:1.普通的new函数:这是我们经常使用的一种开辟堆空间的方式,这里想顺便再总结一下关于他的内部实现。在之前的博客STL源码剖析的读书笔记中其实也总结过关于普通的new的一些内部的做法,首先明确一点,在与C语言的malloc比较之前,我们先分清在C++中new首先是一个关键字。其次,从功能上看,new其实是与malloc类似的功能,都是开辟堆空间,但是在C++中由于引入了对象的概念,因此,当我们对

2021-12-16 20:15:31 992

原创 C++多态以及通过C语言实现多态

最近要开始准备实习了,所以需要对之前学过的知识做一些总结和回顾,最近发的博客可能更多的是对一些概念的理解。通过C语言实现C++多态一.C++多态的原理在C++中,多态机制是通过虚表以及虚表指针来实现的。我们都知道,当要实现多态功能的时候,都会通过virtual关键字来讲基类中的函数变为虚函数,然后通过子类继承后重写就能够达到多态的效果。但是实际上,在其中,编译器也为我们做了很多的工作。1.当编译器发现基类中存在虚函数的时候,他会自动的生成一份虚表,在虚表中,存放的就是我们这些虚函数的入口地址。2

2021-12-16 17:58:37 1292

原创 UNIX 环境高级编程读书笔记(1)

在Liunx系统编程中,经常会出现或多或少的困惑,虽然基本大部分会用,但是可能感觉还是因为对于一些概念性的东西了解的不够深入,因此重新读一遍APUE,从头梳理一遍知识点。这篇总结博客是大概暑假的时候就准备看书总结了,但是这段时间因为个人的一些原因导致状态很差,最近感觉调整的好一些了,因此决定用博客来记录自己的读书笔记,一是为了熟练的使用,自己能动手敲巩固一遍的话效果可能更好,二是为了明年的实习(假如导师放实习的话- -。那就直接从第三章文件I/O开始了。如果存在问题的话,欢迎指出!1.文件I/O1.

2021-12-01 21:37:15 628

原创 计算机网络读书笔记(五)

链路层和局域网我们将看到两种截然不同的链路层信道,第一种类型是广播信道,这种信道用于连接有线局域网、卫星网和混合光纤同轴电缆接入网中的多台主机。第二种类型的链路层信道是点对点通信链路,这一般应用在长距离链路连接的两台路由器之间,或用户办公室计算机与他们所连接的邻近以太网交换机之间等场合。一.概述运行链路层协议的任何设备均称为结点,结点包括主机、路由器、交换机和WiFi接入点。我们把沿着通信路径连接相邻结点的通信信道称为链路,为了将一个数据报从源主机传输到目的主机,数据报必须通过沿端到端路径上的各段链路

2021-08-22 19:58:05 819

原创 计算机网络读书笔记(四)

网络层一.数据平面我们将网络层分解为两个相互作用的部分,即数据平面和物理平面。本章首先学习数据平面的作用,即网络层中路由器的功能,数据平面部分决定到达路由器输入链路之一的数据报如何转发到该路由器的输出链路之一。在下一章中我们将涉及网络层控制平面功能,即网络范围的逻辑,该控制平面功能控制数据报沿着源主机到目的主机的端到端路径中路由器之间的路由方式,我们将学习路由选择算法,以及路由选择协议。1.网络层概述假设一个简单的网络其中有H1和H2两台主机,并在H1和H2之间的路劲上有几台路由器,其中H1正在向H

2021-08-21 13:39:52 1769

原创 计算机网络读书笔记(三)

传输层传输层位与应用层和网络层之间,是分层的网络体系结构的重要部分。该层为运行在不同主机上的应用进程提供直接的通信服务起着至关重要的作用。一. 概述和传输层服务运输层协议为运行在不同主机上的应用程序之间提供了逻辑通信功能。从应用程序的角度看,通过逻辑通信,运行不同进程的主机好像直接相连一样,实际上,这些主机是通过很多路由器及不同类型的链路相连,也就是说,。...

2021-08-18 19:04:33 1188

原创 计算机网络读书笔记(二)

一.应用层1.1 应用层协议原理在Web应用程序中,有两个互相通信的不同的程序:一个是运行在用户主机上的浏览器程序;另一个是运行在Web服务器主机上的Web服务器程序。这里采用的是C/S体系结构,即服务端的服务器一直运行,有固定的IP地址和端口号,扩展性差;客户端则主动与服务器通信,与互联网有间歇性的连接,可能有间歇性的连接,可能是动态的IP地址。另一个例子是P2P文件共享系统,在参与文件共享的社区中的每台主机中都有一个程序。在对等体(P2P)体系结构下,几乎没有一直运行的服务器,任意端系统之间可以通信

2021-08-15 18:16:26 1496

原创 计算机网络读书笔记(一)

一.计算机网络和因特网1.什么是因特网1.1 因特网的具体构成描述因特网是一个世界范围的计算机网络,在因特网中,所有接入因特网的设备都被称为主机或端系统。端系统通过通信链路和分组交换机连接到一起。不同的链路能够以不同的速率传输数据,链路的传输速率以比特/秒(bit/s)度量。当一台端系统要向另一台端系统发送数据时,发送端系统将数据分段,并为每段加上首部字节,由此形成的信息包用计算机网络的术语来说称为分组。这些分组通过网络发送到目的端系统,并在那里被装配成初始数据。分组交换机从它的一条通信链路接收到

2021-08-05 18:16:20 1040

原创 STL源码剖析6:仿函数以及适配器

1.仿函数1.1 简介仿函数即意为函数对象,就是一种具有函数特性的对象,在上一章介绍的算法中,我们可以看到很多的算法往往有两个版本。第一个版本就是表现出最常见的运算,第二个版本则是体现出泛化性的版本,允许用户传入指定的仿函数来完成对应的功能,因此仿函数可以被认为是一种算法的策略作用于算法上。从实现的角度而言,仿函数其实就是一个行为类似于函数的对象,为了能够实现这个功能,因此也就需要我们必须重载function的operator()函数,这样,当我们在仿函数对象后加上()后,对象可以像函数一样的被调用.

2021-07-14 10:16:09 161

原创 STL源码剖析:5.算法

1.简介以有限的步骤解决逻辑或数学上的问题,这一专门的科目被称为算法。本章讨论的就是一些被收录于STL中,极具复用价值的算法(以下仅有部分算法)2.数值算法2.1 算法accumulate用来计算init和[first,last)内所有元素的总和,也就是累加返回总和即可,在某些不可以直接进行加操作的类型中,需要传入相应的仿函数,由仿函数完成类似于加的操作。template<class Iterator, class T>T accumulate(Iterator first, It.

2021-07-13 12:01:51 130 1

原创 STL源码剖析:4.关联式容器(下)

1.hashtable1.1 概述hashtable也被成为哈希表(散列表)等、这种结构在插入和删除,以及搜寻等奥做上具有常数平均时间的表现,而且这种表现是以统计为基础,不需要仰赖输入元素的随机性。hashtable可提供对任何有名项的存取操作和删除操作,由于操作对象是有名项,所以hashtable也可被视为一种字典结构,这种结构的用意在于提供常数时间的基本操作。我们可以开辟一个足够大的数组,在数组的每一位上,记录我们存入的元素,我们通过散列函数hash function来完成将传入的元素转换为相.

2021-07-09 17:05:10 108

原创 STL源码剖析:4.关联式容器(上)

1.简介 除了常见的序列式容器之外,我们也经常使用关联式问题来解决一些问题,关联式容器相比较序列式容器而言,其主要特性就是它可以通过键值对容器内部的元素来进行索引。2.RB-tree2.1 简介在序列式容器中很大一部分都是通过底层使用红黑树来实现的,所谓红黑树首先他是一个二叉搜索树,树上的每一个结点不是红色就是黑色,并且它还需要满足以下性质:(我们将NULL视为黑色的结点)1.每个结点b不是红色就是黑色。2.根节点为黑色。3.如果某一个结点为红色,那么其子节点必定为黑色。4.任意一个.

2021-07-07 20:35:07 101

原创 STL源码剖析:3.序列式容器(下)

1.deque容器deque容器是一种双向开口的连续线性空间,双向开口意为可以在头尾两端分别做元素的插入和删除操作。deque没有容量的概念,因为它是动态地以连续空间组合而成,随时可以增加一段新的空间并链接起来。1.1.deque容器的中控器从逻辑上看来deque是连续空间,deque是由一段一段的定量连续空间组成,一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串联在整个deque的头端或尾端,deque的最大任务便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随.

2021-06-20 17:24:05 298

原创 STL源码剖析:3.序列式容器(上)

1.简介 容器是大部分人对于STL的第一印象,用于存储元素。所谓序列式容器,其中的元素可以是有序的也可以是无序的,除了C++本身提供的array容器以外,STL另外提供vector,list,deque,stack,queue,priority_queue容器。其中的queue以及stack其实就是内部内含一个deque容器,其基本功能都是通过deque来实现的,在之后代码展示中会呈现。2.vector容器 vector容器与平时常用的array数组非常的相似,二者的唯一差别在于空间的运用的

2021-06-20 15:08:33 112

原创 STL源码剖析:2.iterator迭代器以及traits技法

1.简介在STL的设计中,我们最常使用到的就是算法和容器,最为两个独立的个体,我们需要用一个独特的设计来使得二者能够有效的结合,以此来达到我们使用的目的。因此,迭代器便产生了,它的作用是作为一种胶合剂,使得容器以及算法能够有效的结合在一起,以达到我们使用的目的,其中还涉及到了一种巧妙的设计技法traits。每一部分都是按照侯捷老师的《STL源码剖析》的内容以及顺序进行的,如果发现错误欢迎指正。2.迭代器迭代器一共有五种分类:struct input_iterator_tag {};struct.

2021-06-09 23:47:41 237

原创 STL源码剖析:1.空间配置器

1.简介 在STL的使用者层面上,空间配置器一般是隐藏的,使用者不需要知道其具体实现细节即可进行使用;但是从STL的实现角度来说,由于整个STL的操作对象都存放在容器之内,而容器需要配置一定的空间来进行存放数据,因此,想要实现或者说STL的第一步便是掌握空间配置器的原理。每一部分都是按照侯捷老师的《STL源码剖析》的内容以及顺序进行的,如果发现错误欢迎指正。2.空间配置器首先对于C++中的delete和new函数进行一些解释: 在我们使用new来建造一个对象的时候,一般是分为两个步骤的.

2021-06-08 17:27:18 447

原创 Linux系统编程Day09

1.互斥锁1.1 同步与互斥概述 现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能: 1.都需要访问/使用同一种资源. 2.多个任务之间有依赖关系,某个任务的运行依赖于另一个任务 这两种情形是多任务编程中遇到的最基本的问题,也是多任务编程中的核心问题,同步和互斥就是用于解决这两个问题的。 互斥:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这

2021-03-14 17:38:59 56

原创 Linux系统编程Day08

1.终端的概念 进程中,控制终端是保存在PCB中的信息,而fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。 默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。 在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl+C表示SIGINT,Ctrl+\表示SIGQUIT。 函数说明: #include <un

2021-03-13 16:31:53 110

原创 Linux系统编程Day07

1.匿名映射实现父子进程通信 通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。 通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。 其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。 使用MAP_ANONYMOUS (或MAP_ANON)。 int *p =

2021-03-11 19:19:33 101

原创 Linux系统编程Day06

1.进程状态1.1 孤儿进程 父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>int main(){ pid_t pid =

2021-03-07 17:09:24 101

原创 Linux系统编程Day05

1.文件操作1.1 fcnlt函数#include <unistd.h>#include <fcntl.h>​int fcntl(int fd, int cmd, ... /* arg */);功能:改变已打开的文件性质,fcntl针对描述符提供控制。参数: fd:操作的文件描述符 cmd:操作方式 arg:针对cmd的值,fcntl能够接受第三个参数int arg。返回值: 成功:返回某个其他值 失败:-1 fcnt

2021-03-05 20:39:02 148

原创 Linux系统编程Day04

1.系统调用1.1 什么是系统调用 系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。 从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。1.2 系统调用的实现

2021-03-04 17:05:02 173 2

原创 Linux系统编程Day03

1. GCC编译器1.1 GCC工作流程和常用选项 gcc编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了四个步骤: 1.预处理(生成.i文件 即gcc -E test.c -o test.i) 2.编译(生成.s文件 即gcc -S test.i -o test.s) 3.汇编(生成.o文件 即gcc -c test.s -o test.o) 4.链接(生成可执行程序 gcc test.o -o test) 注:如果不加-o test 会自动生成一个可执行a.out文件

2021-03-03 20:18:22 156 1

原创 Linux系统编程Day02

1.Linux基础命令1.1 grep命令 Linux系统中grep命令是一种强大的文本搜索工具,grep允许对文本文件进行模式查找。如果找到匹配模式, grep打印包含模式的所有行。grep一般格式为: grep [-选项] ‘搜索内容串’ 文件名在grep命令中输入字符串参数时,最好引号或双引号括起来。例如:grep ‘a ’1.txt。常用选项说明: 选项 含义 -v 显示不包含匹配文本的所有行(相当于求反) -n 显示匹配行及行号 -i 忽略大小

2021-03-02 18:42:30 98

原创 Linux系统编程Day01

1.Linux常见目录介绍 /:根目录,一般根目录下只存放目录,在Linux下有且只有一个根目录。所有的东西都是从这里开始。当你在终端里输入“/home”,其实是在告诉电脑,先从/(根目录)开始,再进入到home目录。 /bin: /usr/bin: 可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。 /boot:放置linux系统启动时用到的一些文件,如Linux的内核文件:/boot/vmlinuz,系统引导管理器:/boot/grub。 /dev:存放linux系

2021-03-01 18:43:57 121

原创 C++核心编程Day10

1.算法1.1 算法概述 算法主要是由头文件<algorithm><functional><numeric>组成。 1.<algorithm>是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找, 遍历,复制,修改,反转,排序,合并等... 2.<numeric>体积很小,只包括在几个序列容器上进行的简单运算的模板函数. 3.<functional> 定义了一些模板类,用以声明函数对象。1.2 常用遍历算

2021-02-20 19:17:20 78

原创 C++核心编程Day09

1.STL容器1.1 set容器 1.1.1 set容器基本概念 Set的特性是:所有元素都会根据元素的键值自动被排序。Set的元素不像map那样可以 同时拥有实值和键值,set的元素即是键值又是实值。Set不允许两个元素有相同的键值。 set拥有和list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候, 操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一 个例外。 1.1.2 multiset容器基本概念 multiset特性及用

2021-02-19 21:23:48 128 1

原创 C++核心编程Day08

1.STL基本概念1.1简介 STL(Standard Template Library,标准模板库),STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。STL六大组件简介: 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据 算法:各种常用的算法,

2021-02-18 18:41:56 185 1

原创 C++核心编程Day07

1.C++类型转换1.1静态转换 1.用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。 2.进行上行转换(把派生类的指针或引用转换成基类表示)是安全的; 3.进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。 4.用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证。//1.基础类型转换void test01(){ char a = 'a'; //static_ca

2021-02-16 21:26:43 146 1

原创 C++核心编程Day06

1.模板1.1 模板概论 c++提供了函数模板,即是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。 c++提供两种模板机制:函数模板和类模板 总结: 1.模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。 2.模板用于表达逻辑结构相同,但具体数据元素类

2021-02-14 15:58:34 369 1

原创 C++核心编程Day05

1.继承1.1 继承的基本概念 c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。派生类中的成员,包含两大部分: 1.一类是从基类继承过来的,一类是自己增加的成员。 2.从基类继承过过来的表现其共性,而新增的成员体现了其个性。1.2 继承方式三种继承方式: public : 公有继承 1.父类的

2021-02-14 00:51:30 115 1

原创 C++核心编程Day04

1.常函数和常对象1.1.常函数 1.在函数的()后面加上const,这个函数就是常函数 2.常函数内不能修改普通成员变量 3.const修饰的是this指针指向的空间中的变量不能改变 4.mutable修饰的成员变量在常函数中可以修改1.2.常对象 1.在数据类型前面加上const,让对象成为常对象 2.常对象可以调用常函数 3.常对象可以修改mutable修饰的成员变量#define _CRT_SECURE_NO_WARNINGS#include<iostream>

2021-02-12 23:51:14 67

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除