See the next iteration.
I designed and implemented this adaptive heap sort in Java. It works as follows:
- Copy the range to be sorted into auxiliary array \$A\$
- Create an empty run heap \$H\$
- Scan \$A\$ from beginning to the end and whenever we have counted a longest possible run (a run is a longest ascending or descending contiguous subsequence of the input sequence), we store it in \$H\$. 
 Each run \$r\$ is encoded by two integers: \$r.from\$ is the index of the first array component in the run, and \$r.to\$ is the index of the last array component in the run. In the run heap, the top element is the run \$r\$, for which \$A[r.from]\$ is minimal (notice the indirection).
- Then, as long as the run heap is not empty, we remove the top element. This is done by returning \$A[r.from]\$, where \$r\$ is the topmost run in the run heap. After that, we set \$r.from \leftarrow r.from + 1\$, and possibly sift it down in order to restore the indirect heap invariant. If a run is exhausted, it is removed from the heap.
The best case complexity of this sort is \$\Theta(N)\$. The worst case complexity is \$\Theta(N \log N)\$. Linear space complexity and is unstable (may reorder the equal elements) due to heap structure.
HeapSelectionSort.java:
package net.coderodde.util.sorting;
import java.util.Arrays;
import java.util.Comparator;
/**
 * This class implements a sorting algorithm called 
 * <b><i>heap selection sort</i></b>. The worst case complexity is linearithmic
 * O(n log n), best case complexity linear O(n). Linear space complexity and is
 * unstable.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6
 */
