文章目录
- 简析java中的数据结构
- 1、概论
- 1.1 基本概念
- 1.2 数据结构涵盖了什么?
- 2、常见的java实现的数据结构
- 2.1 栈
- 2.2 队列
- 2.3 链表
- 2.4 散列表
- 2.5 排序二叉树
- 2.6 红黑树
- 2.7 B-Tree(顺带了解的)
- 2.8 位图
- 结语
简析java中的数据结构
1、概论
1.1 基本概念
首先来说说几个术语:
- 什么是数据(data)?
所有能被计算机识别、存储和处理的符号的集合包括数字、字符、声音、图像等信息 - 什么是数据元素(data element)?
数据的基本单位,具有完整确定的实际意义(又称元素、结点,顶点、记录等) - 什么是数据项(data item)?
构成数据元素的项目。是具有独立含义的最小标识单位(又称字段、域、属性 等)
以上三者之间的关系如下:
【Question】
1、那么什么是数据结构呢?
答:相互之间存在一种或多种特定关系的数据元素的集合。
2、学习数据结构有啥子用嘞?
答:计算机内的数值运算依靠方程式,而非数值运算(如表、树、图等)则要依靠数据结构。
另外,**同样的数据对象,用不同的数据结构来表示,运算效率可能有明显的差异。**所以有一种说法是,程序设计的实质就是好的算法结合好的数据结构。
1.2 数据结构涵盖了什么?
首先我们来看一张图:
- 数据的逻辑结构:
指数据元素之间的逻辑关系。即从逻辑关系上描述数据,它与数据的存储无关,是独立于计算机的。逻辑结构可细分为4类: - 数据的物理结构
物理结构亦称存储结构,是数据的逻辑结构在计算机存储器内的表示(或映像)。它依赖于计算机。
2、常见的java实现的数据结构
2.1 栈
栈( stack)是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶
(top)。它是后进先出(LIFO)的。对栈的基本操作只有 push(进栈)和 pop(出栈)两种,
前者相当于插入,后者相当于删除最后的元素 。
在java中,对栈(Stack)是有定义的:
public class Stack<E> extends Vector<E> {
// 创建一个空栈
public Stack() {
}
// 增加元素到栈顶,并返回目标元素
public E push(E item) {
addElement(item);
return item;
}
// 移除这个栈的栈顶元素,并返回它的值
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
// 返回栈顶元素,并不从栈中移除
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
// 判断栈是否为空
public boolean empty() {
return size() == 0;
}
// 查找栈中是否有目标元素,有就返回其下标索引,没有就返回-1
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
private static final long serialVersionUID = 1224463164541339165L;
}
Stack类的继承关系:
2.2 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的
后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为
队尾,进行删除操作的端称为队头。
java中的队列是一个接口,拥有很多个实现:
public interface Queue<E> extends Collection<E> {
// 在指定的大小范围内增加元素,成功则返回true;否则会抛出 IllegalStateException
boolean add(E e);
// 增加元素,当使用指定大小的队列时,最好使用 add()
boolean offer(E e);
// 移除队头元素,并返回该元素;当队列为空,会抛出 NoSuchElementException
E remove();
// 移除队头元素,并返回该元素;队列为空会返回 null
E poll();
// 取出元素,并不从队列中删除;当队列为空,会抛出 NoSuchElementException
E element();
// 取出元素,并不从队列中删除;当队列为空会返回 null
E peek();
}
它的实现类:
常用的有个抽象实现:
public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E>
它对应的几个实现类,是挺常用的:
建议,在使用带排序的队列、或其他集合时,可以先使用效率比较高的排序算法,先让数据有序,在添加到目标容器中,当然,如果就几十条数据,那还是直接放吧。
2.3 链表
链表是一种数据结构,和数组同级。比如, Java 中我们使用的 ArrayList
,其实现原理是动态可扩容的数组。而LinkedList
的实现原理就是链表了。链表在进行循环遍历时效率不高,但是插入和删除时优势明显。
LinkedList (双向链表)的定义:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
2.4 散列表
散列表(Hash table,也叫哈希表)是一种查找算法,与链表、树等算法不同的是,散列表算法
在查找时不需要进行一系列和关键字(关键字是数据元素中某个数据项的值,用以标识一个数据
元素)的比较操作。
散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必
须要在数据元素的存储位置和它的关键字(可用 key 表示)之间建立一个确定的对应关系,使每个
关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定
关键字在散列表中的位置即可。这种对应关系被称为散列函数(可用 h(key)表示)。
用的构造散列函数的方法有:
(1) 直接定址法: 取关键字或关键字的某个线性函数值为散列地址。
即: h(key) = key 或 h(key) = a * key + b, 其中 a 和 b 为常数。
(2) 数字分析法
(3) 平方取值法: 取关键字平方后的中间几位为散列地址。
(4) 折叠法: 将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
(5) 除留余数法: 取关键字被某个不大于散列表表长 m 的数 p 除后所得的余数为散列地址,
即: h(key) = key MOD p p ≤ m
(6) 随机数法: 选择一个随机函数,取关键字的随机函数值为它的散列地址,
即: h(key) = random(key)
java中的实现,有两种:HashTable
和HashMap
。
我们先来看看HashTable
的实现,它在给容器中添加元素时,有一段计算元素下标的地方:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
// 先计算当前key的hashcode
int hash = key.hashCode();
// 计算下标,hash表对应的索引
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
再来看看这HashMap
中的计算逻辑:
// 首先它定义了自己的 hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 在存储值的时候,会计算这个key,通过它确定存储的位置
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
2.5 排序二叉树
如果普通二叉树每个节点满足:左子树所有节点值小于它的根节点值,且右子树所有节点值
大于它的根节点值,则这样的二叉树就是排序二叉树 。
对应在java中的 TreeMap
,TreeSet
。
它俩的关系是,TreeSet 在实现的时候,直接 new 了一个 TreeMap,存储的 value 是一个空对象(Object)。
因此主要的实现,在于TreeMap中的比较器(也就是说,可以通过它自定义自己的排序规则)。
// 需要一个比较器,当构造器不传入时,为 null,是按照字典顺序。
private final Comparator<? super K> comparator;
2.6 红黑树
R-B Tree,全称是 Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每
个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black) 。
特点:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 注意:这里叶子节点,是指为空(NIL 或NULL)的叶子节点!
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
在java8中,HashMap
的源码中有涉及到红黑树的部分。定义了一个TreeNode
节点来实现的。用来解决一类元素的hash值一直重复导致拉链法的长度过长,查询元素使用红黑树之后,其查找效率会显著增加。
2.7 B-Tree(顺带了解的)
B-tree 又叫平衡多路查找树。一棵 m 阶的 B-tree (m 叉树)的特性如下(其中 ceil(x)是一个取上限
的函数) :
- 树中每个结点至多有 m 个孩子;
- 除根结点和叶子结点外,其它每个结点至少有有 ceil(m / 2)个孩子;
- 若根结点不是叶子结点,则至少有 2 个孩子(特殊情况:没有孩子的根结点,即根结点为叶子
结点,整棵树只有一个根节点); - 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部结点或查询
失败的结点,实际上这些结点不存在,指向这些结点的指针都为 null); - 每个非终端结点中包含有 n 个关键字信息: (n, P0, K1, P1, K2, P2, …, Kn, Pn)。其中:
a) Ki (i=1…n)为关键字,且关键字按顺序排序 K(i-1)< Ki。
b) Pi 为指向子树根的接点,且指针 P(i-1)指向子树种所有结点的关键字均小于 Ki,但都大于 K(i-1)。
c) 关键字的个数 n 必须满足: ceil(m / 2)-1 <= n <= m-1。
另外:
一棵 m 阶的 B+tree 和 m 阶的 B-tree 的差异在于:
1.有 n 棵子树的结点中含有 n 个关键字; (B-tree 是 n 棵子树有 n-1 个关键字)
2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本
身依关键字的大小自小而大的顺序链接。 (B-tree 的叶子节点并没有包括全部需要查找的信息)
3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。
(B-tree 的非终节点也包含需要查找的有效信息)
2.8 位图
位图的原理就是用一个 bit 来标识一个数字是否存在,采用一个 bit 来存储一个数据,所以这样可
以大大的节省空间。 bitmap 是很常用的数据结构, 比如用于 Bloom Filter 中;用于无重复整数的
排序等等。 bitmap 通常基于数组来实现,数组中每个元素可以看成是一系列二进制数,所有元素
组成更大的二进制集合。
在java中,有一个叫 BitSet
的类,可以用于大数据量排序(常规逻辑下,内存放不下的那么大)
结语
那么本次就点到为止。
主要是介绍整个体系,以及与java中的联系。有些地方可能不太对,还希望大家多多指教。