堆、堆排序

1. 堆
  • 1.1 堆的定义:
    1) 堆的父节点总是大于或小于所有子节点
    2) 堆总是一棵完全二叉树

      完全二叉树:为满二叉树的子集
      满二叉树:每一层的节点数都是最大节点数
    
  • 1.2 堆的实现:
    堆的建立、插入一个元素、删除一个元素、堆排序

    • 1 )堆的建立 O(n)
      堆逻辑上是个完全二叉树,物理上可以用数组实现,完全二叉树经过位置调整后满足父节点小于等于或者大于等于子节点条件后形成堆

      • a、需要一个完全二叉树
      • b、对完全二叉树中的小二叉树进行位置调整:
        具体调整方法就是从下向上、从左向右依次将非叶子节点作为根进行最大或最小堆调整
    • 2 ) 堆插入一个元素:

      • 插入元素放在末尾,向上位置调整
    • 3 ) 堆删除一个元素:

      • 交换堆顶和堆底,取出堆底的即为最大最小值,将交换后的堆顶向下调整
cpp/python 堆建立、添加元素、删除元素 实现
  • cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

void print(const vector<int> &v)
{
    for (auto i : v) {
        cout << i << " ";
    }
    cout << endl;
}

// 初始化建堆需要对每个非叶子节点进行位置调整(建堆的复杂度为O(n)**)
void heapAdjustDown(vector<int> &v, int index, int len); //  调整的方向是每个元素进行向下调整
void heapAdjustUp(vector<int> &v); //  调整的方向是每个元素进行向上调整

// 初始化建堆需要对每个非叶子节点进行位置调整(建堆的复杂度为O(n)**)
void makeHeap(vector<int> &v)
{
    for (int i = v.size()/2; i > 0; i--) {  // 最后一个叶子节点的父节点 用序号/2, 序号从1开始
        heapAdjustDown(v, i, v.size());
    }
}
void heapAdjustDown(vector<int> &v, int index, int len)
{
    int temp = v[index - 1]; // 取出将要调整的节点 ;取容器内元素时下标索引需要index - 1(以下操作均涉及容器下标取值index-1)
    for (int j = 2 * index; j <= len; j *= 2) { // 取子节点,子节点不大于总共节点数,每次移动的步骤为2倍,到达下一层数的子节点。(移动到下一层也可以用递归实现)
        if (j < len && v[j - 1] < v[j + 1 - 1]) { // 找出最大的子节点,j是左子树,所以不能等于len
            j++;
        }
        if (temp > v[j - 1]) { // 如果父节点大于子节点中的最大值(**不能用v[index - 1]去和v[j-1]比较)
            break;
        }
        v[index - 1] = v[j - 1]; // 这个只适用于当前调整树的子树是已经调整好的情况(只调整本小树内情况)
        index = j; // 调整待填充位置, 进行下一轮
    }
    v[index - 1] = temp; // 最后确定的调整位置
}

void heapAdjustUp(vector<int> &v) //  调整最后加入的元素,方向是每个元素进行向上调整
{
    int i = v.size();
    int tmp = v[i - 1];
    while(i >= 1) {
        if (tmp < v[i / 2 - 1]) { // **不能用v[i-1]去比较
            break;
        }
        v[i - 1] = v[i / 2 - 1];
        i /= 2;
    }
    v[i - 1] = tmp;
}

int main()
{
    vector<int> v = {1,24,54,65,34,3,2,56654,342,2,1};
    vector<int> v2 = {1,24,54,65,34,3,2,56654,342,2,1};
    print(v);
    // 构建堆:对完全二叉树由下向上、由左向右、从len/2个节点位置开始调整
    makeHeap(v);
    print(v);
    make_heap(v2.begin(), v2.end()); // 用库函数来校验
    print(v2);

    // 插入一个元素:先对容器插入,在进行调整
    v.push_back(222);
    // heapAdjustUp(v);  // 只调整插入元素
    makeHeap(v); // 重新建堆动作
    v2.push_back(222);
    push_heap(v2.begin(), v2.end());
    print(v);
    print(v2);

    // 删除元素:先交换堆顶,在进行n-1个元素的位置调整
    cout << v[0] << endl;
    v[0] = v[v.size()-1];
    v.resize(v.size()-1);
    heapAdjustDown(v, 1, v.size());
    print(v);
    pop_heap(v2.begin(), v2.end());
    v2.pop_back();
    print(v2);
}
/**
1 24 54 65 34 3 2 56654 342 2 1 
56654 342 54 65 34 3 2 1 24 2 1 
56654 342 54 65 34 3 2 1 24 2 1 
56654 342 222 65 34 54 2 1 24 2 1 3 
56654 342 222 65 34 54 2 1 24 2 1 3 
56654
342 65 222 24 34 54 2 1 3 2 1 
342 65 222 24 34 54 2 1 3 2 1
*/
  • python
