堆排序
介绍
- 堆排序是利用堆这种数据结构而设计的排序算法,堆排序是一种选择排序,最坏、最好、平均时间复杂度均为O(nlogn)
- 堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆。不要求节点左右孩子的大小关系。
- 每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
- 以大顶堆为例,按层序进行编号,映射到数组中可以得到 arr[i] >= arr[2i+1] arr[i] >= arr[2i+2] (i从0开始编号)
- 一般升序采用大顶堆,降序采用小顶堆
堆排序的基本思想
- 将待排序序列构造成一个大顶堆
- 此时,整个序列的最大值就是堆顶的根节点
- 将其与末尾元素进行交换,此时末尾就为最大值。
- 然后将剩余n-1个元素重新构造成一个堆,这样就会得到 n个元素的次小值,如此反复进行,就能够得到一个有序序列了。
构造初始堆
- 找到最后一个非叶子节点,从此节点开始,从左至右,从下至上进行调整。节点索引: arr.Length/2 -1
[注]: 对于最后一个非叶子节点,有 2n+2 = arr.Length,故 n = (arr.Length)/2 = arr.Length/2 -1 - 与左右子节点进行比较,构造大顶堆
- 找下一个非叶子节点,与左右子节点比较,构造大顶堆,此时上一个非叶子节点的结构被打乱,需重新调整。
总结说来就是将堆顶元素和末尾元素进行交换,使末尾元素最大。然后继续调整堆,获得第二大的元素,如此反复交换,重建…
using System;
namespace HeapSortDemo
{
class Program
{
static void Main(string[] args)
{
int[] arr = { 4, 6, 8, 5, 9 };
HeapSort(arr);
foreach (var item in arr)
{
Console.Write(item + " ");
}
}
static void HeapSort(int[] arr)
{
for (int i = arr.Length / 2 - 1; i >= 0; i--)
{
Adjust(arr, i, arr.Length);
}
for (int j = arr.Length - 1; j > 0; j--)
{
//交换
int temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
Adjust(arr, 0, j);
}
#region 分步完成
//Adjust(arr, 1, arr.Length);
//foreach (var item in arr)
//{
// Console.Write(item + " ");
//}
//Console.WriteLine();
//Adjust(arr, 0, arr.Length);
//foreach (var item in arr)
//{
// Console.Write(item + " ");
//}
#endregion
}
/// <summary>
/// 将 i 对应的非叶子节点调整成大顶堆
/// </summary>
/// <param name="arr">待调整数组</param>
/// <param name="i">非叶子节点在数组中的索引</param>
/// <param name="len">对多少个元素进行调整</param>
static void Adjust(int[] arr, int i, int len)
{
int temp = arr[i]; //取出当前元素的值
//开始调整
//将以i为根节点的树进行调整
for (int k = i * 2 + 1; k < len; k = k * 2 + 1)
{
//找到左右子节点中最大值
if (k + 1 < len && arr[k] < arr[k + 1]) //左子节点的值小于右子节点的值
{
k++; //k指向右子节点
}
if (arr[k] > temp) //如果子节点中最大值大于父节点
{
arr[i] = arr[k]; //把较大的值赋给当前结点
i = k; // i 指向 k,继续循环比较
}
else break;
}
//当for循环结束后,我们已经将以i为父节点的树的最大值,放在了最顶部(局部)
arr[i] = temp; //将temp值放到调整后的位置
}
}
}
赫夫曼树
基本介绍
- 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(weighted path length) 达到最小,则称这样的二叉树为最优二叉树,也成为 Huffmantree
- 赫夫曼树是带权路径长度最短的树,权值较大的节点离根较近。
几个概念
- 路径和路径长度: 在一棵树中,从一个节点往下可以达到的孩子或孙子节点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根节点的层数为1,则从根节点到第L层的路径长度为L-1
- 节点的权及带权路径长度:若将树中节点赋给一个有着某种含义的数值,则这个数值称为该节点的权。节点的带权路径长度为,从根节点到该节点之间的路径长度与该节点权的乘积。
- 树的带权路径长度:树的带权路径长度规定为所有叶子节点的带权路径长度之和。记为WPL,权值越大的节点离根节点越近的二叉树才是最优二叉树,WPL最小的就是赫夫曼树。
构成赫夫曼树的步骤
- 从小到大进行排序,每一个数据都是一个节点,每个节点可以看成是一棵最简单的二叉树。
- 取出根节点权值最小的两棵树
- 组成一棵新的二叉树,该新的二叉树的根节点的权值是前面两棵二叉树根节点权值的和。
- 将这棵新的二叉树,以根节点的权值大小再次排序,不断重复,直至所有数据都被处理。
代码
using System;
using System.Collections.Generic;
namespace HuffmanTreeDemo
{
class Program
{
static void Main(string[] args)
{
int[] arr = { 13, 7, 8, 3, 29, 6, 1 };
Node root = CreateHuffmanTree(arr);
root.PreOrder();
}
static Node CreateHuffmanTree(int[] arr)
{
//为了操作方便
/*
* 1.遍历arr数组
* 2.将arr的每个元素构成一个Node
* 3.将node 放入list中
*/
List<Node> nodes = new List<Node>();
foreach (var item in arr)
{
nodes.Add(new Node(item));
}
while (nodes.Count > 1)
{
//从小到大排序
nodes.Sort();
//1 取出权值最小的节点
Node leftNode = nodes[0];
//2 取出权值次小的节点
Node rightNode = nodes[1];
//构建一棵新的二叉树
Node parent = new Node(leftNode.Value + rightNode.Value);
parent.Left = leftNode;
parent.Right = rightNode;
nodes.Remove(leftNode);
nodes.Remove(rightNode);
nodes.Add(parent);
}
//返回赫夫曼树的root节点
return nodes[0];
}
}
class Node : IComparable<Node>
{
public int Value { get; set; }
public Node Left { get; set; }
public Node Right { get; set; }
public Node(int value)
{
Value = value;
}
public override string ToString()
{
return $"Node [Value = {Value}]";
}
public void PreOrder()
{
Console.WriteLine(this);
if (this.Left != null)
this.Left.PreOrder();
if (this.Right != null)
this.Right.PreOrder();
}
//从小到大进行排序
public int CompareTo(Node other)
{
return this.Value - other.Value;
}
}
}