一、数组
什么是数组:数组是定义在连续空 间上相同数据类型的集合。
特点:增删慢,因为插入元素和删除元素需要移动整个数组,而查找的话,因为是连续空间的地址,所以查找很快。
注意二维数组的地址是否相同呢,这跟语言相关,c++二维数组的地址空间是连续的,而java的则不是,比如三行四列的数组,每行的地址是连续的,而列之间没有关系。
数组删除时间复杂度O(n),查询时间复杂度O(1)
上面表述不准确:数组随机按照下标随机访问的时间复杂度为O(1),或者已知下标情况下查找元素的时间复杂度为O(1)。因为要找到待查元素的时间复杂度也是O(n),如果是有序数组用二分法查找时间复杂度也是O(logn)
二、链表
什么是链表:链表是一种通过指针串联在一起的线性结构,分为两个区域,一个数据域,一个指针域。
链表的类型:
单链表:只有一个指针域,用于指向下一个节点,只能单向查询。
双链表:有两个指针域,一个指向上个节点,一个指向下个节点,可以双向查询。
循环链表:就是将单链表的首尾相连。
存储方式:
链表在内存中存储非连续的,通过指针串联在一起
定义:
class ListNode{int val;ListNode next;public ListNode(int val){this.val = val;}}
链表插入时间复杂度:O(1),查询时间复杂度O(n)
上面表述不准确:如果已知待删节点的先驱节点,删除节点的时间复杂度为O(1),如果是通过下标来删除节点的话得先找到待删节点,那么这里的时间复杂度也是O(n)
三、哈希表
什么是哈希表? 哈希表是根据关键码值而直接进行访问的数据结构。
哈希表能够根据哈希值直接定位到元素所在位置。哈希值通过哈希函数计算得到,内部原理是将对象的属性方法等映射称为一个独一无二的数字。
如果不同对象哈希值的余数相等,就到了同一个哈希表中的位置了,这种情况叫做哈希碰撞。
解决办法
拉链法:在那个位置用链表来存储。
线性探测法:如果当前位置有元素了,就找下一个空位来存放。
常见的三种哈希结构
- 数组
- set
- map
什么时候使用:当需要判断一个元素是否在集合中的时候优先考虑哈希结构。哈希表用空间换来了时间。
hashmap扩容为什么是两倍?
hashmap计算添加元素的时候用的是位运算,是很高效的运算,扩容是两倍二进制的话就是向左移了一位,其他位置的二进制数都没变。
四、栈和队列
什么是栈?是一种线性结构,数据按照先进后出的顺序获取。
什么是队列?也是一种线性结构,数据按照先进先出的顺序获取。
可以用数组和链表实现。
Deque是双端队列,可以从队尾插入或者删除数据,ArrayDeque和LinkedList是实现类,都是双端的。
PriorityQueue是优先队列,有最大优先队列和最小优先队列,最大优先队列:无论入队顺序如何,都是当前最大元素优先出对。
Stack的方法:
- peek() 查看栈顶元素,不移除
- pop() 移除顶部元素,并返回
- push() 压入一个元素
Queue的方法:
添加 :add() 从队尾增加元素,如果队列满了就抛异常;
offer() 如果队列满了就返回false
删除: remove()移除头部元素,如果队列为空抛异常;
得到: element() 返回头部元素,如果队列为空抛异常,
peek() 返回头部元素,为空返回null,
poll()移除头部元素并返回,为空返回null
优先队列(PriorityQueue):在优先队列中,元素被赋予优先级,当访问元素时,具有最高优先级的元素最先删除,优先队列具有最高级先出的特征。
优先队列的添加是add()方法
五、二叉树
满二叉树:是一颗只有度为2和度为0的节点,其中度为0的节点在同一层。一颗深度为k的满二叉树(2^k)-1个几点。
完全二叉树:一颗深度为k的二叉树,除了第k层没有节点数没有满之外,其余各层的节点数都满了,每层节点为2^(k-1)个,并且第k层的节点都集中在左边。
二叉搜索树:二叉搜索树是每个节点有数值的树,一个节点的左子树的所有节点都要小于该节点值,右子树的所有节点的值都要大于该节点值。
平衡二叉搜索树(AVL:Adelson-Velsky and Landis):除了是一颗搜索树,它可以是一颗空树或者左子树和右子树的深度差不超过1。
图中第三棵树的左子树深度为2,右子树深度为0,差超过了1。
AVL插入值出现以下几种情况需要做旋转处理:
左左型
右右型
左右型
右左型

总结:左左型—-右旋
右右型—-左旋
左右型—-先左旋,后右旋
右左型—-先右旋,后左旋
AVL的效率:N个节点的AVL,高度可以保持在log2n,插入/查找/删除的时间复杂度也是log2n。
红黑树:
- 每个节点数不是红色就是黑色
- 根节点和叶子节点null为黑色
- 从一个节点到每个叶子节点的黑色节点数量相同
- 红色节点的子节点为黑色

