数据结构之队列

队列可以通过数组和链表来实现, 就看内置的栈和队列类是用哪种方式实现了。

队列:是一种先进先出的线性表,在队头删除元素(出队),在队尾添加元素(入队) 

线性表:  是n个数据元素的有限序列,是连续的。    这也是一个例子

下面是数组队列实现原理:

    #region 数组队列
    /// <summary>
    /// 数组队列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MyArrayQueue<E> : IQueue<E>
    {
        //当前数组
        private MyArray<E> arr;
        /// <summary>
        /// 创建指定长度的数组
        /// </summary>
        /// <param name="capacity"></param>
        public MyArrayQueue(int capacity)
        {
            arr = new MyArray<E>(capacity);
        }
        public MyArrayQueue()
        {
            arr = new MyArray<E>();
        }
        public int Count
        {
            get
            {
                return arr.Count;
            }
        }

        public bool IsEmpty
        {
            get
            {
                return arr.IsNull;
            }
        }
        /// <summary>
        /// 移除队头的元素
        /// </summary>
        /// <returns></returns>
        public E Dequeue()
        {
            return arr.RemoveFirst();
        }
        /// <summary>
        /// 添加元素到队尾
        /// </summary>
        /// <param name="e"></param>
        public void Enqueue(E e)
        {
            arr.AddLast(e);
        }
        /// <summary>
        /// 获取对头元素
        /// </summary>
        /// <returns></returns>
        public E Peek()
        {
            return arr.GetFirst();
        }
    }
    #endregion

 

复杂度:从图中我们可以看到Dequeue移除队头元素的方法是最复杂的,因为数组在移除头元素之后,后面的所有元素都要进行重新移位。

循环队列

在上面代码得知Dequeue方法复杂度太高,所以可以通过循环数组解决。

比如下图:用2个字段记录头部和尾部的位置信息,当元素2后面添加新元素3的时候,last指向的位置添加新元素,last后移移位,这样下次再次添加新元素的时候可以直接根据last的位置进行插入。

 

当我们移除元素0的时候,first++,后移一位。

此时,我们在尾部添加元素4,按照上面逻辑来说last还要后移一位,但是此时已经到了最大长度了,所以可以将last按照图中的算式指向对应的位置。

 

代码实现:

class Array2<E>
    {
        private E[] data;
        private int first;
        private int last;
        //元素数量
        private int N;
        public Array2(int capacity)
        {
            data = new E[capacity];
            N = 0;
            first = 0;
            last = 0;
        }

        public Array2() : this(10)
        {
        }
        /// <summary>
        /// 数组实际存储数量
        /// </summary>
        public int Count
        {
            get
            {
                return N;
            }
        }
        /// <summary>
        /// 可以看数组元素是否为空
        /// </summary>
        public bool IsNull
        {
            get
            {
                return N == 0;
            }
        }
        /// <summary>
        /// 添加元素   队列只能在队尾添加,队头出
        /// </summary>
        /// <param name="e"></param>
        public void AddLast(E e)
        {
            data[last] = e;
            //移动last
            last = (last + 1) % data.Length;
            N++;
        }

        public E RemoveFirst()
        {
            if (IsNull)
            {
                throw new Exception("数组为空");
            }

            E del = data[first];
            //设置为默认值,垃圾回收机制这样就可以自动回收值。
            data[first] = default(E);
            //移动first
            first = (first + 1) % data.Length;
            N--;
            return del;
        }

        public E GetFirst()
        {
            if (IsNull)
            {
                throw new Exception("数组为空");
            }

            return data[first];

        }


    }

 

但是循环数组同样需要扩容操作,比如下图,此时已经没有空间了,

 

 

通过下面2图中的形式来扩容

