/**
 *  Implementation of a Double Linked List;  forward and backward links point to adjacent Nodes.
 *
 */

 public class LinkedList<T>
 {
     private T data;
     private LinkedList<T> prevNode, nextNode;
 
     /**
      *  Constructs a new element
      *
      * @param  data, data of object
      * @param  node, previous node
      */
     public LinkedList(T data, LinkedList<T> node)
     {
         this.setData(data);
         this.setPrevNode(node);
         this.setNextNode(null);
     }
 
     /**
      *  Clone an object,
      *
      * @param  node  object to clone
      */
     public LinkedList(LinkedList<T> node)
     {
         this.setData(node.data);
         this.setPrevNode(node.prevNode);
         this.setNextNode(node.nextNode);
     }
 
     /**
      *  Setter for T data in DoubleLinkedNode object
      *
      * @param  data, update data of object
      */
     public void setData(T data)
     {
         this.data = data;
     }
 
     /**
      *  Returns T data for this element
      *
      * @return  data associated with object
      */
     public T getData()
     {
         return this.data;
     }
 
     /**
      *  Setter for prevNode in DoubleLinkedNode object
      *
      * @param node, prevNode to current Object
      */
     public void setPrevNode(LinkedList<T> node)
     {
         this.prevNode = node;
     }
 
     /**
      *  Setter for nextNode in DoubleLinkedNode object
      *
      * @param node, nextNode to current Object
      */
     public void setNextNode(LinkedList<T> node)
     {
         this.nextNode = node;
     }
 
 
     /**
      *  Returns reference to previous object in list
      *
      * @return  the previous object in the list
      */
     public LinkedList<T> getPrevious()
     {
         return this.prevNode;
     }
 
     /**
      *  Returns reference to next object in list
      *
      * @return  the next object in the list
      */
     public LinkedList<T> getNext()
     {
         return this.nextNode;
     }

     public void setNext(LinkedList<T> next) {
        this.nextNode = next;
    }
    
    // public void setNext(T data) {
    //     this.nextNode = new LinkedList<>(data);
    // }
    
 
 }
import java.util.Iterator;

/**
 * Queue Iterator
 *
 * 1. "has a" current reference in Queue
 * 2. supports iterable required methods for next that returns a generic T Object
 */
class QueueIterator<T> implements Iterator<T> {
    LinkedList<T> current;  // current element in iteration

    // QueueIterator is pointed to the head of the list for iteration
    public QueueIterator(LinkedList<T> head) {
        current = head;
    }

    // hasNext informs if next element exists
    public boolean hasNext() {
        return current != null;
    }

    // next returns data object and advances to next position in queue
    public T next() {
        T data = current.getData();
        current = current.getNext();
        return data;
    }
}

/**
 * Queue: custom implementation
 * @author     John Mortensen
 *
 * 1. Uses custom LinkedList of Generic type T
 * 2. Implements Iterable
 * 3. "has a" LinkedList for head and tail
 */
public class Queue<T> implements Iterable<T> {
    LinkedList<T> head = null, tail = null;

    /**
     *  Add a new object at the end of the Queue,
     *
     * @param  data,  is the data to be inserted in the Queue.
     */
    public void add(T data) {
        // add new object to end of Queue
        LinkedList<T> tail = new LinkedList<>(data, null);

        if (this.head == null)  // initial condition
            this.head = this.tail = tail;
        else {  // nodes in queue
            this.tail.setNextNode(tail); // current tail points to new tail
            tail.setPrevNode(this.tail);
            this.tail = tail;  // update tail
        }
    }

    public void addList(T[] lists) {
        for (T data : lists) {
            this.add(data);
        }
      }

    /**
     *  Returns the data of head.
     *
     * @return  data, the dequeued data
     */
    public T delete() {
        T data = this.peek();
        if (this.tail != null) { // initial condition
            this.head = this.head.getNext(); // current tail points to new tail
            if (this.head != null) {
                this.head.setPrevNode(tail);
            }
        }
        return data;
    }

    /**
     *  Returns the data of head.
     *
     * @return  this.head.getData(), the head data in Queue.
     */
    public T peek() {
        return this.head.getData();
    }

    /**
     *  Returns the head object.
     *
     * @return  this.head, the head object in Queue.
     */
    public LinkedList<T> getHead() {
        return this.head;
    }

    /**
     *  Returns the tail object.
     *
     * @return  this.tail, the last object in Queue
     */
    public LinkedList<T> getTail() {
        return this.tail;
    }

