跳表加散列表实现HashMap

跳表JAVA实现 1.跳表的定义 跳跃表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间),并且对并发算法友好。 SkipList(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。SkipList让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率。 在Java的API中已经有了实现:分别是: Conc 阅读详情

跳表加散列表实现HashMap

在平时的开发中,jdk库中的HashMap是我们在平时的开发中用的非常多的容器之一,我常常在想,自己能否也实现一个呢?以前觉得非常的复杂,但当我学习了跳表,和散列表后,我觉得这个问题我可以尝试了,于是就开始了自己实现MyHashMap之旅。

1. 需求定义

既然要实现一个HashMap,那还是先需求吧。

public class MyHashMap<K, V> {
	public MyHashMap(int capacity){}
    public void put(K key, V value) {}
    public V get(K key) {
        return null;
    }
    public void remove(K key){}
}
  1. 能够支持数据最基本的操作。包括添加一个键值对,根据key获取值,以及按key删除值三个基本的方法。
  2. 能够支持动态的扩容。扩容每次不能占用太多的时间,以免单个操作等待时间过长。
  3. 当遇到极端情况(hash到一个槽)时间复杂度也不能退化为O(n)。

2. 实现分析

针对以上的需求,可以归纳为这几个问题

  1. 如何设计散列函数?
  2. 装载因子过大怎么办?
  3. 如何避免低效的扩容?
  4. 链表冲突的问题解决?

如何设计散列函数?

散列函数设计的好坏决定散列冲突的概率,也决定了散列表的性能。什么样的散列函数才是一个好的散列函数?

  1. 散列函数的设计不能太复杂。复杂的函数会消耗大量的计算资源,间接影响了散列表性能。

  2. 散列函数生成的值要尽可能的随机并且均匀分布。这样能才避免或者最小化冲突,即使冲突,散列到每个槽内的数据也会比较的平均。不会出现严重的数据倾斜问题。

实际上散列方法有很多,这里采用的是HashMap中的使用散列函数.

  private int hash(K key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }

装载因子过大怎么办?

散列表的装载因子 = 装入散列表中的元素个数 / 散列表的长度

装载因子越大说明空闲位置越少,冲突越多,散列表的性能会下降。

针对目前的需求这是一个动态的散列表,数据的集合是变化 的,我们事先根本无法预估出数据个数,我们也无法事先申请一个足够大的散列表,装载因子就会慢慢的变大,当装载因子大到一定程序后,散列冲突就会变得不可接受。针以地这个问题该如何处理?

当装载因子过大的时候,可以采用扩容的办法来解决这个问题。重新申请一个更大的散列表。将数据搬移到这个新的散列表中,假如每次扩容都申请一个原来散列表大小2倍的空间,如果原来散列表的装载因子是1,经过扩容后,新的散列表的装载因子就变成了0.5

如何避免低效的扩容?

在大部分情况,动态扩容的散列表插入都很快,但在特殊情况下,当装载因子已经到阈值,需要先扩容,再将原有的数据搬移到新的散列表中,再插入数据。这个时候就会很慢。甚至无法接受的程度。

举个例子,假如现在的散列表当前大小为10万个大小,想要扩容为原来的2倍大小,那需要新申请一个20万个大小的散列表,然后对这10万个数据进行重新计算hash,将数据保存到新的散列表中,并且将原散列表中的数据删除,这个不用计算了,这个听起来就非常的耗时。那如何来解决这个问题呢?

为了解决一次性扩容耗时过多的问题。可以将扩容操作放到插入操作中进行,分批完成数据的搬移。当装载因子阈值到达时,只申请新的空间,但并不搬移老的数据到新的散列表中。当有新的数据要插入时,将新数据插入到散列表中,并从老的散列表中拿出几人数据放到散列表中。每插入一个数据,重复这一过程。经过多次的插入手,老的散列表中的数据,就被一点一点的搬移到了,新的散列表中。这样没有了一次性搬移操作,插入操作就会变得很快。

但这样操作也会带来新的问题?由于现在存在两个散列表,新散列表与旧散列表。那查询怎么办?

针对这个问题,我们可以分开查询两个散列表,优先从新的散列表开始搜索。当数据被搜索到,直接返回,搜索不到再从旧在的散列表中去搜索。

通过这样均摊的办法,将一次扩容的代价,均摊到了多次插入操作。就避免了一次性扩容过多的情况。这样的实现方式下,任何情况下,插入的数据时间复杂度都是O(1)。

链表冲突的问题解决?

由于散列冲突是一定会存在的。那如何解决散列冲突?

解决链表冲突办法有多种,常用的有两种,分别为开放寻址法和链表法。

这里采用的链表法,但采用链表法在极端情况下,操作的时间复杂度会退化为O(n),这里我加入了跳表来解决这个问题.

那来看看链表与跳表配合解决这个问题的:

来一起看看这整体一个存储的结构吧:

在这里插入图片描述

在插入数据时将检查当前槽内的数据个数,小于8个则为链表,当大于8个时,则将链表转换为跳表,并将链表中的数据搬移到跳表中。

这就是采用链表与跳表结合的方法来解决散列冲突的问题,这样当一个数据插入时,就算遇到极遇的情况,也不会退化成O(n),而是O(logn),那为什么不将所有的都切换为跳表呢?

这是因为跳表不仅需要额外的空间,也是需要对操作进行遍历,当数据个数很少时,其数据量很少时,没有任何的优势。当数据量大了以后,跳表的优势才能发挥。采用跳表,又带来了新的问题了,那就是跳表的高度?

由于数据不断的被插入,如果跳表的高度是不变的情况下,时间复杂度就会退化为O(n/m),这样就需要保证跳表的高度随着数据增加而增加。同样的当数据减少后进行跳表层级的减少。那具体怎么解决这个问题呢?

首先我是通过定义阈值来解决的,添加跳表层级的阈值,这个值是来的呢?跳表的高度是随机分布的,它能证在平均情况下的时间复杂度为O(logn),先来看看跳表的随机函数吧!

private int randomLevel() {
    int level = 1;
    for (int i = 1; i < MAX_LEVEL; ++i) {
      if (random.nextInt() % 2 == 1) {
        level++;
      }
    }

    return level;
  }

可以看出这个随机函数与跳表的最大高度的直接关系,最大高度定义跳表随机能到达最大层级数。这样当跳表的节点增加到某个阈值时,我们以阈值来计算下跳表的高度即可得到最大值层级高度。那来看看计算跳表的高度的方法吧:

 /**
   * 计算log2n
   *
   * @param size
   * @return
   */
  public int powTwo(int size) {
    int powNum = 1;
    int low = 0, mid, high = size;

    while (low <= high && high >= 4) {
      if ((high - low) % 2 != 0) {
        return -1;
      }

      mid = low + (high - low) / 2;
      if (mid % 2 == 0) {
        powNum++;
        high = mid;
      }
      // 如果当前不能被2整除,则说明当前非2的幂
      else {
        return -1;
      }
    }

    return powNum;
  }

这是采用二分法计算的以2为底的n的对数。

这样跳表的高度可以计算了,那跳表的增加的阈值如何计算呢?这里正常来说可以直接使用以2为底,以跳表的调度为指数计算即可,但在阅读jdk8源码时,发现一个更牛的计算方法,可直接用这个方法更好。

 /**
   * The maximum capacity, used if a higher value is implicitly specified by either of the
   * constructors with arguments. MUST be a power of two <= 1<<30.
   */
  private static final int MAXIMUM_CAPACITY = 1 << 30;
  
 /** Returns a power of two size for the given target capacity. */
  static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  }

