数据结构之三(搜索算法)

1、几种常见的查找算法:

  1. 线性查找(Linear Search):逐个比较数据集合中的元素,直到找到目标元素或遍历完整个数据集合。时间复杂度为O(n)。

  2. 二分查找(Binary Search):仅适用于已排序的数据集合。从数据集合的中间元素开始,不断将搜索范围缩小为一半,直到找到目标元素或确定其不存在。时间复杂度为O(log n)。

  3. 哈希查找(Hashing):通过哈希函数将元素映射到索引位置,然后在该位置进行查找。在哈希表中,平均情况下可以达到O(1)的时间复杂度。

  4. 二叉搜索树查找(Binary Search Tree):利用二叉搜索树的特性,在每一步根据节点值的大小关系减少搜索空间。平均时间复杂度为O(log n),最坏情况下可能为O(n)。

  5. 插值查找(Interpolation Search):对于均匀分布的有序数据集合,根据目标元素与数据集合范围的大小关系,预测其可能的位置进行查找。平均时间复杂度为O(log log n)。

  6. 跳表查找(Skip List):基于多层链表的数据结构,通过在不同层级进行跳跃式查找,以降低查找的时间复杂度。平均时间复杂度为O(log n)。

  7. 红黑树查找(Red-Black Tree):一种自平衡的二叉搜索树,保持良好的平衡性能,使得查找操作的时间复杂度为O(log n)。

2、线性查找(Linear Search)

线性查找,也称为顺序查找,是一种简单直接的查找算法。它逐个比较数据集合中的元素,直到找到目标元素或遍历完整个数据集合。

以下是线性查找的基本步骤:

  1. 从数据集合的第一个元素开始,逐个比较元素与目标元素是否相等。
  2. 如果找到了相等的元素,则返回该元素的索引位置。
  3. 如果遍历完整个数据集合仍未找到相等的元素,则返回查找失败的结果。

3、二分查找(Binary Search)

二分查找(Binary Search),又称折半查找,是一种常见而高效的查找算法,用于在有序数组或有序列表中查找目标元素。

二分查找的基本思路是通过比较目标元素与数组或列表中间位置的元素大小关系,来判断目标元素可能存在的位置,然后不断缩小查找范围,直到找到目标元素或确定其不存在。

以下是二分查找的基本步骤:

  1. 确定要查找的目标元素。
  2. 确定查找范围,通常是有序数组或有序列表。
  3. 在范围的中间位置确定一个中间元素,比较目标元素与中间元素大小关系。
  4. 如果目标元素等于中间元素,则查找成功,返回中间元素的索引位置。
  5. 如果目标元素小于中间元素,则在左半部分继续查找。
  6. 如果目标元素大于中间元素,则在右半部分继续查找。
  7. 重复执行步骤3-6,直到找到目标元素或确定其不存在。

二分查找的时间复杂度为O(log n),其中n为数组或列表中的元素个数。这意味着,当数据规模很大时,二分查找比线性查找更快速、更高效,并且适用于有序数据结构。

public class BinarySearch {
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (arr[mid] == target) {
                return mid; // 返回目标元素的索引位置
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1; // 查找失败
    }
}

4、哈希查找(Hashing)

哈希查找(Hashing),也称为散列查找,是一种通过计算键(Key)的哈希值将其映射到桶(Bucket)或槽位(Slot)的查找技术。在哈希查找中,通过哈希函数将键转换为一个唯一的索引,然后在对应的桶或槽位中查找目标元素。

哈希查找的基本思想是将键映射到一个存储位置,使得查找操作具有较高的效率。它通常用于具有大量数据的情况下,可以在常数时间复杂度(O(1))内实现快速查找。

以下是哈希查找的基本步骤:

  1. 创建一个哈希表,作为存储结构。
  2. 定义一个哈希函数,用于将键转换成哈希值。
  3. 根据哈希值将键存储到对应的桶或槽位中。
  4. 在查找时,使用相同的哈希函数计算目标键的哈希值。
  5. 根据哈希值定位到对应的桶或槽位,查找目标元素。

