一、什么是数据结构

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

可视化:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

二、数据结构分类

数据结构一般来说按照存储结构细分为 数组、链表、栈、队列、树、图、等等,如果按照逻辑结构来就简单分为线性非线性
15 数据结构 - 图1
线性结构,指的是 数据元素之间存在着“一对一”的线性关系的数据结构,存在一个绝对的前一个元素和最后一个元素,且每个元素都只存在一个唯一的前驱和后后继元素 。
非线性结构,数学用语,其逻辑特征是一个结点元素可能有多个直接前驱和多个直接后继。

三、线性数据结构

3.1 数组

数组是一种基础的线性数据结构,一个数组的内存空间在物理上是连续的,通过首地址偏移来寻址元素。

一维数组示意图:
15 数据结构 - 图2

二维数组不是线性数据结构。

ArrayList Vector 均采用数组作为底层存储数据的结构

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  3. {
  4. private static final long serialVersionUID = 8683452581122892189L;
  5. /**
  6. * 默认初始化容量
  7. */
  8. private static final int DEFAULT_CAPACITY = 10;
  9. /**
  10. 默认数组。
  11. */
  12. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  13. /**
  14. 存放数据的数组
  15. */
  16. transient Object[] elementData; // non-private to simplify nested class access
  17. /**
  18. 记录元素存放的个数
  19. */
  20. private int size;
  21. }


  1. public boolean add(E e) {
  2. //确保数字的容量足够
  3. ensureCapacityInternal(size + 1); // Increments modCount!!
  4. elementData[size++] = e;
  5. return true;
  6. }
  7. private void ensureCapacityInternal(int minCapacity) {
  8. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  9. }
  10. //计算最低需要的空间
  11. private static int calculateCapacity(Object[] elementData, int minCapacity) {
  12. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  13. return Math.max(DEFAULT_CAPACITY, minCapacity);
  14. }
  15. return minCapacity;
  16. }
  17. //扩容
  18. private void ensureExplicitCapacity(int minCapacity) {
  19. modCount++;
  20. // overflow-conscious code
  21. if (minCapacity - elementData.length > 0)
  22. grow(minCapacity);
  23. }
  24. //扩容逻辑
  25. private void grow(int minCapacity) {
  26. // overflow-conscious code
  27. int oldCapacity = elementData.length;
  28. int newCapacity = oldCapacity + (oldCapacity >> 1);
  29. if (newCapacity - minCapacity < 0)
  30. newCapacity = minCapacity;
  31. if (newCapacity - MAX_ARRAY_SIZE > 0)
  32. newCapacity = hugeCapacity(minCapacity);
  33. // minCapacity is usually close to size, so this is a win:
  34. elementData = Arrays.copyOf(elementData, newCapacity);
  35. }

3.2 链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

单向和双向连表:

15 数据结构 - 图3

LinkedList 底层使用双向连表结构

  1. private static class Node<E> {
  2. E item;
  3. Node<E> next;
  4. Node<E> prev;
  5. Node(Node<E> prev, E element, Node<E> next) {
  6. this.item = element;
  7. this.next = next;
  8. this.prev = prev;
  9. }
  10. }
  1. void linkLast(E e) {
  2. final Node<E> l = last;
  3. final Node<E> newNode = new Node<>(l, e, null);
  4. last = newNode;
  5. if (l == null)
  6. first = newNode;
  7. else
  8. l.next = newNode;
  9. size++;
  10. modCount++;
  11. }

3.3 栈

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈结构示意图:

15 数据结构 - 图4

后进先出 F I L O

Java Stack 通过Vector实现。

3.4 队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列示意图:
image.png
该结构的特点是 先进先出 FIFO

Java 集合中 LinkedList 实现了队列的功能

  1. 入队列,添加到链表尾部
  2. public boolean add(E e) {
  3. linkLast(e);
  4. return true;
  5. }
  6. 出队列,从链表头部移除
  7. public E poll() {
  8. final Node<E> f = first;
  9. return (f == null) ? null : unlinkFirst(f);
  10. }

remove() 获取并移除但是如果队列空了就抛出异常

四、非线性数据结构

4.1 树(Tree)

