堆排序-图片+代码详解

一,堆的有序性

使堆快速执行的性质是堆的有序性
每个节点都大于或者小于其儿子节点的关键字
结论:
堆的插入和删除都是O(logN)

数据结构入队出队
普通数组O(1)O(n)
顺序数组O(n)O(1)
O(logN)O(logN)

堆的结构性质:

①堆是一棵被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右被填入,
这样的树称为完全二叉树

容易证明:
②一棵高是h的完全二叉树有2h到2h+1-1个节点,这意味着完全二叉树的高是O(logN)
③堆中索引为i的元素,左儿子在2i,右儿子在2i+1,父节点在[i/2]位置上

二,堆的操作(最小堆)

①Insert(插入)

在末尾的空穴位置(数组下标为:current_size+1)作为待插入元素的预插入位置,
父节点插入的关键字比较大小,如果插入的更小,把父节点i/2拷贝到子节点i

注:hole就是空穴的意思!!

自己常犯的错误:

我自己手写堆排的时候就是经常把父节点和子节点弄混了,应该是比较父节点和关键字的值
而不是比较父节点和子节点的关键字值,哎。。。。因为把要在父节点和子节点中间插入一个
关键字。。

template<typename T>
void Heap<T>::insert(const T& ele) {
	int hole = ++m_current_size;//在新的位置创建一个空穴
	//hole==1时意味着到了堆顶,要跳出循环了,所以循环的条件就是i>1且空穴比父节点更小
	for (; hole > 1 && ele < m_array[hole / 2]; hole /= 2) {//父节点上移(hole/=2)
		m_array[hole] = m_array[hole / 2];//把父节点(hole/2)移入空穴(hole)中
	}
	m_array[hole] = ele;//循环终止可能是到了堆顶,或者父节点比小,最后要把元素放到空穴的位置

}

配上C++描述的《数据结构和算法分析》理解一下

在这里插入图片描述

不同插入方法的效率差别分析

有一个结论就是:N个节点的完全二叉树(堆),那么你第一个非有序的节点下标就是N/2,
因此从下标为N/2的节点开始下滤,效率最高!!

法一:效率高,复杂度O(N)

void buildHeap() {
		for (int i = current_size / 2; i >= 1; i--) {
			percolateDown(i);
		}
	}

线性的插入方法效率低
法二:复杂度:O(N * logN)

void buildHeap(T arr[],int n){
	for(int i=0;i< n ;i++){
		array[i+1]=arr[i];
	}
}

②DeleteMin

通常的做法是先用一个临时变量储存了堆顶元素,用于返回最小值,
然后把堆中最后一个元素先拷贝到堆顶,然后进行下滤,
原因是:

删除最小元的时候,由于堆中少了一个元素,堆的尾元素必须移动到某个合适的位置
由于删除了堆顶元素,尾元素放到堆顶是比较合适的

所以拷贝到堆顶之后(此时空穴就是堆顶),尾元素多了2个儿子,把尾元素和其两个儿子的较小者比较,
如果尾元素就是最小的了,那么删除操作就完成了,但通常这不太可能。
当儿子的较小者比尾元素更小,那么把儿子拷贝上去,空穴下移,空穴再和新的儿子的
较小者比较,如果儿子更小,空穴继续下移,否则,就把空穴填入之前的尾元素,删除完成。

我自己感觉呢,就是通过2次比较,得到(待删除元素tmp,2个儿子)3个元素中最小的那个然后把最小的那个放到当前的空穴中,(如果放入的是tmp,则完成删除)然后空穴下移,接着就是继续比较。。。
配上C++描述的《数据结构和算法分析》理解一下
在这里插入图片描述

template<typename T>
void Heap<T>::delete_min(T& ele)
{
	if (!isEmpty()) {
		ele = m_array[1];//对引用操作,使得函数不需要返回值了
		m_array[1] = m_array[m_current_size--];拷贝尾元素到堆顶
		percolateDown(1);
	}
}
template<typename T>
void Heap<T>::percolateDown(int hole)
 {
	T tmp = m_array[hole];
	int child;
	//判断有没有左儿子,如果没有儿子,就说明已经到了最底层了,直接把tmp放到空穴即可
	for (; 2 * hole <= m_current_size; hole = child) 
	{
		child = 2 * hole;//获取儿子的下标
		//先判断有没有右儿子,&&再判断哪个儿子更小,获取更小的儿子的下标
		if (child + 1 <= m_current_size && m_array[child + 1] < m_array[child]) 
		{
			child++;
		}
		if (m_array[child] < tmp) 空穴元素和儿子中的较小者比较
		{
			m_array[hole] = m_array[child];//儿子更小就拷贝更小的儿子节点到父节点(空穴)
		}
		else {
			break;//如果空穴更小,就说明不需要下滤了,直接跳出循环后把,待删除元素拷贝到空穴里面
		}
	}
	m_array[hole] = tmp;//待删除元素拷贝到空穴里面
}