下面代码实现了循环数组的扩容和缩容,分别在添加和删除的时候进行的:

   class Array2<E>
    {
        private E[] data;
        private int first;
        private int last;
        //元素数量
        private int N;
        public Array2(int capacity)
        {
            data = new E[capacity];
            N = 0;
            first = 0;
            last = 0;
        }

        public Array2() : this(10)
        {
        }
        /// <summary>
        /// 数组实际存储数量
        /// </summary>
        public int Count
        {
            get
            {
                return N;
            }
        }
        /// <summary>
        /// 可以看数组元素是否为空
        /// </summary>
        public bool IsNull
        {
            get
            {
                return N == 0;
            }
        }
        /// <summary>
        /// 添加元素   队列只能在队尾添加,队头出
        /// </summary>
        /// <param name="e"></param>
        public void AddLast(E e)
        {
            if (N == data.Length)
            {
                //扩容
                ResetCapacity(2 * data.Length);
            }

            data[last] = e;
            //移动last
            last = (last + 1) % data.Length;
            N++;
        }

        public E RemoveFirst()
        {
            if (IsNull)
            {
                throw new Exception("数组为空");
            }

            E del = data[first];
            //设置为默认值,垃圾回收机制这样就可以自动回收值。
            data[first] = default(E);
            //移动first
            first = (first + 1) % data.Length;
            N--;
            if (N == data.Length / 4)
            {
                //扩容
                ResetCapacity(data.Length / 2);
            }

            return del;
        }

        public E GetFirst()
        {
            if (IsNull)
            {
                throw new Exception("数组为空");
            }

            return data[first];

        }


        private void ResetCapacity(int newCapacity)
        {
            E[] newData = new E[newCapacity];
            for (int i = 0; i < N; i++)
            {
                newData[i] = data[(first + i) % data.Length];
            }

            data = newData;
            first = 0;
            last = N;
        } 

    }

 

在上面已经实现了循环数组,所以根据循环数组实现的循环队列代码如下:

  #region 数组队列
    /// <summary>
    /// 循环数组队列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class  Array2Queue<E> : IQueue<E>
    {
        //当前数组
        private Array2<E> arr;
        /// <summary>
        /// 创建指定长度的数组
        /// </summary>
        /// <param name="capacity"></param>
        public Array2Queue(int capacity)
        {
            arr = new Array2<E>(capacity);
        }
        public Array2Queue()
        {
            arr = new Array2<E>();
        }
        public int Count
        {
            get
            {
                return arr.Count;
            }
        }

        public bool IsEmpty
        {
            get
            {
                return arr.IsNull;
            }
        }
        /// <summary>
        /// 移除队头的元素
        /// </summary>
        /// <returns></returns>
        public E Dequeue()
        {
            return arr.RemoveFirst();
        }
        /// <summary>
        /// 添加元素到队尾
        /// </summary>
        /// <param name="e"></param>
        public void Enqueue(E e)
        {
            arr.AddLast(e);
        }
        /// <summary>
        /// 获取对头元素
        /// </summary>
        /// <returns></returns>
        public E Peek()
        {
            return arr.GetFirst();
        }
    }
    #endregion

 

此时Dequeue移除数组队列中的头元素复杂度有O(n)变为O(1),直接根据first便可以找到头元素,移除之后first后移一位,不会像普通数组还要重新排位。

 链表队列   

   一个普通的链表实现的队列:

 #region 链表队列
    /// <summary>
    /// 链表队列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MyLinkedListQueue<E> : IQueue<E>
    {
        //当前链表
        private MyLinkedList<E> arr;
        
        public MyLinkedListQueue()
        {
            arr = new MyLinkedList<E>();
        }
        public int Count
        {
            get
            {
                return arr.Count;
            }
        }

        public bool IsEmpty
        {
            get
            {
                return arr.IsEmpty;
            }
        }
        /// <summary>
        /// 移除队头的元素
        /// </summary>
        /// <returns></returns>
        public E Dequeue()
        {
            return arr.RemoveFirst();
        }
        /// <summary>
        /// 添加元素到队尾
        /// </summary>
        /// <param name="e"></param>
        public void Enqueue(E e)
        {
            arr.AddLast(e);
        }
        /// <summary>
        /// 获取队头元素
        /// </summary>
        /// <returns></returns>
        public E Peek()
        {
            return arr.GetFirst();
        }




    }
    #endregion

 

时间复杂度:

通过上面得知Enqueue添加元素的时间复杂度最高,正常的链表:头->....->尾 ,从左往右2端分别是单链表的头与尾,而队列的规定的原理是要遵循先(尾进)进先出(头出),所以添加的时候是往尾部添加,单链表中从尾部添加元素就要从头往后一步一步的找到尾部节点,所以时间复杂度最高。

