算法笔记--数据结构--堆

堆的定义与基本操作

堆是一棵完全二叉树,树中每个结点的值都不小于(或者不大于)其左右孩子结点的值

  • 大顶堆:父亲结点的值大于或等于左右孩子的值
  • 小顶堆:父亲结点的值小于或等于左右孩子的值

对于给定初始序列,如何建堆?

给定数组 [85, 55, 82, 57, 68, 92, 99, 98, 66, 56]

建堆过程如下

1Snipaste_2020-04-15_18-47-34 2Snipaste_2020-04-15_18-47-51 3Snipaste_2020-04-15_18-48-09 Snipaste_2020-04-15_18-54-43 Snipaste_2020-04-15_18-55-16 Snipaste_2020-04-15_18-55-46

建堆

利用数组来存储完全二叉树,这样结点就按层序存储与数组中,其中第一个结点将存储于数组的1号位,并且数组i号位表示的结点的左孩子就是2i号位,右孩子是2i+1号位

const int maxn = 100;
// heap为堆,n为元素个数
int heap[maxn], n = 10;

回顾之前的建堆过程会发现,每次调整都是把结点从上往下的调整。针对这种向下调整,调整方法是这样的:总是将当前结点V与它的左右孩子比较(如果有的话),假如孩子中存在权值比结点V的权值大的,就将其中权值最大的那个孩子结点与结点V交换;交换完毕后继续让结点V和孩子比较,直到结点V的孩子的权值都比结点V的权值小或是结点V不存在孩子结点。时间复杂度为O(logn)

// 对heap数组在[low, high]范围进行向下调整
// 其中low为欲调整结点的数组下标,high一般为堆的最后一个元素的数组下标
void downAdjust(int low, int high){
    int i = low, j = 2 * i; 	// i为欲调整的结点,j为其左孩子
    while(j <= high){			// 存在孩子结点
        if(j + 1 <= high && heap[j+1] > heap[j]){// 当右孩子存在且右孩子大于左孩子
            j = j + 1;			// 此时j为右孩子
        }
        if(heap[j] > heap[i]){	// 孩子权值最大结点大于父亲结点
            swap(heap[i], heap[j]);	// 交换最大权值孩子与父亲结点
            i = j;					// 保持i为欲调整结点,j为i的左孩子 
            j = i * 2;
        }else{
            break;					// 孩子的权值均比欲调整结点i小,调整结束
        }
    }
}

假设序列中元素的个数为n,由于完全二叉树的叶子结点个数为(left lceil frac{n}{2} ight ceil),因此数组下标在([1, left lfloor frac{n}{2} ight floor])范围内的结点都是非叶子结点,于是从(left lfloor frac{n}{2} ight floor)位置开始倒着枚举结点,对每个遍历到的结点i进行[i, n]范围的调整。倒着枚举是因为每次调整完一个结点后,当前子树中权值最大的结点就会处在根结点的位置,这样当遍历到其父亲结点是,就可以直接使用这个结果,保证每个结点都是以其为根结点的子树中的权值最大的结点

建堆的时间复杂度为O(n)

void createHeap(){
    for(int i = n / 2; i >= 1; i--){
        downAdjust(i, n);
    }
}

删除堆顶

只要最后一个元素覆盖堆顶元素,然后对根结点进行调整即可,时间复杂度为O(logn)

void deleteTop(){
    heap[1] = heap[n];
    n--;
    downAdjust(1, n);
}

添加一个元素

把想要添加的元素放在数组最后,然后进行向上调整操作。向上调整总是把欲调整结点与父亲结点比较,如果权值比父亲结点大,那么就交换其与父亲结点,这样反复比较,直到到达堆顶或是父亲结点的权值较大为止。向上调整的时间复杂度为O(logn)

void upAdjust(int low, int high){
    int i = high, j = i / 2;	// i为待调整结点,j为其父亲结点
    while(j >= low){
        if(heap[j] < heap[i]){
            swap(heap[i], heap[j]);
            i = j;
            j = i / 2;
        }else{
            break;
        }
    }
}

所以,添加元素代码:

void insert(int x){
    heap[++n] = x;		// 让元素个数为1,然后将数组末位赋值为x
    upAdjust(1, n);		// 向上调整新加入的结点n
}

堆排序

堆排序是指使用堆结构对一个序列进行排序

void heapSort(){
    createHeap();					// 建堆
    for(int i = n; i > 1; i--){		// 倒着枚举,直到堆中只有一个元素
        swap(heap[i], heap[1]);		// 交换heap[i]与堆顶
        downAdjust(1, i - 1);		// 调整堆顶
    }
}

Snipaste_2020-04-15_22-01-33Snipaste_2020-04-15_22-02-36

Snipaste_2020-04-15_22-05-44 Snipaste_2020-04-15_22-06-18

Snipaste_2020-04-15_22-07-35

Snipaste_2020-04-15_22-07-35

Snipaste_2020-04-15_22-06-18Snipaste_2020-04-15_22-07-35Snipaste_2020-04-15_22-07-35Snipaste_2020-04-15_22-07-35

Snipaste_2020-04-15_22-07-35

Write by Gqq

原文地址:https://www.cnblogs.com/zgqcn/p/12708903.html