赞
踩
普通队列:先进先出,后进后出
优先队列:出队顺序和入队顺序无关,和优先级有关
动态选择优先级最高的人物执行,优先队列是动态执行的,每次都会更新队列。例如:在N个元素中选择前M个元素,用排序的方法复杂度为NlogN,而使用堆则为NlogM。优先队列的主要操作是:入队,出队(取出游戏那几最高的元素)
优先队列实现的比较:
入队 | 出队 | |
---|---|---|
普通数组 | O(1) | O(n) |
顺序数组(排序后的数组) | O(n) | O(1) |
堆 | O(lgn) | O(lgn) |
由表可以看出,普通数组实现优先队列入队时直接加入到数组末尾,复杂度为O(1);出队时遍历整个数组,复杂度为O(n)。
顺序数组因为是不断维护有序新的数组,出队直接取出队首即可,复杂度为O(1);而入队时则需要找到入队插入合适的顺序,复杂度为O(n)。
用堆这种数据结构实现优先队列要比另外两种平均复杂度要低一些。
最极端的情况下,对于总共N个请求,使用普通数组挥着书序数组,最差情况:O(n^2),而使用堆:O(nlgn)。
上面说到堆的时间复杂度为O(nlgn),可以想到堆是一个树型结构。最经典的实现就是二叉堆,这个二叉树有两个特点:
1.二叉堆的父节点永远大于两个子节点(最大堆);
2.二叉堆是一个完全二叉树(完全二叉树:除最后一层外,其余层都是满的,最后一层的子节点也都在最左边)。
注意:二叉堆并不意味着层数越高数值越大!
如图所示即为一个二叉堆:
因为二叉堆是一个完全二叉树,说以可以用数组来实现。当我们从上到下,从左到右对二叉堆的每一个结点进行编号时可以发现,左结点的序号是父节点的2倍,右节点是父节点序号的2倍+1。可以用如下公式就可以推出左右孩子结点的序号:
首先编写一个堆的骨架,代码如下(c++):
template<typename T> class MaxHeap{ public: // 构造函数, 构造一个空堆, 可容纳capacity个元素 MaxHeap(int capacity){ data = new T[capacity+1];//第一个元素不用 count=0;//初始化数量为0 } //析构函数释放数组 ~MaxHeap(){ delete[] data; } //获取堆元素的个数 int size(){ return count; } //堆是否为空 bool isEmpty(){ return count==0; } //获取元素值 T getValue(int index){ return data[index]; } private: T* data; int count;//堆的元素个数 };
向堆中添加一个元素相当于在数组最后添加一个元素,然后调节新加入元素的位置。
Shift Up操作即新加入的元素不断的和自己的父节点进行比较,如果不符合堆的定义就与父节点进行交换。如下动图所示,最大堆中添加了一个52的结点:
在原代码中添加shiftUp(int k)函数,insert(T item)函数。insert(T item)函数为public,shiftUp(int k)函数为private。
shiftUp(int k)函数主要代码:
void shiftUp(int k){
while( k>1 && data[k/2]<data[k]){
swap(data[k/2],data[k]);
k=k/2;
}
}
insert(T item)函数主要代码(动态添加数组容量,容量增加原来的一半):
//插入元素 void insert(T item){ //动态增加容量,当插入元素超过容量时,容量增加原来的一半 if(count+1>capacity+1){ T* data2=new T[capacity+1]; for(int i=0;i<=count;i++){ data2[i]=data[i]; } if(capacity==1){ capacity=capacity*2; }else{ capacity=capacity+capacity/2; } data=new T[capacity+1]; for(int i=1;i<=count;i++){ data2[i]=data[i]; } delete[] data2; } //插入元素 data[count+1]=item; count++; shiftUp(count);//调整 }
堆的出队只能取出优先级最大的元素,即根节点的元素。将最后一个元素放到根节点的位置,数量count减一,再将这个元素向下调整。
Shift Down操作即将元素与它的左右孩子比较,当它比左右孩子小时,则与左右结点中较大的进行交换。如下动图所示,最大堆中取出元素:
在原代码中添加shiftDown(int k)函数,extractMax()函数。extractMax()函数为public,shiftDown(int k)函数为private。
shiftDown(int k)函数主要代码:
void shiftDown(int k){ while(2*k<=count){//左结点小于元素数目 //先比较左右两个结点,确定编号j int j=k*2; if(j+1<=count&&data[j+1]>data[j]) j++; //如果当前结点比左右结点大,则退出循环 if(data[k]>data[j]) break; //否则data[k]和data[j]交换位置 swap(data[k],data[j]);//(交换函数可以做优化,改成赋值的形式,具体见排序那篇博客) k=j; } }
extractMax()函数主要代码:
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
T extractMax(){
assert( count > 0 );//元素数目要大于0
T ret=data[1];//要取出的数
swap(data[1],data[count])//和最后一个元素交换(交换操作可以改成赋值操作)
count--;
shiftDown(1);
return ret;
}
实现堆排序可以将数组的元素全部插入到中,再将堆中的元素全部重新取出放入数组中即得到有序的队列。
无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)。整个堆排序的整体时间复杂度为O(nlogn)。
template<typename T>
void heapSort1(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(n);
for( int i = 0 ; i < n ; i ++ )
maxheap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
将整个数组构建成一个堆有更好的方式,将一个数组构造成一个堆的过程叫做Heapify。
假设有如下图所示的数组,这个数组自动形成了一个二叉树,但还不是二叉堆。可以观察得到:
1.每一个叶子结点都构成一个堆(因为只有一个元素);
2.第一个非叶子结点序号为总数count / 2(如下图所示,第一个非叶子结点序号为10/2=5)。
Heapify的过程,从后向前的依次讨论不是叶子的结点,对它们依次进行Shift Down操作,使以改结点为根节点的二叉树为一个二叉堆。动画演示如下:
堆中重新编写一个构造函数,构造函数代码如下:
// 构造函数, 通过一个给定数组创建一个最大堆 // 该构造堆的过程, 时间复杂度为O(n) MaxHeap(T arr[], int n){ data = new T[n+1]; capacity=n; //数组的值赋值到data中 for(int i=0;i<n;i++){ data[i+1]=arr[i]; } count=n; for(int i=count/2;i>=1;i--){ shiftDown(i); } }
重新编写堆排序的函数,heapSort2借助我们的heapify过程创建堆。
此时, 创建堆的过程时间复杂度为O(n), 将所有元素依次从堆中取出来, 实践复杂度为O(nlogn)。
堆排序的总体时间复杂度依然是O(nlogn), 但是比上述heapSort1性能更优, 因为创建堆的性能更优。heapSort2代码如下:
template<typename T>
void heapSort2(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
heapSort1的效率要比heapSort2的效率低,堆排序的效率还是不如归并排序和快速排序。
结论:将n个元素逐个插入到一个空堆中,算法复杂度是O(nlogn),而Heapify的过程算法复杂度为O(n)。
之前说到的堆排序是将数组依次插入到堆中进行排序,整个过程又额外增加了n个空间,而实际上排序过程完全可以在原地进行,不需要额外的空间。
具体步骤:一个数组可以看成一个完全二叉树
步骤一:先通过Heapify的过程构造成一个堆,设构造后的堆第一个元素是v,v即是最大的元素,设最后一个元素为w,将v和w交换,此时蓝色的区域是排序的部分,橙色的部分便不满足二叉堆。
步骤二:对橙色部分进行Shift Down操作使之变成一个二叉堆,此时该部分变成红色,继续执行步骤一。
执行步骤如下图所示:
二叉堆的序号变成从0开始,最后一个飞叶子结点的索引为(count-2)/2,使用的索引公式如下图所示:
原地堆排序heapSort3实现代码如下:
// n:数组元素个数,k:对第k个元素进行shiftDown // 相对于shiftDown()只是索引从0开始了 template<typename T> void shiftDown2(T arr[], int n, int k){ while(2*k+1<=n){//左结点小于元素数目 //先比较左右两个结点,确定编号j int j=k*2+1; if(j+1<n&&arr[j+1]>arr[j]) j++; //如果当前结点比左右结点大,则退出循环 if(arr[k]>arr[j]) break; //否则data[k]和data[j]交换位置 swap(arr[k],arr[j]); k=j; } } // 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序 template<typename T> void heapSort3(T arr[], int n){ // 注意,此时我们的堆是从0开始索引的 // 从(最后一个元素的索引-1)/2开始 // 最后一个元素的索引 = n-1 for(int i=(n-2)/2;i>=0;i--){// Heapify的过程构造成一个堆 shiftDown2(arr, n, i); } for(int i=n-1;i>0;i--){ swap(arr[0],arr[i]); shiftDown2(arr, i, 0); } }
平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 | |
---|---|---|---|---|
插入排序 | O(n^2) | √ | O(1) | √ |
归并排序 | O(nlogn) | ╳ | O(n) | √ |
快速排序 | O(nlogn) | √ | O(logn) | ╳ |
堆排序 | O(nlogn) | √ | O(1) | ╳ |
排序算法的稳定性
稳定排序:对于相等元素,排序后,原来靠前的元素依然靠前。即相等元素的相对位置没有发生变化。如图所示:
插入排序是稳定的,是因为元素在和前一个元素比较时,当与前一个元素相等时就不进行交换了。动画演示如下图:
归并排序是稳定的,是因为元素在比较时,如果遇到相同的元素,就先和左边的元素替换。如下图所示,左右的元素3是相同的,先替换左边的3。动画演示如下图:
1.在一般的堆中,需要经常交换两个元素。如果元素十分复杂,比如每个位置上存的是一篇10万字的文章。那么交换它们的位置将产生大量的时间消耗。
2.由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。
3.可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效)
于是就有了索引堆的概念。
将数据和索引分开存储,真正构建堆的是由索引构成的。如下图所示,圆圈里存的是索引号:
当数组变成堆时,就变成了如下图所示:
数组对应的数据data没有改变,改变的是索引index的值。上图index的顺序就是堆的顺序,堆的元素索引为10,对应的元素值为62,以此类推。
索引堆的两大重要特点
1 比较的是真实元素的大小,交换的是对应的索引index的位置,真实的data数组并没有任何改变。数据和索引是分开存储的,这意味着索引数组是连续的,数据数组可以不连续。这一点我想了好久才明白(汗。。。)
2 访问数据元素,必须先找到其索引,即先找到index[]的值
注意:data[]数组我们是从1开始存储的,但是真实的索引是从0开始的
索引堆在图论算法中求最短路径以及最小生成树中都有应用。
相对于基本堆的实现,索引堆比它多了索引数组
int *indexes; // 最大索引堆中的索引数组
相应的构造函数和析构函数对索引数组进行初始化:
// 构造函数, 构造一个空的索引堆, 可容纳capacity个元素
IndexMaxHeap(int capacity){
data = new Item[capacity+1];
indexes = new int[capacity+1];
count = 0;
this->capacity = capacity;
}
~IndexMaxHeap(){
delete[] data;
delete[] indexes;
}
插入函数:(数据和索引是分开存储的,这意味着索引数组是连续的,数据数组可以不连续)
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 void shiftUp( int k ){ //先拿到索引,再比较索引对应的数据 while( k > 1 && data[indexes[k/2]] < data[indexes[k]] ){ swap( indexes[k/2] , indexes[k] ); k /= 2; } } // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item // 传入的i对用户而言,是从0索引的 void insert(int i, T item){ assert( count + 1 <= capacity ); assert( i + 1 >= 1 && i + 1 <= capacity ); i += 1;//从1开始索引 data[i] = item;//数据数组可能不是连续的 indexes[count+1] = i;//索引数组是连续的 count++; shiftUp(count); }
取出元素:
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 void shiftDown( int k ){ while( 2*k <= count ){ int j = 2*k; if( j + 1 <= count && data[indexes[j+1]] > data[indexes[j]] ) j += 1; if( data[indexes[k]] >= data[indexes[j]] ) break; swap( indexes[k] , indexes[j] ); k = j; } } // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据 T extractMax(){ assert( count > 0 ); T ret = data[indexes[1]];//找到索引对应的值 swap( indexes[1] , indexes[count] );//将索引交换 count--; shiftDown(1); return ret; }
最大索引堆的一些特殊操作:
取出最大元素的索引:
// 从最大索引堆中取出堆顶元素的索引
int extractMaxIndex(){
assert( count > 0 );
int ret = indexes[1] - 1;// 对用户而言,是从0索引的
swap( indexes[1] , indexes[count] );
count--;
shiftDown(1);
return ret;
}
将最大索引堆中索引为i的元素修改为newItem,修改后元素先找到其在堆中对应的位置,再先后进行shiftUp和shiftDown操作,代码如下:
// 将最大索引堆中索引为i的元素修改为newItem
void change( int i , T newItem ){
i += 1;//索引堆是从1开始的
data[i] = newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
// 之后shiftUp(j), 再shiftDown(j)
for( int j = 1 ; j <= count ; j ++ )//遍历整个索引数组,找到data[i]在堆中的位置j
if( indexes[j] == i ){
shiftUp(j);
shiftDown(j);
return;
}
}
上面将最大索引堆中索引为i的元素修改为newItem的过程中,重新维护index数组时是遍历整个index数组找到该索引在堆中的位置。
其实可以用反向查找的思路,单独设置一个rev数组来存放索引在堆中的位置。如下图所示,索引1在堆中的位置就是8,以此类推:
rev数组和index数组之间的关系如下图所示:
在原来索引堆中添加反向索引数组:
int *reverse; // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
相应的构造函数和析构函数修改:
// 构造函数, 构造一个空的索引堆, 可容纳capacity个元素 IndexMaxHeap(int capacity){ data = new Item[capacity+1]; indexes = new int[capacity+1]; reverse = new int[capacity+1]; for( int i = 0 ; i <= capacity ; i ++ ) reverse[i] = 0;//堆从索引为1开始,所以初始化为0是表示不存在 count = 0; this->capacity = capacity; } ~IndexMaxHeap(){ delete[] data; delete[] indexes; delete[] reverse; }
插入函数:
// 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item // 传入的i对用户而言,是从0索引的 void insert(int i, Item item){ assert( count + 1 <= capacity ); assert( i + 1 >= 1 && i + 1 <= capacity ); // 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。 assert( !contain(i) ); i += 1; data[i] = item; indexes[count+1] = i; reverse[i] = count+1; count++; shiftUp(count); }
取出元素对应的代码如下:
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 void shiftDown( int k ){ while( 2*k <= count ){ int j = 2*k; if( j + 1 <= count && data[indexes[j+1]] > data[indexes[j]] ) j += 1; if( data[indexes[k]] >= data[indexes[j]] ) break; swap( indexes[k] , indexes[j] ); reverse[indexes[k]] = k; reverse[indexes[j]] = j; k = j; } } // 从最大索引堆中取出 堆顶元素, 即索引堆中所存储的最大数据 Item extractMax(){ assert( count > 0 ); Item ret = data[indexes[1]]; swap( indexes[1] , indexes[count] ); reverse[indexes[count]] = 0; reverse[indexes[1]] = 1; count--; shiftDown(1); return ret; }
将最大索引堆中索引为i的元素修改为newItem,change函数如下:
// 看索引i所在的位置是否存在元素 bool contain( int i ){ assert( i + 1 >= 1 && i + 1 <= capacity ); return reverse[i+1] != 0; } // 将最大索引堆中索引为i的元素修改为newItem void change( int i , Item newItem ){ assert( contain(i) ); i += 1; data[i] = newItem; // 找到indexes[j] = i, j表示data[i]在堆中的位置 // 之后shiftUp(j), 再shiftDown(j) // for( int j = 1 ; j <= count ; j ++ ) // if( indexes[j] == i ){ // shiftUp(j); // shiftDown(j); // return; // } // 有了 reverse 之后, // 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置 shiftUp( reverse[i] ); shiftDown( reverse[i] ); }
回到之前说的那几个问题,操作系统中可以使用堆实现动态选择优先级最高的任务执行,当有新任务加入时就把其加入到堆中。
在10000个元素中找到前100个元素,可以将这10000个元素依次插入到容量为100的最小堆中,当放满元素后,每插入一个元素,将最小的元素出队,这样遍历完10000个元素后,堆中的元素就是前100的元素。
以后要解决的问题:多路归并排序,最大最小队列(同时得到最大值和最小值)(同时拥有一个最大堆和一个最小堆),二项堆,斐波那契堆???
template<typename T> class MaxHeap{ private: T* data; int count;//堆的元素个数 int capacity; void shiftUp(int k){ while( k>1 && data[k/2]<data[k]){ swap(data[k/2],data[k]); k=k/2; } } void shiftDown(int k){ while(2*k<=count){//左结点小于元素数目 //先比较左右两个结点,确定编号j int j=k*2; if(j+1<=count&&data[j+1]>data[j]) j++; //如果当前结点比左右结点大,则退出循环 if(data[k]>data[j]) break; //否则data[k]和data[j]交换位置 swap(data[k],data[j]); k=j; } } public: // 构造函数, 构造一个空堆, 可容纳capacity个元素 MaxHeap(int capacity){ data = new T[capacity+1];//第一个元素不用 count=0;//初始化数量为0 this->capacity = capacity; } // 构造函数, 通过一个给定数组创建一个最大堆 // 该构造堆的过程, 时间复杂度为O(n) MaxHeap(T arr[], int n){ data = new T[n+1]; capacity=n; //数组的值赋值到data中 for(int i=0;i<n;i++){ data[i+1]=arr[i]; } count=n; for(int i=count/2;i>=1;i--){ shiftDown(i); } } //析构函数释放数组 ~MaxHeap(){ delete[] data; } //获取堆元素的个数 int size(){ return count; } //堆是否为空 bool isEmpty(){ return count==0; } //获取元素值 T getValue(int index){ return data[index]; } //插入元素 void insert(T item){ //动态增加容量,当插入元素超过容量时,容量增加原来的一半 if(count+1>capacity+1){ T* data2=new T[capacity+1]; for(int i=0;i<=count;i++){ data2[i]=data[i]; } if(capacity==1){ capacity=capacity*2; }else{ capacity=capacity+capacity/2; } data=new T[capacity+1]; for(int i=1;i<=count;i++){ data2[i]=data[i]; } delete[] data2; } //插入元素 data[count+1]=item; count++; shiftUp(count);//调整位置 } // 从最大堆中取出堆顶元素, 即堆中所存储的最大数据 T extractMax(){ assert( count > 0 );//元素数目要大于0 T ret=data[1];//要取出的数 swap(data[1],data[count]); count--; shiftDown(1); return ret; } };
heapSort1(T arr[], int n):
template<typename T>
void heapSort1(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(n);
for( int i = 0 ; i < n ; i ++ )
maxheap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
heapSort2(T arr[], int n):
template<typename T>
void heapSort2(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
heapSort3(T arr[], int n):
// n:数组元素个数,k:对第k个元素进行shiftDown // 相对于shiftDown()只是索引从0开始了 template<typename T> void shiftDown2(T arr[], int n, int k){ while(2*k+1<=n){//左结点小于元素数目 //先比较左右两个结点,确定编号j int j=k*2+1; if(j+1<n&&arr[j+1]>arr[j]) j++; //如果当前结点比左右结点大,则退出循环 if(arr[k]>arr[j]) break; //否则data[k]和data[j]交换位置 swap(arr[k],arr[j]); k=j; } } // 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序 template<typename T> void heapSort3(T arr[], int n){ // 注意,此时我们的堆是从0开始索引的 // 从(最后一个元素的索引-1)/2开始 // 最后一个元素的索引 = n-1 for(int i=(n-2)/2;i>=0;i--){// Heapify的过程构造成一个堆 shiftDown2(arr, n, i); } for(int i=n-1;i>0;i--){ swap(arr[0],arr[i]); shiftDown2(arr, i, 0); } }
// 最大索引堆 template<typename Item> class IndexMaxHeap{ private: Item *data; // 最大索引堆中的数据 int *indexes; // 最大索引堆中的索引, indexes[x] = i 表示索引i在x的位置 int *reverse; // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置 int count; int capacity; // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 void shiftUp( int k ){ while( k > 1 && data[indexes[k/2]] < data[indexes[k]] ){ swap( indexes[k/2] , indexes[k] ); reverse[indexes[k/2]] = k/2; reverse[indexes[k]] = k; k /= 2; } } // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 void shiftDown( int k ){ while( 2*k <= count ){ int j = 2*k; if( j + 1 <= count && data[indexes[j+1]] > data[indexes[j]] ) j += 1; if( data[indexes[k]] >= data[indexes[j]] ) break; swap( indexes[k] , indexes[j] ); reverse[indexes[k]] = k; reverse[indexes[j]] = j; k = j; } } public: // 构造函数, 构造一个空的索引堆, 可容纳capacity个元素 IndexMaxHeap(int capacity){ data = new Item[capacity+1]; indexes = new int[capacity+1]; reverse = new int[capacity+1]; for( int i = 0 ; i <= capacity ; i ++ ) reverse[i] = 0; count = 0; this->capacity = capacity; } ~IndexMaxHeap(){ delete[] data; delete[] indexes; delete[] reverse; } // 返回索引堆中的元素个数 int size(){ return count; } // 返回一个布尔值, 表示索引堆中是否为空 bool isEmpty(){ return count == 0; } // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item // 传入的i对用户而言,是从0索引的 void insert(int i, Item item){ assert( count + 1 <= capacity ); assert( i + 1 >= 1 && i + 1 <= capacity ); // 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。 assert( !contain(i) ); i += 1; data[i] = item; indexes[count+1] = i; reverse[i] = count+1; count++; shiftUp(count); } // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据 Item extractMax(){ assert( count > 0 ); Item ret = data[indexes[1]]; swap( indexes[1] , indexes[count] ); reverse[indexes[count]] = 0; reverse[indexes[1]] = 1; count--; shiftDown(1); return ret; } // 从最大索引堆中取出堆顶元素的索引 int extractMaxIndex(){ assert( count > 0 ); int ret = indexes[1] - 1; swap( indexes[1] , indexes[count] ); reverse[indexes[count]] = 0; reverse[indexes[1]] = 1; count--; shiftDown(1); return ret; } // 获取最大索引堆中的堆顶元素 Item getMax(){ assert( count > 0 ); return data[indexes[1]]; } // 获取最大索引堆中的堆顶元素的索引 int getMaxIndex(){ assert( count > 0 ); return indexes[1]-1; } // 看索引i所在的位置是否存在元素 bool contain( int i ){ assert( i + 1 >= 1 && i + 1 <= capacity ); return reverse[i+1] != 0; } // 获取最大索引堆中索引为i的元素 Item getItem( int i ){ assert( contain(i) ); return data[i+1]; } // 将最大索引堆中索引为i的元素修改为newItem void change( int i , Item newItem ){ assert( contain(i) ); i += 1; data[i] = newItem; // 找到indexes[j] = i, j表示data[i]在堆中的位置 // 之后shiftUp(j), 再shiftDown(j) // for( int j = 1 ; j <= count ; j ++ ) // if( indexes[j] == i ){ // shiftUp(j); // shiftDown(j); // return; // } // 有了 reverse 之后, // 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置 shiftUp( reverse[i] ); shiftDown( reverse[i] ); } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。