# 向下调整堆
def heapAdjustDown(L, i, lenx):
    j  = i * 2
    length = len(L)
    while True:
        if j > length + 1:
            break
        if j < length and L[j - 1] < L[j + 1 - 1]:
            j += 1
        if L[i - 1] > L[j - 1]:
            break
        L[i - 1], L[j - 1] = L[j - 1], L[i - 1]
        i = j
        j *= 2

# 向上调整堆
def heapAdjustUp(L):
    length = len(L)
    while True:
        if L[length - 1] < L[length // 2 -1]:
            break
        L[length - 1], L[length // 2 -1] = L[length // 2 -1], L[length - 1]
        length //= 2


# 初始化堆
def makeHeap(L):
    lenx = len(L)//2
    for i in range(lenx, 0, -1):
        heapAdjustDown(L, i, lenx)


# 测试 -- 初始化堆
L = [1, 24, 54, 65, 34, 3, 2, 56654, 342, 2, 1]
print(L)
makeHeap(L)
print(L)

# 测试 -- 添加元素
L.append(222)
heapAdjustUp(L)
print(L)

# 测试 -- 删除元素
L[0], L[len(L) - 1] = L[len(L) - 1], L[0]
L = L[:len(L)- 1]
print(L)
heapAdjustDown(L, 1, len(L))
print(L)

'''
[1, 24, 54, 65, 34, 3, 2, 56654, 342, 2, 1]
[56654, 342, 54, 65, 34, 3, 2, 1, 24, 2, 1]
[56654, 342, 222, 65, 34, 54, 2, 1, 24, 2, 1, 3]
[3, 342, 222, 65, 34, 54, 2, 1, 24, 2, 1]
[342, 65, 222, 24, 34, 54, 2, 1, 3, 2, 1]
'''

2. 堆排序:复杂度为nlog(n)

堆本身满足的条件是父节点大于或者小于子节点,则堆顶是个最大或者最小值

  • a) 取出堆顶值和末尾元素调换
  • b) 在末尾元素调换值堆顶时做位置调整(这时调整为n-1个节点)
for (i = L->length; i > 1; i--) { // 每次交换一个堆顶
	swap(L, 1, i);
	HeapAdjust(L, 1, i-1); 
}

3. c++ 相关堆api

push和pop的操作通常配合容器,如pop某个元素,先用pop_heap将heap顶元素和末尾元素交换并将剩下的n-1进行位置调整,再调用容器对应的api取出最后一个位置的元素,这个元素即为原来的堆顶元素

make_heap: 根据指定的迭代器区间以及一个可选的比较函数,来创建一个heap. O(N)
	template <class RandomAccessIterator>
	void make_heap (RandomAccessIterator first, RandomAccessIterator last);
	
	template <class RandomAccessIterator, class Compare>
	void make_heap (RandomAccessIterator first, RandomAccessIterator last, Compare comp );
// push和pop的操作通常配合容器,如pop某个元素,先用pop_heap将heap顶元素和末尾元素交换并将剩下的n-1进行位置调整,再调用容器对应的api取出最后一个位置的元素,这个元素即为原来的堆顶元素
push_heap: 把指定区间的最后一个元素插入到heap中. O(logN)
pop_heap: 弹出heap顶元素, 将其放置于区间末尾. O(logN)
sort_heap:堆排序算法,通常通过反复调用pop_heap来实现. N*O(logN) 默认是小堆

is_heap: 判断给定区间是否是一个heap. O(N)
is_heap_until: 找出区间中第一个不满足heap条件的位置. O(N)

c++优先队列,priority_queue 每次返回的都是队里中最大或最小的值,底层实现依赖堆.
	普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。
	在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。
	优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值