精华内容
下载资源
问答
  • 《mysql存储引擎InnoDB详解,底层看清InnoDB数据结构》,我们已经知道了数据页内各个记录是按主键正序排列并组成了一个单向链表,并且各个数据页之间形成了双向链表。在数据页内,通过页目录,根据主键可以...

    从《mysql存储引擎InnoDB详解,从底层看清InnoDB数据结构》中,我们已经知道了数据页内各个记录是按主键正序排列并组成了一个单向链表的,并且各个数据页之间形成了双向链表。在数据页内,通过页目录,根据主键可以快速定位到一条记录。这一章中,我们深入理解一下mysql索引实现。

    本文主要内容是根据掘金小册《从根儿上理解 MySQL》整理而来。如想详细了解,建议购买掘金小册阅读。

    索引数据结构

    先回顾一下上一章节中数据页基本结构:

    format,png

    从上图可以推断出,查询某条记录关键步骤只有2个:

    定位到数据页

    定位到记录

    如果没有索引,查询某条记录只能先依次遍历数据页,确定记录所在的数据页之后;再从数据页中通过页目录定位到具体的记录,这样做效率肯定是很低的。

    为了方便说明,先建一张示例表:

    mysql> CREATE TABLE index_demo(

    -> c1 INT,

    -> c2 INT,

    -> c3 CHAR(1),

    -> PRIMARY KEY(c1)

    -> ) ROW_FORMAT = Compact;

    Query OK, 0 rows affected (0.03 sec)

    为了展示便方便,行格式中只展示record_type、next_record和实际各列的值。

    format,png

    把一些记录放到页里边的示意图就是:

    format,png

    上面提到过,数据页中的记录是按主键正序排列的。实际上就是为了能够使用二分查找法快速定位一条记录。同理,要想快速定位一个数据页,也得保证各个数据页是按顺序排序的。排序的规则就是后一个数据页的最小主键必须大于当前数据页的最大主键。这样实际上就保证了,所有记录的主键都是正序排列的了。

    页分裂

    假设每个数据页最多只能存放3条记录。现在index_demo插入了3条记录 (1, 4, 'u'), (3, 9, 'd'), (5, 3, 'y')。

    format,png

    然后,再向index_demo插入一条记录(4, 4, 'a')。由于每个数据页最多只能存放3条记录,并且还要保证所有记录主键是按主键正序排列的。mysql会新建一个页面(假设是页28),然后将主键值为5的记录移动到页28中,最后再把主键值为4的记录插入到页10中。

    format,png

    简单来说,当向一个已经存满记录的数据页插入新记录时,mysql会以新插入记录的位置为界,把当前页面分裂为2个页面,最后再将新记录插入进去。

    mysql索引实现

    假设index_demo已经存在多条记录,数据页结构如下所示:

    format,png

    为了能够使用二分法快速查找数据页,我们可以给每个数据页建一个目录项,每个目录项主要包含两部分数据:

    页的用户记录中最小的主键值,我们用key来表示。

    页号,我们用page_no表示。

    format,png

    在mysql中,这些目录项其实就是另一类型的数据记录,称为目录项数据记录(record_type=1),目录项数据记录也是存储在页中的,同一页中的目录项数据记录也可以通过页目录快速定位。

    format,png

    虽然目录项记录基本只存储了主键值和页号。但是当表中的数据很多时,一个数据页肯定是无法保存所有的目录项记录的。因此存储目录项记录的数据页实际上可能有很多个。

    format,png

    这个时候,我们就需要快速定位存储目录项记录的数据页了。实际上,我们只需要生成更高级的目录即可,同时保证最高一级的目录项记录的数据页只有一个。这样就能根据主键从上到下快速定位到一条记录了。

    format,png

    实际上,上面的结构就是一颗B+树。实际的用户记录其实都存放在B+树的叶子节点上,而非叶子节点存放的是目录项。

    聚簇索引

    上面介绍的索引实际上就是聚簇索引,它有两个特点:

    使用主键值的大小进行记录和页的排序,这包括三个方面的含义:

    页内的记录是按照主键的大小顺序排成一个单向链表。

    各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。

    存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。

    B+树的叶子节点存储的是完整的用户记录。

    InnoDB存储引擎会自动根据主键创建聚簇索引。同时,聚簇索引就是InnoDB存储引擎中数据的存储方式(所有的用户记录都存储在了叶子节点),也就是所谓的索引即数据,数据即索引。

    二级索引

    在实际场景中,我们更多的是为某个列建立二级索引。实际上,二级索引和聚簇索引实现的原理一样的。主要的区别只有2个:

    使用索引列的值的大小进行记录和页的排序。

    B+树的叶子节点存储的是对应记录的主键值。

    如图是以c2列建立的二级索引:

    format,png

    由于B+树的叶子节点存储的是对应记录的主键值。如果我们要查询完成记录的话,在拿到主键之后,再需要再到聚簇索引中查出用户记录,这个过程也叫回表。

    联合索引

    在实际场景中,经常也出现为多个列建立一个索引的情况,这种索引也称为联合索引。联合索引本质上也是二级索引,区别仅仅在于由一个列变为多个列而已。简单来说就是同时以多个列的大小作为排序规则,也就是同时为多个列建立索引。比如我们为c2和c3列建立联合索引:

    先把各个记录和页按照c2列进行排序。

    在记录的c2列相同的情况下,采用c3列进行排序。

    format,png

    InnoDB的B+树索引的注意事项

    根节点不变性

    上面介绍B+树的时候,为了理解方便,采用自下而上的方式介绍。实际上,B+树的形成过程如下:

    每次为某个表创建B+索引的时候,都会为这个索引创建一个根节点页面。当表中没有记录时,每个B+根节点既没有用户记录,也没有目录项记录。

    随后向表中插入用户记录时,先把用户记录存储到根节点中。

    当根节点空间用完后,再次插入数据。会将根节点数据复制到一个新页中,再对这个新页进行页分裂操作。此时,根节点自动升级为存储目录项记录的页。

    可以看出,一个B+树索引的根节点自诞生之日起,便不会再移动。

    内节点中目录项记录的唯一性

    我们知道B+树索引的内节点中目录项记录的内容是索引列+页号的搭配,但是这个搭配对于二级索引来说有点儿不严谨。为了保证内节点目录项记录的唯一性,目录项还需要存储主键值数据。也就是说,目录项记录的内容包含索引列的值、主键值和页号。

    format,png

    MyISAM中的索引方案简单介绍

    我们知道InnoDB中索引即数据,也就是聚簇索引的那棵B+树的叶子节点中已经把所有完整的用户记录都包含了,而MyISAM的索引方案虽然也使用树形结构,但是却将索引和数据分开存储:

    MyISAM存储引擎把记录按照记录的插入顺序单独存储在数据文件中。这个文件并不划分为若干个数据页,有多少记录就往这个文件中塞多少记录就成了。我们可以通过行号而快速访问到一条记录。

    format,png

    MyISAM存储引擎会把索引信息另外存储到索引文件中。MyISAM会单独为表的主键创建一个索引,只不过在索引的叶子节点中存储的不是完整的用户记录,而是主键值+行号的组合。也就是先通过索引找到对应的行号,再通过行号去找对应的记录!

    这一点和InnoDB是完全不相同的,在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录。而在MyISAM中却需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引!

    如果有需要的话,我们也可以对其它的列分别建立索引或者建立联合索引,原理和InnoDB中的索引差不多,不过在叶子节点处存储的是相应的列+行号。这些索引也全部都是二级索引。

    索引的使用

    上面介绍了B+索引的原理,接下来介绍如何更好的使用索引。大家都知道索引不是建的越多越好,因为创建索引在空间上和时间上都会付出代价。

    空间上的代价

    每创建一个索引,本质上就是要建立一个B+树,创建索引肯定会占用一部分存储空间。

    时间上的代价

    每次对表中的数据进行增删改操作时,都需要去修改各个B+树索引,而B+树索引的记录又是按照索引列的值排序的。每次增删改操作时,不可避免的会破坏原有记录的顺序,所以存储引擎需要额外的时间来进行记录移位、页面分裂等操作来维护记录的顺序。

    简单来说,一张表的索引越多,占用的存储空间也会越多,增删改的性能会更差。

    B+树索引适用的条件

    首先创建一张示例表person_info,用来存储人的一些基本信息。

    CREATE TABLE person_info(

    id INT NOT NULL auto_increment,

    name VARCHAR(100) NOT NULL,

    birthday DATE NOT NULL,

    phone_number CHAR(11) NOT NULL,

    country varchar(100) NOT NULL,

    PRIMARY KEY (id),

    KEY idx_name_birthday_phone_number (name, birthday, phone_number)

    );

    简要说明一下:

    id列为主键,自动递增。InnoDB会自动为id列建立聚簇索引。

    为name, birthday, phone_number建立了一个联合索引。所以这个二级索引的叶子节点包含了name, birthday, phone_number和id列的值。

    下面,简要画一下idx_name_birthday_phone_number联合索引的示意图。

    format,png

    从图中可以看出,这个idx_name_birthday_phone_number索引对应的B+树中页面和记录的排序方式就是这样的:

    先按照name列的值进行排序。

    如果name列的值相同,则按照birthday列的值进行排序。

    如果birthday列的值也相同,则按照phone_number的值进行排序。

    全值匹配

    全值匹配指的是搜索条件中的列和索引列一致。比如:

    SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday = '1990-09-27' AND phone_number = '15123983239';

    在idx_name_birthday_phone_number联合索引上进行全值匹配的查询过程如下:

    因为B+树的数据页和记录先是按照name列的值进行排序的,所以先可以很快定位name列的值是Ashburn的记录位置。

    在name列相同的记录里又是按照birthday列的值进行排序的,所以在name列的值是Ashburn的记录里又可以快速定位birthday列的值是'1990-09-27'的记录。

    如果name和birthday列的值都是相同的,那记录是按照phone_number列的值排序的,所以联合索引中的三个列都可能被用到。

    联合索引最左匹配

    其实在搜索语句中不用包含全部联合索引的列,只包含左边的列也能够使用索引,这就是联合索引的最左匹配原则。比如:

    SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday = '1990-09-27';

    如果我们想使用联合索引中尽可能多的列,搜索条件中的各个列必须是联合索引中从最左边连续的列。

    前缀匹配

    对于字符串类型的索引列来说,我们只匹配它的前缀也是可以快速定位记录的。因为字符串比较本质上按一个一个字符比较得出的,也就是说这些字符串的前n个字符,也就是前缀都是排好序的。比如:

    SELECT * FROM person_info WHERE name LIKE 'As%';

    但是如果只给出后缀或者中间的某个字符串,是无法使用索引的,比如这样:%As或者%As%。如果实际场景中碰到要以字符串后缀查询数据的话,可以考虑逆序存储,将后缀匹配转化为前缀匹配。

    范围匹配

    因为索引B+树是按照索引列大小排序的,因此按索引列范围查询可以快速查询出数据记录。比如:

    SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';

    由于B+树中的数据页和记录是先按name列排序的,所以我们上边的查询过程其实是这样的:

    找到name值为Asa的记录。

    找到name值为Barlow的记录。

    由于叶子节点记录本身是一个链表,直接取出范围之内的记录。

    回表查询完整记录。

    精确匹配某一列并范围匹配另外一列

    对于同一个联合索引来说,虽然对多个列都进行范围查找时只能用到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进行范围查找,这种场景下依然会使用索引。

    SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday > '1980-01-01' AND birthday < '2000-12-31' AND phone_number > '15100000000';

    整个查询过程大致如下:

    name = 'Ashburn',对name列进行精确查找,当然可以使用B+树索引了。

    birthday > '1980-01-01' AND birthday < '2000-12-31',由于name列是精确查找,所以通过name = 'Ashburn'条件查找后得到的结果的name值都是相同的,它们会再按照birthday的值进行排序。所以此时对birthday列进行范围查找是可以用到B+树索引的。

    phone_number > '15100000000',通过birthday的范围查找的记录的birthday的值可能不同,所以这个条件无法再利用B+树索引了,只能遍历上一步查询得到的记录。

    用于排序

    在实际业务场景中,经常需要对查询出来的结果进行排序。一般情况下,只能将记录全部加载到内存中(结果集太大可能使用磁盘存放中间结果),再使用排序算法排序。这种在内存中或者磁盘上的排序方式统称为文件排序filesort,性能较差。但是如果order by子句使用到了索引列,就可能避免filesort。比如下面这个查询语句:

    SELECT * FROM person_info ORDER BY name, birthday, phone_number LIMIT 10;

    这个查询结果依次按name、birthday和phone_number排序,而idx_name_birthday_phone_numberB+索引树也刚好是按上述规则排好序的,因此只需要直接从索引中提取数据,然后回表即可。

    需要注意的是,对于联合索引来说,ORDER BY的子句后边的列的顺序也必须跟索引列的顺序一致,否则排序的时候就无法使用索引了。

    用于分组

    有时候我们为了方便统计表中的一些信息,会把表中的记录按照某些列进行分组。比如下边这个分组查询:

    SELECT name, birthday, phone_number, COUNT(*) FROM person_info GROUP BY name, birthday, phone_number

    和使用B+树索引进行排序是一个道理,分组列的顺序也需要和索引列的顺序一致,也可以只使用索引列中左边的列进行分组。

    覆盖索引

    上面提到到,所谓回表就是在二级索引中获取到主键id集合之后,再分别到聚簇索引查询出完整记录,简单来说就是一次二级索引查询,多次聚簇索引回表。这意味着二级索引命中的主键记录越多,需要回表的记录也会也多,整体的性能就会越低。因此某些查询,宁可使用全表扫描也不使用二级索引。

    为了更好的使用二级索引+回表的方式进行查询,一般推荐使用limit限制要查询的记录,这样回表的次数也能得到控制。

    为了彻底告别回表操作带来的性能损耗,建议:在查询列表里只包含索引列,比如这样:

    SELECT name, birthday, phone_number FROM person_info WHERE name > 'Asa' AND name < 'Barlow'

    因为只查询name, birthday, phone_number这三个索引列的值,所以就没必要进行回表操作了。我们把这种只需要用到索引的查询方式称为覆盖索引。

    如何挑选索引

    上面主要介绍了索引的适用场景,接下来我们介绍下建立索引时或者编写查询语句时就应该注意的一些事项。

    只为用于搜索、排序或分组的列创建索引

    只为出现在WHERE子句中的列、连接子句中的连接列,或者出现在ORDER BY或GROUP BY子句中的列创建索引。而出现在查询列表中的列就没必要建立索引了。

    考虑列的基数

    列的基数指的是某一列中不重复数据的个数。,在记录行数一定的情况下,列的基数越大,该列中的值越分散,列的基数越小,该列中的值越集中。因此推荐的方式是为那些列的基数大的列建立索引,为基数太小列的建立索引效果可能不好。

    索引列的类型尽量小

    在表示的整数范围允许的情况下,尽量让索引列使用较小的类型。原因如下:

    数据类型越小,在查询时进行的比较操作越快

    数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘I/O带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。

    使用前缀索引

    当字段值比较长的时候,建立索引会消耗很多的空间,搜索起来也会很慢。我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。

    例如:创建一张商户表,因为地址字段比较长,在地址字段上建立前缀索引:

    create table shop(address varchar(120) not null);

    问题是,截取多少呢?截取得多了,达不到节省索引存储空间的目的,截取得少了, 重复内容太多,字段的基数会降低。实际场景中,可以通过不同长度的基数与总记录数据基数的比值,选择一个较为合理的截取长度。

    select count(distinct left(address,10))/count(*) as sub10,

    count(distinct left(address,11))/count(*) as sub11,

    count(distinct left(address,12))/count(*) as sub12,

    count(distinct left(address,13))/count(*) as sub13

    from shop;

    避免索引列字段参与计算

    如果索引列在比较表达式中不是以单独列的形式出现,而是以某个表达式,或者函数调用形式出现的话,是用不到索引的。

    比如有一个整数列my_col,WHERE my_col * 2 < 4查询是不会使用索引的,而WHERE my_col < 4/2能正常使用索引。

    主键插入顺序

    我们知道,对于InnoDB来说,数据实际上是按主键大小正序存储在聚簇索引的叶子节点上的。所以如果我们插入的记录的主键值是依次增大的话,那我们每插满一个数据页就换到下一个数据页继续插入。而如果我们插入的主键值忽大忽小的话,就会造成频繁的页分裂,严重影响性能。因此,为了保证性能,需要保证主键是递增的。

    无法使用索引的几种情况

    ORDER BY的子句后边的列的顺序也必须跟索引列的顺序不一致。

    ASC、DESC混用

    排序列包含非同一个索引的列

    排序列使用了复杂的表达式

    索引列上使用函数(replace\SUBSTR\CONCAT\sum count avg)、表达式、 计算(+ - * /)

    like 条件中前面带%

    字符串不加引号,出现隐式转换

    原创不易,觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~

    展开全文
  • 这就是为什么在面试过程,需要考察软件工程师对数据结构理解。 几乎所有问题都需要面试者对数据结构有深刻理解。无论是初入职场新兵(刚大学或者编程培训班毕业),还是拥有几十年经验职场老鸟...

    常用的八大数据结构(请看下图)

     

     

    瑞士计算机科学家Niklaus Wirth在1976年写了一本书,名为《算法+数据结构=编程》。

    40多年后,这个等式仍被奉为真理。这就是为什么在面试过程中,需要考察软件工程师对数据结构的理解。

    几乎所有的问题都需要面试者对数据结构有深刻的理解。无论你是初入职场的新兵(刚从大学或者编程培训班毕业),还是拥有几十年经验的职场老鸟。

    有些面试题会明确提及某种数据结构,例如,“给定一个二叉树。”而另一些则隐含在面试题中,例如,“我们希望记录每个作者相关的书籍数量。”

    即便是对于一些非常基础的工作来说,学习数据结构也是必须的。那么,就让我们先从一些基本概念开始入手。

     

     

     

    什么是数据结构?

    简单地说,数据结构是以某种特定的布局方式存储数据的容器。这种“布局方式”决定了数据结构对于某些操作是高效的,而对于其他操作则是低效的。首先我们需要理解各种数据结构,才能在处理实际问题时选取最合适的数据结构。

     

    为什么我们需要数据结构?

    数据是计算机科学当中最关键的实体,而数据结构则可以将数据以某种组织形式存储,因此,数据结构的价值不言而喻。

    无论你以何种方式解决何种问题,你都需要处理数据——无论是涉及员工薪水、股票价格、购物清单,还是只是简单的电话簿问题。

    数据需要根据不同的场景,按照特定的格式进行存储。有很多数据结构能够满足以不同格式存储数据的需求。

     

     

     

    常见的数据结构

    首先列出一些最常见的数据结构,我们将逐一说明:

    -数组

    -栈

    -队列

    -链表

    -树

    -图

    -字典树(这是一种高效的树形结构,但值得单独说明)

    -散列表(哈希表)

     

    数组

    数组是最简单、也是使用最广泛的数据结构。栈、队列等其他数据结构均由数组演变而来。下图是一个包含元素(1,2,3和4)的简单数组,数组长度为4。

    每个数据元素都关联一个正数值,我们称之为索引,它表明数组中每个元素所在的位置。大部分语言将初始索引定义为零。

     

    以下是数组的两种类型:

    一维数组(如上所示)多维数组(数组的数组)

     

    数组的基本操作

    Insert——在指定索引位置插入一个元素

    Get——返回指定索引位置的元素

    Delete——删除指定索引位置的元素

    Size——得到数组所有元素的数量

     

    面试中关于数组的常见问题

    寻找数组中第二小的元素找到数组中第一个不重复出现的整数合并两个有序数组重新排列数组中的正值和负值

     

    著名的撤销操作几乎遍布任意一个应用。但你有没有思考过它是如何工作的呢?这个问题的解决思路是按照将最后的状态排列在先的顺序,在内存中存储历史工作状态(当然,它会受限于一定的数量)。这没办法用数组实现。但有了栈,这就变得非常方便了。

    可以把栈想象成一列垂直堆放的书。为了拿到中间的书,你需要移除放置在这上面的所有书。这就是LIFO(后进先出)的工作原理。

    下图是包含三个数据元素(1,2和3)的栈,其中顶部的3将被最先移除:

     

     

    栈的基本操作

    Push——在顶部插入一个元素

    Pop——返回并移除栈顶元素

    isEmpty——如果栈为空,则返回true

    Top——返回顶部元素,但并不移除它

    面试中关于栈的常见问题

    使用栈计算后缀表达式对栈的元素进行排序判断表达式是否括号平衡

     

    队列

    与栈相似,队列是另一种顺序存储元素的线性数据结构。栈与队列的最大差别在于栈是LIFO(后进先出),而队列是FIFO,即先进先出。

    一个完美的队列现实例子:售票亭排队队伍。如果有新人加入,他需要到队尾去排队,而非队首——排在前面的人会先拿到票,然后离开队伍。

    下图是包含四个元素(1,2,3和4)的队列,其中在顶部的1将被最先移除:

    移除先入队的元素、插入新元素

     

    队列的基本操作

    Enqueue()——在队列尾部插入元素

    Dequeue()——移除队列头部的元素

    isEmpty()——如果队列为空,则返回true

    Top()——返回队列的第一个元素

     

    面试中关于队列的常见问题

    使用队列表示栈对队列的前k个元素倒序使用队列生成从1到n的二进制数

     

    链表

    链表是另一个重要的线性数据结构,乍一看可能有点像数组,但在内存分配、内部结构以及数据插入和删除的基本操作方面均有所不同。

    链表就像一个节点链,其中每个节点包含着数据和指向后续节点的指针。 链表还包含一个头指针,它指向链表的第一个元素,但当列表为空时,它指向null或无具体内容。

    链表一般用于实现文件系统、哈希表和邻接表。

     

    这是链表内部结构的展示:

    链表包括以下类型:

    单链表(单向)双向链表(双向)

     

    链表的基本操作:

    InsertAtEnd - 在链表的末尾插入指定元素

    InsertAtHead - 在链接列表的开头/头部插入指定元素

    Delete - 从链接列表中删除指定元素

    DeleteAtHead - 删除链接列表的第一个元素

    Search - 从链表中返回指定元素

    isEmpty - 如果链表为空,则返回true

     

    面试中关于链表的常见问题

    反转链表检测链表中的循环返回链表倒数第N个节点删除链表中的重复项

     

    图是一组以网络形式相互连接的节点。节点也称为顶点。 一对节点(x,y)称为边(edge),表示顶点x连接到顶点y。边可以包含权重/成本,显示从顶点x到y所需的成本。

     

     

    图的类型

    无向图有向图

     

    在程序语言中,图可以用两种形式表示:

    邻接矩阵邻接表

     

    常见图遍历算法

    广度优先搜索深度优先搜索

     

    面试中关于图的常见问题

    实现广度和深度优先搜索检查图是否为树计算图的边数找到两个顶点之间的最短路径

     

    树形结构是一种层级式的数据结构,由顶点(节点)和连接它们的边组成。 树类似于图,但区分树和图的重要特征是树中不存在环路。

    树形结构被广泛应用于人工智能和复杂算法,它可以提供解决问题的有效存储机制。

     

    这是一个简单树的示意图,以及树数据结构中使用的基本术语:

    Root - 根节点

    Parent - 父节点

    Child - 子节点

    Leaf - 叶子节点

    Sibling - 兄弟节点

     

    以下是树形结构的主要类型:

    N元树平衡树二叉树二叉搜索树AVL树红黑树2-3树

    其中,二叉树和二叉搜索树是最常用的树。

     

    面试中关于树结构的常见问题:

    求二叉树的高度在二叉搜索树中查找第k个最大值查找与根节点距离k的节点在二叉树中查找给定节点的祖先节点

     

    字典树(Trie)

    字典树,也称为“前缀树”,是一种特殊的树状数据结构,对于解决字符串相关问题非常有效。它能够提供快速检索,主要用于搜索字典中的单词,在搜索引擎中自动提供建议,甚至被用于IP的路由。

    以下是在字典树中存储三个单词“top”,“so”和“their”的例子:

     

    这些单词以顶部到底部的方式存储,其中绿色节点“p”,“s”和“r”分别表示“top”,“thus”和“theirs”的底部。

     

    面试中关于字典树的常见问题

    计算字典树中的总单词数打印存储在字典树中的所有单词使用字典树对数组的元素进行排序使用字典树从字典中形成单词构建T9字典(字典树+ DFS )

     

    哈希表

    哈希法(Hashing)是一个用于唯一标识对象并将每个对象存储在一些预先计算的唯一索引(称为“键(key)”)中的过程。因此,对象以键值对的形式存储,这些键值对的集合被称为“字典”。可以使用键搜索每个对象。基于哈希法有很多不同的数据结构,但最常用的数据结构是哈希表。

    哈希表通常使用数组实现。

    散列数据结构的性能取决于以下三个因素:

    哈希函数哈希表的大小碰撞处理方法

    下图为如何在数组中映射哈希键值对的说明。该数组的索引是通过哈希函数计算的。

     

     

    面试中关于哈希结构的常见问题:

    在数组中查找对称键值对追踪遍历的完整路径查找数组是否是另一个数组的子集检查给定的数组是否不相交

     

    以上是在编程之前你应该知晓的八大数据结构。

     

    展开全文
  • 知道Excel中的数据有一定的格式,格式不对,轻则不美观,重则出现各种错误。工作,老板给我的Excel表格却常常是这样的:它有什么问题呢?好可怕,随便标记一下,就出来这么多问题。没法子,别人不按章程劳动,...

    都知道Excel中的数据有一定的格式,格式不对,轻则不美观,重则出现各种错误。

    工作中,老板给我的Excel表格却常常是这样的:

    5916a064511763a93e3d9942e46a1913.png

    它有什么问题呢?

    357948f2681b79d2a0c339418dd035fd.png

    好可怕,随便标记一下,就出来这么多问题。

    没法子,别人不按章程劳动,后果却是你要承担的。

    于是,你不得不操刀改造它。

    15095d40aaa5f473198b06de546362f0.png

    从第一张表格,到这一张表格,整个过程耗时4分钟。

    ① 不知道隐藏了多少行。

    别人看表格时常常这边筛选一下,那边筛选一下,结果就不知道隐藏了多少行。

    解决起来很简单,重新点击一下“自动筛选”按钮;或全选后右键取消隐藏。

    d0927bfcf04aee620fff14c6aaf86418.gif

    那么多问题,为啥要第一个解决这个问题呢?

    先思考一下吧,看完第二个问题的解决方案,我再告诉你。

    ② 表格粗边框很丑。

    表格边框这么粗,很可能是因为:这张表格最初是从Word中复制过来的。

    按“Ctrl + A”,然后点击一下“全边框”按钮,粗边框立即就变细啦。

    46f5f1e9c691a7866533e3bdccf25cfe.gif

    现在再回到前面的问题,如果先修改边框粗细,再取消隐藏,会怎样?

    c8bece762b529ae5e2cdf34e473aa0fb.gif

    看到了吗?操作完表格变成了这么丑。边框有粗有细,还得再点击一下“全边框”按钮。

    5a035cfc5cbdf70429e7a83b0062c13b.png

    当然,你还可能会遇到其他的问题。所以,保险起见,先取消隐藏。

    ③ 日期不规范。

    这份表格的日期大概是别人一个数字一个数字敲打进去的吧,所以会出现“2019.2.17”“20190218”这类不规范的日期。

    使用“分列”功能,将格式设置为“日期——YMD”就好啦。

    2c739d8ae3a136d84cbf4ab32f093650.gif

    至于日期中的科学计数格式,加大列宽就好啦。最后在统一设置日期格式。

    ④ 部分数字属于文本型。

    网站导出数据、网站复制数据,以及不小心录入等原因都会造成文本型数字。

    我们可以先选中数据,然后在左侧下拉菜单中选择“转换为数字”。

    627117d527ffde427d2b5c96e841b0ab.gif

    当然不是一个一个转换了。你可以先选中第一个文本型数字,然后按Shift键选中所有的数字区域,最后可以一次行更正所有的数字格式错误。

    注意,一定要严格按照这个操作顺序哦。

    ⑤ 有些文本前后有空格。

    错误的原因和解决方法都和“数字文本型”类似,不管多少个单元格有多余的空格,都可以直接批量“清空前后的空字符串”。

    c86f991e4fda9978f9437d1f965c43a5.gif

    文本型的数字会导致计算错误,文本前后多余的空格也会导致公式失效,例如VLOOKUP函数得到“#N/A”,所以不容忽视。

    ⑥ 数据有重复。

    这一条,没有什么好说的,直接看图就好啦。

    ebe3ded0e47ebf73590fc506ba694246.gif

    ⑦ 有些公式计算不正确。

    这一条,就要具体问题具体解决了。

    之所以放这样的错误,是因为我不只一次看到有人用“=(C3+D3+E3)/3”这样的公式计算平均值,然后每次不停地修改公式。就本例来说,应该用AVERAGE函数。

    e4823bad008322684318f36348febfd3.png

    没有捷径,多熟悉熟悉Excel函数吧。

    ⑧ 有多余的空行。

    多余的空行会导致表格断层,这样就会导致“筛选不全”、“公式双击填充不了”等许多问题,所以强烈建议清除掉空行,不同类型的表格建议放在不同的工作表中。

    清除的方法之前已经介绍过了,请参考《WPS Excel:删除空白行有4种方法,看看你的表格适合用哪种》。

    最后

    不规范的数据都清除好了,再用之前的Excel模板美化一下格式吧。

    f569668a6702dcfcf953db09ae07c1f0.png

    美化的确很花时间的,但如果你有漂亮的Excel模板,也就套用下模板或格式刷一下,一两分钟也可以搞定。


    本文由解晴新生原创,欢迎关注,带你一起长知识!

    展开全文
  • 如果在一个项目在这几个方面的数据处理做的都很不错,对于之后的建模具有极大的帮助,并且能快速达到一个还不错的结果。作者:lswbjtu;来源:知乎专栏为什么数据处理很重要?熟悉数据挖掘和机器学习的小伙伴们...

    数据预处理的主要步骤分为:数据清理、数据集成、数据规约和数据变换。本文将从这四个方面详细的介绍具体的方法。如果在一个项目中,你在这几个方面的数据处理做的都很不错,对于之后的建模具有极大的帮助,并且能快速达到一个还不错的结果。作者:lswbjtu;来源:知乎专栏

    12660abb3e5b7b9884d4a19d994912f4.png

    为什么数据处理很重要?

    熟悉数据挖掘和机器学习的小伙伴们都知道,数据处理相关的工作时间占据了整个项目的70%以上。数据的质量,直接决定了模型的预测和泛化能力的好坏。它涉及很多因素,包括:准确性、完整性、一致性、时效性、可信性和解释性。

    而在真实数据中,我们拿到的数据可能包含了大量的缺失值,可能包含大量的噪音,也可能因为人工录入错误导致有异常点存在,非常不利于算法模型的训练。数据清洗的结果是对各种脏数据进行对应方式的处理,得到标准的、干净的、连续的数据,提供给数据统计、数据挖掘等使用。

    有哪些数据预处理的方法?

    数据预处理的主要步骤分为:数据清理、数据集成、数据规约和数据变换。本文将从这四个方面详细的介绍具体的方法。如果在一个项目中,你在这几个方面的数据处理做的都很不错,对于之后的建模具有极大的帮助,并且能快速达到一个还不错的结果。

    数据清理

    数据清理(data cleaning) 的主要思想是通过填补缺失值、光滑噪声数据,平滑或删除离群点,并解决数据的不一致性来“清理“数据。如果用户认为数据时脏乱的,他们不太会相信基于这些数据的挖掘结果,即输出的结果是不可靠的。

    1、缺失值的处理

    由于现实世界中,获取信息和数据的过程中,会存在各类的原因导致数据丢失和空缺。针对这些缺失值的处理方法,主要是基于变量的分布特性和变量的重要性(信息量和预测能力)采用不同的方法。主要分为以下几种:

    • 删除变量:若变量的缺失率较高(大于80%),覆盖率较低,且重要性较低,可以直接将变量删除。
    • 定值填充:工程中常见用-9999进行替代
    • 统计量填充:若缺失率较低(小于95%)且重要性较低,则根据数据分布的情况进行填充。对于数据符合均匀分布,用该变量的均值填补缺失,对于数据存在倾斜分布的情况,采用中位数进行填补。
    • 插值法填充:包括随机插值,多重差补法,热平台插补,拉格朗日插值,牛顿插值等
    • 模型填充:使用回归、贝叶斯、随机森林、决策树等模型对缺失数据进行预测。

    哑变量填充:若变量是离散型,且不同值较少,可转换成哑变量,例如性别SEX变量,存在male,fameal,NA三个不同的值,可将该列转换成:IS_SEX_MALE, IS_SEX_FEMALE, IS_SEX_NA。若某个变量存在十几个不同的值,可根据每个值的频数,将频数较小的值归为一类'other',降低维度。此做法可最大化保留变量的信息。

    总结来看,楼主常用的做法是:先用pandas.isnull.sum()检测出变量的缺失比例,考虑删除或者填充,若需要填充的变量是连续型,一般采用均值法和随机差值进行填充,若变量是离散型,通常采用中位数或哑变量进行填充。

    注意:若对变量进行分箱离散化,一般会将缺失值单独作为一个箱子(离散变量的一个值)

    7add1ec0bf1284ea24739460ea080c18.png

    2、离群点处理

    异常值是数据分布的常态,处于特定分布区域或范围之外的数据通常被定义为异常或噪声。异常分为两种:“伪异常”,由于特定的业务运营动作产生,是正常反应业务的状态,而不是数据本身的异常;“真异常”,不是由于特定的业务运营动作产生,而是数据本身分布异常,即离群点。主要有以下检测离群点的方法:

    简单统计分析:根据箱线图、各分位点判断是否存在异常,例如pandas的describe函数可以快速发现异常值。

    原则:若数据存在正态分布,偏离均值的3之外。通常定义: 范围内的点为离群点。

    • 基于绝对离差中位数(MAD):这是一种稳健对抗离群数据的距离值方法,采用计算各观测值与平均值的距离总和的方法。放大了离群值的影响。
    • 基于距离:通过定义对象之间的临近性度量,根据距离判断异常对象是否远离其他对象,缺点是计算复杂度较高,不适用于大数据集和存在不同密度区域的数据集
    • 基于密度:离群点的局部密度显著低于大部分近邻点,适用于非均匀的数据集
    • 基于聚类:利用聚类算法,丢弃远离其他簇的小簇。

    总结来看,在数据处理阶段将离群点作为影响数据质量的异常点考虑,而不是作为通常所说的异常检测目标点,因而楼主一般采用较为简单直观的方法,结合箱线图和MAD的统计方法判断变量的离群点。

    具体的处理手段:

    • 根据异常点的数量和影响,考虑是否将该条记录删除,信息损失多
    • 若对数据做了log-scale 对数变换后消除了异常值,则此方法生效,且不损失信息
    • 平均值或中位数替代异常点,简单高效,信息的损失较少
    • 在训练树模型时,树模型对离群点的鲁棒性较高,无信息损失,不影响模型训练效果

    3、噪声处理

    噪声是变量的随机误差和方差,是观测点和真实点之间的误差,即 :

    d8ca618372137dfbe6197ca0d9b194fe.png

    通常的处理办法:对数据进行分箱操作,等频或等宽分箱,然后用每个箱的平均数,中位数或者边界值(不同数据分布,处理方法不同)代替箱中所有的数,起到平滑数据的作用。另外一种做法是,建立该变量和预测变量的回归模型,根据回归系数和预测变量,反解出自变量的近似值。

    数据集成

    数据分析任务多半涉及数据集成。数据集成将多个数据源中的数据结合成、存放在一个一致的数据存储,如数据仓库中。这些源可能包括多个数据库、数据方或一般文件。

    1. 实体识别问题:例如,数据分析者或计算机如何才能确信一个数 据库中的 customer_id 和另一个数据库中的 cust_number 指的是同一实体?通常,数据库和数据仓库 有元数据——关于数据的数据。这种元数据可以帮助避免模式集成中的错误。
    2. 冗余问题。一个属性是冗余的,如果它能由另一个表“导出”;如年薪。属性或 维命名的不一致也可能导致数据集中的冗余。用相关性检测冗余:数值型变量可计算相关系数矩阵,标称型变量可计算卡方检验。
    3. 数据值的冲突和处理:不同数据源,在统一合并时,保持规范化,去重。

    数据规约

    数据归约技术可以用来得到数据集的归约表示,它小得多,但仍接近地保持原数据的完整性。这样,在归约后的数据集上挖掘将更有效,并产生相同(或几乎相同)的分析结果。一般有如下策略:

    b9dfc081921b7a2516c39dc0c0bcf4b7.png

    1、维度规约

    用于数据分析的数据可能包含数以百计的属性,其中大部分属性与挖掘任务不相关,是冗余的。维度归约通过删除不相关的属性,来减少数据量,并保证信息的损失最小。

    • 属性子集选择:目标是找出最小属性集,使得数据类的概率分布尽可能地接近使用所有属性的原分布。在压缩 的属性集上挖掘还有其它的优点。它减少了出现在发现模式上的属性的数目,使得模式更易于理解。

    逐步向前选择:该过程由空属性集开始,选择原属性集中最好的属性,并将它添加到该集合中。在其后的每一次迭代,将原属性集剩下的属性中的最好的属性添加到该集合中。

    逐步向后删除:该过程由整个属性集开始。在每一步,删除掉尚在属性集中的最坏属性。

    向前选择和向后删除的结合:向前选择和向后删除方法可以结合在一起,每一步选择一个最 好的属性,并在剩余属性中删除一个最坏的属性。

    python scikit-learn 中的递归特征消除算法Recursive feature elimination (RFE),就是利用这样的思想进行特征子集筛选的,一般考虑建立SVM或回归模型。

    单变量重要性:分析单变量和目标变量的相关性,删除预测能力较低的变量。这种方法不同于属性子集选择,通常从统计学和信息的角度去分析。

    pearson相关系数和卡方检验,分析目标变量和单变量的相关性。

    • 回归系数:训练线性回归或逻辑回归,提取每个变量的表决系数,进行重要性排序。
    • 树模型的Gini指数:训练决策树模型,提取每个变量的重要度,即Gini指数进行排序。
    • Lasso正则化:训练回归模型时,加入L1正则化参数,将特征向量稀疏化。
    • IV指标:风控模型中,通常求解每个变量的IV值,来定义变量的重要度,一般将阀值设定在0.02以上。

    以上提到的方法,没有讲解具体的理论知识和实现方法,需要同学们自己去熟悉掌握。楼主通常的做法是根据业务需求来定,如果基于业务的用户或商品特征,需要较多的解释性,考虑采用统计上的一些方法,如变量的分布曲线,直方图等,再计算相关性指标,最后去考虑一些模型方法。

    如果建模需要,则通常采用模型方法去筛选特征,如果用一些更为复杂的GBDT,DNN等模型,建议不做特征选择,而做特征交叉。

    2、维度变换:

    维度变换是将现有数据降低到更小的维度,尽量保证数据信息的完整性。楼主将介绍常用的几种有损失的维度变换方法,将大大地提高实践中建模的效率

    主成分分析(PCA)和因子分析(FA):PCA通过空间映射的方式,将当前维度映射到更低的维度,使得每个变量在新空间的方差最大。FA则是找到当前特征向量的公因子(维度更小),用公因子的线性组合来描述当前的特征向量。

    奇异值分解(SVD):SVD的降维可解释性较低,且计算量比PCA大,一般用在稀疏矩阵上降维,例如图片压缩,推荐系统。

    • 聚类:将某一类具有相似性的特征聚到单个变量,从而大大降低维度。
    • 线性组合:将多个变量做线性回归,根据每个变量的表决系数,赋予变量权重,可将该类变量根据权重组合成一个变量。
    • 流行学习:流行学习中一些复杂的非线性方法,可参考skearn:LLE Example

    数据变换

    数据变换包括对数据进行规范化,离散化,稀疏化处理,达到适用于挖掘的目的。

    1、规范化处理:数据中不同特征的量纲可能不一致,数值间的差别可能很大,不进行处理可能会影响到数据分析的结果,因此,需要对数据按照一定比例进行缩放,使之落在一个特定的区域,便于进行综合分析。特别是基于距离的挖掘方法,聚类,KNN,SVM一定要做规范化处理。

    e96669f3c042a778af2acb42124646bf.png

    2、离散化处理:数据离散化是指将连续的数据进行分段,使其变为一段段离散化的区间。分段的原则有基于等距离、等频率或优化的方法。数据离散化的原因主要有以下几点:

    模型需要:比如决策树、朴素贝叶斯等算法,都是基于离散型的数据展开的。如果要使用该类算法,必须将离散型的数据进行。有效的离散化能减小算法的时间和空间开销,提高系统对样本的分类聚类能力和抗噪声能力。

    离散化的特征相对于连续型特征更易理解。

    可以有效的克服数据中隐藏的缺陷,使模型结果更加稳定。

    • 等频法:使得每个箱中的样本数量相等,例如总样本n=100,分成k=5个箱,则分箱原则是保证落入每个箱的样本量=20。
    • 等宽法:使得属性的箱宽度相等,例如年龄变量(0-100之间),可分成 [0,20],[20,40],[40,60],[60,80],[80,100]五个等宽的箱。
    • 聚类法:根据聚类出来的簇,每个簇中的数据为一个箱,簇的数量模型给定。
    ae9d767582e7ac2c616fec58495ee3a8.png

    3、稀疏化处理:针对离散型且标称变量,无法进行有序的LabelEncoder时,通常考虑将变量做0,1哑变量的稀疏化处理,例如动物类型变量中含有猫,狗,猪,羊四个不同值,将该变量转换成is_猪,is_猫,is_狗,is_羊四个哑变量。若是变量的不同值较多,则根据频数,将出现次数较少的值统一归为一类'rare'。稀疏化处理既有利于模型快速收敛,又能提升模型的抗噪能力。

    总结

    以上介绍了数据预处理中会用到的大部分方法和技术,完全适用于初学者学习掌握,并且对于实践建模会有大幅度提升。以上方法的代码实现,均可在python的pandas和sklearn中完成。大家可根据需要去查阅学习,网上资料也很多,楼主只提供方法和经验上的借鉴,希望每个认真学习巩固的同学都能得到提升。

    展开全文
  • 数据清洗主要是删除原始数据中的无关数据,重复数据,平滑噪声数据,筛选掉与建模目的无关的数据,处理缺失值与异常值等。 除了很明显的缺失值(单元格处无值)之外,还有一种隐形的缺失值,比如,要分析一个人...
  • 这是的数据:1111'); }).listen(3000, '127.0.0.1'); console.log('启动服务,监听 127.0.0.1:3000'); </code></pre> 关键是在于设置相应头的 Access-Control-Allow-...
  • 那么一定不能错过 TGO 鲲鹏会上海分会《技术领导者增长黑客落地实践》活动,小红书增长技术负责人占雪亮为大家带来主题为《小红书增长术和道》演讲。以下根据当天演讲整理,有部分不改变原意删减。占...
  • 很多人去面试之前,不知道会问到那些知识,也不知道要做什么准备,今天我们就来整理一下要去面试,并且成功拿到offer需要懂的什么技能,希望对大家有所帮助 JetPack里组件 上图就是JetPack包含组件列表,每...
  • 18、一个用户表有一个积分字段,假如数据库有100多万个用户,若要在每年第一天凌晨将积分清零,将考虑什么将想什么办法解决? 107 19、一个用户具有多个角色,请查询出该表具有该用户所有角色其他...
  • Hello大噶早上好!不知道大家昨天周末过开心吗我是都待在家里...于是我帮她拯救一下,变成这样图表相信我这个闺蜜只是职场典型一个例子,我们工作也常常遇到整理数据的问题,简单数据整理出来这么...
  • 为了让大家更好理解JVM工作机制, 我会在讲解完运行时数据区之后,再通过一个类加载过程到这个类最终在运行时数据中的存储来更进一步理解JVM工作原理。最后,通过对内存回收机制和垃圾回收算法讲解,...
  • 通常java等开发语言,学时候都是一个helloworld开始,这主要是让认识一下它基本语法以及结构,然后后面再看时候就知道它是什么了。这里我就不详写,主要整理一下概念性东西。 1、关键字(区分大...
  • 汉斯明白这点,他脸上看出来。 去,猫!他叫道。离开这屋子好不好! #p#副标题#e# 它缩起身子似乎要跃起来。 汉斯够不着它,除了他那可爱的宝贝故事书外,他没有别东西可以扔过去打它。...
  • 从数据分析角度知道到底是怎么运作,为什么他身边朋友能赚而他是亏损。我猜想关于夺宝很多朋友都会有疑问,于是下载市面上主流一些夺宝软件,自己也投注一些进行研究。发现原来远远没有想象这么...
  • 105页的数据结构和算法 142页的Spring 58页的过滤器和监听器 30页的HTTP xxxx页的Redis:待更新...(最新首发公众号,导航更新可能有延迟) Hibernate AJAX ...... :coffee:Java基础 初学者学Java常遇到的问题,我...
  • 我们知道符合条件的数据结构就有栈、队列和其它。 <p><strong>2.非线性结构其逻辑特征是一个节点元素可以有多个直接前驱或多个直接后继。 那么,符合条件的数据结构就有图、树和其它。 嗯~了解一下就行...
  • 于是,我去了解一下身边不同岗位(HR、产品、运营、市场、数据分析师等)每天需要面对重复性劳动(肯定会有不全,欢迎补充),**总结一些在工作非常常见例子,并且将源码整理好供参考**。希望这些程序可以...

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 203
精华内容 81
关键字:

从整理的数据中你知道了什么