Hash-based Search In Java using linked List

ยท

6 min read

Hashing search, also known as hash-based search or hash table search, is a search algorithm that uses a data structure called a hash table to efficiently store and retrieve data. It leverages a hash function to map the search keys to indexes in the hash table, allowing for fast access to the desired values.

Here is an overview of the hashing search algorithm:

  1. Hash Function:

    • A hash function takes the search key as input and calculates a hash value or index within the hash table. The hash function should distribute the keys uniformly across the table to minimize collisions.
  2. Hash Table Initialization:

    • Create a hash table, typically an array, with a fixed size. The size of the hash table is chosen based on the expected number of elements and the desired load factor to balance performance and memory usage.
  3. Insertion:

    • To insert an element into the hash table, apply the hash function to the element's key to determine its hash value or index.

    • If there is no collision (i.e., the index is empty), store the element at that index in the hash table.

    • If there is a collision (i.e., another element already occupies the index), handle it based on the chosen collision resolution strategy. Common approaches include separate chaining (using linked lists or arrays) or open addressing (probing techniques like linear probing, quadratic probing, or double hashing).

  4. Search:

    • To search for a target element, apply the hash function to the target's key to determine its hash value or index.

    • Access the element at the calculated index in the hash table.

    • If the element exists at the index, compare its key with the target key to determine if it is a match.

    • If the keys match, the search is successful, and the desired element has been found.

    • If the index is empty or the keys do not match, it indicates that the target element is not present in the hash table.

The key advantages of hashing search are its constant-time average case complexity for search operations (O(1)) and efficient insertion and retrieval of elements. However, hash functions and collision resolution strategies must be carefully chosen to ensure a good distribution of elements and minimize collisions for optimal performance.

It's important to note that hash tables do have some trade-offs. They require additional memory to store the hash table structure, and the order of elements may not be preserved. Additionally, hash functions and collision resolution strategies may introduce additional complexity or require tuning for optimal performance in different scenarios.

Let's consider an example with a hash table size of 11 and an input array of [7, 18, 33, 44, 29, 55].

  1. Hashing Function:

    • We'll use a simple hash function that calculates the modulo of the input value with the size of the hash table. The hash function would be hash = value % 11.
  2. Initialization:

    • Create a hash table as an array of linked lists with a size of 11.
  3. Insertion Phase:

    • For each element in the input array, perform the following steps:

      • Calculate the hash value for the current element using the hash function.

      • Create a linked list and insert the value in the linked list.

      • If there is already a linked list present in that index then insert the value in the existing linked list.

After the insertion phase, the hash table would look like this:

    Index 0: 33, 44, 55
    Index 1: 
    Index 2: 
    Index 3: 
    Index 4: 
    Index 5: 
    Index 6: 
    Index 7: 7, 18, 29
    Index 8: 
    Index 9: 
    Index 10:

In this example, we have collisions at index 0, as well as at index 7.

  1. Search Phase:

    • Let's search for the target value 44.

      • Apply the hash function to calculate the hash value for 44, which is 44 % 11 = 0.

      • Traverse the linked list at index 0 in the hash table.

      • The target value is found at index 0 in the linked listn indicating that the target value 44 is present.

    • Now, let's search for the target value 29.

      • Apply the hash function to calculate the hash value for 29, which is 29 % 11 = 7.

      • Traverse the linked list at index 7 in the hash table.

      • The target value is found at index 7 in the linked list, indicating that the target value 29 is present.

    • Finally, let's search for the target value 11.

      • Apply the hash function to calculate the hash value for 11, which is 11 % 11 = 0.

      • Traverse the linked list at index 0 in the hash table.

      • The target value is not found at index 0 in the linked list, indicating that the target value 11 is not present.

In this example, we used a hash table with a size of 11 and separate chaining with linked lists for collision resolution. We successfully inserted the elements into the hash table and performed searches to find the target values.

Code:

public class Node<T> {
    private T data;
    private Node<T> next;    
    public Node() {
    }    
    public Node(T data) {
        this.data = data;
        this.next=null;
    }    
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public Node<T> getNext() {
        return next;
    }
    public void setNext(Node<T> next) {
        this.next = next;
    }    
}

public class LinkedList<T> {

    private Node<T> head;
    private Node<T> tail;

    public void insert(T data) {
        Node<T> node = new Node<T>(data);
        if (this.head == null)
            this.setHead(node);
        else
            this.getTail().setNext(node);
        this.setTail(node);

    }
    public void show() {
        Node<T> n = this.head;
        System.out.print("[");
        while (n.getNext() != null) {
            System.out.print(n.getData().toString() + ", ");
            n = n.getNext();
        }
        System.out.println(n.getData().toString() + "]");
    }
    public Node<T> getHead() {
        return head;
    }
    public void setHead(Node<T> head) {
        this.head = head;
    }
    public Node<T> getTail() {
        return tail;
    }
    public void setTail(Node<T> tail) {
        this.tail = tail;
    }


}
public class HashingSearch {
    public static void main(String[] args) {
        int arr[] = {1,2,3,4,5,6,6,7,12,13,14,15,15,15,18,19};
        int numberToFind = 8;
        boolean flag = search(arr, numberToFind);        
        System.out.println("is number "+numberToFind+ " presnt in the array "+flag);
    }
    public static boolean search(int[] arr, int numberToFind) {
        boolean flag = false;

        // create a Hashtable array of size 11, as we are using 11 to find hashvalue
        LinkedList [] hashTable  =  new LinkedList[11];

        //insert value in hashtable
        for (int i = 0; i < arr.length; i++) {
            Integer hashValue = findHashValue(arr[i]);
            if(hashTable[hashValue] == null){
                LinkedList<Integer> list = new LinkedList<Integer>();
                list.insert(arr[i]);                
                hashTable[hashValue] = list;
            }else
                hashTable[hashValue].insert(arr[i]);
        }        
        Integer targetHashValue = findHashValue(numberToFind);
        Node<Integer> head = hashTable[targetHashValue].getHead();    

        //start searching
        while(head != null){
            if(head.getData() == numberToFind){
                flag = true;
                break;
            }else
                head = head.getNext();
        }        
        return flag;
    }
    private static Integer findHashValue(Integer i) {
        return i%11;
    }
}

Outputs:

is number 8 presnt in the array : false

is number 7 presnt in the array : true

is number 19 presnt in the array : true

NOTE: I know the code looks complicated so u can use java.util.LinkedList class and avoid implementing your own LInkedList and Node class. ๐Ÿ˜‰๐Ÿ˜‰๐Ÿ˜‰

Did you find this article valuable?

Support Hemant Besra by becoming a sponsor. Any amount is appreciated!

ย