要解决上面的的问题,可以通过实现一个具有尾指针的链表来解决,

 

    #region 具有尾指针的单链表
    public class MyLinkedList2<T>
    {
        //当前链表类记录这个链表的头部类
        private Node head;
        //链表的节点数量
        private int N;
        //尾指针
        private Node tail;
        public MyLinkedList2()
        {
            N = 0;
            //因为一开始链表没有元素,所以头部节点指向空。
            head = null;
            tail = null;
        }


        /// <summary>
        /// 链表节点数量
        /// </summary>
        public int Count
        {
            get { return N; }
        }
        /// <summary>
        /// 链表是否为空
        /// </summary>
        public bool IsEmpty
        {
            get { return N == 0; }
        }
        /// <summary>
        /// 队列是先进先出队尾进入队头出,所以
        /// 当前链表只保留队尾元素增加方法即可
        /// </summary>
        /// <param name="e"></param>
        public void AddLast(T e)
        {
            Node node = new Node(e);
            if (IsEmpty)
            {
                //添加前链表为空
                head = node;
                tail = node;
            }
            else
            {
                tail.next = node;
                tail = node;
            }
            N++;
        }
        /// <summary>
        /// 删除
        /// </summary>
        /// <returns></returns>
        public T RemoveFirst()
        {
            if (IsEmpty)
            {
                throw new Exception("链表为空");
            }

            T e = head.e;
            head = head.next;
            N--;
            if (head == null)
            {
                tail = null;
            }

            return e;
        }

        public T GetFirst()
        {
            return head.e;
        }
         

        /// <summary>
        /// 节点类  不让外部知道此类,设为私有
        /// </summary>
        private class Node
        {
            //当前节点
            public T e;
            //当前节点的下一节点
            public Node next;
            public Node(T e, Node next)
            {
                this.e = e;
                this.next = next;
            }
            public Node(T e)
            {
                this.e = e;
            }

        }
         
        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="index"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        public void Set(int index, T e)
        {
            if (index < 0 || index >= N)
            {
                throw new Exception("非法索引");
            }

            Node currentNode = head;//从头结点开始查找
            for (int i = 0; i < index; i++)
            {
                currentNode = currentNode.next;
            }

            currentNode.e = e; ;
        }
        /// <summary>
        /// 查找链表中是否存在此元素
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        public bool Contains(T e)
        {
            Node current = head;
            while (current != null)
            {
                if (current.e.Equals(e))
                {
                    return true;
                }

                current = current.next;
            }

            return false;
        }
        /// <summary>
        /// 可以重写tostring打印方法
        /// 打印出链表信息
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            Node cur = head;
            while (cur != null)
            {
                stringBuilder.Append(cur.e + "->");
                cur = cur.next;
            }
            stringBuilder.Append("null");
            return stringBuilder.ToString();

        }
         
    }
    #endregion

 

代码如上,在队列中使用带有尾指针的类就可以解决上面问题,具体引用实现如下:

 #region 带有尾指针链表队列
    /// <summary>
    /// 链表队列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MyLinkedList2Queue<E> : IQueue<E>
    {
        //当前链表
        private MyLinkedList2<E> arr;

        public MyLinkedList2Queue()
        {
            arr = new MyLinkedList2<E>();
        }
        public int Count
        {
            get
            {
                return arr.Count;
            }
        }

        public bool IsEmpty
        {
            get
            {
                return arr.IsEmpty;
            }
        }
        /// <summary>
        /// 移除队头的元素
        /// </summary>
        /// <returns></returns>
        public E Dequeue()
        {
            return arr.RemoveFirst();
        }
        /// <summary>
        /// 添加元素到队尾
        /// </summary>
        /// <param name="e"></param>
        public void Enqueue(E e)
        {
            arr.AddLast(e);
        }
        /// <summary>
        /// 获取队头元素
        /// </summary>
        /// <returns></returns>
        public E Peek()
        {
            return arr.GetFirst();
        }
         

    }
    #endregion

 

posted @ 2021-08-22 15:36  安静点--  阅读(89)  评论(0编辑  收藏  举报