需要注意的是,由于哈希函数的映射过程是非唯一的,可能会出现不同的键映射到相同的哈希值的情况,这就产生了哈希冲突。为了解决哈希冲突,常用的方法有开放寻址法和链地址法等。

开放寻址法是一种解决哈希冲突的方法,它在发生冲突时,通过逐个探测下一个可用的位置,直到找到一个空闲的位置或达到查找上限。常见的开放寻址法包括线性探测、二次探测和双重哈希等。

链地址法是另一种解决哈希冲突的方法,它使用链表来存储相同哈希值的元素。当发生冲突时,将新元素添加到链表的末尾。在查找时,先计算哈希值,然后在对应的链表中进行线性查找。

哈希查找的效率取决于哈希函数的设计和哈希表的装载因子(Load Factor)。合理选择哈希函数和调整装载因子可以提高哈希查找的效率。

总而言之,哈希查找是一种基于哈希函数和哈希表的快速查找技术,适用于大规模数据集合的快速检索。

public class HashTable {
    private String[] table;
    private int capacity;
    
    public HashTable(int capacity) {
        this.capacity = capacity;
        table = new String[capacity];
    }
    
    private int hashFunction(String key) {
        int hash = 0;
        for (int i = 0; i < key.length(); i++) {
            hash += key.charAt(i);
        }
        return hash % capacity; // 简单取模作为哈希函数
    }
    
    public void insert(String key, String value) {
        int index = hashFunction(key);
        while (table[index] != null) {
            index = (index + 1) % capacity; // 线性探测法解决冲突
        }
        table[index] = value;
    }
    
    public String search(String key) {
        int index = hashFunction(key);
        while (table[index] != null) {
            if (table[index].equals(key)) {
                return table[index];
            }
            index = (index + 1) % capacity; // 线性探测法解决冲突
        }
        return null;
    }
    
    // 示例用法
    public static void main(String[] args) {
        HashTable hashTable = new HashTable(10);
        hashTable.insert("apple", "苹果");
        hashTable.insert("banana", "香蕉");
        hashTable.insert("orange", "橙子");
        
        System.out.println(hashTable.search("apple")); // 输出:苹果
        System.out.println(hashTable.search("banana")); // 输出:香蕉
        System.out.println(hashTable.search("orange")); // 输出:橙子
        System.out.println(hashTable.search("watermelon")); // 输出:null,未找到
    }
}

5、二叉搜索树查找(Binary Search Tree)

二叉搜索树(Binary Search Tree,简称BST)是一种常用的数据结构,用于实现动态集合的查找、插入和删除等操作。它是一棵有序的二叉树,满足以下性质:

  1. 对于任意节点x,其左子树中所有节点的键值小于x的键值,右子树中所有节点的键值大于x的键值。
  2. 不存在两个节点的键值相等。

因此,对于任意节点x,可以采用以下方式进行查找、插入和删除操作。

查找:从根节点开始,若目标节点的键值大于当前节点,则往右子树查找;若小于当前节点,则往左子树查找;直到找到目标节点或遍历完整棵树。

插入:从根节点开始,若目标节点的键值大于当前节点,则往右子树插入;若小于当前节点,则往左子树插入;直到找到一个空位置,将目标节点插入该位置。

删除:先查找目标节点,若存在,则分三种情况处理:

  1. 目标节点为叶子节点:直接删除该节点。
  2. 目标节点只有一个子节点:将该子节点替换目标节点的位置。
  3. 目标节点有两个子节点:找到目标节点的后继节点(即右子树中最小的节点),将其值赋给目标节点,然后删除后继节点。
public class BinarySearchTree {
    private Node root;
    
    // 节点类
    private static class Node {
        int key;
        String value;
        Node left, right;
        
        public Node(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }
    
    public String search(int key) {
        Node current = root;
        while (current != null) {
            if (key == current.key) {
                return current.value; // 找到目标节点,返回其值
            } else if (key < current.key) {
                current = current.left; // 往左子树查找
            } else {
                current = current.right; // 往右子树查找
            }
        }
        return null; // 未找到目标节点
    }
    