    /**
     *  Returns the iterator object.
     *
     * @return  this, instance of object
     */
    public Iterator<T> iterator() {
        return new QueueIterator<>(this.head);
    }

    /**
     * Returns if queue is empty
     * 
     * @return boolean if it is empty
     */
    public boolean isEmpty() {
      return this.head == null;
    }

    /**
     * Changes the head
     * 
     */
    public void setHead(LinkedList<T> h) {
        this.head = h;
    }

    /**
     * Returns size of queue
     * 
     * @return size of queue
     */ 
    public int size() {
        int sz = 0;
        for (T e: this) {
            sz++;
        }
        return sz;
    }
    
    public String toString() {
      int count = 0;
      String str = "";
      for (T e : this) {
        str += e + " ";
        count++;
      }
      return "count: " + count + ", data: " + str;
    }
}

Generic Class

abstract class Sorter<T> {
    String name;
    double stime;
    double etime;
    double difftime;
    int compares;
    int swaps;


    public Sorter(String name) {
        this.name = name;
    }

    abstract public Queue<T> sort(Queue<T> list, boolean verbose);

    public Queue<T> sort(Queue<T> list) {
        return this.sort(list, true);
    }

    public void start() {
        this.stime = System.nanoTime();
    }

    public void end()  {
        this.etime = System.nanoTime();
    }

    public double elapsedtime()  {
        difftime = etime - stime;
        return difftime;
    }

    public void incrementcompare() {
        compares++;
    }

    public void incrementswap() {
        swaps++;
    }

    public int printcomp() {
        return this.compares;
    }

    public int printswap() {
        return this.swaps;
    }
}

Bubble

class BubbleSorter<T extends Comparable<T>> extends Sorter<T> {
    public BubbleSorter() {
        super("Bubble Sort");
    }
    
    public Queue<T> sort (Queue<T> q, boolean verbose) {
        super.start();
        boolean swapped = true;
        LinkedList<T> head = q.getHead();
        while (swapped) {
            swapped = false;
            LinkedList<T> current = head;
            while (current.getNext() != null) {
              if (current.getData().compareTo(current.getNext().getData()) > 0) {
                  T temp = current.getNext().getData();
                  current.getNext().setData(current.getData());
                  current.setData(temp);
                  swapped = true;
                  super.incrementswap();
              }
              super.incrementcompare();
              current = current.getNext();
            }
        }
  
        super.end();
        return q;
    }


  }

Selection

class Selection<T extends Comparable<T>> extends Sorter<T> {
    public Selection() {
        super("Selection Sort");
    }
    
    public Queue<T> sort (Queue<T> q, boolean verbose) {
        super.start();
        boolean swapped = true;
        LinkedList<T> current = q.getHead();
        
        while (current.getNext() != null) {
            LinkedList<T> currentnext = current.getNext();
            LinkedList<T> min = current;
            while (currentnext != null) {
                if (min.getData().compareTo(currentnext.getData()) > 0) {
                    min = currentnext;
                }
                currentnext = currentnext.getNext();
                super.incrementcompare();
            }
          
        
        
        T temp = min.getData();
        min.setData(current.getData());
        current.setData(temp);
        super.incrementswap();
        current = current.getNext();
        
        }
  
        super.end();
        return q;
    }
}

Insertion

class Insertion<T extends Comparable<T>> extends Sorter<T> {
    public Insertion() {
        super("Insertion Sort");
    }
    
    public Queue<T> sort(Queue<T> q, boolean verbose) {
        super.start();
        LinkedList<T> current = q.getHead().getNext();
        
        while (current != null) {
            LinkedList<T> current2 = current;
            while (current2.getPrevious() != null && current2.getPrevious().getData().compareTo(current2.getData()) > 0) {
                T temp = current2.getPrevious().getData();
                current2.getPrevious().setData(current2.getData());
                current2.setData(temp);
                super.incrementswap();
                super.incrementcompare();
                current2 = current2.getPrevious();
            }
            current = current.getNext();
            super.incrementcompare();
        }
  
        super.end();
        return q;
    }
}

Merge

/**

A class representing the Merge Sort algorithm for sorting a Queue of elements

@param <T> The type of elements in the Queue, must implement Comparable
*/
class Merge<T extends Comparable<T>> extends Sorter<T> {

