SB树,看这一篇就够了

news2025/4/12 20:29:44

算法拾遗三十九SB树及跳表

      • SB树
        • SB树四种违规类型
          • 总结
        • SB树Code
      • 跳表

SB树

SB树是基于搜索二叉树来的,也有左旋和右旋的操作,只是不同于AVL树,它也有它自己的一套平衡性方法。
任何以叔叔节点为头的子树的节点个数不小于自己任何一个侄子树的节点个数。
如下图:
在这里插入图片描述
对于节点B是节点GH的叔叔,B整棵树的节点个数,不能比G或者H任何一个节点个数少,同理C整颗节点个数一定不能比E或者F这两棵树的节点个数少。
如果每个节点作为叔叔节点都能满足如上标准,那么称这棵树为Size Balance Tree。
得到:
AVL树维护的平衡因子是两颗子树的高度,而SB树则维护的是节点的size
最悬殊的情况:
假设一棵树左树的节点比较少,右树的节点比较多,左树的A节点不能比右树的C节点以及D节点少,如果这棵树的右树是比较多的节点,也能满足左右两棵树不会特别悬殊,以此来达到高度收敛于O(logN)的效果
在这里插入图片描述

SB树四种违规类型

LL:
我左儿子的左边的节点个数,多余了我父儿子的节点个数
LR:
我左儿子的右孩子的节点个数,多余了我右孩子的节点个数
RL:
我左儿子的节点个数,小于右儿子的左孩子节点个数
RR:
我左儿子的节点个数,授予我右儿子的右孩子的节点个数
假设现在有个LL型违规:
假设进行一个m函数的调整,传入父节点,去做调整,然后返回一个父亲节点。
在这里插入图片描述
如上图所示,假设是AVL树进行右旋,得到如下所示:
在这里插入图片描述
但是SB树则需要进行进一步的调整,需要找到谁的孩子发生了变化:
A的孩子节点发生了变化,以及父节点的孩子也发生了变化。
此时需要递归的调用m(A)以及m(父),重复的查是否发生了违规。
AVL树是一个平衡性非常敏感的树,所以每次加一个节点以及删一个节点都会有一系列的调整,而SB树则是关注侄子节点的个数之间的关系,所以可能是很久才需要调整一次,没有AVL树那么敏感。
LL违规代码片段:
在这里插入图片描述
之所以都用返回值是为了防止在递归的过程中再次出现换头。
在这里插入图片描述
D现在面对的对手是E和C,在原树中D不以E做对手,以C做对手,D的节点比C多才会出现如上的右旋调整,现在D以E做对手了,可能出现违规(E可能把D干掉,可能需要调整),现在的C以E的两个孩子做对手,同时现在的E以F和G做对手也不知道它是否平衡,所以得去做递归。

总结

四种违规类型得调整和AVL树一样,不一样得地方就在于谁的孩子变了,则需要重新递归调用,SB树在删除得时候可以不用去做平衡性调整,在加入节点得过程中进行平衡性调整,因为递归是具有传递性的。

SB树Code

public static class SBTNode<K extends Comparable<K>, V> {
		public K key;
		public V value;
		public SBTNode<K, V> l;
		public SBTNode<K, V> r;
		public int size; // 不同的key的数量

