老外真的很喜欢创造新名词。春节的时候我给我姐选显示器,因为我姐是做设计的,我特地研究了一下显示器的色域。我发现除了我们常用的sRGB外,还有个Adobe RGB。后来经过了解,原来是Photoshop的工程师在参考其他色彩标准的时候搞错了参数,后来懒得改了,便自己申请了专利,搞成了一个新的色彩标准,后来随着Adobe软件的流行现在已经变成了行业标准。因为老外不厌其烦的创造新名词,给我们造成了不小的困扰。比如面试的时候,面试官问你AVL树,你知道什么是平衡二叉树,不知道什么是AVL树,面试官知道问AVL树,却不知道问平衡二叉树,结果大家面面相觑。世界上最远的距离,莫过于你跟面试官背的不是同一套八股文。以后遇到这样的面试官,不妨问下他什么是基数树。基数树不知道吗,那trie树总该知道吧,trie树也不知道的话字典树总该知道吧,字典树也不知道那前缀树总该知道吧。相信面试官会被问的怀疑人生。
言归正传,来讲一下数据结构,下面是百度百科对数据结构的定义:
数据结构(data structure)是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系,并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。简而言之,数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带“结构”的数据元素的集合。“结构”就是指数据元素之间存在的关系,分为逻辑结构和存储结构。
数据的逻辑结构和物理结构是数据结构的两个密切相关的方面,同一逻辑结构可以对应不同的存储结构。算法的设计取决于数据的逻辑结构,而算法的实现依赖于指定的存储结构。
不知道大家觉得好不好理解,简单说来,计算机基本上就做两件事:存储数据和处理数据。数据结构,是老外总结出来的一套行之有效的数据存储方式,并围绕着这些存储方式设计出了一些高效的操作算法。借助数据结构,我们可以高效地管理多个数据,进而完成更复杂的功能。下图列举了一些常见的数据结构,后面会逐个进行讲解:
列表
通常一系列数据A0,A1,A2,A3,...,AN-1 可以采用表来存储。表一般支持几种操作,寻找某元素所在位置,在表的某个位置插入和删除某元素,返回某个位置上的元素等。
栈
栈也叫LIFO(后进先出)表,元素的插入和访问、删除都只能从一个方向进行。通常支持入栈操作、出栈操作以及返回当前栈顶元素。
队列
队列插入时再一端进行,删除时只能在另一端进行,也叫FIFO(先进先出)表。通常支持入队操作,出队操作和返回对头元素。
树
树是数据结构考核的重点,涉及了很多的概念,也发展出来很多的变种,需要花大力气去学习。树是一些节点的集合,这个集合可以是空集;如果不是空集,则树由称作根(root)的节点r和0个或者多个非空的(子)树T1,T2,...,Tk组成,这些子树中每一棵的根都被来自根r的一条有向边(edge)所连接。每一棵子树的根叫做根r的儿子(child),而r是每棵子树的根的父亲(parent)。没有儿子的节点称为树叶(leaf),具有相同父亲的节点为兄弟(siblings)。在上图,A是B和C的父亲,B和C是A的儿子,B和C是兄弟,D和F没有儿子所以叫叶子。
从节点n1到nk的路径(path)定义为节点n1,n2,...,nk的一个序列,使得对于1<=i
树经常被考核到的点是他的几种遍历策略,分别是先序遍历、后序遍历和中序遍历,三种遍历策略的区别主要是根节点是在什么时候处理的。先序遍历是先处理根节点,再处理他的儿子节点;后序遍历是等他所有的子节点处理完后再处理根节点。中序遍历只会出现在二叉树中,二叉树每个根节点最多有左右两个儿子节点,处理顺序分别是左二子节点、根节点、右儿子节点。
散列表
散列表(hash table)常常也会根据英文音译成哈希表。散列表不像普通的表,数据都是一个跟一个紧密存储的,而是根据一个散列算法计算出元素存储的位置再存储。这样做的好处是对应每个特定元素,散列表都能够在O(1)的时间复杂度内通过散列算法算出元素所在位置,从而实现元素的快速访问。散列表关键的技术细节在于冲突的处理。散列函数给两个不同的元素计算出来的位置值是相同时,我们称作冲突(collision)。冲突的解决有几种方式,分别是分离链接法(separate chaining,也叫拉链法,链地址法)、探测散列表(probing hash table,也叫开放定址散列法,open addressing hashing,包括线性探测法、平方探测法和双散列)。网上还有各种各样的冲突解决方法,如果大家感兴趣可以留言评论,我会在后续的博客详细讲解。
优先队列
优先队列设计的目的是找出、返回并删除最小的元素,支持插入操作,删除最小者操作。
这是Wikipedia上各种数据结构的时间复杂度对比,选择数据结构时可以参考一下:
注:插入未排序数组有时被引用为O ( n ),因为假设要插入的元素必须插入到数组的一个特定位置,这需要将所有后续元素移动一个位置。然而,在经典数组中,数组用于存储任意未排序的元素,因此任何给定元素的确切位置无关紧要,插入是通过将数组大小增加 1 并将元素存储在末尾来执行的数组,这是一个O (1) 操作。同样,删除操作有时被引用为O ( n) 由于假设必须移动后续元素,但在经典的未排序数组中,顺序并不重要(尽管元素是按插入时间隐式排序的),因此可以通过将要删除的元素与最后一个元素交换来执行删除数组中的元素,然后将数组大小减 1,这是一个O (1) 操作。
那么多的数据结构我们要如何选择呢?数据结构终究是服务于你的程序的,学习数据结构关键还是理解数据结构的设计思路,不用过于拘泥。以前上大学的时候我自己开发了一个电子词典程序,当时并没有学习过字典树,但是根据电子词典的需要设计出来的数据结构就是字典树。现在的面试官很喜欢问各种术语各种概念,却不知道最重要的其实还是问题的解决本身。当然也不是说不需要学习数据结构,既然我们要站在巨人的肩膀前进,至少我们得知道巨人的肩膀在哪是不是。下面总结了常用数据结构的一些权衡:
引用:
- https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1450?fr=aladdin
- https://en.wikipedia.org/wiki/Search_data_structure
- https://en.wikipedia.org/wiki/Skip_list
- 《数据结构与算法分析——C++语言描述(第四版)》Mark Allen Weiss著