这是计算跳表高度以及计算跳表层级的阈值。有了增加跳表层级,那需要减少跳表的层级吗?

如果不减少层级会有影响吗?这个可以想像下,当数据量扩容到1024个节点后,然后再执行删除,一直删除到只有32个顶点,如果跳表的层级还是10层?这时候就会有一些多余的遍历在这个跳表上,所以需要维护跳表的高度与跳表节点之间的一个平衡。这样也得定义一个减少跳表层级的阈值。这个计算过程就同增加跳表层级类似了。

来看看具体是如何被计算的吧

          // 计算增加层级的阈值
          skipThresholdExpand = tableSizeFor(nodes[nodeIndex].size + 2);
          // 计算减少层级的阈值
          skipThresholdShrink = tableSizeFor(nodes[nodeIndex].size - 2);

3. 最终的代码实现

最后还是把自己实现的HashMap的完整代码贴出来:

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 自己实现动态数据结构hashMap
 *
 * <p>特征: 1. 支持动态扩容。 每次以2倍大小进行扩容。
 *
 * <p>2. 初始化大小为16
 *
 * <p>3。 装载因子为0.75
 *
 * <p>4. 键冲突时,在8个以内,使用链表法,当超过8个时,使用跳表。以保证logn的时间复杂度的操作。
 *
 * <p>5. 当数据删除不足8个时,则切换为链表存储
 *
 * <p>6. 当数据数据搬移使用分批搬移的策略,将搬移的压力分担到多次操作中。
 *
 * @version 0.0.1
 */
public class MyHashMap<K, V> {

  /** 初始化默认大小 */
  private static final int INIT_SIZE = 16;

  /** 默认装载因子 */
  private static final float INIT_DIVISOR = 0.75f;

  /** 链表法的大小 */
  private static final int LINK_FLAG = 8;

  /** 最少跳表的个数 */
  private static final int SKIP_LINKED_SIZE = 16;

  /** 默认跳表层级 */
  private static final int DEF_MAX_SKIP_LEVEL = 3;

  /** 每次搬移数据的个数 */
  private static final int MOVE_SIZE = INIT_SIZE / 2;

  /**
   * The maximum capacity, used if a higher value is implicitly specified by either of the
   * constructors with arguments. MUST be a power of two <= 1<<30.
   */
  private static final int MAXIMUM_CAPACITY = 1 << 30;

  /** 容量 */
  private int capacity;

  /** 装载因子 */
  private float divisor;

  /** 数据存储信息 */
  private Node[] table;

  /** 新的数据存储 */
  private Node[] newTable;

  /** 当前map的大小 */
  private int size;

  /** 当前数据移动标识 */
  private boolean moveFlag;

  /** 搬移到的索引位置 */
  private int moveIndex;

  /** 占用槽的数量 */
  private int slotNum;

  /** 计算当前阈值 */
  private int threshold;

  /** 跳表的扩张阈值 */
  private int skipThresholdExpand;

  /** 跳表的收缩 */
  private int skipThresholdShrink;

  public MyHashMap() {
    this.capacity = INIT_SIZE;
    this.divisor = INIT_DIVISOR;
    this.init();
  }

  public MyHashMap(int capacity) {
    this.capacity = capacity;
    this.divisor = INIT_DIVISOR;
    this.init();
  }

  /** 初始化 */
  private void init() {
    this.table = new Node[capacity];
    // 计算当前的阈值
    threshold = (int) (capacity * divisor);
  }