		public SBTNode(K key, V value) {
			this.key = key;
			this.value = value;
			size = 1;
		}
	}

	public static class SizeBalancedTreeMap<K extends Comparable<K>, V> {
		private SBTNode<K, V> root;

		private SBTNode<K, V> rightRotate(SBTNode<K, V> cur) {
			SBTNode<K, V> leftNode = cur.l;
			cur.l = leftNode.r;
			leftNode.r = cur;
			//不同于AVL树换size
			leftNode.size = cur.size;
			cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;
			return leftNode;
		}

		private SBTNode<K, V> leftRotate(SBTNode<K, V> cur) {
			SBTNode<K, V> rightNode = cur.r;
			cur.r = rightNode.l;
			rightNode.l = cur;
			rightNode.size = cur.size;
			cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;
			return rightNode;
		}

		private SBTNode<K, V> maintain(SBTNode<K, V> cur) {
			if (cur == null) {
				return null;
			}
			int leftSize = cur.l != null ? cur.l.size : 0;
			int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0;
			int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0;
			int rightSize = cur.r != null ? cur.r.size : 0;
			int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0;
			int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0;
			//LL违规,右旋
			if (leftLeftSize > rightSize) {
				cur = rightRotate(cur);
				cur.r = maintain(cur.r);
				cur = maintain(cur);
			} else if (leftRightSize > rightSize) {
				//先左旋后右旋
				cur.l = leftRotate(cur.l);
				cur = rightRotate(cur);
				//返回的新头部被cur的左孩子捕获
				cur.l = maintain(cur.l);
				//返回的新头部被cur的右孩子捕获
				cur.r = maintain(cur.r);
				//自己调整
				cur = maintain(cur);
			} else if (rightRightSize > leftSize) {
				cur = leftRotate(cur);
				cur.l = maintain(cur.l);
				cur = maintain(cur);
			} else if (rightLeftSize > leftSize) {
				cur.r = rightRotate(cur.r);
				cur = leftRotate(cur);
				cur.l = maintain(cur.l);
				cur.r = maintain(cur.r);
				cur = maintain(cur);
			}
			return cur;
		}

		private SBTNode<K, V> findLastIndex(K key) {
			SBTNode<K, V> pre = root;
			SBTNode<K, V> cur = root;
			while (cur != null) {
				pre = cur;
				if (key.compareTo(cur.key) == 0) {
					break;
				} else if (key.compareTo(cur.key) < 0) {
					cur = cur.l;
				} else {
					cur = cur.r;
				}
			}
			return pre;
		}

		private SBTNode<K, V> findLastNoSmallIndex(K key) {
			SBTNode<K, V> ans = null;
			SBTNode<K, V> cur = root;
			while (cur != null) {
				if (key.compareTo(cur.key) == 0) {
					ans = cur;
					break;
				} else if (key.compareTo(cur.key) < 0) {
					ans = cur;
					cur = cur.l;
				} else {
					cur = cur.r;
				}
			}
			return ans;
		}

		private SBTNode<K, V> findLastNoBigIndex(K key) {
			SBTNode<K, V> ans = null;
			SBTNode<K, V> cur = root;
			while (cur != null) {
				if (key.compareTo(cur.key) == 0) {
					ans = cur;
					break;
				} else if (key.compareTo(cur.key) < 0) {
					cur = cur.l;
				} else {
					ans = cur;
					cur = cur.r;
				}
			}
			return ans;
		}

		// 现在,以cur为头的树上,新增,加(key, value)这样的记录
		// 加完之后,会对cur做检查,该调整调整
		// 返回,调整完之后,整棵树的新头部
		private SBTNode<K, V> add(SBTNode<K, V> cur, K key, V value) {
			if (cur == null) {
				return new SBTNode<K, V>(key, value);
			} else {
				cur.size++;
				//新加的key去左边或者右边
				if (key.compareTo(cur.key) < 0) {
					cur.l = add(cur.l, key, value);
				} else {
					cur.r = add(cur.r, key, value);
				}
				//cur自己调m传递下去
				return maintain(cur);
			}
		}

		// 在cur这棵树上,删掉key所代表的节点
		// 返回cur这棵树的新头部
		private SBTNode<K, V> delete(SBTNode<K, V> cur, K key) {
			cur.size--;
			if (key.compareTo(cur.key) > 0) {
				cur.r = delete(cur.r, key);
			} else if (key.compareTo(cur.key) < 0) {
				cur.l = delete(cur.l, key);
			} else { // 当前要删掉cur
				if (cur.l == null && cur.r == null) {
					// free cur memory -> C++
					cur = null;
				} else if (cur.l == null && cur.r != null) {
					// free cur memory -> C++
					cur = cur.r;
				} else if (cur.l != null && cur.r == null) {
					// free cur memory -> C++
					cur = cur.l;
				} else { // 有左有右
					SBTNode<K, V> pre = null;
					SBTNode<K, V> des = cur.r;
					des.size--;
					while (des.l != null) {
						pre = des;
						des = des.l;
						des.size--;
					}
					if (pre != null) {
						pre.l = des.r;
						des.r = cur.r;
					}
					des.l = cur.l;
					des.size = des.l.size + (des.r == null ? 0 : des.r.size) + 1;
					// free cur memory -> C++
					cur = des;
				}
			}
			// cur = maintain(cur); 可以删掉,删除的时候不调整平衡性
			return cur;
		}

		private SBTNode<K, V> getIndex(SBTNode<K, V> cur, int kth) {
			if (kth == (cur.l != null ? cur.l.size : 0) + 1) {
				return cur;
			} else if (kth <= (cur.l != null ? cur.l.size : 0)) {
				return getIndex(cur.l, kth);
			} else {
				return getIndex(cur.r, kth - (cur.l != null ? cur.l.size : 0) - 1);
			}
		}

		public int size() {
			return root == null ? 0 : root.size;
		}

		public boolean containsKey(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNode = findLastIndex(key);
			return lastNode != null && key.compareTo(lastNode.key) == 0 ? true : false;
		}

		// (key,value) put -> 有序表 新增、改value
		public void put(K key, V value) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNode = findLastIndex(key);
			if (lastNode != null && key.compareTo(lastNode.key) == 0) {
				lastNode.value = value;
			} else {
				root = add(root, key, value);
			}
		}

		public void remove(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			if (containsKey(key)) {
				root = delete(root, key);
			}
		}

		public K getIndexKey(int index) {
			if (index < 0 || index >= this.size()) {
				throw new RuntimeException("invalid parameter.");
			}
			return getIndex(root, index + 1).key;
		}

		public V getIndexValue(int index) {
			if (index < 0 || index >= this.size()) {
				throw new RuntimeException("invalid parameter.");
			}
			return getIndex(root, index + 1).value;
		}

		public V get(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNode = findLastIndex(key);
			if (lastNode != null && key.compareTo(lastNode.key) == 0) {
				return lastNode.value;
			} else {
				return null;
			}
		}

		public K firstKey() {
			if (root == null) {
				return null;
			}
			SBTNode<K, V> cur = root;
			while (cur.l != null) {
				cur = cur.l;
			}
			return cur.key;
		}

		public K lastKey() {
			if (root == null) {
				return null;
			}
			SBTNode<K, V> cur = root;
			while (cur.r != null) {
				cur = cur.r;
			}
			return cur.key;
		}

		public K floorKey(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNoBigNode = findLastNoBigIndex(key);
			return lastNoBigNode == null ? null : lastNoBigNode.key;
		}

		public K ceilingKey(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNoSmallNode = findLastNoSmallIndex(key);
			return lastNoSmallNode == null ? null : lastNoSmallNode.key;
		}

	}

	// for test
	public static void printAll(SBTNode<String, Integer> head) {
		System.out.println("Binary Tree:");
		printInOrder(head, 0, "H", 17);
		System.out.println();
	}

	// for test
	public static void printInOrder(SBTNode<String, Integer> head, int height, String to, int len) {
		if (head == null) {
			return;
		}
		printInOrder(head.r, height + 1, "v", len);
		String val = to + "(" + head.key + "," + head.value + ")" + to;
		int lenM = val.length();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		System.out.println(getSpace(height * len) + val);
		printInOrder(head.l, height + 1, "^", len);
	}

	// for test
	public static String getSpace(int num) {
		String space = " ";
		StringBuffer buf = new StringBuffer("");
		for (int i = 0; i < num; i++) {
			buf.append(space);
		}
		return buf.toString();
	}

	public static void main(String[] args) {
		SizeBalancedTreeMap<String, Integer> sbt = new SizeBalancedTreeMap<String, Integer>();
		sbt.put("d", 4);
		sbt.put("c", 3);
		sbt.put("a", 1);
		sbt.put("b", 2);
		// sbt.put("e", 5);
		sbt.put("g", 7);
		sbt.put("f", 6);
		sbt.put("h", 8);
		sbt.put("i", 9);
		sbt.put("a", 111);
		System.out.println(sbt.get("a"));
		sbt.put("a", 1);
		System.out.println(sbt.get("a"));
		for (int i = 0; i < sbt.size(); i++) {
			System.out.println(sbt.getIndexKey(i) + " , " + sbt.getIndexValue(i));
		}
		printAll(sbt.root);
		System.out.println(sbt.firstKey());
		System.out.println(sbt.lastKey());
		System.out.println(sbt.floorKey("g"));
		System.out.println(sbt.ceilingKey("g"));
		System.out.println(sbt.floorKey("e"));
		System.out.println(sbt.ceilingKey("e"));
		System.out.println(sbt.floorKey(""));
		System.out.println(sbt.ceilingKey(""));
		System.out.println(sbt.floorKey("j"));
		System.out.println(sbt.ceilingKey("j"));
		sbt.remove("d");
		printAll(sbt.root);
		sbt.remove("f");
		printAll(sbt.root);

	}

