数据结构:堆(Heap)

数据结构:堆(Heap)

堆就是用数组实现的二叉树(完全二叉树),它没有父指针、左右子指针。

堆分为两种:大根堆(最大堆), 小根堆(最小堆),两者差别在于排序方式。
小根堆:父节点的值比每一个子节点的值都小
大根堆:父节点的值比每一个子节点的值都大

堆中用来存储数据的结构为一个 数组,大/小根堆 的根节点即为数组的第 0 个元素,则第0个元素即为该堆中的 最大/小元素。
小根堆的第0个元素为该堆中的最小值。
大根堆的第0个元素为该堆中的最大值。

如下小根堆:
数组中的排序为0,1,3,5,9,8,4,6,20,10,16
如果展示为二叉树形式如下

                                                0
                       -------------------------------------------------
                       |                                               |
                       1                                               3
           -------------------------                       -------------------------
           |                       |                       |                       |
           5                       9                       8                       4
     -------------           -------------
     |           |           |           |
     6          20          10          16

从上图可以清晰的看到每个节点的值都小于其左右子节点的值(只要满足这一特征,小根堆排序即成立)

同样的数据来构建一个大根堆
如下大根堆:
数组中的排序为 20,16,6,8,10,3,1, 0,4,5,9
如果展示为二叉树形式如下

                                                20
                       -------------------------------------------------
                       |                                               |
                      16                                               6
           -------------------------                       -------------------------
           |                       |                       |                       |
           8                      10                       3                       1
     -------------           -------------
     |           |           |           |
     0           4           5           9

从上图可以清晰的看出每个节点的值均大于其左右子节点的值(只要满足这一特征,大根堆排序即成立)

堆中的存储结构只有一个数组,所以是没有如上图显示的树形结构指针的。
那么如何确认每个节点的父节点、左右子节点呢。它们的索引如下

第 i 个索引节点
父节点索引:parentIndex = ( i - 1) / 2
左节点索引:leftChIndex = i * 2 + 1
右节点索引: righChIndex = i * 2 + 2

取如上大根堆20,16, 6, 8,10,3,1,0, 4,5,9的一个节点 16
该节点在数组中的索引为 1,将 i = 1带入公式计算,
则其父节点 (i - 1) / 2 = (1 - 1) / 2 = 0 索引为 0,对应值为 20
左节点 (i * 2) + 1 = (1 * 2) + 1 = 3 索引为 3,对应的值为 8
右节点 (i * 2) + 2 = (1 * 2) + 2 = 4 索引为4, 对应的值为10

对照树形结构展示图,对应无误

当添加、删除、从一个无序数列构建堆时是如何排序的?
答:通过 上虑、下虑两个操作可以实现堆的排序

下面以大根堆为列
上虑:逆序(父节点<子节点)则互换父/子节点的值
下虑:index 位置的值,比子节点的值小,则互换自身与交大子节点的值

从下图展示当删除根节点后是如何调整排序的
在这里插入图片描述

20,16,6,8,10,3,1,0,4,5,9

当删除根节点20后,将最后一个节点 9 替换掉20,9作为新的根节点
9,16,6,8,10,3,1,0,4,5

此时检查根节点 9的左子树为 16,9 < 16 不满足大根堆特性, 将9下虑, 9 跟16互换位置,
此时排序为
16,9,6,8,10,3,1,0,4,5

接下来依然检测节点9,节点9的右子树为节点10, 9<10 不满足大根堆特性,将9下虑,9跟10互换位置,此时排序为
16,10,6,8,9,3,1,0,4,5

到此排序已经满足堆特性,结束