  /**
   * 数据存入
   *
   * @param key 键信息
   * @param value 值信息
   */
  public void put(K key, V value) {

    // 检查占用的槽数
    if (slotNum > threshold) {
      // 执行扩容
      newTable();
      moveIndex = 0;
      moveFlag = true;
    }

    int hashCode = hash(key);
    // 计算hash位置
    int hashIndex = hashIndex(hashCode, capacity);

    // 如果当前容器为空,说明首次插入
    if (null == table[hashIndex]) {
      table[hashIndex] = new Node(null, null);
      table[hashIndex].next = newLinkedNode(key, value, hashCode);
      table[hashIndex].size++;
      slotNum++;
    }

    // 将数据插入至容器中
    nodeInsert(table, key, value, hashCode);

    // 执行部分数据的搬移。
    if (moveFlag) {
      // 分批搬移数据,每次按固定大小8个进行移动操作
      moveData(newTable, table, MOVE_SIZE);
    }
    // 容量的更新
    size++;
  }

  /**
   * 创建新节点
   *
   * @param key
   * @param value
   * @param hashCode
   * @return
   */
  private Node newLinkedNode(K key, V value, int hashCode) {
    // 构建当前对象节点信息
    Node currInsert = new Node(key, value);
    currInsert.hnext = hashCode;
    return currInsert;
  }

  /** 新的hash表 */
  private void newTable() {
    capacity = capacity * 2;
    newTable = new Node[capacity];
    // 将新集合与老集合位置互换
    Node[] dataTmp = table;
    table = newTable;
    newTable = dataTmp;
    // 计算当前的阈值
    threshold = (int) (capacity * divisor);
  }

  /**
   * 数据搬移
   *
   * @param src 原始数据
   * @param target 目标数据
   * @param size 大小
   */
  private void moveData(Node[] src, Node[] target, int size) {

    if (null == src || moveIndex >= src.length) {
      moveFlag = false;
      return;
    }

    // 执行数据的搬移操作
    int moveIndexTmp = 0;
    while (moveIndexTmp < size && moveFlag) {
      Node<K, V> stage = src[moveIndex];

      // 老数据模式为链表存储
      if (stage instanceof Node) {
        Node<K, V> node = stage.next;

        // 进行数据的移动操作
        if (node != null) {
          // 将老数据插入到新的容器中
          nodeInsert(target, node.key, node.value, node.hnext);
          // 旧数据的移除操作
          stage.next = node.next;
          stage.size--;
        }

        // 检查数据项
        if (stage.next == null) {
          moveIndex++;
        }
      }
      // 老数据模式为跳表存储
      else if (stage instanceof SkipNode) {
        SkipNode<K, V> skipNode = (SkipNode) stage;
        SkipNode<K, V> nodeTmp = skipNode.forward[0];

        if (nodeTmp != null) {
          // 将老数据插入至新的容器中
          nodeInsert(target, nodeTmp.key, nodeTmp.value, nodeTmp.hnext);
          // 进行老数据的移除操作
          skipRemove(nodeTmp, skipNode);
          stage.size--;
        }

        if (((SkipNode) stage).forward[0] == null) {
          moveIndex++;
        }
      }

      if (moveIndex >= src.length) {
        moveFlag = false;
      }

      moveIndexTmp++;
    }
  }

  /**
   * 节点插入
   *
   * @param nodes 容器
   * @param key 插入的key
   * @param value 值信息
   */
  private void nodeInsert(Node[] nodes, K key, V value, int hashCode) {
    int nodeIndex = hashIndex(hashCode, nodes.length);
    // 如果为空节点,则进行创建根节点
    if (null == nodes[nodeIndex]) {
      nodes[nodeIndex] = newLinkedNode(key, value, hashCode);
      slotNum++;
    }

    // 当在规则数值内,执行链表法冲突解决
    if (nodes[nodeIndex].size < LINK_FLAG && nodes[nodeIndex] instanceof Node) {
      // 1,检查当前key是否存在
      Node nodeTmp = nodes[nodeIndex];
      while (nodeTmp.next != null) {
        if (nodeTmp.next.key.equals(key)) {
          break;
        }
        nodeTmp = nodeTmp.next;
      }

      // 如果为空说明,都不存在,直接在尾部插入即可
      if (nodeTmp != null) {
        // 构建当前对象节点信息
        nodeTmp.next = newLinkedNode(key, value, hashCode);
        nodes[nodeIndex].size++;
      }
      // 当前不为空,说明已经存在在相同的key,直接替换即可
      else {
        nodeTmp.value = value;
      }
    }
    // 当超过后,则启用跳表
    else {
      // 将数据存储至跳表中,权首次需要,最后都不需要
      if (nodes[nodeIndex] instanceof Node && nodes[nodeIndex].size == LINK_FLAG) {
        // 计算当前的扩容点
        skipThresholdExpand = tableSizeFor(nodes[nodeIndex].size + 2);
        // 计算缩容点
        skipThresholdShrink = tableSizeFor(nodes[nodeIndex].size - 2);

        SkipNode skipRoot = linkedToSkipList(nodes[nodeIndex]);
        nodes[nodeIndex] = skipRoot;
      }

      // 检查当跳表的节点个数到达2的幂次方时,进行跳表节点层数的修改
      if (nodes[nodeIndex].size >= skipThresholdExpand) {
        // 计算跳表的层级
        int countPow = powTwo(nodes[nodeIndex].size);
        if (countPow != -1 && DEF_MAX_SKIP_LEVEL < countPow) {
          // 仅在首次链表转跳表时进行数据搬移
          SkipNode root = new SkipNode(countPow, null, null);
          SkipNode oldRoot = (SkipNode) nodes[nodeIndex];
          // 执行根节点的下数据的转移
          for (int i = oldRoot.currLevel - 1; i >= 0; i--) {
            root.forward[i] = oldRoot.forward[i];
          }
          root.size = oldRoot.size;
          nodes[nodeIndex] = root;
          oldRoot = null;
          // 计算增加层级的阈值
          skipThresholdExpand = tableSizeFor(nodes[nodeIndex].size + 2);
          // 计算减少层级的阈值
          skipThresholdShrink = tableSizeFor(nodes[nodeIndex].size - 2);
        }
      }

      SkipNode root = (SkipNode) nodes[nodeIndex];
      // 将数据插入跳表
      skipAdd(root, key, value, hashCode);
      nodes[nodeIndex].size++;
    }
  }