跳表

先看流程:
在有序表最顶层上面放一个最小值,可以认为是系统最小,然后进来一个值,经过计算得到一个层数信息。
如下图先进来一个3,它的层数是2,那么就造两层树,让第二层指向3,后面进来一个4,计算它的层数为3,那么则新建层数让第三层指向4,剩下的节点去连空
在这里插入图片描述
下面看一个比较正式的例子:
假设先来一个5,row出来层数为2,先创建层数2
在这里插入图片描述
然后生成5的链表,有一层链表指向空,第二层链表指向空
在这里插入图片描述
然后从第一个图中的高链表开始往下查,查到第一个大于5的节点,没有则进行挂载
在这里插入图片描述
1链表里面找到第一个大于5的节点,没有则又连接起来
在这里插入图片描述
现在用户再给一个3,然后row出来4
在这里插入图片描述
再建立出一个大的节点
在这里插入图片描述
从上图中的4开始找到第一个大于3的节点,发现没有则进行直接连接,然后从下一层的3开始找,发现也没有,则进行直连
在这里插入图片描述
再找到2层里面第一个比3大的,发现有则让左边的2连接右边的3,右边3中的2连右下的5
在这里插入图片描述
以此类推最后的1也是那样
在这里插入图片描述
由此可以推导:
第一层数的个数为N,第二层为N/2,第三层为N/4依次类推,时间复杂度logN,用运气因素决定上层节点所处位置,上层移动一点下层跨过很多中间节点,把输入规律给屏蔽掉。时间复杂度推导可以画如下图:
在这里插入图片描述
假设要找一个节点:
在这里插入图片描述
我第一层跨越后,那么我整棵树的左子树就可以完全屏蔽了,我每次跨都会淘汰掉相当多的节点往右跨则舍去左子树,往下跨则舍弃右子树部分。