当添加一个节点的时候使用上虑来调整排序,在此不详细说明了,下面将代码实现奉上

    class Heap<T> where T : IComparable<T>
    {
        // 此处使用 List 是为了偷懒,因为涉及到 插入 insert 和删除 delet
        // 如果使用数组首先开辟多大空间不确定假设开辟 N 个空间,则还需要
        // 记录当前已经使用到哪个下标索引了记为 size。且当 size >= N 时
        // 还需要手动再次开辟空间
        public List<T> _list = new List<T>();

        /// 是否大根堆
        private bool _isBigHeap = true;
        public Heap() {    }

        public void SetHeapType(bool isBigHeap)
        {
            _isBigHeap = isBigHeap;
        }

        private int ParentIndex(int index)
        {
            index = (index - 1) >> 1;
            return index;
        }

        public void Insert(T value)
        {
            _list.Add(value);
            PercolateUp(_list, _list.Count - 1);
        }

        public T GetRoot()
        {
            if (_list.Count <= 0)
            {
                return default(T);
            }
            return _list[0];
        }

        // 删除最大元素
        public T DelRoot()
        {
            if (_list.Count <= 0)
            {
                return default(T);
            }

            T max = _list[0];
            // 删除堆顶元素,将末元素填补到堆顶。
            _list[0] = _list[_list.Count - 1];
            _list.RemoveAt(_list.Count - 1);

            // 对堆顶元素下虑
            PercolateDown(_list, 0, _list.Count);

            return max;
        }

        // 批量建堆
        public void HeapCreate()
        {
            // 批量建堆思路为从最后一个非叶子节点开始下虑,一直到跟节点结束
            // 所有非叶子节点执行完下虑堆自然而成
            for (int i = (_list.Count / 2) - 1; i >= 0; --i)
            {
                PercolateDown(_list, i, _list.Count);
            }
        }

        // 上虑
        private void PercolateUp(List<T> dataList, int index)
        {
            if (index >= dataList.Count)
            {
                return;
            }

            // 直到抵达堆顶
            while (0 < index)
            {
                // 获取 index 的父节点
                int parentIndex = ParentIndex(index);
                // 逆序(父节点<子节点)则互换父/子节点的值
                if (Compare(dataList[parentIndex], dataList[index]) >= 0)
                {
                    break;
                }

                T temp = dataList[parentIndex];
                dataList[parentIndex] = dataList[index];
                dataList[index] = temp;

                index = parentIndex;
            }
        }

        // 下虑
        public void PercolateDown(List<T> dataList, int index, int length)
        {
            if (index >= dataList.Count)
            {
                return;
            }

            // 令 index 位置的值 为自身和子节点中最大者
            int maxIndex = 0;
            while (index != (maxIndex = ProperParent(dataList, index, length)))
            {
                // index 位置的值,比子节点的值小,则互换自身与较大子节点的值
                T temp = dataList[maxIndex];
                dataList[maxIndex] = dataList[index];
                dataList[index] = temp;

                // 互换位置,继续下虑
                index = maxIndex;
            }
        }

        // 自己和左右两个子节点中最大者
        public int ProperParent(List<T> dataList, int index, int length)
        {
            int leftChildIndex = index * 2 + 1;
            int rightChildIndex = index * 2 + 2;

            if (length > leftChildIndex)
            {
                index = Compare(dataList[index], dataList[leftChildIndex]) >= 0 ? index : leftChildIndex;
            }
            if (length > rightChildIndex)
            {
                index = Compare(dataList[index], dataList[rightChildIndex]) >= 0 ? index : rightChildIndex;
            }
            return index;
        }

        public int Compare(T x, T y)
        {
            int compare = x.CompareTo(y);
            return _isBigHeap ? compare : compare * -1;
        }
    }

测试代码如下

            Heap<int> heap = new Heap<int>();
            heap.SetHeapType(true);
            heap.Insert(4);
            heap.Insert(6);
            heap.Insert(8);
            heap.Insert(5);
            heap.Insert(9);
            heap.Insert(3);
            heap.Insert(1);
            heap.Insert(0);
            heap.Insert(20);
            heap.Insert(10);
            heap.Insert(16);

            Console.WriteLine();

            // 获取跟
            int root = heap.GetRoot();

            // 删除跟
            heap.DelRoot();

            // 将数组中数据清除添加 N 个数值
            heap._list.Clear();
            for (int i = 0; i < 10; ++i)
            {
                heap._list.Add(i);
            }
            // 批量建堆将序列排序
            heap.HeapCreate();

堆的原理以及实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值