数据结构整理

2020-08-07  本文已影响0人  我就是小政政

常用数据结构时间复杂度

数据结构 新增 查询/Find 删除/Delete GetByIndex
数组 Array (T[]) O(n) O(n) O(n) O(1)
链表 Linked list (LinkedList<T>) O(1) O(n) O(n) O(n)
Resizable array list (List<T>) O(1) O(n) O(n) O(1)
Stack (Stack<T>) O(1) - O(1) -
Queue (Queue<T>) O(1) - O(1) -
Hash table (Dictionary<K,T>) O(1) O(1) O(1) -
Tree-based dictionary(SortedDictionary<K,T>) O(log n) O(log n) O(log n) -
Hash table based set (HashSet<T>) O(1) O(1) O(1) -
Tree based set (SortedSet<T>) O(log n) O(log n) O(log n) -

Map

map用来存储k、v结构数据,相信大家使用的都比较熟练了,现在我们就深入分析一下它的细节。

Java中的Map

map都有哪些方法?


image.png

这个是map的接口规定了一系列的操作方法以及一个Entity接口。


image.png

这个是Java集合的类图,可以看到Map的一些主要实现。

HashMap的原理

在HashMap中存储的元素定义如下

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;  // key的hash值
    final K key;     //  key值
    V value;         //  value值
    Node<K,V> next;  // 下一个node
    ... ...
}

HashMap中定义了一个Node数组,用来存放Node,这些Node根据key的hash值均匀分布在数组上,但是不可避免的存在hash冲突(不同Node它们的key的hash值相同),那么就在每个数组元素上向后拓展链表结构。
查询的时候,先根据key的hash确定数组下标,然后再使用equals方法遍历链表比较值最终找到想要的Node并返回。
这里有一些关键数据,Node数组的初始大小为16(2的n次方),hash冲突链表长度超过8将转换为红黑树(红黑树查询速度比链表快),map会记录一共存了多少个Node就是size,同时规定了一个加载因子loadFactor默认为0.75,利用这个加载因子来估算当前map最大能存多少个Node比较合理就是threshold(最大容量)。
插入的时候一旦size>threshold了就需要扩容,扩容时Node数组大小*2,并将所有的Node重新hash存一遍,这部分开销较大。每次结构发生改变会记录到变量modCount(结构变化次数)中。

TreeMap

参考: TreeMap原理实现及常用方法

基础概念

结点:树中的每个元素称为结点
根结点:最顶上的结点,一个树只有一个根结点
每个结点可以拥有子树,一个结点的所有子树都不想交
普通树

image.png
结点的度:一个结点有几个子节点就有几度
结点关系:父结点、子结点、兄弟结点
结点层次:最大层数表示树的深度
image.png
二叉树:结点的度不超过2,结点在左在右是有顺序的
左斜树:左左左左左
右斜树:右右右右右
满二叉树:最后一层结点的度为0,其他层结点的度都为2
image.png
完全二叉树:把满二叉树按层、从左到右编号。按编号新填充一个二叉树,这个树就是个完全二叉树
按顺序填充ABCDEF就是个完全二叉树
image.png

二叉树

二叉树存储结构:如果每一层用一个数组存,显然有的地方没有结点,会浪费数组资源。
所以使用二叉链表

image.png
代码表示:
typedef struct BiTNode{
    TElemType data;//数据
    struct BiTNode *lchild, *rchild;//左右孩子指针
} BiTNode, *BiTree;
image.png
二叉树遍历
image.png
前序遍历根左右
ABDECF
中序遍历左根右
DBEAFC
后序遍历左右根
DEBFCA
层序遍历:逐层从左到右
[A]
[BC]
[DEF]

二叉搜索树BST

二叉搜索树(二叉查找树)BST:左结点<根结点<右结点

image.png
每个结点结构如下:
image.png

:1.查找到空没结果;2.值相等查找成功;3.小于结点递归查找其左子树,大于结点递归查找其右子树;
:1.存在不插入;2.不存在,执行查找时执行到哪个位置就插入到哪个位置;
:1.若为叶子结点直接删;2.若只有左子树或右子树,将左子树或右子树代替该结点;3.若左右子树都存在,找到左子树中最大值结点,替换到待删除的位置