  /** Returns a power of two size for the given target capacity. */
  static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  }

  /**
   * 检查当前是否为2的幂
   *
   * @param size
   * @return
   */
  public int powTwo(int size) {
    int powNum = 1;
    int low = 0, mid, high = size;

    while (low <= high && high >= 4) {
      if ((high - low) % 2 != 0) {
        return -1;
      }

      mid = low + (high - low) / 2;
      if (mid % 2 == 0) {
        powNum++;
        high = mid;
      }
      // 如果当前不能被2整除,则说明当前非2的幂
      else {
        return -1;
      }
    }

    return powNum;
  }

  /**
   * 计算hash值
   *
   * @param hasCode 哈希码
   * @param capacity 容量
   * @return 索引位置
   */
  private int hashIndex(int hasCode, int capacity) {
    return hasCode % capacity;
  }

  private int hash(K key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }

  /**
   * 获取数据的操作
   *
   * @param key 当前的链信息
   * @return 结果
   */
  public V get(K key) {

    int hashCode = hash(key);
    // 优先在旧的哈希表中搜索
    V value = containerGet(table, key, hashCode);

    // 如果没有找到,再到新的map中搜索
    if (null == value) {
      value = containerGet(newTable, key, hashCode);
    }

    return value;
  }

  /**
   * 在指定的容器中搜索
   *
   * @param searchData 容器对象
   * @param key key信息
   * @return
   */
  private V containerGet(Node[] searchData, K key, int hashCode) {
    if (null == searchData) {
      return null;
    }

    // capacity
    int insertIndex = hashIndex(hashCode, searchData.length);

    // 如果为跳表节点,优先在跳表中搜索
    if (searchData[insertIndex] instanceof SkipNode) {
      SkipNode<K, V> searchNode = skipGet((SkipNode) searchData[insertIndex], key);
      if (null != searchNode) {
        return searchNode.value;
      }
    }
    // 如果数据还在链表中,遍历链表
    else if (searchData[insertIndex] instanceof Node) {
      Node<K, V> node = searchData[insertIndex].next;
      while (node != null) {
        if (node.key.equals(key)) {
          return node.value;
        }
        node = node.next;
      }
    }

    return null;
  }

  /**
   * 进行数据的删除操作,当低于分界线时,切换为链表
   *
   * @param key
   */
  public void remove(K key) {

    int hashCode = hash(key);
    // 在旧容器中移除
    boolean removeFlag = nodeRemove(table, key, hashCode);

    // 当删除失败时,则进行尝试删除
    if (!removeFlag) {
      // 在新容器中移除
      nodeRemove(newTable, key, hashCode);
    }
    // 跳表切换为链表
    this.skipListMoveToLinked(hashCode);
  }

  /**
   * 将跳表切换为链表
   *
   * @param hashCode 索引信息
   */
  private void skipListMoveToLinked(int hashCode) {
    // 当小于8个顶点时,则切换为链表法
    int insertIndex = hashIndex(hashCode, table.length);
    if (table[insertIndex] instanceof SkipNode && table[insertIndex].size < LINK_FLAG) {
      // 执行跳表的迭代操作
      SkipNode tmpNode = ((SkipNode) table[insertIndex]).forward[0];
      Node linkedRoot = new Node(null, null);
      Node tmpNodeItem = linkedRoot;
      while (tmpNode != null) {
        Node linkNode = new Node(tmpNode.key, tmpNode.value);
        tmpNodeItem.next = linkNode;
        tmpNodeItem = linkNode;
        // 跳表中节点的移除操作
        this.skipRemove(tmpNode, (SkipNode) table[insertIndex]);
        tmpNode = tmpNode.forward[0];
      }
      // 设置根节点
      table[insertIndex] = linkedRoot;
    }
    // 当跳表节点减少时,也需要减少跳表的层数
    else if (table[insertIndex] instanceof SkipNode && table[insertIndex].size > SKIP_LINKED_SIZE) {
      // 检查当跳表的节点个数到达2的幂次方时,进行跳表节点层数的修改
      if (table[insertIndex].size <= skipThresholdShrink) {
        int countPow = powTwo(table[insertIndex].size);
        SkipNode oldRoot = (SkipNode) table[insertIndex];
        // 节点数减少时,进行相应层的减少
        if (countPow != -1 && countPow < oldRoot.currLevel) {
          // 仅在首次链表转跳表时进行数据搬移
          SkipNode root = new SkipNode(countPow, null, null);
          // 执行根节点的下数据的转移
          for (int i = root.currLevel - 1; i >= 0; i--) {
            root.forward[i] = oldRoot.forward[i];
          }
          root.size = oldRoot.size;
          table[insertIndex] = root;
          oldRoot = null;

          // 计算当前的扩容点
          skipThresholdExpand = tableSizeFor(table[insertIndex].size + 2);
          // 计算缩容点
          skipThresholdShrink = tableSizeFor(table[insertIndex].size - 2);
        }
      }
    }
  }

  /**
   * 在指定的容器中搜索
   *
   * @param searchData 容器对象
   * @param key key信息
   * @return
   */
  private boolean nodeRemove(Node[] searchData, K key, int hashCode) {
    if (null == searchData) {
      return false;
    }
    boolean removeFlag = false;
    // capacity
    int insertIndex = hashIndex(hashCode, searchData.length);

    // 优先处理跳表
    if (searchData[insertIndex] instanceof SkipNode) {
      removeFlag = skipRemoveKey((SkipNode) searchData[insertIndex], key);
    }
    // 如果数据还在链表中,遍历链表
    else if (searchData[insertIndex] instanceof Node) {
      Node node = searchData[insertIndex];
      while (node != null) {
        if (node.next.key.equals(key)) {
          node.next = node.next.next;
          removeFlag = true;
          break;
        }
        node = node.next;
      }
    }

    if (removeFlag) {
      searchData[insertIndex].size--;
      size--;
    }

    return removeFlag;
  }

  public boolean isEmpty() {
    return size == 0;
  }

  public boolean containsKey(K key) {
    V value = this.get(key);
    if (null != value) {
      return true;
    }
    return false;
  }

  public void clean() {
    // 数据清空
    for (int i = 0; i < capacity; i++) {
      this.table[i] = null;
    }

    size = 0;
  }

  /** 进行空检查 */
  public boolean checkEmpty() {
    boolean emptyFlag = true;

    for (int i = 0; i < table.length; i++) {
      if (table[i] instanceof Node) {
        if (table[i].next != null) {
          emptyFlag = false;
        }
      } else if (table[i] instanceof SkipNode) {
        if (((SkipNode) table[i]).forward[0] != null) {
          emptyFlag = false;
        }
      }
    }

    return emptyFlag;
  }

  public int size() {
    return size;
  }

  private class Node<K, V> {
    /** 当前值 */
    protected V value;
    /** 后继指针 */
    protected Node next;
    /** 用于记录下当前的hast值 */
    protected int hnext;
    /** 记录下key用作比较使用 */
    protected K key;
    /** 当前链表法中的节点个数 */
    protected int size;

    public Node(K key, V value) {
      this.value = value;
      this.key = key;
    }
  }

  private class SkipNode<K, V> extends Node<K, V> {
    /** 当前的跳表层级 */
    private int currLevel;
    /** 跳表的层级 */
    private SkipNode<K, V>[] forward;

    public SkipNode(int currLevel, K key, V value) {
      super(key, value);
      this.currLevel = currLevel;
      this.forward = new SkipNode[this.currLevel];
    }
  }

  /**
   * 将数据转化为跳表存储
   *
   * @param data 原链表数据
   * @return 跳表的节点
   */
  private SkipNode linkedToSkipList(Node data) {
    // 仅在首次链表转跳表时进行数据搬移
    SkipNode root = new SkipNode(DEF_MAX_SKIP_LEVEL, null, null);
    root.size = 1;
    Node<K,V> tmpNode = data.next;

    while (tmpNode != null) {
      skipAdd(root, tmpNode.key, tmpNode.value, tmpNode.hnext);
      root.size++;
      tmpNode = tmpNode.next;
    }
    return root;
  }

  /**
   * 向跳表中添加一个数据
   *
   * @param root 节点信息
   * @param key 插入的键
   * @param value 插入的值
   * @param hnext hashcode
   */
  private void skipAdd(SkipNode root, K key, V value, int hnext) {
    int randomLevel = randomLevel(root);
    SkipNode curr = new SkipNode(randomLevel, key, value);
    curr.hnext = hnext;

    // 找到单链表的位置
    SkipNode[] findNode = new SkipNode[randomLevel];
    for (int i = 0; i < randomLevel; i++) {
      findNode[i] = root;
    }
    SkipNode temp = root;
    for (int i = randomLevel - 1; i >= 0; i--) {
      while (temp.forward[i] != null && ((Comparable) temp.forward[i].key).compareTo(key) == -1) {
        temp = temp.forward[i];
      }
      findNode[i] = temp;
    }
    // 将数据插入跳表
    for (int i = 0; i < randomLevel; i++) {
      curr.forward[i] = findNode[i].forward[i];
      findNode[i].forward[i] = curr;
    }
  }

  private int randomLevel(SkipNode root) {
    int level = 1;
    Random random = ThreadLocalRandom.current();
    for (int i = 1; i < root.currLevel; i++) {
      if (random.nextInt() % 2 == 0) {
        level++;
      }
    }

    return level;
  }

  /**
   * 获取一个数据
   *
   * @param key
   * @return
   */
  private SkipNode skipGet(SkipNode stateInfo, K key) {
    SkipNode skipRootTmp = (SkipNode) stateInfo;
    for (int i = skipRootTmp.currLevel - 1; i >= 0; i--) {
      while (skipRootTmp.forward[i] != null
          && ((Comparable) skipRootTmp.forward[i].key).compareTo(key) == -1) {
        skipRootTmp = skipRootTmp.forward[i];
      }
    }

    if (null != skipRootTmp.forward[0] && skipRootTmp.forward[0].key.equals(key)) {
      return skipRootTmp.forward[0];
    }
    return null;
  }

  /**
   * 删除一个数据
   *
   * @param key
   * @return
   */
  private boolean skipRemoveKey(SkipNode skipRootTmp, K key) {
    SkipNode[] deletePrefix = new SkipNode[skipRootTmp.currLevel];
    for (int i = skipRootTmp.currLevel - 1; i >= 0; i--) {
      while (skipRootTmp.forward[i] != null
          && ((Comparable) skipRootTmp.forward[i].key).compareTo(key) == -1) {
        skipRootTmp = skipRootTmp.forward[i];
      }
      deletePrefix[i] = skipRootTmp;
    }

    boolean removeFlag = false;

    // 如果数据被找到,则执行移除操作
    if (null != skipRootTmp && skipRootTmp.forward[0].key.equals(key)) {
      for (int i = deletePrefix.length - 1; i >= 0; i--) {
        if (deletePrefix[i].forward[i] != null
            && ((Comparable) deletePrefix[i].forward[i].key).compareTo(key) == 0) {
          deletePrefix[i].forward[i] = deletePrefix[i].forward[i].forward[i];
          removeFlag = true;
        }
      }
    }
    return removeFlag;
  }

  /**
   * 数据的移除操作
   *
   * @return 移除结果
   */
  private boolean skipRemove(SkipNode node, SkipNode root) {
    SkipNode[] deletePrefix = new SkipNode[root.currLevel];
    SkipNode skipRootTmp = root;

    for (int i = root.currLevel - 1; i >= 0; i--) {
      while (skipRootTmp.forward[i] != null
          && ((Comparable) skipRootTmp.forward[i].key).compareTo(node.key) == -1) {
        skipRootTmp = skipRootTmp.forward[i];
      }
      deletePrefix[i] = skipRootTmp;
    }

    boolean removeFlag = false;

    // 如果数据被找到,则执行移除操作
    if (null != skipRootTmp && skipRootTmp.forward[0].key.equals(node.key)) {
      for (int i = root.currLevel - 1; i >= 0; i--) {
        if (deletePrefix[i].forward[i] != null
            && ((Comparable) deletePrefix[i].forward[i].key).compareTo(node.key) == 0) {
          deletePrefix[i].forward[i] = deletePrefix[i].forward[i].forward[i];
          removeFlag = true;
        }
      }
    }
    return removeFlag;
  }
}