    public void insert(int key, String value) {
        Node newNode = new Node(key, value);
        if (root == null) {
            root = newNode;
            return;
        }
        
        Node parent = null, current = root;
        while (current != null) {
            parent = current;
            if (key < current.key) {
                current = current.left; // 往左子树插入
            } else if (key > current.key) {
                current = current.right; // 往右子树插入
            } else {
                current.value = value; // 覆盖已有节点的值
                return;
            }
        }
        
        // 找到空位置,插入新节点
        if (key < parent.key) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
    }
    
    // 示例用法
    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();
        bst.insert(5, "apple");
        bst.insert(3, "banana");
        bst.insert(7, "orange");
        
        System.out.println(bst.search(5)); // 输出:apple
        System.out.println(bst.search(3)); // 输出:banana
        System.out.println(bst.search(7)); // 输出:orange
        System.out.println(bst.search(9)); // 输出:null,未找到
    }
}

6、插值查找(Interpolation Search)

插值查找是一种基于二分查找的算法,它在有序数组中查找目标值时采用了更加快速的搜索策略。与二分查找相比,插值查找的关键在于确定每次查找的中间位置。

插值查找假设数组中的元素均匀分布,即每个元素在数组中出现的概率相等。在这种情况下,我们可以通过以下公式来计算中间位置:

mid = left + (right - left) * (target - arr[left]) / (arr[right] - arr[left])

其中,leftright分别表示当前查找范围的左右边界,target表示目标值,arr表示待查找的数组。该公式会根据目标值在数组中的相对位置,计算出一个更接近目标值的位置作为中间位置,从而加速查找过程。

public class InterpolationSearch {
    public static int search(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        
        while (left <= right && target >= arr[left] && target <= arr[right]) {
            int mid = left + (right - left) * (target - arr[left]) / (arr[right] - arr[left]);
            
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return -1; // 未找到目标值
    }
    
    // 示例用法
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        System.out.println(search(arr, 5)); // 输出:2
        System.out.println(search(arr, 6)); // 输出:-1,未找到
    }
}

7、跳表查找(Skip List)

跳表(Skip List)是一种用于实现有序集合的数据结构,它通过添加多级索引层次来加速查找操作。跳表在有序链表的基础上增加了多级索引,使得在查找元素时可以跳过部分节点,从而降低了查找的时间复杂度。

跳表的核心思想是在原始链表上建立一组索引链表,每一级索引链表中的节点都指向下一级索引链表中的节点。最底层的索引链表就是原始链表本身。通过这种方式,我们可以在查找元素时先在较高级别的索引链表中进行查找,然后逐级下降,直到找到目标元素或确定目标元素不存在。

import java.util.Random;

public class SkipList {
    private static final int MAX_LEVEL = 16; // 最大索引层级
    private Node head; // 跳表头节点
    private int levelCount; // 当前索引层级
    
    // 节点类
    private static class Node {
        private int value;
        private Node[] next;
        
        public Node(int value, int level) {
            this.value = value;
            this.next = new Node[level];
        }
    }
    
    public SkipList() {
        this.head = new Node(-1, MAX_LEVEL); // 头节点的值为-1
        this.levelCount = 1;
    }
    
    // 获取随机索引层级
    private int randomLevel() {
        int level = 1;
        Random rand = new Random();
        while (rand.nextDouble() < 0.5 && level < MAX_LEVEL) {
            level++;
        }
        return level;
    }
    
    public void insert(int value) {
        int level = randomLevel();
        Node newNode = new Node(value, level);
        
        Node[] update = new Node[level];
        for (int i = 0; i < level; i++) {
            update[i] = head; // 初始化更新数组为头节点
        }
        
        Node current = head;
        for (int i = level - 1; i >= 0; i--) {
            while (current.next[i] != null && current.next[i].value < value) {
                current = current.next[i]; // 向右查找插入位置
            }
            update[i] = current; // 更新插入位置的前驱节点
        }
        
        for (int i = 0; i < level; i++) {
            newNode.next[i] = update[i].next[i];
            update[i].next[i] = newNode; // 插入新节点
        }
        
        if (levelCount < level) {
            levelCount = level; // 更新当前索引层级
        }
    }
    
