4-1 为什么使用堆
普通队列:先进先;后进后出
优先队列:出队顺序和入队顺序无关;和优先级相关
任务处理中心(动态)
先排序在依次处理不可行
在1000000个元素中选出前100名?
在N个元素中选出前M个元素
排序?NlogN
使用优先队列?NlogM
入队
出队(取出优先级最高的元素)
入队 | 出队 |
---|---|
普通数组 | O(1) |
顺序数组 | O(n) |
堆 | O(lgn) |
对于总共有N个请求:
使用普通数组或者顺序数组,最差情况:O(n^2)
使用堆:O(nlgn)
4-2 堆的基本存储
二叉堆 Binary Heap
二叉堆 是一棵 完全二叉树
堆中某个节点的值总是不大于其父节点的值;
堆总是一棵完全二叉树。(最大堆)
用数组存储二叉堆
public class MaxHeap<Item> {
private Item[] data;
private int count;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeap(int capacity){
data = (Item[])new Object[capacity+1];
count = 0;
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 测试 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
System.out.println(maxHeap.size());
}
}
4-3 Shift Up
// 在堆的有关操作中,需要比较堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最大堆核心辅助函数
//********************
private void shiftUp(int k){
while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
swap(k, k/2);
k /= 2;
}
}
// 测试 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
int N = 50; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
System.out.println(maxHeap.size());
}
}
// 我们的PrintableMaxHeap只能处理整数信息,所以继承的是MaxHeap<Comparable<Integer>>
public class PrintableMaxHeap extends MaxHeap<Comparable<Integer>>{
public PrintableMaxHeap(int capacity){
super(capacity);
}
// 以树状打印整个堆结构
public void treePrint(){
if( size() >= 100 ){
System.out.println("This print function can only work for less than 100 integer");
return;
}
System.out.println("The max heap size is: " + size());
System.out.println("Data in the max heap: ");
for( int i = 1 ; i <= size() ; i ++ ){
// 我们的print函数要求堆中的所有整数在[0, 100)的范围内
assert (Integer)data[i] >= 0 && (Integer)data[i] < 100;
System.out.print(data[i] + " ");
}
System.out.println();
System.out.println();
int n = size();
int maxLevel = 0;
int numberPerLevel = 1;
while( n > 0 ){
maxLevel += 1;
n -= numberPerLevel;
numberPerLevel *= 2;
}
int maxLevelNumber = (int)Math.pow(2, maxLevel-1);
int curTreeMaxLevelNumber = maxLevelNumber;
int index = 1;
for( int level = 0 ; level < maxLevel ; level ++ ){
String line1 = new String(new char[maxLevelNumber*3-1]).replace('\0', ' ');
int curLevelNumber = Math.min(count-(int)Math.pow(2,level)+1,(int)Math.pow(2,level));
boolean isLeft = true;
for( int indexCurLevel = 0 ; indexCurLevel < curLevelNumber ; index ++ , indexCurLevel ++ ){
line1 = putNumberInLine( (Integer)data[index] , line1 , indexCurLevel , curTreeMaxLevelNumber*3-1 , isLeft );
isLeft = !isLeft;
}
System.out.println(line1);
if( level == maxLevel - 1 )
break;
String line2 = new String(new char[maxLevelNumber*3-1]).replace('\0', ' ');
for( int indexCurLevel = 0 ; indexCurLevel < curLevelNumber ; indexCurLevel ++ )
line2 = putBranchInLine( line2 , indexCurLevel , curTreeMaxLevelNumber*3-1 );
System.out.println(line2);
curTreeMaxLevelNumber /= 2;
}
}
private String putNumberInLine( Integer num, String line, int indexCurLevel, int curTreeWidth, boolean isLeft){
int subTreeWidth = (curTreeWidth - 1) / 2;
int offset = indexCurLevel * (curTreeWidth+1) + subTreeWidth;
assert offset + 1 < line.length();
if( num >= 10 )
line = line.substring(0, offset+0) + num.toString()
+ line.substring(offset+2);
else{
if( isLeft)
line = line.substring(0, offset+0) + num.toString()
+ line.substring(offset+1);
else
line = line.substring(0, offset+1) + num.toString()
+ line.substring(offset+2);
}
return line;
}
private String putBranchInLine( String line, int indexCurLevel, int curTreeWidth){
int subTreeWidth = (curTreeWidth - 1) / 2;
int subSubTreeWidth = (subTreeWidth - 1) / 2;
int offsetLeft = indexCurLevel * (curTreeWidth+1) + subSubTreeWidth;
assert offsetLeft + 1 < line.length();
int offsetRight = indexCurLevel * (curTreeWidth+1) + subTreeWidth + 1 + subSubTreeWidth;
assert offsetRight < line.length();
line = line.substring(0, offsetLeft+1) + "/" + line.substring(offsetLeft+2);
line = line.substring(0, offsetRight) + "\\" + line.substring(offsetRight+1);
return line;
}
// 测试 PrintableMaxHeap
public static void main(String[] args) {
PrintableMaxHeap maxHeap = new PrintableMaxHeap(100);
int N = 31; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
maxHeap.treePrint();
}
}
4-4 Shift Down
// 在堆的有关操作中,需要比较堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
public Item getMax(){
assert( count > 0 );
return data[1];
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最大堆核心辅助函数
//********************
private void shiftUp(int k){
while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
swap(k, k/2);
k /= 2;
}
}
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k].compareTo(data[j]) >= 0 ) break;
swap(k, j);
k = j;
}
}
// 测试 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将maxheap中的数据逐渐使用extractMax取出来
// 取出来的顺序应该是按照从大到小的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从大到小排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] >= arr[i];
}
}
4-5 基础堆排序和Heapify
public class HeapSort1 {
// 我们的算法类不允许产生任何实例
private HeapSort1(){}
// 对整个arr数组使用HeapSort1排序
// HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
// 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)
// 整个堆排序的整体时间复杂度为O(nlogn)
public static void sort(Comparable[] arr){
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
maxHeap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 测试 HeapSort1
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr);
return;
}
}
public class HeapSort2 {
// 我们的算法类不允许产生任何实例
private HeapSort2(){}
// 对整个arr数组使用HeapSort2排序
// HeapSort2, 借助我们的heapify过程创建堆
// 此时, 创建堆的过程时间复杂度为O(n), 将所有元素依次从堆中取出来, 实践复杂度为O(nlogn)
// 堆排序的总体时间复杂度依然是O(nlogn), 但是比HeapSort1性能更优, 因为创建堆的性能更优
public static void sort(Comparable[] arr){
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(arr);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 测试 HeapSort2
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr);
return;
}
}
public class InsertionSort{
// 我们的算法类不允许产生任何实例
private InsertionSort(){}
// 对整个arr数组使用InsertionSort排序
public static void sort(Comparable[] arr){
int n = arr.length;
for (int i = 0; i < n; i++) {
Comparable e = arr[i];
int j = i;
for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
}
// 对arr[l...r]的区间使用InsertionSort排序
public static void sort(Comparable[] arr, int l, int r){
assert l >= 0 && l <= r && r < arr.length;
for( int i = l + 1 ; i <= r ; i ++ ){
Comparable e = arr[i];
int j = i;
for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试InsertionSort
public static void main(String[] args) {
int N = 10000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.InsertionSort", arr);
return;
}
}
public class Main {
/// 比较 Merge Sort, 三种 Quick Sort 和本节介绍的两种 Heap Sort 的性能效率
// 注意, 这几种排序算法都是 O(nlogn) 级别的排序算法
public static void main(String[] args) {
int N = 1000000;
// 测试1 一般性测试
System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");
Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr4 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr5 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr6 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr1);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr2);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr3);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr4);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr5);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr6);
System.out.println();
// 测试2 测试近乎有序的数组
int swapTimes = 100;
assert swapTimes >= 0;
System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);
arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
arr2 = Arrays.copyOf(arr1, arr1.length);
arr3 = Arrays.copyOf(arr1, arr1.length);
arr4 = Arrays.copyOf(arr1, arr1.length);
arr5 = Arrays.copyOf(arr1, arr1.length);
arr6 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr1);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr2);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr3);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr4);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr5);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr6);
System.out.println();
// 测试3 测试存在包含大量相同元素的数组
System.out.println("Test for random array, size = " + N + " , random range [0,10]");
arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
arr2 = Arrays.copyOf(arr1, arr1.length);
arr3 = Arrays.copyOf(arr1, arr1.length);
arr4 = Arrays.copyOf(arr1, arr1.length);
arr5 = Arrays.copyOf(arr1, arr1.length);
arr6 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr1);
// 这种情况下, 普通的QuickSort退化为O(n^2)的算法, 不做测试
//SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr2);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr3);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr4);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr5);
SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr6);
return;
}
}
// 在堆的有关操作中,需要比较堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 构造函数, 通过一个给定数组创建一个最大堆
// 该构造堆的过程, 时间复杂度为O(n)
public MaxHeap(Item arr[]){
int n = arr.length;
data = (Item[])new Comparable[n+1];
capacity = n;
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for( int i = count/2 ; i >= 1 ; i -- )
shiftDown(i);
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
public Item getMax(){
assert( count > 0 );
return data[1];
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最大堆核心辅助函数
//********************
private void shiftUp(int k){
while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
swap(k, k/2);
k /= 2;
}
}
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k].compareTo(data[j]) >= 0 ) break;
swap(k, j);
k = j;
}
}
// 测试 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将maxheap中的数据逐渐使用extractMax取出来
// 取出来的顺序应该是按照从大到小的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从大到小排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] >= arr[i];
}
}
// 自顶向下的归并排序, 含优化
public class MergeSort{
// 我们的算法类不允许产生任何实例
private MergeSort(){}
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
// 其中aux为完成merge过程所需要的辅助空间
private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {
System.arraycopy(arr, l, aux, l, r-l+1);
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int i = l, j = mid+1;
for( int k = l ; k <= r; k ++ ){
if( i > mid ){ // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j]; j ++;
}
else if( j > r ){ // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i]; i ++;
}
else if( aux[i].compareTo(aux[j]) < 0 ){ // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i]; i ++;
}
else{ // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j]; j ++;
}
}
}
// 递归使用归并排序,对arr[l...r]的范围进行排序
// 其中aux为完成merge过程所需要的辅助空间
private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
int mid = (l+r)/2;
sort(arr, aux, l, mid);
sort(arr, aux, mid + 1, r);
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge
// 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
if( arr[mid].compareTo(arr[mid+1]) > 0 )
merge(arr, aux, l, mid, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
Comparable[] aux = new Comparable[n];
sort(arr, aux, 0, n-1);
}
// 测试 MergeSort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr);
return;
}
}
public class QuickSort {
// 我们的算法类不允许产生任何实例
private QuickSort(){}
// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
private static int partition(Comparable[] arr, int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
Comparable v = arr[l];
int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
for( int i = l + 1 ; i <= r ; i ++ )
if( arr[i].compareTo(v) < 0 ){
j ++;
swap(arr, j, i);
}
swap(arr, l, j);
return j;
}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
int p = partition(arr, l, r);
sort(arr, l, p-1 );
sort(arr, p+1, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试 QuickSort
public static void main(String[] args) {
// Quick Sort也是一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr);
return;
}
}
public class QuickSort2Ways {
// 我们的算法类不允许产生任何实例
private QuickSort2Ways(){}
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
private static int partition(Comparable[] arr, int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
Comparable v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l+1, j = r;
while( true ){
// 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
// 思考一下为什么?
while( i <= r && arr[i].compareTo(v) < 0 )
i ++;
// 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
// 思考一下为什么?
while( j >= l+1 && arr[j].compareTo(v) > 0 )
j --;
// 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)
// 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html
if( i > j )
break;
swap( arr, i, j );
i ++;
j --;
}
swap(arr, l, j);
return j;
}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
int p = partition(arr, l, r);
sort(arr, l, p-1 );
sort(arr, p+1, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试 Quick Sort 2 Ways
public static void main(String[] args) {
// 双路快速排序算法也是一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr);
return;
}
}
public class QuickSort3Ways {
// 我们的算法类不允许产生任何实例
private QuickSort3Ways(){}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l, (int)(Math.random()*(r-l+1)) + l );
Comparable v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while( i < gt ){
if( arr[i].compareTo(v) < 0 ){
swap( arr, i, lt+1);
i ++;
lt ++;
}
else if( arr[i].compareTo(v) > 0 ){
swap( arr, i, gt-1);
gt --;
}
else{ // arr[i] == v
i ++;
}
}
swap( arr, l, lt );
sort(arr, l, lt-1);
sort(arr, gt, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试 QuickSort3Ways
public static void main(String[] args) {
// 三路快速排序算法也是一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr);
return;
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}
4-6 优化的堆排序
// 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序
public class HeapSort {
// 我们的算法类不允许产生任何实例
private HeapSort(){}
public static void sort(Comparable[] arr){
int n = arr.length;
// 注意,此时我们的堆是从0开始索引的
// 从(最后一个元素的索引-1)/2开始
// 最后一个元素的索引 = n-1
for( int i = (n-1-1)/2 ; i >= 0 ; i -- )
shiftDown2(arr, n, i);
for( int i = n-1; i > 0 ; i-- ){
swap( arr, 0, i);
shiftDown2(arr, i, 0);
}
}
// 交换堆中索引为i和j的两个元素
private static void swap(Object[] arr, int i, int j){
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 原始的shiftDown过程
private static void shiftDown(Comparable[] arr, int n, int k){
while( 2*k+1 < n ){
int j = 2*k+1;
if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )
j += 1;
if( arr[k].compareTo(arr[j]) >= 0 )break;
swap( arr, k, j);
k = j;
}
}
// 优化的shiftDown过程, 使用赋值的方式取代不断的swap,
// 该优化思想和我们之前对插入排序进行优化的思路是一致的
private static void shiftDown2(Comparable[] arr, int n, int k){
Comparable e = arr[k];
while( 2*k+1 < n ){
int j = 2*k+1;
if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )
j += 1;
if( e.compareTo(arr[j]) >= 0 )
break;
arr[k] = arr[j];
k = j;
}
arr[k] = e;
}
// 测试 HeapSort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr);
return;
}
}
public class HeapSort1 {
// 我们的算法类不允许产生任何实例
private HeapSort1(){}
// 对整个arr数组使用HeapSort1排序
// HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
// 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)
// 整个堆排序的整体时间复杂度为O(nlogn)
public static void sort(Comparable[] arr){
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
maxHeap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 测试 HeapSort1
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr);
return;
}
}
public class HeapSort2 {
// 我们的算法类不允许产生任何实例
private HeapSort2(){}
// 对整个arr数组使用HeapSort2排序
// HeapSort2, 借助我们的heapify过程创建堆
// 此时, 创建堆的过程时间复杂度为O(n), 将所有元素依次从堆中取出来, 实践复杂度为O(nlogn)
// 堆排序的总体时间复杂度依然是O(nlogn), 但是比HeapSort1性能更优, 因为创建堆的性能更优
public static void sort(Comparable[] arr){
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(arr);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 测试 HeapSort2
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr);
return;
}
}
public class InsertionSort{
// 我们的算法类不允许产生任何实例
private InsertionSort(){}
// 对整个arr数组使用InsertionSort排序
public static void sort(Comparable[] arr){
int n = arr.length;
for (int i = 0; i < n; i++) {
Comparable e = arr[i];
int j = i;
for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
}
// 对arr[l...r]的区间使用InsertionSort排序
public static void sort(Comparable[] arr, int l, int r){
assert l >= 0 && l <= r && r < arr.length;
for( int i = l + 1 ; i <= r ; i ++ ){
Comparable e = arr[i];
int j = i;
for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试InsertionSort
public static void main(String[] args) {
int N = 10000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.InsertionSort", arr);
return;
}
}
public class Main {
// 比较 Merge Sort, 三种 Quick Sort 和三种 Heap Sort 的性能效率
// 注意, 这几种排序算法都是 O(nlogn) 级别的排序算法
public static void main(String[] args) {
int N = 1000000;
// 测试1 一般性测试
System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");
Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr4 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr5 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr6 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr7 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr1);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr2);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr3);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr4);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr5);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr6);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr7);
System.out.println();
// 测试2 测试近乎有序的数组
int swapTimes = 100;
assert swapTimes >= 0;
System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);
arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
arr2 = Arrays.copyOf(arr1, arr1.length);
arr3 = Arrays.copyOf(arr1, arr1.length);
arr4 = Arrays.copyOf(arr1, arr1.length);
arr5 = Arrays.copyOf(arr1, arr1.length);
arr6 = Arrays.copyOf(arr1, arr1.length);
arr7 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr1);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr2);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr3);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr4);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr5);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr6);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr7);
System.out.println();
// 测试3 测试存在包含大量相同元素的数组
System.out.println("Test for random array, size = " + N + " , random range [0,10]");
arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
arr2 = Arrays.copyOf(arr1, arr1.length);
arr3 = Arrays.copyOf(arr1, arr1.length);
arr4 = Arrays.copyOf(arr1, arr1.length);
arr5 = Arrays.copyOf(arr1, arr1.length);
arr6 = Arrays.copyOf(arr1, arr1.length);
arr7 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr1);
// 这种情况下, 普通的QuickSort退化为O(n^2)的算法, 不做测试
//SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr2);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr3);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr4);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr5);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr6);
SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr7);
return;
}
}
// 在堆的有关操作中,需要比较堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 构造函数, 通过一个给定数组创建一个最大堆
// 该构造堆的过程, 时间复杂度为O(n)
public MaxHeap(Item arr[]){
int n = arr.length;
data = (Item[])new Comparable[n+1];
capacity = n;
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for( int i = count/2 ; i >= 1 ; i -- )
shiftDown(i);
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
public Item getMax(){
assert( count > 0 );
return data[1];
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最大堆核心辅助函数
//********************
private void shiftUp(int k){
while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
swap(k, k/2);
k /= 2;
}
}
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k].compareTo(data[j]) >= 0 ) break;
swap(k, j);
k = j;
}
}
// 测试 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将maxheap中的数据逐渐使用extractMax取出来
// 取出来的顺序应该是按照从大到小的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从大到小排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] >= arr[i];
}
}
// 自顶向下的归并排序, 含优化
public class MergeSort{
// 我们的算法类不允许产生任何实例
private MergeSort(){}
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
// 其中aux为完成merge过程所需要的辅助空间
private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {
System.arraycopy(arr, l, aux, l, r-l+1);
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int i = l, j = mid+1;
for( int k = l ; k <= r; k ++ ){
if( i > mid ){ // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j]; j ++;
}
else if( j > r ){ // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i]; i ++;
}
else if( aux[i].compareTo(aux[j]) < 0 ){ // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i]; i ++;
}
else{ // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j]; j ++;
}
}
}
// 递归使用归并排序,对arr[l...r]的范围进行排序
// 其中aux为完成merge过程所需要的辅助空间
private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
int mid = (l+r)/2;
sort(arr, aux, l, mid);
sort(arr, aux, mid + 1, r);
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge
// 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
if( arr[mid].compareTo(arr[mid+1]) > 0 )
merge(arr, aux, l, mid, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
Comparable[] aux = new Comparable[n];
sort(arr, aux, 0, n-1);
}
// 测试 MergeSort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr);
return;
}
}
public class QuickSort {
// 我们的算法类不允许产生任何实例
private QuickSort(){}
// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
private static int partition(Comparable[] arr, int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
Comparable v = arr[l];
int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
for( int i = l + 1 ; i <= r ; i ++ )
if( arr[i].compareTo(v) < 0 ){
j ++;
swap(arr, j, i);
}
swap(arr, l, j);
return j;
}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
int p = partition(arr, l, r);
sort(arr, l, p-1 );
sort(arr, p+1, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试 QuickSort
public static void main(String[] args) {
// Quick Sort也是一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr);
return;
}
}
public class QuickSort2Ways {
// 我们的算法类不允许产生任何实例
private QuickSort2Ways(){}
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
private static int partition(Comparable[] arr, int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
Comparable v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l+1, j = r;
while( true ){
// 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
// 思考一下为什么?
while( i <= r && arr[i].compareTo(v) < 0 )
i ++;
// 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
// 思考一下为什么?
while( j >= l+1 && arr[j].compareTo(v) > 0 )
j --;
// 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)
// 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html
if( i > j )
break;
swap( arr, i, j );
i ++;
j --;
}
swap(arr, l, j);
return j;
}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
int p = partition(arr, l, r);
sort(arr, l, p-1 );
sort(arr, p+1, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试 Quick Sort 2 Ways
public static void main(String[] args) {
// 双路快速排序算法也是一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr);
return;
}
}
public class QuickSort3Ways {
// 我们的算法类不允许产生任何实例
private QuickSort3Ways(){}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l, (int)(Math.random()*(r-l+1)) + l );
Comparable v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while( i < gt ){
if( arr[i].compareTo(v) < 0 ){
swap( arr, i, lt+1);
i ++;
lt ++;
}
else if( arr[i].compareTo(v) > 0 ){
swap( arr, i, gt-1);
gt --;
}
else{ // arr[i] == v
i ++;
}
}
swap( arr, l, lt );
sort(arr, l, lt-1);
sort(arr, gt, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 测试 QuickSort3Ways
public static void main(String[] args) {
// 三路快速排序算法也是一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr);
return;
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}
4-7 排序算法总结
平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 |
---|---|---|---|
插入排序 Insertion Sort | O(n^2) | √ | O(1) |
归并排序 Merge Sort | O(nlogn) | x | O(n) |
快速排序 Quick Sort | O(nlogn) | √ | O(logn) |
堆排序 Heap Sort | O(nlogn) | √ | O(1) |
稳定排序:对于相等元素,在排序后,原来靠前的元素依然靠前。
相等元素的相对位置没有发生改变。
可以通过自定义比较函数,让排序算法不存在稳定性的问题。
4-8 索引堆
// 使用最大索引堆进行堆排序, 来验证我们的最大索引堆的正确性
// 最大索引堆的主要作用不是用于排序, 我们在这里使用排序只是为了验证我们的最大索引堆实现的正确性
// 在后续的图论中, 无论是最小生成树算法, 还是最短路径算法, 我们都需要使用索引堆进行优化:)
public class IndexHeapSort {
// 我们的算法类不允许产生任何实例
private IndexHeapSort(){}
public static void sort(Comparable[] arr){
int n = arr.length;
IndexMaxHeap<Comparable> indexMaxHeap = new IndexMaxHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
indexMaxHeap.insert( i , arr[i] );
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = indexMaxHeap.extractMax();
}
// 测试 Index Heap Sort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_8.IndexHeapSort", arr);
return;
}
}
// 最大索引堆
public class IndexMaxHeap<Item extends Comparable> {
protected Item[] data; // 最大索引堆中的数据
protected int[] indexes; // 最大索引堆中的索引
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public IndexMaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
indexes = new int[capacity+1];
count = 0;
this.capacity = capacity;
}
// 返回索引堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示索引堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
// 传入的i对用户而言,是从0索引的
public void insert(int i, Item item){
assert count + 1 <= capacity;
assert i + 1 >= 1 && i + 1 <= capacity;
i += 1;
data[i] = item;
indexes[count+1] = i;
count ++;
shiftUp(count);
}
// 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[indexes[1]];
swapIndexes( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 从最大索引堆中取出堆顶元素的索引
public int extractMaxIndex(){
assert count > 0;
int ret = indexes[1] - 1;
swapIndexes( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最大索引堆中的堆顶元素
public Item getMax(){
assert count > 0;
return data[indexes[1]];
}
// 获取最大索引堆中的堆顶元素的索引
public int getMaxIndex(){
assert count > 0;
return indexes[1]-1;
}
// 获取最大索引堆中索引为i的元素
public Item getItem( int i ){
assert i + 1 >= 1 && i + 1 <= capacity;
return data[i+1];
}
// 将最大索引堆中索引为i的元素修改为newItem
public void change( int i , Item newItem ){
i += 1;
data[i] = newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
// 之后shiftUp(j), 再shiftDown(j)
for( int j = 1 ; j <= count ; j ++ )
if( indexes[j] == i ){
shiftUp(j);
shiftDown(j);
return;
}
}
// 交换索引堆中的索引i和j
private void swapIndexes(int i, int j){
int t = indexes[i];
indexes[i] = indexes[j];
indexes[j] = t;
}
//********************
//* 最大索引堆核心辅助函数
//********************
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shiftUp(int k){
while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0 ){
swapIndexes(k, k/2);
k /= 2;
}
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k;
if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0 )
j ++;
if( data[indexes[k]].compareTo(data[indexes[j]]) >= 0 )
break;
swapIndexes(k, j);
k = j;
}
}
// 测试索引堆中的索引数组index
// 注意:这个测试在向堆中插入元素以后, 不进行extract操作有效
public boolean testIndexes(){
int[] copyIndexes = new int[count+1];
for( int i = 0 ; i <= count ; i ++ )
copyIndexes[i] = indexes[i];
copyIndexes[0] = 0;
Arrays.sort(copyIndexes);
// 在对索引堆中的索引进行排序后, 应该正好是1...count这count个索引
boolean res = true;
for( int i = 1 ; i <= count ; i ++ )
if( copyIndexes[i-1] + 1 != copyIndexes[i] ){
res = false;
break;
}
if( !res ){
System.out.println("Error!");
return false;
}
return true;
}
// 测试 IndexMaxHeap
public static void main(String[] args) {
int N = 1000000;
IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<Integer>(N);
for( int i = 0 ; i < N ; i ++ )
indexMaxHeap.insert( i , (int)(Math.random()*N) );
assert indexMaxHeap.testIndexes();
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}
4-9 索引堆的优化
// 使用最大索引堆进行堆排序, 来验证我们的最大索引堆的正确性
// 最大索引堆的主要作用不是用于排序, 我们在这里使用排序只是为了验证我们的最大索引堆实现的正确性
// 在后续的图论中, 无论是最小生成树算法, 还是最短路径算法, 我们都需要使用索引堆进行优化:)
public class IndexHeapSort {
// 我们的算法类不允许产生任何实例
private IndexHeapSort(){}
public static void sort(Comparable[] arr){
int n = arr.length;
IndexMaxHeap<Comparable> indexMaxHeap = new IndexMaxHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
indexMaxHeap.insert( i , arr[i] );
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = indexMaxHeap.extractMax();
}
// 测试 Index Heap Sort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_9.IndexHeapSort", arr);
return;
}
}
// 最大索引堆
public class IndexMaxHeap<Item extends Comparable> {
protected Item[] data; // 最大索引堆中的数据
protected int[] indexes; // 最大索引堆中的索引, indexes[x] = i 表示索引i在x的位置
protected int[] reverse; // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public IndexMaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
indexes = new int[capacity+1];
reverse = new int[capacity+1];
for( int i = 0 ; i <= capacity ; i ++ )
reverse[i] = 0;
count = 0;
this.capacity = capacity;
}
// 返回索引堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示索引堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
// 传入的i对用户而言,是从0索引的
public void insert(int i, Item item){
assert count + 1 <= capacity;
assert i + 1 >= 1 && i + 1 <= capacity;
// 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。
assert !contain(i);
i += 1;
data[i] = item;
indexes[count+1] = i;
reverse[i] = count + 1;
count ++;
shiftUp(count);
}
// 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[indexes[1]];
swapIndexes( 1 , count );
reverse[indexes[count]] = 0;
count --;
shiftDown(1);
return ret;
}
// 从最大索引堆中取出堆顶元素的索引
public int extractMaxIndex(){
assert count > 0;
int ret = indexes[1] - 1;
swapIndexes( 1 , count );
reverse[indexes[count]] = 0;
count --;
shiftDown(1);
return ret;
}
// 获取最大索引堆中的堆顶元素
public Item getMax(){
assert count > 0;
return data[indexes[1]];
}
// 获取最大索引堆中的堆顶元素的索引
public int getMaxIndex(){
assert count > 0;
return indexes[1]-1;
}
// 看索引i所在的位置是否存在元素
boolean contain( int i ){
assert i + 1 >= 1 && i + 1 <= capacity;
return reverse[i+1] != 0;
}
// 获取最大索引堆中索引为i的元素
public Item getItem( int i ){
assert contain(i);
return data[i+1];
}
// 将最大索引堆中索引为i的元素修改为newItem
public void change( int i , Item newItem ){
assert contain(i);
i += 1;
data[i] = newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
// 之后shiftUp(j), 再shiftDown(j)
// for( int j = 1 ; j <= count ; j ++ )
// if( indexes[j] == i ){
// shiftUp(j);
// shiftDown(j);
// return;
// }
// 有了 reverse 之后,
// 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置
shiftUp( reverse[i] );
shiftDown( reverse[i] );
}
// 交换索引堆中的索引i和j
// 由于有了反向索引reverse数组,
// indexes数组发生改变以后, 相应的就需要维护reverse数组
private void swapIndexes(int i, int j){
int t = indexes[i];
indexes[i] = indexes[j];
indexes[j] = t;
reverse[indexes[i]] = i;
reverse[indexes[j]] = j;
}
//********************
//* 最大索引堆核心辅助函数
//********************
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shiftUp(int k){
while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0 ){
swapIndexes(k, k/2);
k /= 2;
}
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k;
if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0 )
j ++;
if( data[indexes[k]].compareTo(data[indexes[j]]) >= 0 )
break;
swapIndexes(k, j);
k = j;
}
}
// 测试索引堆中的索引数组index和反向数组reverse
// 注意:这个测试在向堆中插入元素以后, 不进行extract操作有效
public boolean testIndexes(){
int[] copyIndexes = new int[count+1];
int[] copyReverseIndexes = new int[count+1];
for( int i = 0 ; i <= count ; i ++ ) {
copyIndexes[i] = indexes[i];
copyReverseIndexes[i] = reverse[i];
}
copyIndexes[0] = 0;
copyReverseIndexes[0] = 0;
Arrays.sort(copyIndexes);
Arrays.sort(copyReverseIndexes);
// 在对索引堆中的索引和反向索引进行排序后,
// 两个数组都应该正好是1...count这count个索引
boolean res = true;
for( int i = 1 ; i <= count ; i ++ )
if( copyIndexes[i-1] + 1 != copyIndexes[i] ||
copyReverseIndexes[i-1] + 1 != copyReverseIndexes[i] ){
res = false;
break;
}
if( !res ){
System.out.println("Error!");
return false;
}
return true;
}
// 测试 IndexMaxHeap
public static void main(String[] args) {
int N = 1000000;
IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<Integer>(N);
for( int i = 0 ; i < N ; i ++ )
indexMaxHeap.insert( i , (int)(Math.random()*N) );
assert indexMaxHeap.testIndexes();
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}
4-10 和堆相关的其他问题
动态选择优先级最高的任务执行
在1000000个元素中选出前100名?
在N个元素中选出前M个元素
使用优先队列?NlogM
多路归并排序
d叉堆
最大堆 最大索引堆
最小堆 最小索引堆
ShiftUp 和 ShiftDown 中使用赋值操作替换swap操作
表示堆的数组从0开始索引
没有capacity的限制,动态的调整堆中数组的大小
最大最小队列
二项堆
斐波那契堆
补充
- 补充1 优化的Shift Up和Shift Down
public class Main {
// 比较 MaxHeapSort 和 MaxHeapOSort 的性能的不同
// 以此来检验 MaxHeap 进行ShiftUp和ShiftDown优化前后效率的不同
public static void main(String[] args) {
int N = 1000000;
System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");
Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapSort", arr1);
SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapOSort", arr2);
return;
}
}
// 在堆的有关操作中,需要比较堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 构造函数, 通过一个给定数组创建一个最大堆
// 该构造堆的过程, 时间复杂度为O(n)
public MaxHeap(Item arr[]){
int n = arr.length;
data = (Item[])new Comparable[n+1];
capacity = n;
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for( int i = count/2 ; i >= 1 ; i -- )
shiftDown(i);
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
public Item getMax(){
assert( count > 0 );
return data[1];
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最大堆核心辅助函数
//********************
private void shiftUp(int k){
while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
swap(k, k/2);
k /= 2;
}
}
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k].compareTo(data[j]) >= 0 ) break;
swap(k, j);
k = j;
}
}
// 测试 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将maxheap中的数据逐渐使用extractMax取出来
// 取出来的顺序应该是按照从大到小的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从大到小排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] >= arr[i];
}
}
// 将 ShiftUp 和 ShiftDown 函数使用类似插入排序算法的方式进行优化的最大堆
public class MaxHeapO<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MaxHeapO(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 构造函数, 通过一个给定数组创建一个最大堆
// 该构造堆的过程, 时间复杂度为O(n)
public MaxHeapO(Item arr[]){
int n = arr.length;
data = (Item[])new Comparable[n+1];
capacity = n;
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for( int i = count/2 ; i >= 1 ; i -- )
shiftDown(i);
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
public Item getMax(){
assert( count > 0 );
return data[1];
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最大堆核心辅助函数
//********************
private void shiftUp(int k){
Item e = data[k];
while( k > 1 && data[k/2].compareTo(e) < 0 ){
//swap(k, k/2);
data[k] = data[k/2];
k /= 2;
}
data[k] = e;
}
private void shiftDown(int k){
Item e = data[k];
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( e.compareTo(data[j]) >= 0 ) break;
//swap(k, j);
data[k] = data[j];
k = j;
}
data[k] = e;
}
// 测试 MaxHeapO
public static void main(String[] args) {
MaxHeapO<Integer> maxHeap = new MaxHeapO<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insert( new Integer((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将maxheap中的数据逐渐使用extractMax取出来
// 取出来的顺序应该是按照从大到小的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从大到小排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] >= arr[i];
}
}
// 使用将n个元素插入最大堆中, 再将这n个元素从最大堆中取出的方式, 来对n个元素进行排序
// 以此来检验 MaxHeap 进行ShiftUp和ShiftDown优化前后效率的不同
public class MaxHeapOSort {
// 我们的算法类不允许产生任何实例
private MaxHeapOSort(){}
// 对整个arr数组使用MaxHeapOSort排序
// MaxHeapOSort, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
public static void sort(Comparable[] arr){
int n = arr.length;
MaxHeapO<Comparable> maxHeap = new MaxHeapO<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
maxHeap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 测试 MaxHeapOSort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapOSort", arr);
return;
}
}
// 使用将n个元素插入最大堆中, 再将这n个元素从最大堆中取出的方式, 来对n个元素进行排序
// 以此来检验 MaxHeap 进行ShiftUp和ShiftDown优化前后效率的不同
public class MaxHeapSort {
// 我们的算法类不允许产生任何实例
private MaxHeapSort(){}
// 对整个arr数组使用MaxHeapSort排序
// MaxHeapSort, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
public static void sort(Comparable[] arr){
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
maxHeap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 测试 MaxHeapSort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapSort", arr);
return;
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}
- 补充2 最小堆
// 在堆的有关操作中,需要比较堆中元素的大小,所以Item需要extends Comparable
public class MinHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public MinHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
count = 0;
this.capacity = capacity;
}
// 构造函数, 通过一个给定数组创建一个最小堆
// 该构造堆的过程, 时间复杂度为O(n)
public MinHeap(Item arr[]){
int n = arr.length;
data = (Item[])new Comparable[n+1];
capacity = n;
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for( int i = count/2 ; i >= 1 ; i -- )
shiftDown(i);
}
// 返回堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 向最小堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
// 从最小堆中取出堆顶元素, 即堆中所存储的最小数据
public Item extractMin(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
// 获取最小堆中的堆顶元素
public Item getMin(){
assert( count > 0 );
return data[1];
}
// 交换堆中索引为i和j的两个元素
private void swap(int i, int j){
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//********************
//* 最小堆核心辅助函数
//********************
private void shiftUp(int k){
while( k > 1 && data[k/2].compareTo(data[k]) > 0 ){
swap(k, k/2);
k /= 2;
}
}
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1].compareTo(data[j]) < 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最小值
if( data[k].compareTo(data[j]) <= 0 ) break;
swap(k, j);
k = j;
}
}
// 测试 MinHeap
public static void main(String[] args) {
MinHeap<Integer> minHeap = new MinHeap<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
minHeap.insert( new Integer((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将minheap中的数据逐渐使用extractMin取出来
// 取出来的顺序应该是按照从小到大的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = minHeap.extractMin();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从小到大排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] <= arr[i];
}
}
public class MinHeapSort {
// 我们的算法类不允许产生任何实例
private MinHeapSort(){}
// 将所有的元素依次添加到最小堆中, 再在将所有元素从堆中依次取出来, 完成排序
// 使用这样的一个最小堆排序, 来检验我们的最小堆的正确性
// 思考:使用最小堆可不可以编写如第6小节所介绍的优化的快速排序算法?
public static void sort(Comparable[] arr){
int n = arr.length;
MinHeap<Comparable> minHeap = new MinHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
minHeap.insert(arr[i]);
for( int i = 0 ; i < n ; i ++ )
arr[i] = minHeap.extractMin();
}
// 测试 MinHeapSort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_extra2.MinHeapSort", arr);
return;
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}
- 补充3 最小索引堆
// 最小索引堆
public class IndexMinHeap<Item extends Comparable> {
protected Item[] data; // 最小索引堆中的数据
protected int[] indexes; // 最小索引堆中的索引, indexes[x] = i 表示索引i在x的位置
protected int[] reverse; // 最小索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
protected int count;
protected int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public IndexMinHeap(int capacity){
data = (Item[])new Comparable[capacity+1];
indexes = new int[capacity+1];
reverse = new int[capacity+1];
for( int i = 0 ; i <= capacity ; i ++ )
reverse[i] = 0;
count = 0;
this.capacity = capacity;
}
// 返回索引堆中的元素个数
public int size(){
return count;
}
// 返回一个布尔值, 表示索引堆中是否为空
public boolean isEmpty(){
return count == 0;
}
// 向最小索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
// 传入的i对用户而言,是从0索引的
public void insert(int i, Item item){
assert count + 1 <= capacity;
assert i + 1 >= 1 && i + 1 <= capacity;
// 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。
assert !contain(i);
i += 1;
data[i] = item;
indexes[count+1] = i;
reverse[i] = count + 1;
count ++;
shiftUp(count);
}
// 从最小索引堆中取出堆顶元素, 即索引堆中所存储的最小数据
public Item extractMin(){
assert count > 0;
Item ret = data[indexes[1]];
swapIndexes( 1 , count );
reverse[indexes[count]] = 0;
count --;
shiftDown(1);
return ret;
}
// 从最小索引堆中取出堆顶元素的索引
public int extractMinIndex(){
assert count > 0;
int ret = indexes[1] - 1;
swapIndexes( 1 , count );
reverse[indexes[count]] = 0;
count --;
shiftDown(1);
return ret;
}
// 获取最小索引堆中的堆顶元素
public Item getMin(){
assert count > 0;
return data[indexes[1]];
}
// 获取最小索引堆中的堆顶元素的索引
public int getMinIndex(){
assert count > 0;
return indexes[1]-1;
}
// 看索引i所在的位置是否存在元素
boolean contain( int i ){
assert i + 1 >= 1 && i + 1 <= capacity;
return reverse[i+1] != 0;
}
// 获取最小索引堆中索引为i的元素
public Item getItem( int i ){
assert contain(i);
return data[i+1];
}
// 将最小索引堆中索引为i的元素修改为newItem
public void change( int i , Item newItem ){
assert contain(i);
i += 1;
data[i] = newItem;
// 有了 reverse 之后,
// 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置
shiftUp( reverse[i] );
shiftDown( reverse[i] );
}
// 交换索引堆中的索引i和j
// 由于有了反向索引reverse数组,
// indexes数组发生改变以后, 相应的就需要维护reverse数组
private void swapIndexes(int i, int j){
int t = indexes[i];
indexes[i] = indexes[j];
indexes[j] = t;
reverse[indexes[i]] = i;
reverse[indexes[j]] = j;
}
//********************
//* 最小索引堆核心辅助函数
//********************
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shiftUp(int k){
while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) > 0 ){
swapIndexes(k, k/2);
k /= 2;
}
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k;
if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) < 0 )
j ++;
if( data[indexes[k]].compareTo(data[indexes[j]]) <= 0 )
break;
swapIndexes(k, j);
k = j;
}
}
// 测试 IndexMinHeap
public static void main(String[] args) {
int N = 1000000;
IndexMinHeap<Integer> indexMinHeap = new IndexMinHeap<Integer>(N);
for( int i = 0 ; i < N ; i ++ )
indexMinHeap.insert( i , (int)(Math.random()*N) );
}
}
// 使用最小索引堆进行堆排序, 来验证我们的最大索引堆的正确性
public class IndexMinHeapSort {
// 我们的算法类不允许产生任何实例
private IndexMinHeapSort(){}
public static void sort(Comparable[] arr){
int n = arr.length;
IndexMinHeap<Comparable> indexMinHeap = new IndexMinHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
indexMinHeap.insert( i , arr[i] );
for( int i = 0 ; i < n ; i ++ )
arr[i] = indexMinHeap.extractMin();
}
// 测试 Index Min Heap Sort
public static void main(String[] args) {
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("com.imooc.ch4_extra3.IndexMinHeapSort", arr);
return;
}
}
public class SortTestHelper {
// SortTestHelper不允许产生任何实例
private SortTestHelper(){}
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {
assert rangeL <= rangeR;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++)
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}
// 生成一个近乎有序的数组
// 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据
// swapTimes定义了数组的无序程度:
// swapTimes == 0 时, 数组完全有序
// swapTimes 越大, 数组越趋向于无序
public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){
Integer[] arr = new Integer[n];
for( int i = 0 ; i < n ; i ++ )
arr[i] = new Integer(i);
for( int i = 0 ; i < swapTimes ; i ++ ){
int a = (int)(Math.random() * n);
int b = (int)(Math.random() * n);
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
return arr;
}
// 打印arr数组的所有内容
public static void printArray(Object[] arr) {
for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();
return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){
for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间打印在控制台上
public static void testSort(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
// 将算法的运行时间以long类型返回, 单位为毫秒(ms)
public static long testSort2(String sortClassName, Comparable[] arr){
// 通过Java的反射机制,通过排序的类名,运行排序函数
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();
assert isSorted( arr );
return endTime - startTime;
}
catch(Exception e){
e.printStackTrace();
}
return 0;
}
}