public class SkipListMap {

	// 跳表的节点定义
	public static class SkipListNode<K extends Comparable<K>, V> {
		public K key;
		public V val;
		//每个Node里面缓存多层链表
		public ArrayList<SkipListNode<K, V>> nextNodes;

		public SkipListNode(K k, V v) {
			key = k;
			val = v;
			nextNodes = new ArrayList<SkipListNode<K, V>>();
		}

		// 遍历的时候,如果是往右遍历到的null(next == null), 遍历结束
		// 头(null), 头节点的null,认为最小
		// node  -> 头,node(null, "")  node.isKeyLess(!null)  true
		// node里面的key是否比otherKey小,true,不是false
		public boolean isKeyLess(K otherKey) {
			//  otherKey == null -> false 
			return otherKey != null && (key == null || key.compareTo(otherKey) < 0);
		}

		public boolean isKeyEqual(K otherKey) {
			return (key == null && otherKey == null)
					|| (key != null && otherKey != null && key.compareTo(otherKey) == 0);
		}

	}

	public static class SkipListMap<K extends Comparable<K>, V> {
		private static final double PROBABILITY = 0.5; // < 0.5 继续做,>=0.5 停
		//头节点
		private SkipListNode<K, V> head;
		//大小
		private int size;
		//最高层级
		private int maxLevel;