public class HeapSelectionSort {
    /**
     * Sorts the range {@code array[fromIndex], array[fromIndex + 1], ...,
     * array[toIndex - 2], array[toIndex - 1]} using the specified comparator.
     * 
     * @param <T>        the array component type.
     * @param array      the array holding the range to sort.
     * @param fromIndex  the starting (inclusive) index of the range to sort.
     * @param toIndex    the ending (exclusive) index of the range to sort.
     * @param comparator the array component comparator.
     */
    public static <T> void sort(T[] array, 
                                int fromIndex, 
                                int toIndex, 
                                Comparator<? super T> comparator) {
        rangeCheck(array.length, fromIndex, toIndex);
        if (toIndex - fromIndex < 2) {
            return;
        }
        T[] aux = Arrays.copyOfRange(array, fromIndex, toIndex);
        Heap<T> heap = createHeap(aux, comparator);
        for (; fromIndex < toIndex; ++fromIndex) {
            array[fromIndex] = heap.popElement();
        }
    }
    /**
     * Sorts the entire array using the specified comparator.
     * 
     * @param <T>        the array component type.
     * @param array      the array holding the range to sort.
     * @param comparator the array component comparator.
     */
    public static <T> void sort(T[] array, Comparator<? super T> comparator) {
        sort(array, 0, array.length, comparator);
    }
    /**
     * Sorts the range {@code array[fromIndex], array[fromIndex + 1], ...,
     * array[toIndex - 2], array[toIndex - 1]} using the natural comparator.
     * 
     * @param <T>        the array component type.
     * @param array      the array holding the range to sort.
     * @param fromIndex  the starting (inclusive) index of the range to sort.
     * @param toIndex    the ending (exclusive) index of the range to sort.
     */
    public static <T> void sort(T[] array, int fromIndex, int toIndex) {
        sort(array, fromIndex, toIndex, NaturalOrder.INSTANCE);
    }
    /**
     * Sorts the entire array using the natural comparator.
     * 
     * @param <T>        the array component type.
     * @param array      the array holding the range to sort.
     */
    public static <T> void sort(T[] array) {
        sort(array, 0, array.length);
    }
    private static <T> Heap<T> createHeap(T[] array, 
                                          Comparator<? super T> comparator) {
        Heap<T> heap = new Heap<>(array.length / 2 + 1, array, comparator);
        int head;
        int left = 0;
        int right = 1;
        int last = array.length - 1;
        while (left < last) {
            head = left;
            // Decide the direction of the next run.
            if (comparator.compare(array[left++], array[right++]) <= 0) {
                // Scanning ascending run.
                while (left < last
                        && comparator.compare(array[left], array[right]) <= 0) {
                    ++left;
                    ++right;
                }
                heap.pushRun(new Run(head, left));
            } else {
                // Scanning descending run.
                while (left < last
                        && comparator.compare(array[left], array[right]) >= 0) {
                    ++left;
                    ++right;
                }
                Run run = new Run(head, left);
                reverseRun(array, run);
                heap.pushRun(run);
            }
            ++left;
            ++right;
        }
        // A special case: the very last element may be left without buddies
        // so make it (the only) 1-element run.
        if (left == last) {
            heap.pushRun(new Run(left, left));
        }
        return heap;
    }
    private static class Run {
        int from;
        int to;
        Run(int from, int to) {
            this.from = from;
            this.to = to;
        }
    }
    private static class Heap<T> {
        private int size;
        private final T[] array;
        private final Run[] runs;
        private final Comparator<? super T> comparator;
        Heap(int size, T[] array, Comparator<? super T> comparator) {
            this.runs = new Run[size];
            this.array = array;
            this.comparator = comparator;
        }
        T popElement() {
            T ret = array[runs[0].from];
            if (runs[0].from == runs[0].to) {
                // The run at the head of the heap is fully processed, remove.
                Run last = runs[--size];
                runs[0] = last;
            } else {
                // Increment to the next element.
                ++runs[0].from;
            }
            // Possibly sift down the top element in order to restore the
            // heap invariant.
            siftDown();
            return ret;
        }
        void pushRun(Run run) {
            int nodeIndex = size++;
            runs[nodeIndex] = run;
            siftUp(nodeIndex);
        }
        void siftUp(int index) {
            Run target = runs[index];
            for (;;) {
                int parentIndex = (index - 1) >> 1;
                if (parentIndex < 0) {
                    runs[0] = target;
                    return;
                }
                if (comparator.compare(array[runs[parentIndex].from], 
                                       array[target.from]) > 0) {
                    runs[index] = runs[parentIndex];
                    index = parentIndex;
                } else {
                    runs[index] = target;
                    return;
                }
            }
        }
        private void siftDown() {
            int nodeIndex = 0;
            int leftChildIndex = 1;
            int rightChildIndex = 2;
            int minIndex = 0;
            for (;;) {
                if (leftChildIndex < size 
                        && comparator.compare(array[runs[leftChildIndex].from], 
                                              array[runs[nodeIndex].from]) < 0) {
                    minIndex = leftChildIndex;
                }
                if (rightChildIndex < size
                        && comparator.compare(array[runs[rightChildIndex].from], 
                                              array[runs[minIndex].from]) < 0) {
                    minIndex = rightChildIndex;
                }
                if (minIndex == nodeIndex) {
                    return;
                }
                Run run = runs[minIndex];
                runs[minIndex] = runs[nodeIndex];
                runs[nodeIndex] = run;
                nodeIndex = minIndex;
                leftChildIndex = (nodeIndex << 1) + 1;
                rightChildIndex = leftChildIndex + 1;
            }
        }
    }
    private static <T> void reverseRun(T[] array, Run run) {
        for (int i = run.from, j = run.to; i < j; ++i, --j) {
            T tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }
    private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
        if (fromIndex > toIndex) {
            throw new IllegalArgumentException(
                    "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
        }
        if (fromIndex < 0) {
            throw new ArrayIndexOutOfBoundsException(
                    "'fromIndex' is negative: " + fromIndex);
        }
        if (toIndex > arrayLength) {
            throw new ArrayIndexOutOfBoundsException(
                    "'toIndex' is too large: " + toIndex + ", array length: " +
                    arrayLength);
        }
    }
    private static final class NaturalOrder implements Comparator<Object> {
        @SuppressWarnings("unchecked")
        @Override
        public int compare(Object first, Object second) {
            return ((Comparable<Object>) first).compareTo(second);
        }
        private static final NaturalOrder INSTANCE = new NaturalOrder();
    }
}
Demo.java:
package net.coderodde.util.sorting;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;
import static net.coderodde.util.sorting.HeapSelectionSort.sort;
public class Demo {
    public static void main(final String... args) {
        int size = 500000;
        long seed = System.currentTimeMillis();
        Random random = new Random(seed);
        Integer[] array1 = createRandomIntegerArray(size, random);
        Integer[] array2 = array1.clone();
        System.out.println("Seed: " + seed);
        int fromIndex = 2;
        int toIndex = size - 2;
        //// Random arrays.
        System.out.println("--- Random integer array ---");
        long ta = System.currentTimeMillis();
        Arrays.sort(array1, fromIndex, toIndex, Integer::compare);
        long tb = System.currentTimeMillis();
        System.out.println("java.util.Arrays.sort in " + (tb - ta) + 
                           " ms. Sorted: " + 
                           isSorted(array1, fromIndex, toIndex));
        ta = System.currentTimeMillis();
        sort(array2, fromIndex, toIndex, Integer::compare);
        tb = System.currentTimeMillis();
        System.out.println("Heap selection sort in " + (tb - ta) +
                           " ms. Sorted: " + 
                           isSorted(array2, fromIndex, toIndex));
        System.out.println("Array contents same: " + arraysEqual(array1,
                                                                 array2)); 
        // Presorted arrays.
        array1 = createPresortedArray(size, 23);
        array2 = array1.clone();
        System.out.println("--- Presorted array ---");
        ta = System.currentTimeMillis();
        Arrays.sort(array1, fromIndex, toIndex, Integer::compare);
        tb = System.currentTimeMillis();
        System.out.println("java.util.Arrays.sort in " + (tb - ta) + 
                           " ms. Sorted: " + 
                           isSorted(array1, fromIndex, toIndex));
        ta = System.currentTimeMillis();
        sort(array2, fromIndex, toIndex, Integer::compare);
        tb = System.currentTimeMillis();
        System.out.println("Heap selection sort in " + (tb - ta) +
                           " ms. Sorted: " + 
                           isSorted(array2, fromIndex, toIndex));
        System.out.println("Array contents same: " + arraysEqual(array1,
                                                                 array2));
    }
    private static Integer[] createRandomIntegerArray(int size, Random random) {
        Integer[] ret = new Integer[size];
        for (int i = 0; i < size; ++i) {
            ret[i] = random.nextInt(size / 2);
        }
        return ret;
    }
    private static Integer[] createPresortedArray(int size, int runs) {
        Integer[] ret = createRandomIntegerArray(size, new Random());
        int runLength = size / runs;
        for (int i = 0; i < runs; ++i) {
            Arrays.sort(ret, runLength * i, 
                        Math.min(size, runLength * (i + 1)));
        }
        return ret;
    }
    private static boolean isSorted(Integer[] array, 
                                    int fromIndex, 
                                    int toIndex) {
        for (int i = fromIndex; i < toIndex - 1; ++i) {
            if (array[i].compareTo(array[i + 1]) > 0) {
                return false;
            }
        }
        return true;
    }
    private static <T> boolean arraysEqual(T[] array1, T[] array2) {
        if (array1.length != array2.length) {
            return false;
        }
        for (int i = 0; i < array1.length; ++i) {
            if (!Objects.equals(array1[i], array2[i])) {
                return false;
            }
        }
        return true;
    }    
}
So, what do you think?
Have you seen this algorithm discussed in computer scientific literature?