二叉树的应用 堆排序, 赫夫曼树

堆排序

介绍

  1. 堆排序是利用堆这种数据结构而设计的排序算法,堆排序是一种选择排序,最坏、最好、平均时间复杂度均为O(nlogn)
  2. 堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆。不要求节点左右孩子的大小关系。
  3. 每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
  4. 以大顶堆为例,按层序进行编号,映射到数组中可以得到 arr[i] >= arr[2i+1] arr[i] >= arr[2i+2] (i从0开始编号)
  5. 一般升序采用大顶堆,降序采用小顶堆

堆排序的基本思想

  1. 将待排序序列构造成一个大顶堆
  2. 此时,整个序列的最大值就是堆顶的根节点
  3. 将其与末尾元素进行交换,此时末尾就为最大值。
  4. 然后将剩余n-1个元素重新构造成一个堆,这样就会得到 n个元素的次小值,如此反复进行,就能够得到一个有序序列了。

构造初始堆

  1. 找到最后一个非叶子节点,从此节点开始,从左至右,从下至上进行调整。节点索引: arr.Length/2 -1
    [注]: 对于最后一个非叶子节点,有 2n+2 = arr.Length,故 n = (arr.Length)/2 = arr.Length/2 -1
  2. 与左右子节点进行比较,构造大顶堆
  3. 找下一个非叶子节点,与左右子节点比较,构造大顶堆,此时上一个非叶子节点的结构被打乱,需重新调整。

总结说来就是将堆顶元素和末尾元素进行交换,使末尾元素最大。然后继续调整堆,获得第二大的元素,如此反复交换,重建…

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值放到调整后的位置
        }
    }
}

赫夫曼树

基本介绍

  1. 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(weighted path length) 达到最小,则称这样的二叉树为最优二叉树,也成为 Huffmantree
  2. 赫夫曼树是带权路径长度最短的树,权值较大的节点离根较近。

几个概念

  1. 路径和路径长度: 在一棵树中,从一个节点往下可以达到的孩子或孙子节点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根节点的层数为1,则从根节点到第L层的路径长度为L-1
  2. 节点的权及带权路径长度:若将树中节点赋给一个有着某种含义的数值,则这个数值称为该节点的权。节点的带权路径长度为,从根节点到该节点之间的路径长度与该节点权的乘积。
  3. 树的带权路径长度:树的带权路径长度规定为所有叶子节点的带权路径长度之和。记为WPL,权值越大的节点离根节点越近的二叉树才是最优二叉树,WPL最小的就是赫夫曼树。

构成赫夫曼树的步骤

  1. 从小到大进行排序,每一个数据都是一个节点,每个节点可以看成是一棵最简单的二叉树。
  2. 取出根节点权值最小的两棵树
  3. 组成一棵新的二叉树,该新的二叉树的根节点的权值是前面两棵二叉树根节点权值的和。
  4. 将这棵新的二叉树,以根节点的权值大小再次排序,不断重复,直至所有数据都被处理。

代码

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;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值