是一种数据结构,它是由n(n≥1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  1. 每个节点有零个或多个子节点;
  2. 没有父节点的节点称为根节点;
  3. 每一个非根节点有且只有一个父节点;
  4. 除了根节点外,每个子节点可以分为多个不相交的子树。

15 数据结构 - 图6

4.2 二叉树(Binary Tree)

二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树

15 数据结构 - 图7

4.3 二叉查找树(Binary Serarch Tree) 又称二叉搜索树

二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:

  • 若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
  • 若其右子树存在,则其右子树中每个节点的值都不小于该节点值。

15 数据结构 - 图8

二叉树遍历
rnaj6mzn3u.png

4.4 二叉平衡查找树(self-balancing binary search tree)

AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它

15 数据结构 - 图10

左旋右旋操作:

  1. 左旋:对X节点左旋,即以X的右子树Y为轴将X节点转下来,变为Y的左子树,
    简单记为左旋即把该节点变为左子树
  2. 右旋:对X节点右旋,即以X的左子树Y为轴将X节点转下来,变为Y的右子树,
    简单记为右旋即把该节点变为右子树

image.png

4.5 红黑数( Red-Black Tree )

红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。 红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。 由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。

要求:

  1. 每个节点非红即黑
  2. 根节点是黑的;
  3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
  4. 如图所示,如果一个节点是红的,那么它的两儿子都是黑的;
  5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
  6. 每条路径都包含相同的黑节点;

红黑树示意图:

15 数据结构 - 图12

4.6 散列表结构( HashTable )

hash表示意图:

15 数据结构 - 图13

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  2. boolean evict) {
  3. Node<K,V>[] tab; Node<K,V> p; int n, i;
  4. // 如果当前hashtable 为null
  5. if ((tab = table) == null || (n = tab.length) == 0)
  6. // 初始化hashtable 返回了底层hashtable
  7. n = (tab = resize()).length;
  8. // 通过生成hashcode 判断 此位置是否为null
  9. if ((p = tab[i = (n - 1) & hash]) == null)
  10. //如果为空直接保存
  11. tab[i] = newNode(hash, key, value, null);
  12. else {
  13. //此位置有人,且key相同
  14. Node<K,V> e; K k;
  15. if (p.hash == hash &&
  16. ((k = p.key) == key || (key != null && key.equals(k))))
  17. e = p;
  18. // key 不同,如果这里是一个红黑树,添加到树上
  19. else if (p instanceof TreeNode)
  20. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  21. else {
  22. // 这里存在的元素是一个链表,添加链表尾部
  23. for (int binCount = 0; ; ++binCount) {
  24. //找到了末尾
  25. if ((e = p.next) == null) {
  26. //元素添加到末尾
  27. p.next = newNode(hash, key, value, null);
  28. // 如果链表长度超过8 把链表转成红黑树(添加次数可能很多,使用红黑树效率更好)
  29. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  30. treeifyBin(tab, hash);
  31. break;
  32. }
  33. //寻找过程中如果和链表中的某个节点和添加的重复放弃存储
  34. if (e.hash == hash &&
  35. ((k = e.key) == key || (key != null && key.equals(k))))
  36. break;
  37. //
  38. p = e;
  39. }
  40. }
  41. //如果添加的key存在则覆盖以前的值,并返回被覆盖的旧的值。
  42. if (e != null) { // existing mapping for key
  43. V oldValue = e.value;
  44. if (!onlyIfAbsent || oldValue == null)
  45. e.value = value;
  46. afterNodeAccess(e);
  47. return oldValue;
  48. }
  49. }
  50. ++modCount;
  51. if (++size > threshold)
  52. resize();
  53. afterNodeInsertion(evict);
  54. return null;
  55. }
  1. 分类树结构
  2. [ 千锋北京总部 ]
  3. 重庆 成都 西安 广州
  4. 石桥铺 杨家坪
  5. ID 名字 父级ID
  6. 1 数码 0
  7. 2 手机 1
  8. 3 相机 1
  9. 4 单反 3
  10. 5 交卷 3
  11. 6 智能手机 2
  12. 7 华为 6
  13. 8 Vivo 6
  14. 数码
  15. 手机 相机
  16. 智能手机 单反 交卷
  17. 华为 vivo
  18. clss Node{
  19. id
  20. name
  21. pid
  22. List<Node> child;
  23. }