堆排序
1. 堆简单介绍,数据存储及堆上定义的操作
2. 堆排序简单实现及算法的时间复杂度
3. 代码下载
1. 堆简单介绍,数据存储及堆上的定义的操作
二叉堆在本文中使用数组(.net中的List)来存储,它可以完全被看作是一颗二叉树。
除了叶子节点外,其他每层都是满的。二叉堆可以分为最大堆和最小堆。最大堆定义如下:
有定义可知,最大堆中中根元素是最大的。最小堆定义与最大堆定义正好相反,最小堆中根元素是最小元素。下面定义堆上的操作。
1. 给定某个节点下标i,该节点的父节点PARENT(i) = floor(i / 2)
2. 给定某个节点下标i,该节点的左孩子LEFT(i) = 2 * i
3. 给定某个节点下标i,该节点的右孩子RIGHT(i) = 2 × i + 1;
4. 定义操作MaxHeapfiy,该函数保持堆栈特性,时间负责度lg(n)
5. 定义堆排序HeapSort,时间复杂度nlg(n)
6. 下面定义有限队列操作。HeapMaxium返回堆栈中最大元素
7. HeapExtractMax函数返回堆中最大元素,同时将最大元素从堆栈中删除
8. HeapInsert向堆栈中插入一个元素
9. HeapIncreaseKeyValue增加堆中某个元素的值
2. 堆排序的简单实现及算法的时间复杂度
下面是上面定义操作的伪代码实现:
MaxHeapfiy:
BuildMaxHeap:
HeapSort:
实现代码如下:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Alice.DataStructure
{
public class Heap
{
// 数据存储
private List<int> heapData = new List<int>();
private int length;
#region 构造函数
public Heap(List<int> data)
{
this.heapData = data;
this.length = data.Count;
}
// 默认构造函数
public Heap()
{
this.length = 0;
}
#endregion
#region 堆上操作
// 父节点下标
public int Parent(int i)
{
// 向下取整
return (int)(i / 2);
}
// 左孩子下标
public int Left(int i)
{
// 由于list的第一个元素的下标是0
return (2 * i) + 1;
}
// 右孩子下标
public int Right(int i)
{
// 由于list的第一个元素的下标是0
return (2 * i + 2);
}
// 建立最大堆,这里假设left和right对应的子树已经
// 是最大堆
public void MaxHeapify(int i)
{
int left = this.Left(i);
int right = this.Right(i);
// 递归出口条件
// 1. left超界,return
// 2. left未超界,right超界,继续
// 3. left未超界,right未超界,继续
if ((left > (this.length - 1)))
return;
// 查找i,left,right中的最大值
int largest = i; // largest最大元素下标
// 这里需要加上限制条件来判断是否能够使用left和right
if ( (left <= this.length - 1) && (this.heapData[left] > this.heapData[largest]))
largest = left;
if ( (right <= this.length - 1) && (this.heapData[right] > this.heapData[largest]))
largest = right;
// 递归调用
int tmp;
if (largest != i)
{
// 交换largest和i的值
tmp = this.heapData[largest];
this.heapData[largest] = this.heapData[i];
this.heapData[i] = tmp;
// 递归调用
this.MaxHeapify(largest);
}
}
// 建立最大堆,调用MaxHeapify
public void BuildMaxHeap()
{
// 从数组的最后位置开始计算
int last = (this.length + 1) / 2 - 1;
for (int i = last; i >= 0; --i)
{
this.MaxHeapify(i);
}
}
// 堆排序
public void HeapSort()
{
this.BuildMaxHeap();
Console.WriteLine(this.ToString());
int tmp;
// 交换第一个元素和最后一个元素
for (int i = 0; this.length > 0; ++i )
{
// 交换第一个元素和最后一个元素
tmp = this.heapData[0];
this.heapData[0] = this.heapData[this.length - 1];
this.heapData[this.length - 1] = tmp;
// 堆长度减少1
this.length--;
// 保持堆特性
this.MaxHeapify(0);
}
}
// 输出堆栈中内容
public override string ToString()
{
System.Text.StringBuilder builder =
new System.Text.StringBuilder();
for (int i = 0; i < this.length - 1; ++i )
{
builder.Append(this.heapData[i] + "\t");
}
return builder.ToString();
}
#endregion
#region 优先队列操作
// 仅仅是返回最大堆中的最大元素
public int HeapMaximum()
{
return this.heapData[0];
}
// 提取最大堆的最大元素,并将该元素删除
public int HeapExtractMax()
{
// 得到最大值
int max = this.heapData[0];
// 交换最后一个和第一个元素
this.heapData[0] = this.heapData[this.length - 1];
this.length--;
// 保持堆特性
this.MaxHeapify(0);
// 返回最大值
return max;
}
// 增加堆中第i个元素的值为val
public void HeapIncreaseKeyValue(int i, int val)
{
if (val < this.heapData[i])
return;
this.heapData[i] = val;
// 保持堆特性
int tmp;
//
while ( (i >= 0) &&
(this.heapData[i] > this.heapData[this.Parent(i)]))
{
tmp = this.heapData[i];
this.heapData[i] = this.heapData[this.Parent(i)];
this.heapData[this.Parent(i)] = tmp;
i = this.Parent(i);
}
}
// 向堆大队中插入元素val
public void HeapInsert(int val)
{
this.length++;
this.heapData.Add(int.MinValue);
this.HeapIncreaseKeyValue((this.length - 1), val);
}
#endregion
public int Get(int i)
{
if (i > this.length - 1)
throw new Exception();
return this.heapData[i];
}
}
}
其中需要注意的是:
1. 如何对所有情况进行分类讨论。
2. 对于函数传递的参数,不去假设出入参数的可用性,需要进行参数的检查。
3. 递归算法中如何确定递归的终止条件:只能通过简单的实例来验证,然后debug,最终确定递归终止条件。
3. 代码下载
作者:许强1. 本博客中的文章均是个人在学习和项目开发中总结。其中难免存在不足之处 ,欢迎留言指正。 2. 本文版权归作者和博客园共有,转载时,请保留本文链接。