完整堆排序代码如下:
先给出模板堆排序,再给出测试的主函数

#pragma once
#include<iostream>
#include<vector>
using namespace std;
template<typename T>
class Heap
{
private:
	vector<T>    m_array;
	unsigned int m_current_size;
public:
	Heap(const unsigned int size):m_array(size),m_current_size(0){
		buildHeap();
	}
	Heap(const vector<T> & v) : m_array(v.size()+10),m_current_size(v.size())
	{
		for (int i = 0; i < v.size(); i++) {
			m_array[i + 1] = v[i];
		}
		buildHeap();
	}
	void buildHeap();
	void percolateDown(int i);
	void insert(const T& ele);
	void delete_min(T& ele);
	void delete_min();
	bool isEmpty()const;
	bool isFull()const;
	void for_each();
};
template<typename T>
void Heap<T>::buildHeap() {
	for (int i = m_current_size / 2; i >= 1; i--) {
		percolateDown(i);
	}
}
template<typename T>
void Heap<T>::percolateDown(int hole) {
	T tmp = m_array[hole];
	int child;
	for (; 2 * hole <= m_current_size; hole = child) {
		child = 2 * hole;
		if (child + 1 <= m_current_size && m_array[child + 1] < m_array[child]) {
			child++;
		}if (m_array[child] < tmp) {
			m_array[hole] = m_array[child];
		}
		else {
			break;
		}
	}
	m_array[hole] = tmp;
}
template<typename T>
void Heap<T>::insert(const T& ele) {
	//if (!isFull()) {
		int hole = ++m_current_size;
		for (; hole > 1 && ele < m_array[hole / 2]; hole /= 2) {
			m_array[hole] = m_array[hole / 2];
		}
		m_array[hole] = ele;
	//}
	//else {
		//return;
	//}
}

template<typename T>
void Heap<T>::delete_min(T& ele)
{
	if (!isEmpty()) {
		ele = m_array[1];
		m_array[1] = m_array[m_current_size--];
		percolateDown(1);
	}
}

template<typename T>
void Heap<T>::delete_min()
{
	if (!isEmpty()) {
		m_array[1] = m_array[m_current_size--];
		percolateDown(1);
	}
}

template<typename T>
inline bool Heap<T>::isEmpty() const
{
	return m_current_size == 0;
}

template<typename T>
void Heap<T>::for_each()
{
	int a;
	while (!isEmpty()) {
		delete_min(a);
		cout << a << "\t";
	}
}
// MyHeap.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include"Heap.h"
#include <iostream>
#include<cstdlib>
#include<ctime>
int main()
{
    const int MAX = 50;
    srand(time(NULL));
    vector<int> v;
    Heap<int> heap(10);
    heap.insert(888);
    heap.insert(555);
    heap.insert(552);
    heap.insert(587);
    heap.insert(365);
    heap.insert(582);
    heap.insert(999);
    heap.for_each();
}

在这里插入图片描述

个人对堆操作的一些理解

《数据结构与算法分析C语言描述》P147页
①DecreaseKey降低关键字的值,有可能破坏堆的有序性,因此从指定下标P处改变关键字的值需要进行上滤操作!!
原因是本书前面讨论是最小堆,所以在P处降低关键字的值,不需要更新P下面的节点,相反,
有可能因为P的关键字变小了,比父节点更小,就是需要【上滤】了!!
②IncreaseKey提升关键字的值,同①的分析

对n个数组的前第k大个数的理解

最快的排序也就O(N*log(N)),直接暴力也就这样,但是堆排在这题里面比较特殊
可以优化到O(N+ k * log(N)),弹出来的时候控制一下输出格式,只弹出第k个就行

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿维的博客日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值