【数据结构与算法】堆排序、堆化 heapify

关于堆的介绍已经一大堆了,这里主要总结下笔记 && 对比下这几个 O(nlogn) 的排序效率。

基础知识

堆的基本知识:
大堆:堆顶元素大于左右孩子。
小堆:堆顶元素小于左右孩子。

堆的操作:
插入:在堆的最后位置插入一个元素,然后对这个元素进行向上调整,直至堆的结构稳定。
删除:弹出堆顶元素,可以将堆顶元素和最后一个叶子节点互换,然后对这个叶子节点进行向下调整。

堆的实现

// 大堆
class MaxHeap {
public:
	// 初始化一个空堆
	MaxHeap(const int cap) {
		m_data = new int[cap];
		m_cnt = 0;
		m_cap = cap;
	}
	
	int size() { return m_cnt; }

	// 插入一个元素
	void insert(const int item) {
		assert(m_cnt + 1 <= m_cap);
		m_data[++m_cnt] = item;
		__shiftUp(m_cnt);
	}

	// 删除堆顶元素
	int pop() {
		assert(m_cnt >= 0);
		int ret = m_data[0];
		swap(m_data[0], m_data[m_cnt--]);
		__shiftDown(0);
		return ret;
	}
private:
	// 向上调整
	void __shiftUp(int k) {
		// testPrintTree();
		// cout << "==============" << endl;
		int parent_index = (k - 1) / 2;
		while (k > 0 && m_data[k] > m_data[parent_index]) {
			swap(m_data[k], m_data[parent_index]);
			k = parent_index;
			parent_index = (k - 1) / 2;
		}
	}

	// 向下调整
	void __shiftDown(int k) {
		while (k * 2 + 1 <= m_cnt) {
			int j = k * 2 + 1;
			if (j + 1 <= m_cnt &&m_data[j] < m_data[j + 1]) ++j;
			if (m_data[k] >= m_data[j]) break;

			swap(m_data[k], m_data[j]);
			k = j;
		}
	}
private:
	int* m_data;
	int m_cnt;  // 有效元素个数
	int m_cap;  // 总容量
};

堆排序

实现方式 1:

先对一个无序的数组每个元素都 insert 入堆,完成后,每次弹出最大元素放在数组最后一个,这样就形成了一个有序的数组。

void heapSort1(int arr[], int n) {
	MaxHeap mh(n);
	for (int i = 0; i < n; ++i) {
		mh.insert(arr[i]);
	}

	for (int i = n - 1; i >= 0; --i) {
		arr[i] = mh.pop();
	}
}

插入操作时间复杂度为 O(logn),所以构建堆的过程为 O(nlogn)。弹出堆的时间复杂度也为 O(logn)。
空间复杂度为 O(n)。

实现方式 2:

heapify,堆化。

首先认为叶子节点为一个堆。
从最后一个非叶子节点开始,对此节点进行向下调整,至堆顶,这样堆化就完成了。

通过数学推导,heapify 的时间复杂度为 O(n)。

	// heapify, 使用 arr 初始化一个大堆
	MaxHeap(int* arr, int n) {
		m_data = new int[n];
		m_cap = n;
		for (int i = 0; i < n; ++i) {
			m_data[i] = arr[i];
		}
		m_cnt = n;

		for (int i = (m_cnt - 1 - 1)/2; i >= 0; --i) {
			__shiftDown(i);
		}
	}
void heapSort2(int arr[], int n) {
	MaxHeap mh(arr, n);
	for (int i = n - 1; i >= 0; --i) {
		arr[i] = mh.pop();
	}
}

此排序的空间复杂度为 O(n)。

heapify
实现方式 3:

先堆化,再原地对堆进行排序。
时间复杂度 O(n),空间 O(1)。

void __shiftDown(int arr[], int n, int k) {
	while (k * 2 + 1 < n) {
		int j = k * 2 + 1;
		// 左孩子 < 右孩子
		if (j + 1 < n && arr[j] < arr[j + 1]) {
			++j;
		}
		// parent > max(left, right)
		if (arr[k] > arr[j]) break;
		swap(arr[k], arr[j]);
		k = j;
	}
}

void heapSort3(int arr[], int n) {
	// heapify
	for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
		__shiftDown(arr, n, i);
	}

	// sort
	for (int i = n - 1; i > 0; --i) {
		swap(arr[0], arr[i]);
		__shiftDown(arr, i, 0);
	}
}

性能对比

对 O(nlogn) 级别的排序和堆进行比较:
堆排序的效率是稳定的,但是哺乳其他排序来得快。
性能对比

既然堆排序效率并没有其他的高,那为什么要有它呢?

堆的主要作用并不是一个静态的排序。

堆主要是为了维护一个动态变化的东西,比如操作系统的任务管理,根据优先级不用执行不同的操作。


EOF

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值