- 堆:堆是一个数组,近似看作一个完全二叉树。
length:可能具有得长度
heap-size:堆存放的有效元素
结点下标表示: 父亲结点:i/2(向下取整) LEEF: 2i RIGHT: 2i+1
大部分情况,在堆排序中,我们建立的是最大堆,而最小堆我们常用于构造优先队列。
(补充知识点:[左移右移])(https://blog.youkuaiyun.com/u011240016/article/details/53573792))
左移(<<)右移(>>)比单纯的乘除更快。 大部分左移1位相当于乘以2, 左移n位就是乘以2的n次方了
习题:
6.1-1:
6.1-2:(写对了,但是格式问题直接放答案了)
6.1-3
我的:如果存在一个子树,他的根结点值小于左孩子,则与最大堆的性质相反
答案:max-heap属性确保堆的子树中的最大元素位于子树的根。
6.1.-4
我的:叶子结点,emm
答案:假设所有元素都是不同的,则max-heap中的最小元素始终位于树的叶子中。
6.1-5
我的:是
答案:不一定是最小堆
(我简直傻了,排序可以小到大,也可以大到小)
6.1-6
no,有一颗子树6,5,7违背了性质
6.1-7
这个我一直只知道不知道证明嘻嘻
根据完全二叉树的性质:某节点下标为i(非根节点),其父节点的下标为floor(i/2),最后一个结点下标为n,因此它的父节点的下标为floor(n/2),所以从下标floor(n/2)+1开始到n都是叶节点。
-
MAX-HEAPIFY:维护堆的性质
每次改变一个结点,都会引起起子树的变化
书上伪代码:
代码实现:void MAXHEAPIFY(int A[],int length,int i) { int l,k,largest=0; l = 2 * i; k = 2 * i + 1; if(l <= length && A[l] > A[i]) largest = l; else largest = i; if(k <= length && A[k] > A[largest]) largest = k; if(largest != i) swap(A[i],A[largest]); MAXHEAPIFY(A,length,largest); }
算法分析:
时间复杂度为O(h)(h为二叉树的高度)
习题:
6.2-1:
6.2-2:
我觉得运行时间应该是一样的
MIN-HEAPLFY(A , i )
1. l = LEET(i)
2. r = RIGHT(i)
3. if ( l <= A.heap-size and A[l] <A[i])
4. min = l;
5. if( r <= A.heap-size and A[r] <A[min])
6. min = r;
7. if( i != min )
8. exchange A[i ] and A[min]
9. MIN-HEAPLFY(A, min)
6.2-3
没有改变
6.2-4
A.heap-size/2这个位置应该是最后一个非终端节点,大于它的都是叶子结点,没有左右子树,当然值就不会改变
6.2-5
void MAXHEAPIFY(int A[],int length,int i)
{
while(i<=(length / 2))
{
int l,k,largest=0;
l = 2 * i;
k = 2 * i + 1;
if(l <= length && A[l] > A[i])
largest = l;
else
largest = i;
if(k <= length && A[k] > A[largest])
largest = k;
if(largest != i)
swap(A[i],A[largest]);
i = largest;
}
}
6.2-6
对于最大堆,最坏的情况不过就是最小堆让他从头开始调整?那就是从头调到尾,则高度为lgn,emmm果然我还是错的,?
下面大概是大佬的答案
假设大小为n的堆的高度为h
首先如何使得从根节点到叶节点的路径上的每个节点都递归调用max_heapify?
每次调用后的子树都符合子树的根节点小于左右儿子节点,
则最初的根节点的值比所有左右儿子的值都小。
接着要考虑的就是路径-----如何使得从根节点到叶节点的路径更长(最坏的情况)?“
堆数据结构是一种数组对象,可以视为一棵完全二叉树。
树的每一层都是填满的,最后一层除外(最后一层从一个节点的左子树开始填)”
------《算导》原文。
因此每次根节点与其左儿子节点交换,路径会是最长的,则所有左子树的值大于或等于右子树。
这样设置,max_heapify会调用h次,所以运行时间为Θ(h),即Θ(lgn)
。根据大Θ符号的定义(详细见算导第三章----函数的增长),因此的得最坏运行时间为Ω(lgn)。
- 建立堆
用自底向上的方法利用MAX-HEAPIFY
书上伪代码
代码:
void BULDMAXHEAP(int A[],int length)
{
int heapsize;
heapsize = length;
for(int i = length/2; i >=1 ;i++)
{
MAXHEAPIFY(A,i);
}
}
6.3-1
6.3-2
因为要保证在每次调用MAX-HEAPIFY的时候,要保证左右子树是各自的最大堆
6.3-3
这个证明对于我而言,难度天大,我数学不太行?
我放弃了
设H为堆的高度。
提醒的两个细微之处:
- 注意不要混淆节点的高度(离叶子最远的距离)和它的深度(距离根部的距 离)。
- 如果堆不是完整的二叉树(底层未满),则给定级别(深度)的节点并不都具有相同的高度。例如,尽管深度为H的所有节点都具有高度0,但是深度为H-1的节点可以具有高度0或高度1。
对于完整的二叉树,很容易证明[n / 2^h +1]个高度为h的节点。但是对于不完整树的证明是棘手的,并不是从完整树的证明中得出的。
证明:通过归纳来证明h。
- 堆排序
数组A[1…n]的最大元素为A[1],通过与A[n]交换达到最终正确的位置。原来根的子女依然是最大堆,但是新的根元素可能违背最大堆的性质。因此要调用维持最大堆性质的函数,然后在A[1…n-1]里重复这个过程。
伪代码
代码:
void Heapsort(int A[],int length)
{
int heapsize;
heapsize = length;
BULDMAXHEAP(A,length);
for(int i=length ;i >= 2;i--)
{
swap(A[i],A[1]);
heapsize--;
MAXHEAPIFY(A,heapsize,1);
}
}
习题
6.4-1:
6.4-2:
证明其算法的正确性
初始化:
第一次循环之前 ,i = A.length = n,子数组A[1,…n]的确是一个包含了第 i 个元素的最大堆,另一个数组A[n+1…(不纯在)的确也包含了0个已排序好的元素。
。因此,在进入迭代之前,循环不变式为真。
保持:
这里我真不会?,看了别人写的。
假设在第i 次迭代之前,循环不变式为真。那么此时子数组A[1…i] 包含了整个数组A[1…n] 中最小的i个元素,并且A[1…i]是一个最大堆,因此A[1] 保存了子数组A[1…i]中的最大元素,也就是整个数组A[1…n] 中第n−i+1大的元素(有n−i 个元素比它大,所以按从大到小顺序,它是第
终止:
最后一次时,i = 1 ,此时 A[1,1]是含有第1个元素的最大堆,另一个数组A[2,…n]是已经排好序的数组,又因为A[1]是最大值,明显的从A[1,…n]都已经排好序。
6.4-3
无论什么序排列,都要建立堆,即运行时间为O(n),
n次的MAXHEAPIFY操作所以需要时间 = O(nlgn))
因此运行时间为O(nlgn)
6.4-4
排序好像,下届都是这个?
-
优先队列
一种用于维护一组元素构成的集合S的数据结构,其中的每个元素都有一个相关的值,称为关键字key。
一个最大优先队列支持以下操作 -
INSERT(S,X):把元素x插入集合S中
伪代码:
1 A.heap-size = A.heap-size +1
2 A[A.heap-size] = -INF
3 INCREASE-KEY(A , A.heap-size ,key)
代码:
void Insert(int A[ ], int key, int &length)
{
length++;
A[length] = -1;
HeapIncreaseKey(A, length, key, length);
}
算法分析:O(lgn)
- MAXMUM(S) : 返回S中具有最大key的元素
这个就是A[I] - EXTRACT-MAX(S):去掉并返回S中的最大元素
伪代码:
1. if A.size < 1
2. error “heap underflow”
3. max = A[1]
4. A[1]=A[A.size]
5. A.size = A.size - 1
6. MaxHeapity (A,1)
7. return max
代码:
int Extractmax(int A[],int length)
{
if(length < 1)
{
cout<<"heap underflow"<<endl;
return 0;
}
int max;
max = A[1];
A[1] = A[length];
length--;
MAXHEAPIFY(A,length,1);
return max;
}
算法分析:时间复杂度为O(lgn)除了时间复杂度为O(lgn)的MAXHEAPFY之外,其他操作都是常熟阶。
-
INCREASE-KEY(S,X,K):将元素x的key值增加到k
伪代码1. if key < A[i] 2. error “new key is smaller than current key” 3. A[i]=key 4. while i>1 and A[parent(i)] < A[i] 5. exchange A[i] with A[parent(i)] 6. i=parent(i)
代码:
void Increasekey(int A[],int i,int key)
{
if(key < A[ i ])
{
cout<<"new key is smaller than current key"<<endl;
return ;
}
A[i] = key;
while(i > 1 && A[i / 2 ] < A[i])
{
swap(A[ i ],A[i / 2]);
i = i /2 ;
}
}
算法分析:包含n个元素的堆上,时间复杂度为O(lgn),做了更新结点到根结点的路径长度为O(lgn)
习题:
6.5-1
6.5-2
6.5-3
HEAP-MINMUM
int MINMUM( A )
return A[ 1 ]
HEAP-Extract-MIN
if A.heap-size < 1
error " heap underflow"
min = A [ 1 ]
A[ 1 ] = A[ A.heap-size ]
A.heap-size --
HEAP- DECREASE-KEY(A, 1 )
return min
f
if key > A[ i ]
error " new key is bigger than current key"
A[ i ] = key
while i > 1 and A[ PARENT( i ) ] > A [ i ]
exchange A[ i ] with A [ PARENT [ i ] ]
i = PARENT ( i )
MIN-HEAP-INSERT
A.heap-size ++;
A [ A.heap-size ] = +INF
HEAP- DECREASE-KEY(A ,A.heap-size , key)
6.5-4
由于堆数据结构由数组表示,并且通过减小数组的大小来实现删除,因此数组中可能存在未定义的值超过堆的末尾。
因此,MAX-HEAP-INSERT必须将插入节点的密钥设置为负无穷,以便HEAP-INCREASE-KEY不会失败。
6.5-5
初始化:
此时 i = n(n = A.heap-size),除了变化的A[ i ] 之外,其他都满足最大堆的性质
保持:
当新加入的A [ i ] 破坏了最大堆的性质时,会对它进行更新操作,让他和parent结交换,以来维持最大堆性质
终止:
while的终止条件表明,在迭代结束时,i 和它的父节点之间的max-heap属性被恢复,或者i是堆的根。我们通过循环不变量看到堆属性被恢复在迭代结束时。
6.5-6
其实我一般用swap
A[i]=key
while (i>0&&A[PARENT(i)]<=key)
{
A[i]=A[PARENT(i)]
i=PARENT(i)
}
A[i]=key
6.5-7
6.5-8