		public SkipListMap() {
			//初始化
			head = new SkipListNode<K, V>(null, null);
			//第0层链表
			head.nextNodes.add(null); // 0
			size = 0;
			maxLevel = 0;
		}

		// 从最高层开始,一路找下去,
		// 最终,找到第0层的<key的最右的节点
		private SkipListNode<K, V> mostRightLessNodeInTree(K key) {
			if (key == null) {
				return null;
			}
			int level = maxLevel;
			SkipListNode<K, V> cur = head;
			while (level >= 0) { // 从上层跳下层
				//  cur  level  -> level-1
				//在这一层里小于key的最右的节点,这里cur接住返回值,是为了继续找下一层
				cur = mostRightLessNodeInLevel(key, cur, level--);
			}
			return cur;
		}

		// 在level层里,如何往右移动
		// 现在来到的节点是cur,来到了cur的level层,在level层上,找到<key最后一个节点并返回
		private SkipListNode<K, V> mostRightLessNodeInLevel(K key, 
				SkipListNode<K, V> cur, 
				int level) {
			SkipListNode<K, V> next = cur.nextNodes.get(level);
			while (next != null && next.isKeyLess(key)) {
				cur = next;
				next = cur.nextNodes.get(level);
			}
			return cur;
		}

		public boolean containsKey(K key) {
			if (key == null) {
				return false;
			}
			SkipListNode<K, V> less = mostRightLessNodeInTree(key);
			SkipListNode<K, V> next = less.nextNodes.get(0);
			return next != null && next.isKeyEqual(key);
		}

		// 新增、改value
		public void put(K key, V value) {
			if (key == null) {
				return;
			}
			// 0层上,最右一个,< key 的Node -> >key(注意是最下层第0层)
			SkipListNode<K, V> less = mostRightLessNodeInTree(key);
			//看找到的最右一个的下一个节点是否等于传入的key
			SkipListNode<K, V> find = less.nextNodes.get(0);
			if (find != null && find.isKeyEqual(key)) {
				//如果相等则更新value,不用挂新记录
				find.val = value;
			} else { // find == null   8   7   9
				//新来一条记录
				size++;
				int newNodeLevel = 0;
				//row层数
				while (Math.random() < PROBABILITY) {
					newNodeLevel++;
				}
				// newNodeLevel如果大于maxlevel则最左边的head层数会升到和newNodeLevel一样高
				while (newNodeLevel > maxLevel) {
					head.nextNodes.add(null);
					maxLevel++;
				}
				//新节点建立并且也建立那么多层
				SkipListNode<K, V> newNode = new SkipListNode<K, V>(key, value);
				for (int i = 0; i <= newNodeLevel; i++) {
					newNode.nextNodes.add(null);
				}
				//从最高层开始
				int level = maxLevel;
				//最左节点
				SkipListNode<K, V> pre = head;
				while (level >= 0) {
					// level 层中,找到最右的 < key 的节点
					pre = mostRightLessNodeInLevel(key, pre, level);
					//此处if判断是防止我原本左边的层数大于现在row出来的层数,那么就应该跳过
					if (level <= newNodeLevel) {
						//新节点在level层挂pre的下一个
						newNode.nextNodes.set(level, pre.nextNodes.get(level));
						//pre的下一个挂我自己
						pre.nextNodes.set(level, newNode);
					}
					level--;
				}
			}
		}

