提交 49d9d2ef authored 作者: Thomas Mueller's avatar Thomas Mueller

In-place stable sorting algorithms (just for fun).

上级 7bd0e66d
......@@ -139,6 +139,7 @@ import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestJmx;
import org.h2.test.unit.TestModifyOnWrite;
import org.h2.test.unit.TestObjectDeserialization;
import org.h2.test.unit.TestSort;
import org.h2.test.unit.TestTraceSystem;
import org.h2.test.unit.TestMathUtils;
import org.h2.test.unit.TestNetUtils;
......@@ -347,6 +348,8 @@ java org.h2.test.TestAll timer
int testWith_TCP_PROTOCOL_VERSION_10;
int todoSupportTrailingSemicolonsInDatabaseUrl;
// System.setProperty("h2.storeLocalTime", "true");
// speedup
......@@ -691,6 +694,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
runTest("org.h2.test.unit.TestServlet");
new TestSecurity().runTest(this);
new TestShell().runTest(this);
new TestSort().runTest(this);
new TestStreams().runTest(this);
new TestStringCache().runTest(this);
new TestStringUtils().runTest(this);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.h2.dev.sort.InPlaceStableMergeSort;
import org.h2.dev.sort.InPlaceStableQuicksort;
import org.h2.test.TestBase;
/**
* Tests the stable in-place sorting implementations.
*/
public class TestSort extends TestBase {
AtomicInteger compareCount = new AtomicInteger();
Comparator<Long> comp = new Comparator<Long>() {
public int compare(Long o1, Long o2) {
compareCount.incrementAndGet();
return Long.valueOf(o1 >> 32).compareTo(o2 >> 32);
}
};
private Long[] array = new Long[100000];
private Class<?> clazz;
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws Exception {
test(InPlaceStableMergeSort.class);
test(InPlaceStableQuicksort.class);
test(Arrays.class);
}
void test(Class<?> clazz) throws Exception {
this.clazz = clazz;
ordered(array);
shuffle(array);
stabalize(array);
test("random");
ordered(array);
stabalize(array);
test("ordered");
ordered(array);
reverse(array);
stabalize(array);
test("reverse");
ordered(array);
stretch(array);
shuffle(array);
stabalize(array);
test("few random");
ordered(array);
stretch(array);
stabalize(array);
test("few ordered");
ordered(array);
reverse(array);
stretch(array);
stabalize(array);
test("few reverse");
System.out.println();
}
/**
* Sort the array and verify the result.
*
* @param type the type of data
*/
private void test(String type) throws Exception {
compareCount.set(0);
// long t = System.currentTimeMillis();
clazz.getMethod("sort", Object[].class, Comparator.class).invoke(null, array, comp);
// System.out.printf(
// "%4d ms; %10d comparisons sorder: %s data: %s\n",
// (System.currentTimeMillis() - t),
// compareCount.get(), clazz, type);
verify(array);
}
private static void verify(Long[] array) {
long last = Long.MIN_VALUE;
int len = array.length;
for (int i = 0; i < len; i++) {
long x = array[i];
long x1 = x >> 32, x2 = x - (x1 << 32);
long last1 = last >> 32, last2 = last - (last1 << 32);
if (x1 < last1) {
if (array.length < 1000) {
System.out.println(Arrays.toString(array));
}
throw new RuntimeException("" + x);
} else if (x1 == last1 && x2 < last2) {
if (array.length < 1000) {
System.out.println(Arrays.toString(array));
}
throw new RuntimeException("" + x);
}
last = x;
}
}
private static void ordered(Long[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = (long) i;
}
}
private static void stretch(Long[] array) {
for (int i = array.length - 1; i >= 0; i--) {
array[i] = array[i / 4];
}
}
private static void reverse(Long[] array) {
for (int i = 0; i < array.length / 2; i++) {
long temp = array[i];
array[i] = array[array.length - i - 1];
array[array.length - i - 1] = temp;
}
}
private static void shuffle(Long[] array) {
Random r = new Random(1);
for (int i = 0; i < array.length; i++) {
long temp = array[i];
int j = r.nextInt(array.length);
array[j] = array[i];
array[i] = temp;
}
}
private static void stabalize(Long[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = (array[i] << 32) + i;
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.sort;
import java.util.Comparator;
/**
* A stable merge sort implementation that uses at most O(log(n)) memory
* and O(n*log(n)*log(n)) time.
*
* @param <T> the element type
*/
public class InPlaceStableMergeSort<T> {
/**
* The minimum size of the temporary array. It is used to speed up sorting
* small blocks.
*/
private static final int TEMP_SIZE = 1024;
/**
* Blocks smaller than this number are sorted using binary insertion sort.
* This usually speeds up sorting.
*/
private static final int INSERTION_SORT_SIZE = 16;
/**
* The data array to sort.
*/
private T[] data;
/**
* The comparator.
*/
private Comparator<T> comp;
/**
* The temporary array.
*/
private T[] temp;
/**
* Sort an array using the given comparator.
*
* @param data the data array to sort
* @param comp the comparator
*/
public static <T> void sort(T[] data, Comparator<T> comp) {
new InPlaceStableMergeSort<T>().sortArray(data, comp);
}
/**
* Sort an array using the given comparator.
*
* @param data the data array to sort
* @param comp the comparator
*/
public void sortArray(T[] data, Comparator<T> comp) {
this.data = data;
this.comp = comp;
int len = Math.max((int) (100 * Math.log(data.length)), TEMP_SIZE);
len = Math.min(data.length, len);
@SuppressWarnings("unchecked")
T[] t = (T[]) new Object[len];
this.temp = t;
mergeSort(0, data.length - 1);
}
/**
* Sort a block recursively using merge sort.
*
* @param from the index of the first entry to sort
* @param to the index of the last entry to sort
*/
void mergeSort(int from, int to) {
if (to - from < INSERTION_SORT_SIZE) {
binaryInsertionSort(from, to);
return;
}
int middle = (from + to) >>> 1;
mergeSort(from, middle);
mergeSort(middle + 1, to);
merge(from, middle + 1, to);
}
/**
* Sort a block using the binary insertion sort algorithm.
*
* @param from the index of the first entry to sort
* @param to the index of the last entry to sort
*/
private void binaryInsertionSort(int from, int to) {
for (int i = from + 1; i <= to; i++) {
T x = data[i];
int ins = binarySearch(x, from, i - 1);
for (int j = i - 1; j >= ins; j--) {
data[j + 1] = data[j];
}
data[ins] = x;
}
}
/**
* Find the index of the element that is larger than x.
*
* @param x the element to search
* @param from the index of the first entry
* @param to the index of the last entry
* @return the position
*/
private int binarySearch(T x, int from, int to) {
while (from <= to) {
int m = (from + to) >>> 1;
if (comp.compare(x, data[m]) >= 0) {
from = m + 1;
} else {
to = m - 1;
}
}
return from;
}
/**
* Merge two arrays.
*
* @param from the start of the first range
* @param second start of the second range
* @param to the last element of the second range
*/
private void merge(int from, int second, int to) {
int len1 = second - from, len2 = to - second + 1;
if (len1 == 0 || len2 == 0) {
return;
}
if (len1 + len2 == 2) {
if (comp.compare(data[second], data[from]) < 0) {
swap(data, second, from);
}
return;
}
if (len1 <= temp.length) {
System.arraycopy(data, from, temp, 0, len1);
mergeSmall(data, from, temp, 0, len1 - 1, data, second, to);
return;
} else if (len2 <= temp.length) {
System.arraycopy(data, second, temp, 0, len2);
System.arraycopy(data, from, data, to - len1 + 1, len1);
mergeSmall(data, from, data, to - len1 + 1, to, temp, 0, len2 - 1);
return;
}
mergeBig(from, second, to);
}
/**
* Merge two (large) arrays. This is done recursively by merging the
* beginning of both arrays, and then the end of both arrays.
*
* @param from the start of the first range
* @param second start of the second range
* @param to the last element of the second range
*/
private void mergeBig(int from, int second, int to) {
int len1 = second - from, len2 = to - second + 1;
int firstCut, secondCut, newMid;
if (len1 > len2) {
firstCut = from + len1 / 2;
secondCut = findLower(data[firstCut], second, to);
int len = secondCut - second;
newMid = firstCut + len;
} else {
int len = len2 / 2;
secondCut = second + len;
firstCut = findUpper(data[secondCut], from, second - 1);
newMid = firstCut + len;
}
swapBlocks(firstCut, second, secondCut - 1);
merge(from, firstCut, newMid - 1);
merge(newMid, secondCut, to);
}
/**
* Merge two (small) arrays using the temporary array. This is done to speed
* up merging.
*
* @param target the target array
* @param pos the position of the first element in the target array
* @param s1 the first source array
* @param from1 the index of the first element in the first source array
* @param to1 the index of the last element in the first source array
* @param s2 the second source array
* @param from2 the index of the first element in the second source array
* @param to2 the index of the last element in the second source array
*/
private void mergeSmall(T[] target, int pos, T[] s1, int from1, int to1, T[] s2, int from2, int to2) {
T x1 = s1[from1], x2 = s2[from2];
while (true) {
if (comp.compare(x1, x2) <= 0) {
target[pos++] = x1;
if (++from1 > to1) {
System.arraycopy(s2, from2, target, pos, to2 - from2 + 1);
break;
}
x1 = s1[from1];
} else {
target[pos++] = x2;
if (++from2 > to2) {
System.arraycopy(s1, from1, target, pos, to1 - from1 + 1);
break;
}
x2 = s2[from2];
}
}
}
/**
* Find the largest element in the sorted array that is smaller than x.
*
* @param x the element to search
* @param from the index of the first entry
* @param to the index of the last entry
* @return the index of the resulting element
*/
private int findLower(T x, int from, int to) {
int len = to - from + 1, half;
while (len > 0) {
half = len / 2;
int mid = from + half;
if (comp.compare(data[mid], x) < 0) {
from = mid + 1;
len = len - half - 1;
} else {
len = half;
}
}
return from;
}
/**
* Find the smallest element in the sorted array that is larger than or
* equal to x.
*
* @param x the element to search
* @param from the index of the first entry
* @param to the index of the last entry
* @return the index of the resulting element
*/
private int findUpper(T x, int from, int to) {
int len = to - from + 1, half;
while (len > 0) {
half = len / 2;
int mid = from + half;
if (comp.compare(data[mid], x) <= 0) {
from = mid + 1;
len = len - half - 1;
} else {
len = half;
}
}
return from;
}
/**
* Swap the elements of two blocks in the data array. Both blocks are next
* to each other (the second block starts just after the first block ends).
*
* @param from the index of the first element in the first block
* @param second the index of the first element in the second block
* @param to the index of the last element in the second block
*/
private void swapBlocks(int from, int second, int to) {
int len1 = second - from, len2 = to - second + 1;
if (len1 == 0 || len2 == 0) {
return;
}
if (len1 < temp.length) {
System.arraycopy(data, from, temp, 0, len1);
System.arraycopy(data, second, data, from, len2);
System.arraycopy(temp, 0, data, from + len2, len1);
return;
} else if (len2 < temp.length) {
System.arraycopy(data, second, temp, 0, len2);
System.arraycopy(data, from, data, from + len2, len1);
System.arraycopy(temp, 0, data, from, len2);
return;
}
reverseBlock(from, second - 1);
reverseBlock(second, to);
reverseBlock(from, to);
}
/**
* Reverse all elements in a block.
*
* @param from the index of the first element
* @param to the index of the last element
*/
private void reverseBlock(int from, int to) {
while (from < to) {
T old = data[from];
data[from++] = data[to];
data[to--] = old;
}
}
/**
* Swap two elements in the array.
*
* @param data the array
* @param a the index of the first element
* @param b the index of the second element
*/
private void swap(T[] data, int a, int b) {
T temp = data[a];
data[a] = data[b];
data[b] = temp;
}
}
\ No newline at end of file
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.sort;
import java.util.Comparator;
/**
* A stable quicksort implementation that uses O(log(n)) memory. It normally
* runs in O(n*log(n)*log(n)), but at most in O(n^2).
*
* @param <T> the element type
*/
public class InPlaceStableQuicksort<T> {
/**
* The minimum size of the temporary array. It is used to speed up sorting
* small blocks.
*/
private static final int TEMP_SIZE = 1024;
/**
* Blocks smaller than this number are sorted using binary insertion sort.
* This usually speeds up sorting.
*/
private static final int INSERTION_SORT_SIZE = 16;
/**
* The data array to sort.
*/
private T[] data;
/**
* The comparator.
*/
private Comparator<T> comp;
/**
* The temporary array.
*/
private T[] temp;
/**
* Sort an array using the given comparator.
*
* @param data the data array to sort
* @param comp the comparator
*/
public static <T> void sort(T[] data, Comparator<T> comp) {
new InPlaceStableQuicksort<T>().sortArray(data, comp);
}
/**
* Sort an array using the given comparator.
*
* @param data the data array to sort
* @param comp the comparator
*/
public void sortArray(T[] data, Comparator<T> comp) {
this.data = data;
this.comp = comp;
int len = Math.max((int) (100 * Math.log(data.length)), TEMP_SIZE);
len = Math.min(data.length, len);
@SuppressWarnings("unchecked")
T[] t = (T[]) new Object[len];
this.temp = t;
quicksort(0, data.length - 1);
}
/**
* Sort a block using the quicksort algorithm.
*
* @param from the index of the first entry to sort
* @param to the index of the last entry to sort
*/
private void quicksort(int from, int to) {
while (to > from) {
if (to - from < INSERTION_SORT_SIZE) {
binaryInsertionSort(from, to);
return;
}
T pivot = selectPivot(from, to);
int second = partition(pivot, from, to);
if (second > to) {
pivot = selectPivot(from, to);
pivot = data[to];
second = partition(pivot, from, to);
if (second > to) {
second--;
}
}
quicksort(from, second - 1);
from = second;
}
}
/**
* Sort a block using the binary insertion sort algorithm.
*
* @param from the index of the first entry to sort
* @param to the index of the last entry to sort
*/
private void binaryInsertionSort(int from, int to) {
for (int i = from + 1; i <= to; i++) {
T x = data[i];
int ins = binarySearch(x, from, i - 1);
for (int j = i - 1; j >= ins; j--) {
data[j + 1] = data[j];
}
data[ins] = x;
}
}
/**
* Find the index of the element that is larger than x.
*
* @param x the element to search
* @param from the index of the first entry
* @param to the index of the last entry
* @return the position
*/
private int binarySearch(T x, int from, int to) {
while (from <= to) {
int m = (from + to) >>> 1;
if (comp.compare(x, data[m]) >= 0) {
from = m + 1;
} else {
to = m - 1;
}
}
return from;
}
/**
* Move all elements that are bigger than the pivot to the end of the list,
* and return the partitioning index. The partitioning index is the start
* index of the range where all elements are larger than the pivot. If the
* partitioning index is larger than the 'to' index, then all elements are
* smaller or equal to the pivot.
*
* @param pivot the pivot
* @param from the index of the first element
* @param to the index of the last element
* @return the the first element of the second partition
*/
private int partition(T pivot, int from, int to) {
if (to - from < temp.length) {
return partitionSmall(pivot, from, to);
}
int m = (from + to + 1) / 2;
int m1 = partition(pivot, from, m - 1);
int m2 = partition(pivot, m, to);
swapBlocks(m1, m, m2 - 1);
return m1 + m2 - m;
}
/**
* Partition a small block using the temporary array. This will speed up partitioning.
*
* @param pivot the pivot
* @param from the index of the first element
* @param to the index of the last element
* @return the the first element of the second partition
*/
private int partitionSmall(T pivot, int from, int to) {
int tempIndex = 0, dataIndex = from;
for (int i = from; i <= to; i++) {
T x = data[i];
if (comp.compare(x, pivot) <= 0) {
if (tempIndex > 0) {
data[dataIndex] = x;
}
dataIndex++;
} else {
temp[tempIndex++] = x;
}
}
if (tempIndex > 0) {
System.arraycopy(temp, 0, data, dataIndex, tempIndex);
}
return dataIndex;
}
/**
* Swap the elements of two blocks in the data array. Both blocks are next
* to each other (the second block starts just after the first block ends).
*
* @param from the index of the first element in the first block
* @param second the index of the first element in the second block
* @param to the index of the last element in the second block
*/
private void swapBlocks(int from, int second, int to) {
int len1 = second - from, len2 = to - second + 1;
if (len1 == 0 || len2 == 0) {
return;
}
if (len1 < temp.length) {
System.arraycopy(data, from, temp, 0, len1);
System.arraycopy(data, second, data, from, len2);
System.arraycopy(temp, 0, data, from + len2, len1);
return;
} else if (len2 < temp.length) {
System.arraycopy(data, second, temp, 0, len2);
System.arraycopy(data, from, data, from + len2, len1);
System.arraycopy(temp, 0, data, from, len2);
return;
}
reverseBlock(from, second - 1);
reverseBlock(second, to);
reverseBlock(from, to);
}
/**
* Reverse all elements in a block.
*
* @param from the index of the first element
* @param to the index of the last element
*/
private void reverseBlock(int from, int to) {
while (from < to) {
T old = data[from];
data[from++] = data[to];
data[to--] = old;
}
}
/**
* Select a pivot. To ensure a good pivot is select, the median element of a
* sample of the data is calculated.
*
* @param from the index of the first element
* @param to the index of the last element
* @return the pivot
*/
private T selectPivot(int from, int to) {
int count = (int) (6 * Math.log10(to - from));
count = Math.min(count, temp.length);
int step = (to - from) / count;
for (int i = from, j = 0; i < to; i += step, j++) {
temp[j] = data[i];
}
T pivot = select(temp, 0, count - 1, count / 2);
return pivot;
}
/**
* Select the kth smallest element.
*
* @param data the array
* @param from the index of the first element
* @param to the index of the last element
* @param k which element to return
* @return the kth smallest element
*/
private T select(T[] data, int from, int to, int k) {
while (true) {
int pivotIndex = (to + from) >>> 1;
int pivotNewIndex = selectPartition(data, from, to, pivotIndex);
int pivotDist = pivotNewIndex - from + 1;
if (pivotDist == k) {
return data[pivotNewIndex];
} else if (k < pivotDist) {
to = pivotNewIndex - 1;
} else {
k = k - pivotDist;
from = pivotNewIndex + 1;
}
}
}
/**
* Partition the elements to select the kth element.
*
* @param data the array
* @param from the index of the first element
* @param to the index of the last element
* @param pivotIndex the index of the pivot
* @return the new index
*/
private int selectPartition(T[] data, int from, int to, int pivotIndex) {
T pivotValue = data[pivotIndex];
swap(data, pivotIndex, to);
int storeIndex = from;
for (int i = from; i <= to; i++) {
if (comp.compare(data[i], pivotValue) < 0) {
swap(data, storeIndex, i);
storeIndex++;
}
}
swap(data, to, storeIndex);
return storeIndex;
}
/**
* Swap two elements in the array.
*
* @param data the array
* @param a the index of the first element
* @param b the index of the second element
*/
private void swap(T[] data, int a, int b) {
T temp = data[a];
data[a] = data[b];
data[b] = temp;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论