平衡二叉树AVL

平衡二叉树AVL
当结点数目一定时,保持树的左右两端平衡,树的查找效率最高。这种左右子树层级相差不超过1的树叫平衡二叉树。 AVL是通过平衡深度来解决BST的搜索效率降低的策略。

image.png
平衡因子:结点的左子树与右子树的高度(深度)差叫做平衡因子(0,-1,1)
平衡二叉树代码结构
typedef struct AVLNode *Tree;
typedef int ElementType;
struct AVLNode
{
    int depth; //深度,这里计算每个结点的深度,通过深度的比较可得出是否平衡
    Tree parent; //该结点的父节点,方便操作
    ElementType val; //结点值
    Tree lchild;
    Tree rchild;
    AVLNode(int val=0) //默认构造函数
    {
        parent=NULL;
        depth=0;
        lchild=rchild=NULL;
        this->val=val;
    }
};

左旋:右子树过深,要平衡到左边去

image.png
因为右子树深度超出左子树深度2,要进行左旋平衡,流程:
1.原根结点的右结点代替根节点
2.现根节点的左子树变为原根节点的右子树
3.原根节点变为现根节点的左子树
右旋:左子树过深,要平衡到右边去
方向相反,流程同上
image.png
插入结点
在根结点的左孩子的左子树插入结点LL:直接右旋
在根结点的右孩子的右子树插入结点RR:直接左旋
在根结点的左孩子的右子树插入结点LR:先对根结点的左孩子左旋,在对根结点右旋
在根结点的右孩子的左子树插入结点RL:先对根结点的右孩子右旋,在对根结点左旋
删除
删除流程同二叉搜索树,但要考虑删除后是否平衡,不平衡要进行调整。

2-3树

2-3树:AVL虽然解决了BST的查询效率降低问题,但是却引入了插入、删除时的繁琐操作,这些性能的损耗可能会大于检索带来的提升。所以引入了2-3树。存在2度、3度结点,2度结点结构同BST,3度结点数据域a、b,3个指针,左子树所有结点小于a,中子树所有结点在ab之间,右子树所有结点大于b。
2-3树的查找:同BST,都是通过比较选定路线继续查找。

image.png
2-3树插入

2-3-4树

2-3树不再是单纯的二叉树了,因为2-3树中除了2-节点之外还存在3-节点。在2-3树的基础上进一步扩展,2-3-4树在2-3树的基础上添加4-节点。4-节点可以存储3个键值,最多可以拥有4棵子树。


image.png

红黑树

2-3-4树和红黑树是完全等价的,但是2-3-4树的编程实现相对复杂,所以一般是通过实现红黑树来实现替代2-3-4树,而红黑树也同样保证在O(logN)的时间内完成查找、插入和删除操作。


image.png

B树

数据量大如数据库时,不能全放内存,需要放磁盘,每一个结点代表一个磁盘页,访问一次新结点代表一次磁盘io,那么树的高度决定磁盘io的次数,所以让每个结点增加key值,就可以将“瘦高”的树变为“矮胖”的树。


image.png

B+树

在B树的基础上优化,分为索引结点(只存key不存data)、叶子结点(key、data),叶子结点使用链表连接。


image.png

(1)B+树中索引节点只存储key值,不存储数据,则相同的大小磁盘页中,使用B+树可以存储更多的节点元素,使得B+树比B树更为矮胖,减少磁盘IO次数。

(2)在查找单一元素时,B树最好情况是在根节点查找成功,最坏情况是查找至叶子节点,导致B树的查找过程是不稳定的。而使用B+—树的查找最终查找节点为叶子节点,使得B+树查找稳定。

(3)在进行某个范围内数据查找,B树需要进行中序遍历,而B+树的叶子节点采用单链表形式链接,只需按顺序遍历链表即可完成范围查找,提高了查找效率。

上一篇下一篇

猜你喜欢

热点阅读