优先队列

优先队列(c#)

真实场景

现实生活中存在按一定优先级处理一组事务,对象的场景:

  • 按照排队先后顺序结账收银,越早加入排队的人优先级越高,越早结账;
  • 按照接收的先后顺序处理邮件,最新收到的邮件优先处理;
  • 对医院、学校、餐馆进行综合打分(综合考虑收费,技术,服务,环境等),分数高的被优先选择;
  • ...

抽象

为获得最大的通用性,我们对上述各特异的场景进行抽象,会发现存在以下几个要素和动作。

要素

  • 一组需要处理的事务
  • 一种优先级规则,如时间顺序,综合打分

动作

  • 添加新事务
  • 移除最高优先事务
  • 了解待处理事务的规模

API

我们将上述要素和动作封装成一种数据结构————优先队列,其api如下:

apidescription
MaxPQ()无参构造函数
MaxPQ(IComparer<T> comparer)构造函数,参数为优先级比较器
Push(T item)添加对象
T Pop()取出最高优先级对象
int Size获取当前待处理事务的规模
bool IsEmpty()当前对象数量是否为0

实现

思路

内部维护对象的集合(可以是有序链表,有序数组,无序数组,堆有序的二叉堆),通过一个int记录集合的规模,从而可以在常数时间内获取对象集合的规模。

内部对象集合实现的不同,会对实现思路,执行效率产生巨大影响。

集合类型插入取出效率
无序数组插入到数组的末尾对数组进行排序,取出最大的插入效率:1取出效率:N
有序数组添加到数组末端,重新有序化(插入排序)取出最大的插入效率:N 取出效率:1
链表添加到链表末端,重新有序化(插入到合适位置)取出最大的插入效率:N 取出效率:1
堆有序的堆添加到堆底,重新有序化(上浮)取出堆顶,将堆底移动到堆顶,重新有序化(下沉)log N
using System;
using System.Collections.Generic;

namespace LearnAL.DataStructs.Generic
{
    public class MaxPQ<T>
    {
        private T[] _heap;
        private int _size;
        private IComparer<T> _cmper;
        public MaxPQ() : this(null) { }

        /// <summary>
        /// 定义某种优先级规则下的优先队列
        /// </summary>
        /// <param name="cmper">优先级比较器</param>
        public MaxPQ(IComparer<T> cmper)
        {
            _heap = new T[2];
            _heap[0] = default;
            _cmper = cmper == null ? Comparer<T>.Default : cmper;
        }

        public (bool done, T value) Pop()
        {

            if (IsEmpty())
            {
                return (false, default);
            }

            var result = _heap[1];
            Exchange(1, _size);
            _size--;
            Sink(1);

            if ((_size + 1) <= (_heap.Length / 4))
            {
                Resize(ref _heap, _heap.Length / 2);
            }

            return (true, result);
        }
        public void Push(T item)
        {
            if (_size == _heap.Length - 1)
            {
                Resize(ref _heap, _heap.Length * 2);
            }

            _size++;
            _heap[_size] = item;
            Swim(_size);
        }

        public bool IsEmpty()
        {
            return _size == 0;
        }


        #region 辅助函数
        private void Swim(int downIdx)
        {
            while (downIdx >= 2 && downIdx <= _size)
            {
                var upIdx = downIdx / 2;

                var upValue = _heap[upIdx];
                var downValue = _heap[downIdx];
                if (_cmper.Compare(upValue, downValue) <= 0)
                {
                    Exchange(upIdx, downIdx);
                    downIdx = upIdx;
                }
                else
                {
                    break;
                }
            }
        }

        private void Sink(int upIdx)
        {
            while (upIdx * 2 <= _size)
            {
                var leftDownIdx = upIdx * 2;
                var rightDownIdx = upIdx * 2 + 1;

                var downIdx = MaxSide(_heap, leftDownIdx, rightDownIdx);

                var up = _heap[upIdx];
                var down = _heap[downIdx];

                if (_cmper.Compare(up, down) <= 0)
                {
                    Exchange(upIdx, downIdx);
                    upIdx = downIdx;
                }
                else
                {
                    break;
                }
            }
        }

        private void Resize(ref T[] heap, int size)
        {
            Array.Resize(ref _heap, size);
        }

        private void Exchange(int idx1, int idx2)
        {
            var temp = _heap[idx1];
            _heap[idx1] = _heap[idx2];
            _heap[idx2] = temp;
        }

        private int MaxSide(IList<T> heap, int leftDownIdx, int rightDownIdx)
        {
            if (rightDownIdx > _size)
            {
                return leftDownIdx;
            }
            else
            {
                var left = heap[leftDownIdx];
                var right = heap[rightDownIdx];

                var result = _cmper.Compare(left, right) >= 0 ? leftDownIdx : rightDownIdx;
                return result;
            }
        }
    }
    #endregion
}

测试

using System;
using Xunit;
using LearnAL.DataStructs.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace LearnAL.Tests
{
    public class MaxPQTests
    {
        [Fact]
        public void Test()
        {
            //测试只有一个对象的时候的正确性
            var pq0 = new MaxPQ<Person>(new PersonCompaer());
            pq0.Push(new Person(1));
            Assert.True(pq0.Pop().value.Age == 1);

            var pq = new MaxPQ<Person>(new PersonCompaer()); ;

            var values = new List<int>();
            values.Add(1);
            values.Add(9999999);
            values.Add(8888888);
            values.Add(7777777);

            var middleValueCount = 996;

            for (int i = 0; i < middleValueCount; i++)
            {
                var rand = new Random();
                var value = rand.Next(2, 999999);
                values.Add(value);
            }

            for (int i = 0; i < values.Count; i++)
            {
                var person = new Person(values[i]);
                pq.Push(person);
            }

            Assert.True(pq.Pop().value.Age == 9999999);
            Assert.True(pq.Pop().value.Age == 8888888);
            Assert.True(pq.Pop().value.Age == 7777777);

            for (int i = 0; i < values.Count - 4; i++)
            {
                pq.Pop();
            }

            Assert.True(pq.Pop().value.Age == 1);
        }

        public class Person
        {
            public Person(int age)
            {
                this.Age = age;

            }
            public int Age { get; set; }
        }
        public class PersonCompaer : Comparer<Person>
        {

            public override int Compare([AllowNull] Person x, [AllowNull] Person y)
            {
                return x.Age.CompareTo(y.Age);
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值