		public V get(K key) {
			if (key == null) {
				return null;
			}
			SkipListNode<K, V> less = mostRightLessNodeInTree(key);
			SkipListNode<K, V> next = less.nextNodes.get(0);
			return next != null && next.isKeyEqual(key) ? next.val : null;
		}

		public void remove(K key) {
			if (containsKey(key)) {
				//存在削减最高层的情况
				size--;
				int level = maxLevel;
				SkipListNode<K, V> pre = head;
				while (level >= 0) {
					//在本层找到最右的节点
					pre = mostRightLessNodeInLevel(key, pre, level);
					SkipListNode<K, V> next = pre.nextNodes.get(level);
					// 1)在这一层中,pre下一个就是key
					// 2)在这一层中,pre的下一个key是>要删除key
					if (next != null && next.isKeyEqual(key)) {
						// free delete node memory -> C++
						// level : pre -> next(key) -> ...
						pre.nextNodes.set(level, next.nextNodes.get(level));
					}
					// 在level层只有一个节点了,就是默认节点head
					if (level != 0 && pre == head && pre.nextNodes.get(level) == null) {
						head.nextNodes.remove(level);
						maxLevel--;
					}
					level--;
				}
			}
		}

		public K firstKey() {
			return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null;
		}

		public K lastKey() {
			int level = maxLevel;
			SkipListNode<K, V> cur = head;
			while (level >= 0) {
				SkipListNode<K, V> next = cur.nextNodes.get(level);
				while (next != null) {
					cur = next;
					next = cur.nextNodes.get(level);
				}
				level--;
			}
			return cur.key;
		}

		public K ceilingKey(K key) {
			if (key == null) {
				return null;
			}
			SkipListNode<K, V> less = mostRightLessNodeInTree(key);
			SkipListNode<K, V> next = less.nextNodes.get(0);
			return next != null ? next.key : null;
		}

		public K floorKey(K key) {
			if (key == null) {
				return null;
			}
			SkipListNode<K, V> less = mostRightLessNodeInTree(key);
			SkipListNode<K, V> next = less.nextNodes.get(0);
			return next != null && next.isKeyEqual(key) ? next.key : less.key;
		}

		public int size() {
			return size;
		}

	}

	// for test
	public static void printAll(SkipListMap<String, String> obj) {
		for (int i = obj.maxLevel; i >= 0; i--) {
			System.out.print("Level " + i + " : ");
			SkipListNode<String, String> cur = obj.head;
			while (cur.nextNodes.get(i) != null) {
				SkipListNode<String, String> next = cur.nextNodes.get(i);
				System.out.print("(" + next.key + " , " + next.val + ") ");
				cur = next;
			}
			System.out.println();
		}
	}

