堆排序

堆排序

详细参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
* 堆排序
* 堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。
* 可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。
* 大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。
* 在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶
* 定义
* n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):
* (1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2),当然,这是小根堆,大根堆则换成>=号(2)ki>=k(2i)且ki>=k(2i+1)(1≤i≤ n/2)。//k(i)相当于二叉树的非叶子结点,K(2i)则是左子节点,k(2i+1)是右子节点
* 若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
* 树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
* 【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。
* 大根堆和小根堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。
* 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。注意:①堆中任一子树亦是堆。②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。
* 堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
* 平均性能
* O(N*logN)。
*
* (2)大根堆排序算法的基本操作:
①建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。
建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),
选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。
调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。
然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。
因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn) [2]
* 其他性能
* 由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
* 堆排序是就地排序,辅助空间为O(1).
* 它是不稳定的排序方法。(排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化)
*
* 总结:
* 1.建立最大堆
* 2.把最大堆元素移动至最后为有序
*/
public class HeapSort implements Sort {
public static void main(String[] args) {
HeapSort bubbleSort = new HeapSort();
System.out.println("HeapSort");
bubbleSort.test();

}

@Override
public void sort(int[] data) {
//step1.buildMaxHeapify
//没有子节点的才需要创建最大堆,从最后一个的父节点开始
int startIndex = getParentIndex(data.length - 1);
//从尾端开始创建最大堆,每次都是正确的堆
for (int i = startIndex; i >= 0; i--) {
maxHeapify(data, data.length, i);
}
//---此时最大堆创建完成
System.out.println("printTree MaxTree Ok");
//step2.heapSort
//末尾与头交换,交换后调整最大堆
for (int i = data.length - 1; i > 0; i--) {
System.out.print("i=" + i);
printTree(data);
swap(data, 0, i);
printTree(data);
maxHeapify(data, i, 0);
}
printTree(data);
}


/**
* 创建最大堆
*
* @paramdata
* @paramheapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了
* @paramindex当前需要创建最大堆的位置
*/
private void maxHeapify(int[] data, int heapSize, int index) {

//当前点与左右子节点比较
int left = getChildLeftIndex(index);
int right = getChildRightIndex(index);
System.out.print("index=" + index + " left=" + left+" right=" + right);
printTree(data);
int largest = index;
if (left < heapSize && data[index] < data[left]) {
largest = left;
}
if (right < heapSize && data[largest] < data[right]) {
largest = right;
}
//得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整
if (largest != index) {
swap(data, largest, index);
maxHeapify(data, heapSize, largest);
}
}

/**
* 父节点位置
*
* @return
* @paramcurrent
*/
private int getParentIndex(int current) {
return (current - 1) >> 1;
}

/**
* 左子节点position注意括号,加法优先级更高
*
* @return
* @paramcurrent
*/
private int getChildLeftIndex(int current) {
return (current << 1) + 1;
}

/**
* 右子节点position
*
* @return
* @paramcurrent
*/
private int getChildRightIndex(int current) {
return (current << 1) + 2;
}

public void printTree(int[] data) {
int pre = -2;
for (int i = 0; i < data.length; i++) {
if (pre < (int) getLog(i + 1)) {
pre = (int) getLog(i + 1);
System.out.println();
}
System.out.print(data[i] + "|");
}
System.out.println();
System.out.println();
}

/**
* 以2为底的对数
*
* @return
* @paramparam
*/
private static double getLog(double param) {
return Math.log(param) / Math.log(2);
}
}