【算法】一个小白的算法笔记:堆排序
参考资料
什么是二叉堆
二叉堆:一个堆有序的完全二叉树, 叫做二叉堆。
完全二叉树
堆有序
二叉堆的表示方法
二叉堆节点间的位置关系
堆有序化的基本算法:上浮和下沉
- 当某个节点变得比它的父节点更大而被打破(或是在堆底加入一个新元素时候),我们需要由下至上恢复堆的顺序
- 当某个节点的优先级下降(例如,将根节点替换为一个较小的元素)时,我们需要由上至下恢复堆的顺序
- 该节点比它的父节点大
- 该节点比它的其中一个儿子节点小
上浮实现堆有序
/** * @param a 表示堆的数组 * @param k 堆中的节点位置 */ private void swim (int [] a, int k) { while(k>1&&a[k]>a[k/2]){ // 当该结点存在父节点,且大于父节点的时候 exchange(a, k, k/2); // 交换它和它的父节点值 k = k/2; // 取得父节点的位置 } }
下沉实现堆有序
/** * @param a 表示堆的数组 * @param k 堆中的节点位置 * @param N 堆中节点总数 */ private void sink (int [] a, int k, int N) { while(2*k<=N) { // 当该节点存在至少一个子节点的时候 int j = 2*k; // 取得左儿子的位置 if(j<N&&a[j]<a[j+1]) { j++; } // 取得左儿子和右儿子中的较大者 if(a[k]<a[j]) { // 当该节点的值小于较大的左儿子的时候 exchange(a, k, j); // 交换它和该儿子节点的值 k = j; // 取得该儿子节点的位置 } else { break; } } }
二叉堆的基本操作:插入元素和删除最大元素
向堆中插入元素
/** * 向堆中插入值 * @param v 要插入的值 */ public void insert (int v) { a[N+1]= v; // 元素被放入堆的最末端成为新节点 N++; // 增加堆的大小 swim(N); // 对末端节点进行上浮操作使其有序 }
向堆中删除最大元素
/** * 删除堆中的最大值, 并且将其返回 * @return 堆节点最大值 */ public int delMax () { if(isEmpty()) return 0; // 当堆为空时, 返回 int max = a[1]; // 取得堆中根节点(最大值) exchange(a, 1, N); // 交换根节点和末端节点(最后一个元素)的值 N--; // 减少堆的大小 (“删除”完毕) sink(1); // 下沉操作,让刚放上根节点的新元素下沉到合适的位置 return max; }
public class Heap { /** * N: 记录二叉堆中的节点总数 * a: 容纳二叉堆节点的数组,从a[0]开始存放 */ private int N = 0; private int [] a; /** * @param maxN 创建堆中节点的总数 */ public Heap (int maxN) { a = new int [maxN+1]; } private static void exchange(int [] a , int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } /** * 上浮操作 * @param k 堆中的节点位置 */ private void swim (int k) { while(k>1&&a[k]>a[k/2]){ // 当该结点存在父节点,且大于父节点的时候 exchange(a, k, k/2); // 交换它和它的父节点值 k = k/2; // 取得父节点的位置 } } /** * 下沉操作 * @param k 堆中的节点位置 */ private void sink ( int k ) { while(2*k<=N) { // 当该节点存在至少一个子节点的时候 int j = 2*k; // 取得左儿子的位置 if(j<N&&a[j]<a[j+1]) { j++; } // 取得左儿子和右儿子中的较大者 if(a[k]<a[j]) { // 当该节点的值小于较大的左儿子的时候 exchange(a, k, j); // 交换它和该儿子节点的值 k = j; // 取得该儿子节点的位置 } else { break; } } } /** * 向堆中插入值 * @param v 要插入的值 */ public void insert (int v) { a[N+1]= v; // 元素被放入堆的最末端成为新节点 N++; // 增加堆的大小 swim(N); // 对末端节点进行上浮操作使其有序 } /** * 删除堆中的最大值, 并且将其返回 * @return 堆节点最大值 */ public int delMax () { if(isEmpty()) return 0; // 当堆为空时, 返回 int max = a[1]; // 取得堆中根节点(最大值) exchange(a, 1, N); // 交换根节点和末端节点(最后一个元素)的值 N--; // 减少堆的大小 (“删除”完毕) sink(1); // 下沉操作,让刚放上根节点的新元素下沉到合适的位置 return max; } /** * 判断堆数组是否为空 */ public boolean isEmpty() { return N == 0; } }
public class Test { public static void main (String [] args) {
// 创建一个能容纳10个元素的堆 Heap heap = new Heap(10); int [] array = {2,6,3,9,1,5,4,3,0,2}; // 将数组元素依次放入堆中 for(int i =0; i<array.length;i++) { heap.insert(array[i]); } // 依次删除堆中的最大元素并输出 for(int i =0; i<array.length;i++) { System.out.println(heap.delMax()); } } }
9 6 5 4 3 3 2 2 1 0
堆排序
堆的构造阶段
int N = a.length; // 取得节点总数 for(int i=N/2;i>0; i--) { sink(a, i, N); // 对所有父节点,从最后一个父节点到根节点,依次下沉排序 }
下沉排序阶段
while(N>1){ exchange(a, 1, N); // 将数组中最大的元素放到数组后端 N--; // 将最大的节点元素移出堆 sink(a, 1, N); // 下沉操作,再次实现堆有序 }
如图所示:
/** * 堆排序方法 * @param a 待排序数组 */ public static void sort(int [] a) { // 堆的构造阶段 int N = a.length; // 取得节点总数 for(int i=N/2;i>0; i--) { sink(a, i, N); // 对所有父节点,从最后一个父节点到根节点,依次下沉排序 } // 到这里数组已经完全堆有序
// 下沉排序阶段 while(N>1){ exchange(a, 1, N); // 将数组中最大的元素放到数组后端 N--; // 将最大的节点元素移出堆 sink(a, 1, N); // 下沉操作,再次实现堆有序 } }
/** * 交换两个数组元素的值 * 注意! 不同于一般的exchange, 这里的i和j要减1! */ private static void exchange(int [] a , int i, int j) { int temp = a[i-1]; a[i-1] = a[j-1]; a[j-1] = temp; } /** * 比较i和j下标的数组元素的大小 * 注意! 不同于一般的less, 这里的i和j要减1! */ private static boolean less (int [] a, int i, int j) { return a[i-1]-a[j-1]<0 ? true : false; }
public class HeapSort { /** * 交换两个数组元素的值 * 注意! 不同于一般的exchange, 这里的i和j要减1! */ private static void exchange(int [] a , int i, int j) { int temp = a[i-1]; a[i-1] = a[j-1]; a[j-1] = temp; } /** * 比较i和j下标的数组元素的大小 * 注意! 不同于一般的less, 这里的i和j要减1! */ private static boolean less (int [] a, int i, int j) { return a[i-1]-a[j-1]<0 ? true : false; } /** * 下沉操作 * @param a 待排序数组 * @param k 堆中的节点位置 * @param N 堆中的节点总数 */ private static void sink (int [] a, int k, int N) { while(2*k<=N) { // 当该节点存在至少一个子节点的时候 int j = 2*k; // 取得左儿子的位置 if(j<N&&less(a, j, j+1)) { j++; } // 取得左儿子和右儿子中的较大者 if(less(a, k, j)) { // 当该节点的值小于较大的左儿子的时候 exchange(a, k, j); // 交换它和该儿子节点的值 k = j; // 取得该儿子节点的位置 } else { break; } } } /** * 堆排序方法 * @param a 待排序数组 */ public static void sort(int [] a) { // 堆的构造阶段 int N = a.length; // 取得节点总数 for(int i=N/2;i>0; i--) { sink(a, i, N); // 对所有父节点,从最后一个父节点到根节点,依次下沉排序 } // 到这里数组已经完全堆有序 // 下沉排序阶段 while(N>1){ exchange(a, 1, N); // 将数组中最大的元素放到数组后端 N--; // 将最大的节点元素移出堆 sink(a, 1, N); // 下沉操作,再次实现堆有序 } } }
public class Test { public static void main (String [] args) { int [] array = {3,0,8,9,1,5,4,2,7,1,2}; HeapSort.sort(array); for(int i=0;i<array.length;i++) { System.out.println(array[i]); } } }
0 1 1 2 2 3 4 5 7 8 9