二叉堆

二叉堆

我们知道堆栈是一种LIFO(后进先出)结构,队列是一种FIFO(先进先出)结构,而二叉堆是一种最小值先出的数据结构,因此二叉堆很适合用来做排序

二叉树的性质:二叉堆是一棵完全二叉树,且任意一个结点的键值总是小于或等于其子结点的键值,

二叉堆采用数组来存储(按广度优先遍历的顺序),而没有像普通的树结构使用指针来表示节点间的关系。

如下图所示:

如图,最小的一个元素就是数组第一个元素。

将二叉堆按最广优先遍历的顺序编号从0到N-1,根据完全二叉树的性质,则第n个结点的子结点分别是2n+1和2n+2,其父结点是(n-1)/2。并且,叶子结点的下标是从 (N-1)/2开始,一直到N-1。这样,对于数组中的任意一个元素,我们可以很方便的计算出它的父节点、孩子节点所在的位置(即数组下标)。


插入

如果要在二叉堆中插入或删除一个元素,必须保证堆性质仍能满足。

新插入的节点new 放在完全二叉树最后的位置,再和父节点比较。如果new节点比父节点小,那么交换两者。交换之后,继续和新的父节点比较…… 直到new节点不比父节点小,或者new节点成为根节点。这样得到的树,就恢复了堆的性质。

 


删除

删除操作一定是踢出数组的第一个元素(即根节点),这么来第一个元素以前的位置就成了空位,让最后一个节点 last成为新的节点,从而构成一个新的二叉树。再将last节点不断的和子节点比较。如果last节点比两个子节点中小的那一个大,则和该子节点交换。直到last节点不大于任一子节点都小,或者last节点成为叶节点。


C代码实现:

插入操作:

void fix_up_min_heap( int arr[], int n, int len, int i )  // n 为数组容量,len为当前长度  
{  
    int j = ( i - 1 ) / 2;  // parent index  
    int tmp = arr[i];  

    while (j >= 0 && tmp < arr[j]) {   
        arr[i] = arr[j]; 
        i = j;   

        if (j <= 0) break;  

        j = ( j - 1 ) / 2;  
    }   

    arr[i] = tmp;  
}  

int insert_min_heap( int arr[], int n, int * len, int x )   
{  
    if( n == *len ) return -1;  
    arr[(*len)++] = x;  
    fix_up_min_heap( arr, n, *len, *len - 1 );  
    return 1;  
}  

删除操作:

void fix_down_min_heap( int arr[], int n, int len, int i ) 
{  
    int j = 2 * i + 1;  
    int tmp = arr[i];  

    while( j < len ) {   
        if( j < len - 1 && arr[j + 1] < arr[j] ) ++j;  

        if( arr[j] < tmp ) {   
            arr[i] = arr[j];   
            i = j;   
            j = 2 * i + 1;  

        } else {   
            break;  
        }   
    }   

    arr[i] = tmp;  
}  
  
  
void pop_min_heap( int arr[], int n, int * len )   
{  
    if( *len <= 0 ) return;  
    arr[0] = arr[--*len];
    fix_down_min_heap( arr, n, *len, 0 );  
} 

最小堆和最大堆

最大堆:父结点的键值总是大于或等于任何一个子结点的键值;最大堆常用于排序算法。
最小堆:父结点的键值总是小于或等于任何一个子结点的键值;最小堆常用于优先队列。

堆的意义就在于:最快的找到最大/最小值,在堆结构中插入一个值重新构造堆结构,取走最大/最下值后重新构造堆结构;其时间复杂度为O(logN);

堆实践中用途不在于排序,其主要用在调度算法中,比如优先级调度,每次取优先级最高的,时间驱动,取时间最小/等待最长的 等等 ,分为最大堆/最小堆。

利用最大堆和最小堆可以求一个包含N个元素的数组的前K大(或小)的数,原理如下:

我们首先取这N个元素中的前K个元素来建立一个由K个元素组成的小(大)顶堆,这样堆顶元素便是当前已读取元素中的第K大(小)者;然后,依次读取剩下的N-K个元素,而对于其中的每个元素x,若x大于(小于)当前的堆顶元素,则将堆顶元素替换为x,并自堆顶至向下调整堆;这样,在读取完所有元素后,堆中的K个元素即为这N个数中的前K个最大(小)元素,同时堆顶元素为这N个数中的第K大(小)元素。

原文地址:https://www.cnblogs.com/chenny7/p/4108954.html