这就是我自己实现的一个HashMap。与java原生的HashMap相比,性能相差无几。这简单的做了一个单元测试的对比。

public class TestMyHashMapCompare {

  private static final int MAX_SIZE = 2999999;

  @Test
  public void testHashMap() {

    HashMap instanceMap = new HashMap();
    for (int i = 0; i < MAX_SIZE; i++) {
      Data item = new Data(i, "i" + i);
      instanceMap.put(item, "name:" + i);
    }

    for (int i = MAX_SIZE - 1; i > 0; i--) {
      Data item = new Data(i, "i" + i);
      Object value = instanceMap.get(item);
      Assert.assertEquals(value, "name:" + i);
      instanceMap.remove(item);
    }
  }

  /** 自己实现的HashMap测试 */
  @Test
  public void testHashMapMe() {

    MyHashMap instance = new MyHashMap();

    for (int i = 0; i < MAX_SIZE; i++) {
      Data item = new Data(i, "i" + i);
      instance.put(item, "name:" + i);
    }

    for (int i = MAX_SIZE - 1; i > 0; i--) {
      Data item = new Data(i, "i" + i);
      Object value = instance.get(item);
      Assert.assertEquals(value, "name:" + i);
      instance.remove(item);
    }
  }
}

来看下对比结果:

