4.2堆
堆就我理解就是用数组表示一个完全二叉树,用上hashcode应该就是能更快找到数据
4.2.1堆上的运算
堆上的运算在此进行的shiftdown,不演示shiftup,为了实现最终的heapsort,shiftdown本身就是对数组进行堆排序
下面展示的是书中的伪代码:
过程:SHIFT-DOWN
输入:数组H[1···n]和位于1和n之间的索引i
输出:下移H[i],以使它不小于子结点
1.done <- false
2.if 2i > n then exist {结点i是叶子结点} //2i>n都是叶子结点(可以自行检验),如果是叶子结点则无法进行堆排序,则退出
3.repeat
4. i <- 2i //为当前结点的左子结点
5. if i+1 <= n and key(H[i+1])>key(H[i]) then i <- i+1 //i+1为右结点,右结点大于左节点i+1,防止总是左节点跟根结点进行互换,而i+1则为右结点,右结点也能得到充分排序
6. if key(H[i/2]) < key(H[i]) then 互换H[i]和H[i/2] //互换左子节点和父结点,因为满二叉树左结点总是存在,右结点不一定
7. else done <- true //已经排序号,不需要互换,直接退出排序
8. end if
9.until 2i > n or done
此处没有数组长度也就是i,所以无法进行代码演示,代码演示将在下面的MakeHeap中做出
4.2.2堆的创建
直接贴出伪代码:
过程:MAKEHEAP
输入:n个元素的数组A[1···n]
输出:A[1···n]转换成堆
1.for i <- n/2 downto 1 // 进入shiftdown时,i会乘以2,所以n/2为自底向上进行shiftdown(从叶子结点出发),为1的时候,1代表最上面的根,已经排序好,所以退出循环
2.shiftdown(A,1)
下面展示的是Java的实现:
package com.sheye;
/**
* @author Sheye
* @date 2019-10-17 17:33
*/
public class MakeHeap {
private static int[] A = {3,4,5,5,7,9,10,11,17,20};
static void shiftDown(int[] A,int i) {
int n = A.length;
boolean done = false;
if (2 * i > n + 1) {
return;
}
do {
i = 2 * i;
if (i + 1 <= n && A[i + 1 - 1] > A[i - 1]) {
i = i + 1;
}
if (A[i / 2 - 1] < A[i - 1]) {
int tmp = A[i - 1];
A[i - 1] = A[i / 2 - 1];
A[i / 2 - 1] = tmp;
} else {
done = true;
}
}
while (!(2 * i > n || done)) ;
}
static void makeHeap(int[] A,int n){
for (int i=n/2;i>=1;i--){ //此处对i不进行处理,直接在交换的时候在数组下标里-1则完成
shiftDown(A,i);
}
for (int j = 0; j < A.length; j++) {
System.out.print(A[j]+",");
}
}
public static void main(String[] args) {
int n = A.length;
makeHeap(A,n);
}
}
贴上运行结果
排序的结果如下:
20,17,10,11,7,9,5,3,5,4,
4.2.3堆排序
堆排序就是对一个堆进行一个类似于选择排序的排序
HEAPSORT
输入:n个元素的数组A[1···n]
输出:以非降序排列的数组A
1.MAKEHEAP
2.for j <- downto 2
3. 互换A[1]和A[j]
4. shiftdown(A[1···j-1],1) //代码中用MakeHeap (A[1..j-1])代替此行
5.end for
上传代码之前先述说自己遇到的问题:
- 一开始,我们都在执着于下标是0还是1的问题,直到今天,我再重新看了看MAKEHEAP,算法中的n应该指的是实际二叉树里面的结点数(或者数组的长度),如果进行结点的大小的比较,或者交换值,直接再在原有的位置下标下-1就足够了(A.get(j-1))
makeHeap(A, n); //10个数建立堆
for (int j = n; j >= 2; j--) { //最后一个已经自动归位,到1则跳出循环
int tmp1 = A.get(j - 1);
int tmp2 = A.get(0);
A.set(0, tmp1);
A.set(j - 1, tmp2); //因为堆的最左边总是最大值,将最大值移到数组的最后一位
makeHeap(A, j - 1); //排序完之后(移动最大的数到最右边),再将剩余的数(第一次是9个数)进行堆的建立
for (int a : A) {
System.out.print(a + ",");
}
System.out.println("");
}
为了更简便使用heapsort,java的实现会与伪代码有出入,比如shiftDown(A, n, i)的三形参,我想有备注大家可以理解。
下面展示的是Java的实现:
package com.sheye;
import java.util.ArrayList;
import java.util.List;
/**
* @author Sheye
* @date 2019-10-17 19:55
*/
public class HeapSort{
private static List<Integer> A = new ArrayList();
static void shiftDown(List<Integer> A, int n, int i) {
boolean done = false;
if (2 * i > n) {
return;
}
do {
i = 2 * i;
if (i + 1 <= n && A.get(i + 1 - 1) > A.get(i - 1)) {
i = i + 1;
}
if (A.get(i / 2 - 1) < A.get(i - 1)) {
int tmp1 = A.get(i / 2 - 1);
int tmp2 = A.get(i - 1);
A.set(i - 1, tmp1);
A.set(i / 2 - 1, tmp2);
} else {
done = true;
}
}
while (!(2 * i > n || done));
}
static void makeHeap(List<Integer> A, int n) {
for (int i = n / 2; i >= 1; i--) {
shiftDown(A, n, i); //A为数组A,n为将要建立堆的数组大小,i为数组1···n的索引,也就是当前结点
}
}
static void heapSort(List<Integer> A, int n) {
makeHeap(A, n); //10个数建立堆
for (int j = n; j >= 2; j--) { //最后一个已经自动归位,到1则跳出循环
int tmp1 = A.get(j - 1);
int tmp2 = A.get(0);
A.set(0, tmp1);
A.set(j - 1, tmp2); //因为堆的最左边总是最大值,将最大值移到数组的最后一位
makeHeap(A, j - 1); //排序完之后(移动最大的数到最右边),再将剩余的数(第一次是9个数)进行堆排序
for (int a : A) {
System.out.print(a + ",");
}
System.out.println("");
}
}
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) { //测试数据为1到10
A.add(i);
}
int n = A.size();
heapSort(A,n);
}
}
为了方便大家查看每次排序的结果,特意输出每次排序结果
贴上运行结果:
每一次循环排序的结果如下:
9,8,7,4,5,6,3,1,2,10,
8,5,7,4,2,6,3,1,9,10,
7,5,6,4,2,1,3,8,9,10,
6,5,3,4,2,1,7,8,9,10,
5,4,3,1,2,6,7,8,9,10,
4,2,3,1,5,6,7,8,9,10,
3,2,1,4,5,6,7,8,9,10,
2,1,3,4,5,6,7,8,9,10,
1,2,3,4,5,6,7,8,9,10,
前段时间一直使用数组进行算法的实现,但是数组始终不能进行删除和替换,虽然此处用数组进行也没问题,但是之后的算法我都将用list进行,list底层就是数组,还能进行删除与替换,可以实现数组无法实现的功能,对了,还有本人能力浅薄,对算法的理解难免有错误,望大家指出。