    /**
    
    Creates a new instance of Merge Sort
    */
    public Merge() {
    super("Merge Sort");
    }
    /**
    
    Sorts the given Queue of elements using Merge Sort algorithm
    @param q The Queue of elements to be sorted
    @param verbose A boolean indicating whether to print out the sorting process or not
    @return The sorted Queue of elements
    */
    public Queue<T> sort(Queue<T> q, boolean verbose) {
    super.start();
    q.setHead(mergeSort(q.getHead()));
    super.end();
    return q;
    }

    private LinkedList<T> mergeSort(LinkedList<T> head) {
    // Base case: if the linked list is empty or has only one element
    if (head == null || head.getNext() == null) {
    return head;
    }
    
    // Find the middle node of the linked list
    LinkedList<T> middle = getMiddle(head);
    LinkedList<T> nextOfMiddle = middle.getNext();
    middle.setNext(null);
    
    // Recursively sort the left and right halves of the linked list
    LinkedList<T> left = mergeSort(head);
    LinkedList<T> right = mergeSort(nextOfMiddle);
    
    // Merge the two sorted halves of the linked list
    return merge(left, right);
    }
    
    private LinkedList<T> getMiddle(LinkedList<T> head) {
    // Base case: if the linked list is empty
    if (head == null) {
    return head;
    }
    
    // Initialize two pointers: slow and fast
    LinkedList<T> slow = head;
    LinkedList<T> fast = head;
    
    // Traverse the linked list using two pointers:
    // slow moves one node at a time, while fast moves two nodes at a time
    while (fast.getNext() != null && fast.getNext().getNext() != null) {
    slow = slow.getNext();
    fast = fast.getNext().getNext();
    }
    
    // The slow pointer is now pointing to the middle node of the linked list
    return slow;
    }
    
    private LinkedList<T> merge(LinkedList<T> left, LinkedList<T> right) {
    LinkedList<T> result = null;
    
    // Base case: if one of the linked lists is empty, return the other one
    if (left == null) {
    return right;
    }
    
    if (right == null) {
    return left;
    }
    
    // Compare the first nodes of the left and right linked lists,
    // and recursively merge the remaining halves until all nodes are merged
    if (left.getData().compareTo(right.getData()) <= 0) {
            result = left;
            result.setNext(merge(left.getNext(), right));
        } else {
            result = right;
            result.setNext(merge(left, right.getNext()));
        }

        super.incrementswap();
        super.incrementcompare();

        return result;
    }
}

Tester

public class Tester {
    private static double calcAvg(ArrayList<Double> arr) {
        double sum = 0;
        if(!arr.isEmpty()) {
            for (Double i : arr) {
                sum += i;
            }
            return sum / arr.size();
        }
        return sum;
    }
    public static void main (String[] args) {
        List<Sorter<Integer>> sorters = new ArrayList<Sorter<Integer>>();
        sorters.add(new BubbleSorter<>());
        sorters.add(new Selection<>());
        sorters.add(new Insertion<>());
        sorters.add(new Merge<>());
        int size = 5000;
        for (Sorter i : sorters) {
            int test = 1;
            ArrayList<Double> elapsed = new ArrayList<Double>();
            ArrayList<Double> comp = new ArrayList<Double>();
            ArrayList<Double> swap = new ArrayList<Double>();
            while (test <= 20) {
                ArrayList<Integer> arr = new ArrayList<Integer>();
                for (int j = 0; j < size; j++) {
                    int rand = (int) Math.floor(Math.random() * size * 10);
                    arr.add(rand);
                }
                Queue<Integer> q = new Queue<>();
                q.addList(arr.toArray(new Integer[size]));
                i.sort(q);
                elapsed.add(i.elapsedtime());
                comp.add(new Double(i.printcomp()));
                swap.add(new Double(i.printswap()));
                test++;
            }
            System.out.println(i.name);
            System.out.printf("Average Elapsed time: %.10fs\n", calcAvg(elapsed)/1000000000);
            System.out.printf("Average Number of compares: %.2f\n", calcAvg(comp));
            System.out.printf("Average Number of swaps: %.2f\n", calcAvg(swap));
            System.out.println();
        }
        System.out.println();
    }
}

Tester.main(null);
Bubble Sort
Average Elapsed time: 0.1399708400s
Average Number of compares: 258080123.65
Average Number of swaps: 65697475.40

Selection Sort
Average Elapsed time: 0.0544021950s
Average Number of compares: 131223750.00
Average Number of swaps: 52489.50

Insertion Sort
Average Elapsed time: 0.0281362850s
Average Number of compares: 65930296.10
Average Number of swaps: 65877806.60

Merge Sort
Average Elapsed time: 0.0009457550s
Average Number of compares: 579713.60
Average Number of swaps: 579713.60