在这里插入图片描述

这个结果可以看作与java原生版本的HashMap相关无几。甚至可能快个几秒。但这里仅是简单的一个测试。并不能证明我实现的Map比java原生的好。

总结

我通过将自己实现一个散列表。将散列表中相关的知识做了一个实践。关于散列表,需要重点掌握散列函数、装载因子、动态扩容策略、散列冲突的解决。通过这些实践与学习能更好的理解HashMap这一工业级的散列表的实现。当然java版本的HashMap使用红黑树来做的动态数据结构。

【R-terra包|第一节】- 栅格相关性、散点图 、波段的计算与异常值处理 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 —```mermaid flowchat st=>start: 开始 e=>end: 结束 op=>operation: 我的操作 cond=>condition: 确认? st->op->cond cond(yes)->e cond(no)->op 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能. 阅读详情

相关推荐

西门子交换机环网冗余设置(实战篇2.2)

MRP的重组时间为 200ms 因此 PROFINET 通信必须选择大于 200ms 的看门狗时间,看门狗。ET200SP默认为 2ms*3=6ms) MRP 的重组时间为 200ms 故系统会报出看门狗超时。更新,默认是 3 个周期,超出设置的周期数就会报 IO 设备故障。时间不是直接配置的,而是作为没有 IO 数据可接受的更新周期,也就是允许几个周期没有数据。来达到大于 200ms 的看门狗时间的要求(看门狗时间 可接受更新周期数 更新时间)。是IO 控制器和 IO 设备)

weixin_42580591的博客 364

Java面试集合类之HashMap代码级详解

HashMap是懒创建数组的,第一次put才创建数组计算索引(桶下标)如果桶下标没有值,创建Node占位返回如果桶下标有值已经是TreeNode走红黑树的添加或更新普通的Node,走链表的添加或更新,如果链表长度超过阈值8,树化。返回前检查容量是否超过阈值,一旦超过则进行扩容1.8:链表插入节点时,1.7是头插法,1.8是尾插法1.7是大于等于阈值时且没有空位(当前元素个数大于等于阈值时,且当下一个元素插入时对应的桶有元素了)时才扩容,而1.8是大于阈值就扩容。

ONLY1024的博客 847

vivado生成及调用自定义IP的方法

开发环境:vivado 2018.2 1.将整个工程封装为自定义IP 将整个工程封装为自定义IP主要有以下步骤: 步骤1.选中工程中的top模块。点击Tools ------> Create and Package New IP,如图1所示 图1 步骤2.弹出界面后,点击next至图2界面,选择Package your current project,然后继续点击next 图2 步骤3.选择生成的IP路径,如图3所示。这个路径就是后续添加该IP的路径 图3 步骤4.继续点击next,然后选择fi

进击的阿日比 4726

跳跃表( 也是Redis中 sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序 )

1. 跳跃表的原理       学过数据结构的都知道,在单链表中查询一个元素的时间复杂度为O(n),即使该单链表是有序的,我们也不能通过2分的方式缩减时间复杂度。        如上图,我们要查询元素为55的结点,必须从头结点,循环遍历到最后一个节点,不算-INF(负无穷)一共查询8次。那么用什么办法能够用更少的次数访问55呢?最直观的...

weixin_43743711的博客 628

比亚迪璇玑架构:10ms级响应引领智能驾驶革命

比亚迪璇玑架构通过三大创新,实现10ms级控制响应时延,使AEB刹车距离比竞品缩短20米。

百态·数智思维 | 聚焦大数据、量化与未来AI 1255

Emscripten安装(wasm编译)

5:配置对应环境变量--替换成自己对应的安装路径(2025最新的Emscripten工具链默认使用LLVM/WebAssembly标准工作流,java依赖已经移除了,所以以下java环境不配,需要使用java的手动添加即可---->emsdk install java-8-152-64bit。3:下载相应的依赖库---->emsdk install latest(我这里下的是最新的,如果有指定版本,把latest替换成版本号即可)6:测试是否成功---> emcc- v/em++ -v。

qq_31578301的博客 554

跳表和散列表

因此需要维护一个常用密码的字典表,将字典中的密码进行哈希计算再哈希,针对字典攻击,可以引入盐(salt),和用户密码组合在一起,增加密码的复杂度。,动态扩容需要重新通过散列函数计算数据的存储位置,所以会导致在动态扩容的时候时间复杂度变为O(n),为了避免动态扩容时导致耗时过长,可以选择将扩容后的数据迁移操作分散开,每次插入只迁移部分数据,这样平均下来每次时间复杂度也是O(1)。链表要存储指针,存储较小对象时,比较消耗内存,另外链表中的节点时零散的,对CPU的缓存不友好。,这样可以进行区间和范围查找。

peng_shakalaka的博客 725

基于YOLOv11的工业装配完整性智能检测系统

在现代工业生产中,产品质量控制是确保企业竞争力的关键环节。其中,装配完整性检查——即检测产品是否安装了所有应有的部件,是质量检测中的重要一环。传统的人工检测方法存在效率低、易疲劳、主观性强等问题。本文提出了一种基于深度学习的目标检测算法YOLOv11的工业装配完整性智能检测系统,通过计算机视觉技术自动识别和验证产品装配的完整性。本文将详细阐述从数据准备、模型训练到系统部署的完整流程,提供可执行的代码实现,并介绍一个适用于该任务的参考数据集。

YOLO 551

数据结构与算法05:跳表和散列表

对有序链表增加“索引”,改造后的数据结构就叫做 跳表(跳跃表),跳表的空间复杂度是 O(n),在跳表中查询任意数据的时间复杂度是O(logn),跳表中插入和删除操作的时间复杂度也是 O(logn)。散列表的本质是一个数组,可以在O(1)的时间复杂度查找元素。常用的散列冲突解决方法有两类,分别是:开放寻址法 和 链表法。散列表中装载因子越大,说明空闲位置越少,也就是冲突越多,散列表的性能会下降。

码农兴哥的技术笔记 1461

英飞凌 AURIX TC3XX 系列单片机的 NVM-Flash 介绍

简单介绍英飞凌 AURIX TC3XX 系列多核单片机的 Flash,便于快速上手使用

大橙子的博客 6637

AI Agent智能体在Android开发中的应用

本文探讨了AIAgent(智能代理体)在Android开发中的应用,涵盖技术原理、实践案例与未来趋势。AIAgent通过机器学习、自然语言处理等技术实现代码生成、测试自动化及性能优化,显著提升开发效率。例如,强化学习模型可优化APP响应时间(公式:$\text{响应时间}=f(\text{负载},\text{资源})$),自动化测试覆盖率提升至95%。

郑伟强dev的专栏 123

跳表(SkipList)及ConcurrentSkipListMap源码解析

跳表SkipList原理,ConcurrentSkipListMap源码解析、无锁编程与基于锁的编程的比较

void 3万+

zabbix深度监控之邮件告警、微信群和微信告警

本文详细介绍了Zabbix监控系统实现邮件告警和微信告警的配置方法。邮件告警部分包含QQ和163邮箱的SMTP服务器配置、消息模板设置、告警流程三步骤(媒介配置、触发器动作、用户设置)。微信告警分为两种方式:企业微信群机器人告警(通过webhook实现)和企业微信应用告警(需创建应用并获取API参数)。两种告警方式都提供了完整的配置流程,包括脚本编写、权限设置、消息模板定制等关键步骤,并特别说明了如何使个人微信接收企业微信告警消息。文章还对比了SMTP、POP3、IMAP三种邮件协议的差异及适用场景。

提供C#相关开发 、云计算、运维测试、网络安全相关技术分享与服务,有意向可联系。 814

跳表加散列表实现有序的散列表(MySkipListHashMap)

跳表加散列表实现有序的散列表(MySkipListHashMap) 介绍 有序的,还是散列表,是不是听起来就觉得这不太现实啊!但现实往往就是这么打脸,听起来不可思议的东西,却是可以做到的。 散列表,一个无序的数据结构。通过散列函数将数据散列分布到各个数据的槽里。那如何保证数据的有序呢?在java的库中有一个是实现的有序的数据结构LinkedHashMap,按照插入顺序来保存的有序,这个数据结构是通过散列表与双向链表的结合,来实现的一个保存数据。受此启发。我来实现一个按照数据的顺序来保存的有序数结构。 操作:

bug_null的博客 370

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

数据结构☞散列表

散列表(Hash Table) 散列表的英文叫“Hash Table”,我们平时也叫它“哈希表”或者“Hash 表”。 散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。 key:键或者关键字。 散列函数(或“Hash 函数”“哈希函数”):把key值转化为数组下标的映射方法。 散列函数计算得到的值就叫作散列值(或“Hash 值”“哈希值”)。 散列函数 散列函数设计的基本要求: 散列函数计算得到的散列值是一个非负整

weixin_47251999的博客 2万+

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

跳表+散列表+哈希算法

跳表 链表加多级索引的结构就是跳表 跳表中快速的插入、删除、查询任意数据的时间复杂度是O(logn);跳表的空间复杂度是O(n) 散列表 散列表英文叫hash table,就是哈希表或者hash表。 散列表用的事数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一中扩展,由数组演化而来。可以说,如果没有数组就没有散列表。 散列表中有键、散列函数、散列值 散列函数,就是一个函数,它可以定义成hash(key),其中可以表示元素的键值,hash(key)的值表示经过散列函数计算得到的散列值

annuoa的专栏 311

跳表(Skip List)+散列表(Hash Table)+哈希算法

跳表: 支持二分查找的链表算法,这是一种链表加多索引的结构。 在跳表中查询某个数据的时候,如果每一层都要遍历 m 个结点,那在跳表中查询一个数据的时间复杂度就是 O(m*logn)。跳表占用内存但是速度快。 散列表: 平时叫他为“哈希表”或者“Hash 表”,散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。 散列函数设计的基本要求: 散列函数计算得到的散列值是一个非负整数; 如果 key1 = key2,那 hash(key1) == hash(key

SHZNIU的博客 990

区块链算法核心数据结构:哈希表与跳表的终极指南

在区块链技术中,高效的数据结构是确保交易处理、账本维护和共识机制顺畅运行的基础。哈希表与跳表作为两种关键数据结构,凭借其优异的查找、插入和删除性能,成为区块链底层算法的核心组件。本文将深入解析这两种数据结构的工作原理、应用场景及在区块链中的实践价值,帮助开发者构建更高效的分布式系统。 ## 哈希表:区块链中的高效键值存储方案 哈希表(Hash Table)通过将键映射到数组索引实现快速访问,是

gitblog_01089的博客 1229

跳表Java实现

跳跃表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间),并且对并发算法友好。 跳跃表的应用 Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入

偏离的定弦 5948

散列表终极指南:原理、实现、工业优化与工程实战

本文系统介绍了散列表的原理、实现与工业优化。首先阐述了散列表的本质是通过哈希函数将任意键映射到数组下标,实现O(1)时间复杂度的快速操作。重点解析了两种冲突解决方法:链表法和开放寻址法,并对比了它们的工程适用场景。深入探讨了工业级哈希函数设计、动态扩容策略(如Java HashMap的0.75装载因子阈值),以及渐进式扩容等关键技术。最后展示了散列表与链表/跳表结合的经典案例(如LRU缓存、Redis有序集合),通过组合数据结构实现高效查找和有序遍历。文章还总结了散列表在拼写检查、URL去重等实际场景的应用

星之尘1021的博客 1084

redis的zset结构跳表

跳表:为什么 Redis 一定要用跳表来实现有序集合? 上几篇主要是学习二分查找算法,但是二分查找底层依赖的是数组随机访问的特性,所以只能用数组来实现。如果数据存储在链表中,就没办法使用二分查找了吗? 此时跳表出现了,跳表(Skip list)实际上就是在链表的基础上改造生成的。 跳表是一种各方面性能都比较优秀的动态数据结构,可以支持快速的插入、删除、查找操作,写起来也不复杂,甚至...

适恒 7440

数据结构(一):跳表

敲黑板: 每级遍历 3 个结点即可,而跳表的高度为 h ,所以每次查找一个结点时,需要遍历的结点数为3*跳表高度,所以忽略低阶项和系数后的时间复杂度就是 ○(㏒n),空间复杂度是O(n) 数据结构 实现原理 key查询方式 查找效率 存储大小 插入、删除效率 Hash 哈希表 支持单key 接近O(1) 小,除了数据没有额外的存储...

scofen的博客 1973

哈希表(Hash Table)、跳表(Skip List) 和 有序字典(Ordered Dictionary) 的详细介绍

关于 哈希表(Hash Table)、跳表(Skip List) 和 有序字典(Ordered Dictionary) 的详细介绍

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 1527

HashMap/跳表(SkipList)/红黑树比较及ConcurrentSkipListMap源码解析

youkuaiyun.com/sunxianghuang/article/details/52221913 转载地址:http://blog.youkuaiyun.com/sunxianghuang/article/details/52221913 二分查找和AVL树查找 二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存。这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的

Wj741238436的博客 8494

11. 跳表 为什么Redis一定要用跳表来实现有序集合

一、什么是跳表? 为一个值有序的链表建立多级索引,比如每2个节点提取一个节点到上一级,我们把抽出来的那一级叫做索引或索引层。如下图所示,其中down表示down指针,指向下一级节点。以此类推,对于节点数为n的链表,大约可以建立log2n-1级索引。像这种为链表建立多级索引的数据结构就称为跳表。 二、跳表的时间复杂度? 1.计算跳表的高度 如果链表有n个节点,每2个节点抽取抽出一个节点作为上...

扮猪吃老虎二号的博客 750

什么是跳表

Redis有序集合是复合数据结构,它是由一个双hashmap构成的字典和跳表实现的,本文将为大家详细介绍Redis有序集合实现的原理以及使用场景和案例。 文章目录有序集合概述 有序集合概述 Redis有序集合(sorted set)是复合数据结构,它是由一个双hashmap构成的字典和跳表实现的。 跟集合一样,有序集合也是string类型元素的集合,不同的是每个元素都会关联一个权。通过权值可以有序的获取集合中的元素。 ...

wh柒八九的博客 462

hash+跳表,玩转Redis有序集合

导语 | Redis有序集合是复合数据结构,它是由一个双hashmap构成的字典和跳表实现的,本文将为大家详细介绍Redis有序集合实现的原理以及使用场景和案例,希望与大家一同交流。文章...

QcloudCommunity的博客 894
上一篇: 快速排序
下一篇: 手写LinkedHashMap
null_null_bug
博客等级 码龄7年 72粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值