    public boolean search(int value) {
        Node current = head;
        for (int i = levelCount - 1; i >= 0; i--) {
            while (current.next[i] != null && current.next[i].value < value) {
                current = current.next[i]; // 向右查找目标节点
            }
        }
        
        if (current.next[0] != null && current.next[0].value == value) {
            return true; // 找到目标节点
        }
        
        return false; // 未找到目标节点
    }
    
    public void print() {
        Node current = head.next[0];
        while (current != null) {
            System.out.print(current.value + " ");
            current = current.next[0];
        }
        System.out.println();
    }
    
    // 示例用法
    public static void main(String[] args) {
        SkipList skipList = new SkipList();
        skipList.insert(3);
        skipList.insert(6);
        skipList.insert(9);
        skipList.insert(2);
        skipList.insert(5);
        skipList.insert(8);
        
        skipList.print(); // 输出:2 3 5 6 8 9
        
        System.out.println(skipList.search(6)); // 输出:true
        System.out.println(skipList.search(4)); // 输出:false
    }
}

8、红黑树查找(Red-Black Tree)

红黑树(Red-Black Tree)是一种自平衡二叉查找树,它采用了一种颜色标记的方式来保证平衡性,具有较高的查找、插入和删除效率。红黑树的平衡性能够保证查找、插入和删除的时间复杂度都为 O(log n)。

红黑树的每个节点都被标记为红色或黑色,根节点是黑色的,而叶子节点(空节点)是黑色的。如果一个节点是红色的,则它的子节点必须是黑色的,这样就保证了从任意一个节点到其叶子节点的路径上包含相同数目的黑色节点。这种性质称为红黑树的黑高度(Black Height),用 bh(x) 表示节点 x 的黑高度。

红黑树的查找操作与普通的二叉查找树相同,从根节点开始递归查找目标元素,直到找到目标元素或确定目标元素不存在。红黑树的插入和删除操作则需要通过重新染色、旋转等操作来保持平衡。

public class RedBlackTree {
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private static class Node {
        private int key;
        private boolean color;
        private Node left;
        private Node right;

        public Node(int key, boolean color) {
            this.key = key;
            this.color = color;
            this.left = null;
            this.right = null;
        }
    }

    private Node root;

    public RedBlackTree() {
        this.root = null;
    }

    private boolean isRed(Node node) {
        if (node == null) {
            return false;
        }
        return node.color == RED;
    }

    private Node rotateLeft(Node node) {
        Node x = node.right;
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

    private Node rotateRight(Node node) {
        Node x = node.left;
        node.left = x.right;
        x.right = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

    private void flipColors(Node node) {
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

    public void insert(int key) {
        root = insert(root, key);
        root.color = BLACK;
    }

    private Node insert(Node node, int key) {
        if (node == null) {
            return new Node(key, RED);
        }

        if (key < node.key) {
            node.left = insert(node.left, key);
        } else if (key > node.key) {
            node.right = insert(node.right, key);
        } else {
            // 如果已经存在相同的节点,则不插入
            return node;
        }

        // 插入节点后需要保持红黑树的平衡性
        if (isRed(node.right) && !isRed(node.left)) {
            node = rotateLeft(node);
        }
        if (isRed(node.left) && isRed(node.left.left)) {
            node = rotateRight(node);
        }
        if (isRed(node.left) && isRed(node.right)) {
            flipColors(node);
        }

        return node;
    }

    public boolean search(int key) {
        Node node = root;
        while (node != null) {
            if (key == node.key) {
                return true;
            } else if (key < node.key) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
        return false;
    }

    // 示例用法
    public static void main(String[] args) {
        RedBlackTree tree = new RedBlackTree();
        tree.insert(3);
        tree.insert(6);
        tree.insert(9);
        tree.insert(2);
        tree.insert(5);
        tree.insert(8);

        System.out.println(tree.search(6)); // 输出:true
        System.out.println(tree.search(4)); // 输出:false
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AngleoLong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值