	public static void main(String[] args) {
		SkipListMap<String, String> test = new SkipListMap<>();
		printAll(test);
		System.out.println("======================");
		test.put("A", "10");
		printAll(test);
		System.out.println("======================");
		test.remove("A");
		printAll(test);
		System.out.println("======================");
		test.put("E", "E");
		test.put("B", "B");
		test.put("A", "A");
		test.put("F", "F");
		test.put("C", "C");
		test.put("D", "D");
		printAll(test);
		System.out.println("======================");
		System.out.println(test.containsKey("B"));
		System.out.println(test.containsKey("Z"));
		System.out.println(test.firstKey());
		System.out.println(test.lastKey());
		System.out.println(test.floorKey("D"));
		System.out.println(test.ceilingKey("D"));
		System.out.println("======================");
		test.remove("D");
		printAll(test);
		System.out.println("======================");
		System.out.println(test.floorKey("D"));
		System.out.println(test.ceilingKey("D"));
		

	}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1001161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PAT】攀拓(PAT)- 程序设计(甲级)2023年夏季考试自测

个人学习记录&#xff0c;代码难免不尽人意。 今天又斥资巨款买了PAT甲级2023年夏季的考试真题做了做&#xff0c;得分 95&#xff0c;还买了时光机&#xff0c;在当时排名42名。总的来说还是比较满意的&#xff01;有些地方当时做的时候卡住了但是后面回过头来重新想的时候还是…

小程序环境搭建

第一种&#xff1a;微信开发者工具 1. 微信公众平台注册小程序 注册类型选择‘个人’即可&#xff0c;‘企业’需要公司相关信息&#xff08;企业信用代码、法人信息等&#xff09; 注册成功后&#xff0c;在‘开发’-‘开发管理’-‘开发设置’中找到AppID 并纪录。 2. …

关于前端就业前景的一点看法

一、前言 最近&#xff0c;很多人在知乎上鼓吹前端未来会没掉的概念。在此我想说这个说法并不真实&#xff0c;而是一种极端的观点。 事实上&#xff0c;前端开发在当今的互联网行业中扮演着至关重要的角色&#xff0c;它是构建 Web 应用程序所必需的一部分&#xff0c;能够实现…

高速电路设计-----第二章

本章主要讲解的是电阻、电容、电感的选型。 一、电阻&#xff1a;关键还是限流。 1、通常在电源滤波时除了LC外&#xff0c;还会串接一个R。目的是为了降低信号的Q值&#xff0c;防止信号失真。常用于失真电源滤波。 2、选型的电阻的封装太小&#xff0c;电路的电流超过电阻能…

让你不再惧怕内存优化

原文链接 让你不再惧怕内存优化 之前曾经写过一篇关于如何做性能优化的文章&#xff0c;现在针对内存这一专项再做精细化的讨论。对于安卓应用开发来说&#xff0c;内存究竟会遇到什么样的问题&#xff0c;有什么方法可以用来测试和分析&#xff0c;以及有什么样的策略可以去实…

【LeetCode-中等题】904. 水果成篮

文章目录 题目方法一&#xff1a;滑动窗口方法二&#xff1a; 题目 题目的意思就是&#xff1a;找至多包含两种元素的最长子串&#xff0c;返回其长度 方法一&#xff1a;滑动窗口 class Solution { // 滑动窗口 找至多包含两种元素的最长子串&#xff0c;返回其长度public …

红队打靶:ConnectTheDots打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现和端口扫描 第二步&#xff1a;FTP和NFS渗透&#xff08;失败&#xff09; 第三步&#xff1a;web渗透 第四步&#xff1a;jsfuck解码 第五步&#xff1a;再次FTP渗透与莫尔斯电码解码 第六步&#xff1a;vim读取断电swp文件…

数据科学家必备的20个Python库

公众号&#xff1a;尤而小屋作者&#xff1a;Peter编辑&#xff1a;Peter 大家好&#xff0c;我是Peter~ 小屋里面一直在输出关于数据科学领域的文章&#xff0c;绝大部分都是基于Python&#xff0c;少量的MySQL&#xff08;MySQL存储数据用&#xff09;。本文重点给大家介绍P…

保姆级教程 --redis启动命令

1、在redis目录 打开命令 windowr 输入cmd 2、输入 redis-server.exe redis.windows.conf 启动redis命令&#xff0c;看是否成功 3、可能会启动失败&#xff0c;报28 Nov 09:30:50.919 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 4、报错后&am…

获取该虚拟机的所有权失败,主机上的某个应用程序正在使用该虚拟机

点击“openstack-controller”虚机 打开出现如下错误&#xff0c;点击“获取所有权” 点击“取消” 这时候不要删除虚拟机&#xff0c;这种错误一般是由于虚拟机没有正常关闭引起的。 找到openstack-controller的虚拟磁盘文件及配置文件存放的位置&#xff0c;删除openstack-…

【系统设计系列】缓存

系统设计系列初衷 System Design Primer&#xff1a; 英文文档 GitHub - donnemartin/system-design-primer: Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards. 中文版&#xff1a; https://github.com/donnemart…

电子信息工程专业课复习知识点总结:(三)数电

绪论 第一章 数字逻辑概论 1.数字集成电路相比模拟电路的优点&#xff1f; ①稳定i性高&#xff0c;抗干扰能力强 ②数字电路只用0和1进行逻辑运算&#xff0c;所以比较容易设计电路。 ③便于集成&#xff0c;体积小、成本低 ④可编程性强&#xff0c;可以使用加密技术提高保…

SQL6 查找学校是北大的学生信息

描述 题目&#xff1a;现在运营想要筛选出所有北京大学的学生进行用户调研&#xff0c;请你从用户信息表中取出满足条件的数据&#xff0c;结果返回设备id和学校。 示例&#xff1a;user_profile iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male…

Spring Boot将声明日志步骤抽离出来做一个复用类

上文Spring Boot日志基础使用 设置日志级别中我们写了个比较基本的日志操作 但也随之产生了一个问题 我们这行代码 能不能不写&#xff1f; 具体说 我们不希望每个需要日志的类都声明一个在这 看着太不美观了 我们最简单方法当然是继承 我们找个目录创建一个类 叫 BaseClass…

星际争霸之小霸王之小蜜蜂(十二)--猫有九条命

系列文章目录 星际争霸之小霸王之小蜜蜂&#xff08;十一&#xff09;--杀杀杀 星际争霸之小霸王之小蜜蜂&#xff08;十&#xff09;--鼠道 星际争霸之小霸王之小蜜蜂&#xff08;九&#xff09;--狂鼠之灾 星际争霸之小霸王之小蜜蜂&#xff08;八&#xff09;--蓝皮鼠和大…

力扣刷题61-旋转链表

题目来源&#xff1a; 力扣61-旋转链表 题目描述&#xff1a; 思路&#xff1a;双指针 因为它倒得永远是倒数k个 class Solution {public ListNode rotateRight(ListNode head, int k) {ListNode slowhead,fasthead,lenhead;int l0;//1 求长度while(len!null){l;lenlen.next…

【JAVA-Day05】深入理解Java数据类型和取值范围

深入理解Java数据类型和取值范围 深入理解Java数据类型和取值范围摘要一、Java的数据类型1.1 存储单位1.2 Java基本数据类型 二、Java的取值范围2.1 变量定义2.2 取值范围验证 三、总结 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb;…

孙哥Spring源码第21集

第21集 refresh-invokeBeanFactoryPostProcessor-下半部分BeanFactoryPostPrcessor处理 【视频来源于&#xff1a;B站up主孙帅suns Spring源码视频】【微信号&#xff1a;suns45】 1、注解处理添加的的BeanDefinitionRegistryFactoryProcessor在哪个地方&#xff1f; 在第一…

【Flutter】引入网络图片时,提示:Failed host lookup: ‘[图片host]‘

在使用 NetworkImage 组件加载外部图片时&#xff0c;提示 Failed host lookup: [图片host] 错误。 排查方向 1、清理缓存 解决方案&#xff1a; 尝试flutter clean清空缓存后重新安装依赖flutter pub get重新构建项目flutter create . 走完上述三个步骤后&#xff0c;再次…

COMO-ViT论文阅读笔记

Low-Light Image Enhancement with Illumination-Aware Gamma Correction and Complete Image Modelling Network 这是一篇美团、旷视、深先院、华为诺亚方舟实验室、中国电子科技大学 五个单位合作的ICCV2023的暗图增强论文&#xff0c;不过没有开源代码。 文章的贡献点一个是…