算法集:快速排序、布隆过滤器、LRU算法、Dijkstra算法、字符串转整数、翻转二叉树

记录几个算法:快速排序、布隆过滤器、LRU算法

快速排序

老是容易把快速排序写歪了;这里给自己定几个关键点:

  1. 选择数组最后一项作为pivot;遍历的时候就不需要遍历最后一项了;(这样的好处是index++肯定不会有越界的风险,也不需要额外判断)
  2. 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));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值