数学建模社区-数学中国
标题:
十大经典排序算法之堆排序(Java语言)
[打印本页]
作者:
杨利霞
时间:
2020-4-23 15:00
标题:
十大经典排序算法之堆排序(Java语言)
( W% O. q' H$ x9 m# Q
十大经典排序算法之堆排序(Java语言)
* f, l1 y( U. T- ~3 i0 v2 o
文章目录
# R' e5 U+ R% z' b" e
- m( X* s% V" p/ k7 r- |; j
什么是堆
1 }" J( t# W& Z; P
如何进行堆排序呢
/ l# Y' x0 T# r5 ^; R7 }1 n" p6 C
用数组构建一个堆
) q$ \6 `0 ?5 {! F/ x" a
上代码
$ `6 k# ?2 H4 ?8 H; ], q% p- \( K) I
什么是堆
& K+ l& _7 m- U3 s! y& e+ d
, ?% K5 h# T8 D' _
在了解什么是堆之前一定要先了解什么是完全二叉树
- _5 j8 V( r+ R7 b
看一下百度百科的介绍
8 O5 L: T/ E8 o J* J4 _& T/ s
6 q. {8 j) |; s- d9 s, a8 d
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
( J2 T! @- G& I) P& @; e
百度百科拗口版性质介绍,能看懂上面的就行,下面的大概看下
6 R2 ]. C( e. T% m" b3 J- Y/ g
" X8 g9 d( b) l( s3 S( z& d
完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
g) L8 ?1 t( A, v9 x
(1)所有的叶结点都出现在第k层或k-l层(层次最大的两层)
( ]% z4 Z- `7 ?: j
(2)对任一结点,如果其右子树的最大层次为L,则其左子树的最大层次为L或L+l。
0 A2 V+ d* I5 M+ T. [ [
一棵二叉树至多只有最下面的两层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树,并且最下层上的结点都集中在该层最左边的若干位置上,而在最后一层上,右边的若干结点缺失的二叉树,则此二叉树成为完全二叉树。
0 o; R. t6 d4 ?
那么在了解到什么是完全二叉树之后,我们再来看什么是堆
3 X" S( j% N+ w9 Q
堆有以下两个性质
; s5 {5 _, c* C& G0 L
/ P: r- b G+ a# A+ M
1 堆中某个节点的值总是不大于或不小于其父节点的值;
6 M. g7 i; o8 I& D( z, R9 ?6 r
2 堆总是一棵完全二叉树。
9 h) t5 o7 N; B# B
其中堆顶就对应二叉树的根
, n6 j+ G* l4 p; Y2 X5 d
* v6 ~9 E& V# o, v* w" G
堆又分为大顶堆和小顶堆,根据堆的第一个性质来进行区分
3 n8 G2 }; [' I9 m# C+ D
$ E+ A8 e" B; B$ @) L) y/ Z8 m3 O
当堆中某个节点的值总是大于它的子节点的时候这个堆为大顶堆,反之为小顶堆
3 p: ~* u a1 x
如何进行堆排序呢
) a# ]) Q+ @: s
% n! G+ L: u/ t$ ]" g* X2 n' W
堆排序,其实就是每次构造出一个大顶堆或者小顶堆,然后取出堆顶的值,再将剩下的值重新构造成大顶堆或者小顶堆,最终到堆里的值全部取出来,取出来的数就是排好序的
/ o! s: V, ]/ _" p* u+ m
. R) p, d8 Y) x! i5 l- x
用数组构建一个堆
8 [( L7 L' o) x8 l6 ?7 c
- y, K6 w! j' U' a+ @
因为堆是一颗完全二叉树,所以我们可以用数组来对其进行存储
' Q/ I' ]' m3 p# V- `9 k: ~. u
对于用数组存储的二叉树,我们可以用如下方法来定义:
) e. N7 i1 e" d# L) i
假设当前节点的下标为 n
& R i) b, I6 C
+ h( B; c* U5 _# f% L
1、那么他的左子节点的下标 2*n + 1
}0 c2 z# t# @% P1 m) A, c
2、那么他的右子节点的下标 2*n + 2
" @8 u& x. C6 z' I
3、他左边的节点是 n-1,如果当前节点是第 h 层的最左节点,那么第h-1层的最右节点的下标就是 n-1
8 ]3 w. s( U. {5 L& _* {5 _4 ]
4、根据1、2可以推出来n节点的父节点是 (n-1)/2,不管当前节点是父节点的左子节点还是右子节点,都用 (n-1)/2就行了,因为整型数字相除小数点后面的会被截断
; P& [2 N; ^7 j* T6 {6 O- N3 P( Q
那么有了上面四条性质,我们就可以开始动手了
?! ]- R A `6 ?* n& J
7 y* l* E* X8 _7 P. `- u% }
1、假设我们要构建的堆是大顶堆,那么根据大顶堆的性质,任意节点都比它的左右子节点要大,所以我们肯定有个heapify方法,该方法调整指定节点和其子节点的位置,并且继续调整被调整的子节点和孙子节点的关系,直到没有调整或者到数的最底层
, ?9 b% X# Q3 M* h4 K
2、然后我们要有构造大顶堆的方法,构造大顶堆就是从最后一个节点的父节点开始调整,接设最后一个节点的父节点是n,那么我们就将n,n-1,n-2 ··· ··· 0,这些节点逐次,从大到小调用heapify方法,这些节点都调整完成后,大顶堆就构造完成了
7 C/ F/ q' N" X# B5 f& _' c
3、接下来就开始将堆顶和堆尾互换,并砍断堆尾的操作了,由于互换之前,这是一个符合条件的大顶堆,但是换完只有只有一个堆顶这里不满足了,那么我们重新调整一下堆顶的三个元素就可以,还是调用heapify方法,这个方法会自上而下的重新调整堆,使其成为一个大顶堆
' `( e6 A$ b2 Q: H% ]$ q
% e& y" o2 Z, _
堆排序的性质
$ e: w+ c( j) R5 v* C. O
' f9 g1 r7 _& U$ \. ]
中文名称 英文名称 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 稳定性
4 S2 v% @6 S2 y; `! A3 a3 W% b
堆排序 Heap n*logn n*logn n*logn 1 不稳定
5 K( h0 F. c2 Z& j9 C- |1 j+ L8 \
上代码
. u' D4 e4 G4 z# d9 {
* w6 A8 B0 o1 F* \# G
/**
- v# d' b4 b6 x$ x8 S4 H
* 交换第n和m个元素
& Z* B0 \2 i6 g% e6 F5 P
*/
5 f' F9 b- I7 e1 E& A8 G
private static void swap(int arr[], int n, int m){
$ {. v4 j' {3 _7 R: e
int temp = arr[n];
! Z3 `+ @( t( U
arr[n] = arr[m];
5 H( a* g& B0 ~7 Y8 U
arr[m] = temp;
4 A7 F- Y6 \+ Q |" w% q
}
* q- [3 a9 x) V- a, Y8 J& z X
1 [7 f* d, @- A
/**
% a% `4 l4 n" f+ ]' A% |& X/ p
* 调整指定节点和其子节点
+ W T" X4 B* P" {* k
*
@param
tree 整棵树
P7 M! @6 C u
* @param n 数组长度,树的元素个数
* \/ }# y( N4 Q/ I
* @param i 要调整的节点的下标
& j, A8 ~, ?: I. f+ G: X8 Y
*/
% c) {# T# a$ F& ?9 z8 D
private static void heapIfy(int tree[], int n, int i){
0 ^1 J H. v/ w
if(i >= n){
) j& k' t" B) J$ \7 |- C
return;
9 r) ~. W: }8 L, z5 H& m; f
}
- {: I4 H4 o# k" ^7 E
int c1 = 2 * i + 1;//左子节点的下标
8 {: G) p' S L( _2 @$ O# _8 b5 z5 b
int c2 = 2 * i + 2;//右子节点的下标
) G# T3 B$ ^# A
int max = i;//假设父节点是最大的
! c; b% s% c# a: U( c3 k0 F+ |
//找出最大值的下下标
& I% u. {" F6 Z1 R# c P
if(c1 < n && tree[c1] > tree[max]){
7 G7 S+ g6 U8 r3 p5 o& u
max = c1;
9 E: w+ @" S4 C7 N1 i2 C
}
" e$ T: ?, X' P* L$ t! |. F
if(c2 < n && tree[c2] > tree[max]){
1 P6 L3 ?1 K- f; x# p
max = c2;
0 G* _' e6 K& l! h( L, e' {0 ^
}
0 x& E! U( z2 a
if(max != i){//如果最大值不是父节点,需要做换位置操作
" w: f( z k: d0 x5 T
swap(tree, max, i);
0 a4 |2 g, [8 t. s* Y
//此时,i节点被换成最大值了,符合大顶堆的性质
; S# \8 [8 I( v1 P" n
//但是换到下面的节点不能保证比他的两个子节点都要大
* `8 w5 b1 D% }- J
//所以被换位置的节点继续调整
, o( y. w! w3 M G! D5 C. G
heapIfy(tree, n, max);
) F" k0 U1 E5 ]% p6 X* s- N8 m
}
l! {; H" b6 b5 y- L8 ]: ?
}
" z/ v5 m2 O6 s3 E# u
# \# H- N; T4 N6 s; P0 N
/**
) K# T* w8 q$ X' t
* 完整构建大顶堆
% R! W& i4 d" u* _: {
* @param arr 用于构建堆的数组
" d9 B! r/ K7 Y3 e/ M" d# \6 [
* @param n 堆的最后一个节点的下标
l% b5 }4 W+ G3 `
*/
1 E% A& l2 ^. Z% h2 U. @. C- n
private static void buildHeap(int arr[],int n){
$ j; b2 T. w9 s2 ]
int lastNode = n - 1;
. P- j0 e! O2 U, S0 ]+ B
int parent = (lastNode - 1) / 2;
% _6 i) V9 I$ p2 R; p; r) o" J8 F. G
for (int i = parent; i >= 0; i--){
) m8 Z1 [8 \( _ A/ @, M: V
heapIfy(arr, n, i);
9 L8 x- P: l8 x& w
}
' L7 W1 T- ?' a: S, R& h' }
}
. d1 `4 O) S6 E; O0 p( Y4 z3 f8 ]
1 j# p! D7 N% n: `, M; U/ i
/**
* F0 k) k) J, K8 q
* 堆排序
& l& ~* j4 z- a9 _
* @param arr 待排数组
. b% [; L. Z: e9 m+ c1 M: W
*/
+ q9 f, e0 h( ~4 G# B/ k6 r [! j
public static void sort(int arr[]){
2 K( N9 n) Y& l' h) M
buildHeap(arr, arr.length);//先构造大顶堆
8 e8 u0 n ~2 P. Y! o
//每次构建堆后将根节点和最后一个节点进行交换
4 t8 S) q T( F5 R/ X6 q9 d
//然后砍断最后一个节点
; ]% m N6 v# x6 O* [- K
//所以从最后一个节点向前循环
/ A, @% N2 M1 e. k+ n- Y( |! g
for (int i = arr.length - 1; i > 0; i--){
0 P/ ]* p' Q5 E# B% H
swap(arr, 0, i);
+ \$ e& i* A1 `- ^" n9 \5 S) ?
heapIfy(arr, i, 0);
4 k2 V r7 L* t- B: M
}
p/ s( C- t8 _7 w: g6 B& ~8 U
}
M8 n: U2 j/ u! z3 h
————————————————
9 Y) ~1 e+ z4 ?$ \+ R( c
版权声明:本文为CSDN博主「qq_34912889」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
% V9 r7 v% T+ e! o2 j5 ^0 r7 }
原文链接:https://blog.csdn.net/qq_34912889/article/details/105690644
8 E% s1 E( x& V# }' m
! _" Y7 w$ q; i- g& P6 a% M
# [! N0 _9 i" W- K$ y
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5