这里继续整理常见的排序算法.
本文介绍堆排序
type right by Thomas Alan 光风霁月023 .XDU
1. 构建二叉堆
二叉堆实现的重点是对堆这个类的设计以及元素上移和下移操作.
便于插入新元素和取出最大元素.
上移: 比较当前节点和父节点的大小决定是否移动
下移: 比较当前节点和子节点的大小决定是否移动
同时这里提供两种初始化二叉堆的方法
i) 初始化再插入元素: 时间复杂度为O(nlogn)
ii) 通过传入数组来自我构造堆: 时间复杂度为O(n)
注: 这里二叉堆的索引从位置1开始
首先是最大堆MaxHeap类的设计
这里给出代码
template
class MaxHeap
{
private:
Item* m_data = nullptr;
TINT32 m_count = 0;
TINT32 m_capacity = 0;
private:
void shiftUp(TINT32 k)
{
// 判断索引位置的元素和父节点的大小关系, 不符则换位
while (k > 1 && m_data[k / 2] < m_data[k])
{
swap(m_data[k / 2], m_data[k]);
k /= 2;
}
}
void shiftDown(TINT32 k)
{
// 确保这个节点有孩子(左孩子索引 <= 元素数量)
while (2 * k <= m_count)
{
TINT32 idj = 2 * k;
// 右孩子存在且右孩子比左孩子大
if (idj + 1 <= this->m_count && m_data[idj + 1] > m_data[idj])
{
idj += 1;
}
// 它本身比左右孩子大, 不做变动
if (m_data[k] >= m_data[idj])
{
break;
}
// 交换它和孩子的位置
swap(m_data[k], m_data[idj]);
k = idj;
}
}
public:
MaxHeap(TINT32 capacity)
{
this->m_data = new Item[capacity + 1];
this->m_capacity = capacity + 1;
}
MaxHeap(Item arr[], TINT32 num)
{
// 分配空间
this->m_data = new Item[num + 1];
this->m_capacity = num + 1;
// 赋值
for (TINT32 idx = 0; idx < num; idx++)
{
this->m_data[idx + 1] = arr[idx];
}
this->m_count = num;
// 从最后一个非叶子节点开始向前遍历, 进行shiftDown操作
for (TINT32 idx = this->m_count / 2; idx >= 1; idx--)
{
shiftDown(idx);
}
}
~MaxHeap()
{
delete[] this->m_data;
this->m_data = nullptr;
}
TINT32 size()
{
return this->m_count;
}
bool isEmpty()
{
return 0 == this->m_count;
}
void insert(Item item)
{
assert(this->m_count + 1 <= this->m_capacity); // 理论上, 插入新的元素如果超过了堆的容量需要申请新的空间, 暂时不做处理直接assert
m_data[this->m_count + 1] = item;
this->m_count++;
this->shiftUp(this->m_count);
}
Item extractMax()
{
assert(this->m_count > 0);
Item ret = m_data[1];
swap(m_data[1], m_data[this->m_count]); // 交换首尾元素
this->m_count--;
shiftDown(1); // 将换来的第一个元素下移
return ret;
}
};
这里给出程序入口
// 构造函数1
template
void heapSort1(T arr[], TINT32 num)
{
MaxHeap max_heap = MaxHeap(num);
for (TINT32 idx = 0; idx < num; idx++)
{
max_heap.insert(arr[idx]);
}
for (TINT32 idx = num - 1; idx >= 0; idx--)
{
arr[idx] = max_heap.extractMax();
}
}
// 构造函数2
template
void heapSort2(T arr[], TINT32 num)
{
MaxHeap max_heap = MaxHeap(arr, num);
for (TINT32 idx = num - 1; idx >= 0; idx--)
{
arr[idx] = max_heap.extractMax();
}
}
2. 原地堆排序
为了将一个数组升序排列, 我们可以把一个数组做成一个最大堆, 然后将数组最大的元素和末尾元素进行交换, 再将数组末尾之前的部分重新组成最大堆(对交换来的元素进行 shift down 操作), 如此循环.
此时为了方便维护, 二叉堆的索引从0开始.
索引idx节点的父节点索引为: (idx - 1) / 2,
左孩子索引为: 2 * i + 1,
右孩子索引为: 2 * i + 2.
下面是程序实现
// 1. shift down 操作
template
void __shiftDown(T arr[], TINT32 num, TINT32 k)
{
while (2 * k + 1 < num)
{
TINT32 idj = 2 * k + 1;
if (idj + 1 < num && arr[idj + 1] > arr[idj])
{
idj += 1;
}
if (arr[k] >= arr[idj])
{
break;
}
swap(arr[k], arr[idj]);
k = idj;
}
}
// 2. 入口
template
void heapSort(T arr[], TINT32 num)
{
// heapify, 构建堆
for (TINT32 idx = (num - 1) / 2; idx >= 0; idx--)
{
__shiftDown(arr, num, idx);
}
for (TINT32 idx = num - 1; idx > 0; idx--)
{
swap(arr[0], arr[idx]);
__shiftDown(arr, idx, 0);
}
}
3. 索引堆
如果堆元素是一个比较复杂的结构, 那么按照1.中的堆结构改变元素位置就变得比较复杂.
来回交换复杂结构的元素, 会带来很大的不必要的消耗.
因此我们可以引入索引堆的概念, 构造存储元素的索引的数组m_index_list, 将堆中的元素以及元素对应索引分开存储, 对元素进行位置改变时, 实际上只改变元素所对应的索引, 就可以容易地改变元素位置.
引入索引堆的概念后, 我们可以轻易实现两件事情:
i) 取出某一索引对应的元素 Item getItem(TINT32 idx)
ii) 将某一索引的元素替换为新的元素 void change(TINT32 idx, Item new_item). 需要在索引数组m_index_list中遍历找到需要替换的idx的元素在堆中真正的索引idj.
注: 这里提到的索引是m_data的存储索引idx, 元素在堆中实际的位置idj对用户来说是透明的
同时, 在进行操作 ii) 时, 需要遍历m_index_list数组, 因此我们可以再引入一个反向查找数组m_rev_index_list, 存储的是元素在堆中的实际位置, 这样就可以快速找到idj, idj = m_rev_index_list[idx]. 这个数组在每次元素变化时进行维护. 将其初始化为0数组.
下面是索引堆的设计, 在二叉堆的基础上, 新增的内容加粗表示
下面是代码实现
template
class IndexMaxHeap
{
private:
Item* m_data;
TINT32* m_index_list;
TINT32* m_rev_index_list;
TINT32 m_count = 0;
TINT32 m_capacity = 0;
private:
void shiftUp(TINT32 k)
{
while (k > 1 && m_data[this->m_index_list[k / 2]] < m_data[this->m_index_list[k]])
{
swap(this->m_index_list[k / 2], this->m_index_list[k]);
// 维护反向查找数组
this->m_rev_index_list[this->m_index_list[k / 2]] = k / 2;
this->m_rev_index_list[this->m_index_list[k]] = k;
k /= 2;
}
}
void shiftDown(TINT32 k)
{
while (2 * k <= m_count)
{
TINT32 idj = 2 * k;
if (idj + 1 <= this->m_count && m_data[this->m_index_list[idj + 1]] > this->m_index_list[m_data[idj]])
{
idj += 1;
}
if (m_data[this->m_index_list[k]] >= m_data[this->m_index_list[idj]])
{
break;
}
swap(this->m_index_list[k], this->m_index_list[idj]);
// 维护反向查找数组
this->m_rev_index_list[this->m_index_list[k]] = k;
this->m_rev_index_list[this->m_index_list[idj]] = idj;
k = idj;
}
}
public:
IndexMaxHeap(TINT32 capacity)
{
this->m_data = new Item[capacity + 1];
this->m_index_list = new TINT32[capacity + 1];
this->m_rev_index_list = new TINT32[capacity + 1];
for (TINT32 idx = 0; idx <= capacity; idx++)
{
this->m_rev_index_list[idx] = 0;
}
this->m_count = 0;
this->m_capacity = capacity;
}
~IndexMaxHeap()
{
delete[] this->m_data;
// 新增的需要释放的数组
delete[] this->m_index_list;
delete[] this->m_rev_index_list;
}
TINT32 size()
{
return this->m_count;
}
bool isEmpty()
{
return 0 == this->m_count;
}
// 插入元素的同时需要指定元素的索引
void insert(TINT32 idx, Item item)
{
assert(this->m_count + 1 <= this->m_capacity);
// 对索引是否越界的检测, 维护
assert(idx + 1 >= 1 && idx + 1 <= this->m_capacity);
idx += 1;
// 数据列idx是item
this->m_data[idx] = item;
// 在index_list的末尾是添加了新的索引idx
this->m_index_list[this->m_count + 1] = idx;
// 维护反向查找数组
this->m_rev_index_list[idx] = this->m_count + 1;
this->m_count++;
this->shiftUp(this->m_count);
}
Item extractMax()
{
assert(this->m_count > 0);
Item ret = m_data[this->m_index_list[1]];
swap(this->m_index_list[1], this->m_index_list[this->m_count]);
// 维护反向查找数组
this->m_rev_index_list[this->m_index_list[1]] = 1;
this->m_rev_index_list[this->m_index_list[this->m_count]] = 0;
this->m_count--;
shiftDown(1);
return ret;
}
TINT32 extractMaxIndex()
{
assert(this->m_count > 0);
TINT32 ret = this->m_index_list[1] - 1;
swap(this->m_index_list[1], this->m_index_list[this->m_count]);
this->m_rev_index_list[this->m_index_list[1]] = 1;
this->m_rev_index_list[this->m_index_list[this->m_count]] = 0;
this->m_count--;
shiftDown(1);
return ret;
}
Item getItem(TINT32 idx)
{
return this->m_data[idx + 1];
}
// 判断当前的索引堆是否包含idx对应索引的元素
bool contain(TINT32 idx)
{
assert(idx + 1 >= 0 && idx + 1 <= this->m_capacity);
return 0 != this->m_rev_index_list[idx];
}
void change(TINT32 idx, Item new_item)
{
// 判断当前的索引堆是否包含idx对应索引的元素
assert(contain(idx));
idx += 1;
this->m_data[idx] = new_item;
// 接下来需要对堆进行维护, 因此需要在索引数组m_index_list中找到索引idx所在位置记为idj
// idj即数据m_data[idx]在堆中实际的位置, 之后进行shiftUp, shiftDown
// 遍历m_index_list找到idj(不引入反向查找数组)
// for (TINT32 idj = 1; idj <= this->m_count; idj++)
// {
// if (this->m_index_list[idj] == idx)
// {
// shiftUp(idj);
// shiftDown(idj);
// return;
// }
// }
TINT32 idj = this->m_rev_index_list[idx];
shiftUp(idj);
shiftDown(idj);
}
};