(用于复习)
目录
树概念及结构
名词概念
二叉树概念及结构
特殊的二叉树
满二叉树
完全二叉树
运算性质
二叉树存储结构
顺序存储
链式存储
堆 - 顺序存储
堆的性质
堆的实现
堆的应用
堆排序
直接建堆法
概念:非线性的数据结构(形成的倒挂似树的结构 - 根朝上,叶朝下,子树之间不能有交集)。
由一个根节点加上两棵别称为左子树和右子树的二叉树组成 - 子树可为空。
每一个层的结点数都达到最大值,则结点总数:2^k - 1(K层数)。
特殊的完全二叉树 - 最后一层不满,但是是左到右是连续的。
(满二叉树是特殊的完全二叉树)
一个具有767个节点的完全二叉树,其叶子节点个数为()
A、383B、384C、385D、386------------------------------------------正确答案:B------------------------------------------解析:不要只想最后一层,倒数第二层也是会有叶子节点的。首先以:可以推算出是第1 ~ 9层为满二叉树,对应节点数:511。可以知道最后一层一定为叶子节点:256个。
然后根据完全二叉树是最后一层不满,但是是左到右是连续的,于是256 / 2 = 128,所以倒数第二层有128个是最后一层的父节点。
再根据:
可知倒数第二层有256个节点,于是叶子节点:256 + 256 - 128 = 384。
用数组来存储,适合表示完全二叉树。
用链表来表示一棵二叉树。
堆是一种特殊的完全二叉树,只不过父亲与儿子节点间有关系。顺序存储的完全二叉树典型的就是堆。(普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储)
#include
#include
namespace qcr_heap
{
typedef int HeapType;
struct Heap
{
int64_t _capacity; // 动态开辟可用大小
int64_t _size; // 实际数据占用大小
HeapType *_array; // 动态开辟一维数组
};
/*********
* 初始化堆
*********/
void HeapInit(Heap *heap)
{
assert(heap);
heap->_capacity = 0;
heap->_size = 0;
heap->_array = 0;
}
/*********
* 销毁堆
*********/
void HeapDestory(Heap *heap)
{
assert(heap);
heap->_capacity = 0;
heap->_size = 0;
free(heap->_array);
heap->_array = nullptr;
}
/*********
* 小根堆
*********/
bool less(HeapType element_1, HeapType element_2)
{
return element_1 < element_2;
}
/*********
* 大根堆
*********/
bool greater(HeapType element_1, HeapType element_2)
{
return element_1 > element_2;
}
/*********
* 交换数据
*********/
void swap(HeapType *element_1, HeapType *element_2)
{
HeapType tmp = *element_1;
*element_1 = *element_2;
*element_2 = tmp;
}
/*****************************
* 向上调整
* heap: 输入型参数,堆地址
* child: 输入型参数,排序的插入节点
* Func: 输入型参数,大小堆
*****************************/
void AdjustUp(Heap *heap, int64_t child, bool (*Func)(HeapType, HeapType))
{
assert(heap);
int64_t parent = (child - 1) / 2;
while (child > 0)
{
if (Func(heap->_array[child], heap->_array[parent]))
{
swap(&(heap->_array[child]), &(heap->_array[parent]));
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
/*****************************
* 向下调整
* heap: 输入型参数,堆地址
* root: 输入型参数,排序的根节点
* Func: 输入型参数,大小堆
*****************************/
void AdjustDown(Heap *heap, int64_t root, bool (*Func)(HeapType, HeapType))
{
assert(heap);
int64_t parent = root;
int64_t child = parent * 2 + 1;
while (child < heap->_size)
{
if (child + 1 < heap->_size && Func(heap->_array[child + 1], heap->_array[child]))
{
child++;
}
if (Func(heap->_array[child], heap->_array[parent]))
{
swap(&(heap->_array[child]), &(heap->_array[parent]));
parent = child;
child = parent * 2 + 1;
}
else
{
break; // 符合堆就成立了,就没必要进行交换了。
}
}
}
/*****************************
* 存入数据
* heap: 输入型参数,堆地址
* data: 输入型参数,插入的数据
* Func: 输入型参数,大小堆
*****************************/
void HeapPush(Heap *heap, HeapType data, bool (*Func)(HeapType, HeapType))
{
assert(heap);
if (heap->_capacity == heap->_size)
{
int64_t newcapacity = heap->_capacity == 0 ? 5 : heap->_capacity * 2;
HeapType * tmp = (HeapType *)realloc(heap->_array, heap->_capacity*sizeof(HeapType);
if (tmp == nullptr)
{
printf("Capacuty Get Error!\n");
exit(-1);
}
heap->_array = tmp;
heap->_capacity = newcapacity;
}
heap->_array[heap->_size] = data;
AdjustUp(heap, heap->_size, Func);
(heap->_size)++;
}
/*****************************
* 按顺序全部输出
* heap: 输入型参数,堆地址
*****************************/
void HeapPrint(Heap *heap)
{
assert(heap);
for (uint64_t i = 0; i < heap->_size; i++)
{
std::cout << heap->_array[i] << " ";
}
std::cout << '\n';
}
/*****************************
* 首元素
* heap: 输入型参数,堆地址
*****************************/
HeapType HeapTop(Heap *heap)
{
assert(heap);
assert(heap->_size > 0);
return heap->_array[0];
}
/*****************************
* 判空
* heap: 输入型参数,堆地址
*****************************/
bool HeapEmpty(Heap *heap)
{
assert(heap);
return heap->_size == 0;
}
/*****************************
* 有效数据个数
* heap: 输入型参数,堆地址
*****************************/
int HeapSize(Heap *heap)
{
assert(heap);
return heap->_size;
}
/*****************************
* 判空
* heap: 输入型参数,堆地址
* Func: 输入型参数,大小堆
*****************************/
void HeapPop(Heap *heap, bool (*Func)(HeapType, HeapType))
{
assert(heap);
assert(heap->_size > 0);
heap->_array[0] = heap->_array[heap->_size - 1];
(heap->_size)--;
AdjustDown(heap, 0, Func);
}
}
已知小根堆为8,15,10,21,34,16,12,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次数是()A、1B、2C、3D、4------------------------------------------正确答案:B------------------------------------------解析:首先我们需要知道,删除对应的调整算法是向下调整,所以其实在比较中有一个很重要的一项就是左右节点的比较,于是此处本质上的比较是需要在加上一次左右节点的比较。
利用堆删除思想来进行排序。
TOP-K问题
1. 用数据集合中前K个元素来建堆
面试题 17.14. 最小K个数 - 力扣(LeetCode)
class Solution
{
public:
// 向上建堆
void adjustUp(vector &nums, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (nums[child] > nums[parent])
{
swap(nums[child], nums[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
// 向下建堆
void adjustDown(vector &nums, int parent)
{
int child = parent * 2 + 1;
while (child < nums.size())
{
if (child + 1 < nums.size() && nums[child + 1] > nums[child])
{
child++;
}
if (nums[child] > nums[parent])
{
swap(nums[child], nums[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆排序的TOP-k问题
vector smallestK(vector &arr, int k)
{
vector nums;
nums.reserve(k);
// 前K个元素来建堆
for (int i = 0; i < k; i++)
{
nums.push_back(arr[i]);
adjustUp(nums, nums.size() - 1);
}
// 对比堆顶元素
if (k != 0)
{
for (int i = k; i < arr.size(); i++)
{
if (arr[i] < nums[0])
{
nums[0] = arr[i];
adjustDown(nums, 0);
}
}
}
return nums;
}
};
并不是最优的,并且还实现了两个堆算法,编码效率过低。
原本利用向上建堆的方式,是并不够完美的,建堆的时间复杂度为O(N)。
而直接建堆法时间复杂度O(logn),其根本是利用向下建堆实现。
for (int i = (size - 1 - 1) / 2; i >= 0; i--)
{
ADjustDown(nums, i);
}
class Solution
{
public:
// 向下建堆
void adjustDown(vector &nums, int parent)
{
int child = parent * 2 + 1;
while (child < nums.size())
{
if (child + 1 < nums.size() && nums[child + 1] > nums[child])
{
child++;
}
if (nums[child] > nums[parent])
{
swap(nums[child], nums[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆排序的TOP-k问题
vector smallestK(vector &arr, int k)
{
vector nums;
nums.reserve(k);
// 前K个元素来建堆
for (int i = 0; i < k; i++)
{
nums.push_back(arr[i]);
}
for(int i = (k - 1 - 1) / 2; i >= 0; i--)
{
adjustDown(nums, i);
}
// 对比堆顶元素
if (k != 0)
{
for (int i = k; i < arr.size(); i++)
{
if (arr[i] < nums[0])
{
nums[0] = arr[i];
adjustDown(nums, 0);
}
}
}
return nums;
}
};