记录几个算法:快速排序、布隆过滤器、LRU算法
快速排序
老是容易把快速排序写歪了;这里给自己定几个关键点:
- 选择数组最后一项作为pivot;遍历的时候就不需要遍历最后一项了;(这样的好处是index++肯定不会有越界的风险,也不需要额外判断)
- index = low;每次遇到比pivot小的值就交换到arr[index];最终再将arr[index]和arr[high]交换即可
通常会拆分为两个方法:partition和quickSort;partition负责对当前数组排序,返回数组分界下标;quickSort负责判断是否需要继续排序(low < high)和递归调用
private static void quickSort(int arr[], int low, int high) {
if (low < high) {
int index = partition(arr, low, high);
// 根据数组分界点,递归调用
quickSort(arr, low, index - 1);
quickSort(arr, index + 1, high);
}
}
// 排序实现
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int index = low;
for (int i = low; i < high; i++) {
// 交换
if (arr[i] < pivot) {
int tmp = arr[i];
arr[i] = arr[index];
arr[index++] = tmp;
}
}
// 交换arr[high]和arr[index]
int tmp = arr[high];
arr[high] = arr[index];
arr[index] = tmp;
// 返回数组分界下标
return index;
}
布隆过滤器
import java.util.BitSet;
public class BloomFilter<E> {
private final BitSet bitSet;
private final int bitSetSize;
private final int numOfHashFunctions;
private static final int DEFAULT_SIZE = 1 << 25;
private static final int DEFAULT_NUM_HASH = 1 << 3;
/**
* 构造函数,初始化布隆过滤器
*
* @param expectedNumItems 预期插入元素的数量
* @param falsePositiveProbability 期望的误判率
*/
public BloomFilter(int expectedNumItems, double falsePositiveProbability) {
// 计算 bitSet 的大小
bitSetSize = (int) Math.ceil(
expectedNumItems * Math.log(falsePositiveProbability) / Math.log(1.0 / (Math.pow(2.0, Math.log(2.0)))));
// 计算 hash 函数的个数
numOfHashFunctions = (int) Math.round((double) bitSetSize / expectedNumItems * Math.log(2.0));
// 初始化 bitSet 和随机数生成器
bitSet = new BitSet(bitSetSize);
}
/**
* 默认无参构造函数
*/
public BloomFilter() {
bitSetSize = DEFAULT_SIZE;
bitSet = new BitSet(bitSetSize);
numOfHashFunctions = DEFAULT_NUM_HASH;
}
/**
* 插入一个元素到布隆过滤器中
*
* @param element 要插入的元素
*/
public void add(E element) {
// 对元素进行多次 hash,将对应的 bit 置为 1
for (int i = 0; i < numOfHashFunctions; i++) {
int hashValue = hash(element, i);
bitSet.set(hashValue, true);
}
}
/**
* 判断一个元素是否在布隆过滤器中存在
*
* @param element 要判断的元素
* @return true 表示可能存在,false 表示一定不存在
*/
public boolean contains(E element) {
// 对元素进行多次 hash,判断对应的 bit 是否都为 1
for (int i = 0; i < numOfHashFunctions; i++) {
int hashValue = hash(element, i * 100);
if (!bitSet.get(hashValue)) {
return false;
}
}
return true;
}
/**
* 计算元素的 hash 值
*
* @param element 要计算 hash 的元素
* @param seed 随机种子
* @return 元素的 hash 值
*/
private int hash(E element, int seed) {
int h;
int value = (element == null) ? 0 : Math.abs((h = element.hashCode()) ^ (h >>> 16));
return (bitSetSize - 1) & (seed * value);
}
public static void main(String[] args) {
BloomFilter bloomFilter = new BloomFilter<>();
bloomFilter.add("123");
}
}
布隆过滤器的核心在于hash函数的设计,上面的例子中使用了类似hashMap计算hash值的方式。
java的bitset作为存储结构,这种数据结构是可以自动扩容的,但是通常情况下我们应该保持bitmap结构稳定;所以在hash之后跟(bitSetSize - 1) & (seed * value)保证结果在bitmap范围内,不用扩容。该代码示例之前的hash实现是:
return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
我认为这样是不合理的,结果可能超出范围,乘法跟位运算是相同优先级,随机种子选择素数,seed * (cap - 1) 就超了bitmap的长度;在进行&运算,感觉会溢出导致扩容,不知道是不是我理解的有问题,反正我觉得不合理。
LRU算法
LRU(Least Recently Used,最近最少使用)是一种常见的页面置换算法,用于解决操作系统中内存页的管理问题。
LRU 算法的基本思想是:当内存不足时,将最近最少被访问的页替换出去,以腾出空间存储新的页。
LRU 算法的实现需要用到一个数据结构,通常是一个双向链表和一个哈希表。哈希表中存储每个页的地址和对应的节点在链表中的位置,链表中按照访问时间的先后顺序存储了所有的页,最近访问的页在链表的头部,最久未访问的页在链表的尾部。
具体的实现过程如下:
当一个页被访问时,如果该页已经在链表中,则将其移动到链表头部,表示该页最近被访问过。
如果该页不在链表中,则需要将其添加到链表头部,并将其地址和对应的节点位置存储在哈希表中。
当需要替换一页时,选择链表尾部的页进行替换,并将该页从链表和哈希表中删除。
LRU 算法的时间复杂度为 O(1),因为链表和哈希表的查找和删除操作都可以在常数时间内完成。但是,由于需要维护一个链表和一个哈希表,算法的空间复杂度比较高。
LRU算法及其优化策略——算法篇
public class MyLRUCache {
// 使用双向链表和map来维护
private final int capacity;
private final Map<String, Node> map;
private final Node head;
private final Node tail;
public MyLRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>(capacity);
head = new Node("HEAD");
tail = new Node("TAIL");
head.next = tail;
tail.pre = head;
}
public void put(String value) {
Node node = map.get(value);
if (node != null) { // 节点已经存在
// 判断是否为头节点,是则不需要操作
if (node.pre == head) {
return;
}
// 移到链表头
moveToHead(node);
} else { // 节点不存在,加入链表头
// 判断是否需要进行移除操作
eliminate();
node = new Node(value);
// 处理当前节点
node.pre = head;
node.next = head.next;
// 处理head.next
head.next.pre = node;
// 处理head
head.next = node;
map.put(value, node);
}
}
public String get(String value) {
Node node = map.get(value);
if (node == null) {
return null;
}
if (node.pre != head) {
// 移到链表前
moveToHead(node);
}
return node.value;
}
private void moveToHead(Node node) {
// 先断
Node pre = node.pre;
Node next = node.next;
pre.next = node.next;
next.pre = node.pre;
// 再加入链表头
// 处理node
node.pre = head;
node.next = head.next;
// 处理head.next
head.next.pre = node;
// 处理head
head.next = node;
}
private void eliminate() {
if (map.size() < capacity) {
return;
}
// 移除链表尾元素
Node node = tail.pre;
// 处理node.pre
node.pre.next = tail;
// 处理tail
tail.pre = node.pre;
map.remove(node.value);
}
static class Node {
String value;
Node next;
Node pre;
public Node(String value) {
this.value = value;
next = null;
pre = null;
}
}
public static void main(String[] args) {
MyLRUCache myLRUCache = new MyLRUCache(3);
myLRUCache.put("A");
myLRUCache.put("B");
myLRUCache.put("C");
myLRUCache.get("A");
myLRUCache.put("D");
}
}
Dijkstra算法
Dijkstra算法是找某个节点到图的其他节点的最短路径,它要求边不能有负权重
public class Dijkstra {
private int size;
private int[][] graph; // 邻接矩阵
private int target; // 目标节点,即求该节点到其他所有节点的最短路径
private int[] distance; // 记录最短距离
private boolean[] visited; // 记录是否访问过
private char[] precursor; // 前驱
/**
* 初始化
*
* @param graph
* @param target
*/
public Dijkstra(int[][] graph, int target) {
this.graph = graph;
this.target = target;
size = graph.length;
distance = new int[graph.length];
visited = new boolean[graph.length];
precursor = new char[graph.length];
init();
}
private void init() {
for (int i = 0; i < size; i++) {
distance[i] = graph[target][i] >= 0 ? graph[target][i] : Integer.MAX_VALUE;
precursor[i] = 'A';
}
visited[target] = true;
}
public void run() {
calShortestPath(searchMinDistance());
}
private void calShortestPath(int index) {
// 计算经过index到其他节点的距离,小于则更新
visited[index] = true;
for (int i = 0; i < size; i++) {
if (visited[i]) {
continue;
}
if (graph[index][i] == -1) {
continue;
}
if (distance[index] + graph[index][i] < distance[i]) {
distance[i] = distance[index] + graph[index][i];
precursor[i] = (char) ('A' + index);
}
}
int newIndex = searchMinDistance();
if (newIndex < 0) {
System.out.println(Arrays.toString(distance));
System.out.println(Arrays.toString(precursor));
return;
}
calShortestPath(newIndex);
}
private int searchMinDistance() {
int min = Integer.MAX_VALUE;
int index = -1;
for (int i = 0; i < size; i++) {
if (visited[i]) {
continue;
}
if (min > distance[i]) {
min = distance[i];
index = i;
}
}
return index;
}
public static void main(String[] args) {
int[][] graph =
new int[][] {{0, 4, -1, 2, -1}, {4, 0, 4, 1, -1}, {-1, 4, 0, 1, 3}, {2, 1, 1, 0, 7}, {-1, -1, 3, 7, 0}};
int target = 0;
Dijkstra dijkstra = new Dijkstra(graph, target);
dijkstra.run();
}
}
字符串转int类型
主要学习java中Integer.parseInt()方法,实现了一个简易版的字符串转int类型;并且具有一定的健壮性。
public class StringToInt {
public static void main(String[] args) {
// "123", "0123", "-1234", "+31231",
String[] ss = new String[]{"-2147483649", "0123", "-123", "123", "2147483647", "-2147483647", "-2147483648", "2147483648"};
for (String s : ss) {
try {
System.out.println(str2Int(s));
} catch (Exception e) {
System.out.println("invalid in str2Int: " + s);
}
try {
System.out.println(Integer.parseInt(s));
} catch (Exception e) {
System.out.println("invalid in parseInt: " + s);
}
}
}
public static int str2Int(String s) {
if (s == null || s.length() == 0) {
throw new NumberFormatException();
}
// 判断正负
boolean isNegative = false;
int index = 0;
char ch = s.charAt(index);
if (ch == '-') {
index++;
isNegative = true;
} else if (ch == '+') {
index++;
}
if (index >= s.length()) {
throw new NumberFormatException();
}
return str2IntCore(s, isNegative, index);
}
private static int str2IntCore(String s, boolean isNegative, int index) {
// 统一用负值的情况来处理
int limit = isNegative ? Integer.MIN_VALUE : -Integer.MAX_VALUE;
int multmin = limit / 10; // 判断int位数
int res = 0;
for (int i = index; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch < '0' || ch > '9') throw new NumberFormatException();
// 因为是负值,res只有比multmin大才能继续 * 10
if (res < multmin) throw new NumberFormatException();
res *= 10;
int digit = ch - '0';
if (res < limit + digit) throw new NumberFormatException();
res -= digit;
}
return isNegative ? res : -res;
}
}
翻转二叉树
包括构建树、翻转树、验证翻转结果(验证逻辑:对翻转前跟翻转后按层次遍历,每层节点list刚好翻转说明翻转正确)
public class Tree {
static class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
public TreeNode buildTree(int[] nodeValues) {
TreeNode[] treeNodes = new TreeNode[nodeValues.length];
// 根据value构造treeNodes
TreeNode root = null;
for (int i = 0; i < nodeValues.length; i++) {
TreeNode node = null;
if (nodeValues[i] != -1) {
node = new TreeNode(nodeValues[i]);
}
treeNodes[i] = node;
if (i == 0) {
root = node;
}
}
// 把treeNode挂到root下面
for (int i = 0; i * 2 + 1 < treeNodes.length; i++) { // 这里注意是只到i * 2 + 1;为什么不是i * 2 + 2;因为它可能有左节点但没有右节点
if (treeNodes[i] != null) {
treeNodes[i].left = treeNodes[i * 2 + 1];
if (i * 2 + 2 < treeNodes.length) {
treeNodes[i].right = treeNodes[i * 2 + 2];
}
}
}
return root;
}
public ArrayList<ArrayList<Integer>> hierarchicalTraverse(TreeNode root) {
ArrayList<ArrayList<Integer>> list = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
ArrayList<Integer> subList = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (node != null) {
subList.add(node.value);
queue.offer(node.left);
queue.offer(node.right);
} else {
subList.add(-1);
}
}
list.add(subList);
}
return list;
}
// 递归写多了,这里使用迭代翻转二叉树
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
// 交换左右节点
TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return root;
}
public boolean verify(List<ArrayList<Integer>> list1, List<ArrayList<Integer>> list2) {
if (list1 == null) {
return list2 == null;
}
if (list2 == null) {
return false;
}
if (list1.size() != list2.size()) {
return false;
}
// 遍历每层
for (int i = 0; i < list1.size(); i++) {
List<Integer> subList1 = list1.get(i);
List<Integer> subList2 = list2.get(i);
if (subList2.size() != subList1.size()) {
return false;
}
for (int j = 0; j < subList1.size(); j++) {
if (subList2.get(subList2.size() - j - 1) != subList1.get(j)) {
return false;
}
}
}
return true;
}
public static void main(String[] args) {
Tree tree = new Tree();
int[] nodeValues = new int[] {4, 1, 6, 0, 2, 5, 7, -1, -1, -1, 3, -1, -1, -1, 8};
TreeNode root = tree.buildTree(nodeValues);
// 注意这里的结果会多一层空节点,还没有想好在遍历的时候怎么去掉它们;可以在处理完成之后删除list的最后一项
List<ArrayList<Integer>> list1 = tree.hierarchicalTraverse(root);
System.out.println(list1);
root = tree.invertTree(root);
List<ArrayList<Integer>> list2 = tree.hierarchicalTraverse(root);
System.out.println(list2);
System.out.println(tree.verify(list1, list2));
}
}