在java中的TreeMap和TreeSet的底层原理就是红黑树,用于存储有序数组的,操作效率为O(logn)
AVL和红黑树的区别?
红黑树是一种弱平衡树,如果有相同节点AVL的高度比红黑树低,所以红黑树降低了对旋转的要求更适合,红黑树适合插入删除较多的情况,AVL更适合查找更多的情况。
树节点的定义:
class TreeNode{int val;TreeNode left;TreeNode right;public TreeNode(){}public TreeNode(int val){this.val = val;}public TreeNode(int val, TreeNode left, TreeNode right){this.value = value;this.left = left;this.right = right;}}
用数组表示树怎么遍历的呢?
a = {1,2,3,4,5,6}, 一般根节点为1,左子节点2,右子节点3,左子节点的左子节点4,左子节点的右子节点5,右子节点的左子节点6。用数组表示树比较少,一般用链式结构比较多。
二叉树的遍历:
- 深度优先:先往深走,遇到子节点返回
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
前序遍历:中左右; 中序遍历:左中右; 后序遍历:左右中; 前中后指的就是中的位置。
因为栈先进后出适合模拟深度优先的遍历方法,可以用栈来做迭代法。队列的先进先出又适合一层一层的广度优先遍历。
- 广度优先:一层一层的遍历
- 层次遍历(迭代法)
队列法:层次遍历的思路是用一个队列记录每层的节点,然后取出每层节点的值。然后把这层每个节点的的下两个节点放到队列里面去。
public List<List<Integer>> breadthTracerse(TreeNode root){if(root == null){return null;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);//用来存放每层的节点List<List<Integer>> result = new ArrayList<>();while(!queue.isEmpty()){List<Integer> temp = new ArrayList<>();int size = queue.size();//取出当前这层所有节点,顺便连把下一层的节点全部放到队列里面for(int i = 0; i < size; ++i){TreeNode t = queue.poll();temp.add(t.val);if(t.left != null){queue.offer(t.left);}if(t.right != null){queue.offer(t.right);}}result.add(temp);}return result;}
递归法:递归法用一个层次变量来记录当前递归到了哪层,如果到了到那层就把数据加到哪层。
public void breadthTraverse(TreeNode node,int deep,List<List<Integer>> result2){if(node == null){return;}deep++;//如果当前层没有容器来装里面就新建一个容器if(result2.size() < deep){result2.add(new ArrayList<Integer>());}result2.get(deep-1).add(node.val);breadthTraverse(node.left,deep,result2);breadthTraverse(node.right,deep,result2);}
层序遍历思路,就遍历每层节点,遇到节点就把节点的左右子节点交换一下就OK啦。
public void reverseTree1(TreeNode root){if(root == null){return;}Queue<TreeNode> queue = new LinkedList();queue.offer(root);while(!queue.isEmpty()){int size = queue.size();for(int i = 0; i < size; ++i){TreeNode t = queue.poll();TreeNode temp = t.left;t.left = t.right;t.right = temp;if(t.left != null){queue.offer(t.left);}if(t.right != null){queue.offer(t.right);}}}return;}
六、B树
B树相当于一颗多叉查找树,对于一颗m阶的B数具有具有如下特性:
- 根节点至少有两个孩子;
- 每个中间节点都包含k-1个元素和k个孩子,其中m/2<=k<=m;
- 每一个叶子都包含k-1个元素,其中m/2<=k<=m;
- 所有的叶子节点都位于同一侧;
- 每个节点中的元素从小到大排列,节点中的k-1个元素正好是k个孩子包含的元素的值域划分。

对于这棵m=3的B树,这个树中是是有多个元素的,并且和二叉树一样,左节点的所有元素的值都比父亲元素小,对于[3,7]这个节点,2个节点刚好分成了三个域,对应三个孩子,2相当于3的左孩子节点,[4,6]相当于3的右孩子节点且是7的左孩子节点,9是7的右孩子节点。只是B树每个节点中可以装多个元素,且有多个分支。
为什么用B+树不用二叉树和哈希表?
从查找次数来说B+树和二叉树的查找时间差不多,但是树的节点在磁盘中的存储是不连续的,磁盘中的数据是以页为单位加载到内存中的,不同节点很可能分配到不同的磁盘页中,而B+树节点中的元素地址是连续的,所以在磁盘中的寻址次数减少了许多。
如果要进行模糊查询的话,hash表也只能遍历所有数据,如果哈希碰撞太多的话,查找效率也会降低很多。
B+树
先看定义,一个m阶的k子树有如下几个特征:
- 有k个子树的中间节点包含有k个元素(B树是k-1个),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点中;
- 所有叶子节点包含了全部元素的信息,及指向含有这些元素记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接。
- 所有的中间节点元素都同时存在于子节点,在子节点中是最大(或最小)元素

图中,每个中间节点有几个元素就有几个孩子,并且元素在孩子中是最大值,8是258节点的最大值,8是68节点的最大值。根元素的最大值就是整个b+树的最大值,以后无论插入删除多少元素,都要保持最大元素在根节点中。每个叶子节点中都有指向下一个节点的指针形成一个有序链表。
卫星数据:索引元素指向的数据记录,比如数据表中的某一行,B-树中无论中间节点还是叶子节点中都有卫星数据,B+树中只有叶子节点带有卫星数据,其余中间节点仅仅是索引,没有任何数据关联。
B+树中的卫星数据:
在数据库中的聚集索引(Clustered Index)中,叶子节点直接包含卫星数据,在非聚集索引中,叶子节点直接指向卫星数据的指针。
聚集索引是在B+中存储所有数据;非聚集索引叶子节点中存储的是主键,需要再根据主键查找数据。
B+树好处:范围查询更方便;
磁盘IO查询更少,因为中间节点不存储数据。
