數據結構之隊列和棧

不知道大家有沒有玩過一個叫《拖板車》的撲克遊戲(貌似暴露了年齡。。),本篇我們嘗試讓計算機來玩這個遊戲,並且我們在確定雙方的牌和順序後就能立馬知道誰將是贏家。在玩這個遊戲前我們需要先了解下隊列和棧。

隊列簡介

隊列是一種特殊的線性結構,它只允許在隊列的首部進行刪除操作,這稱作“出隊”,而在隊列的尾部進行插入操作,這稱爲“入隊"。這和我們實際生活中排隊買東西的場景一樣,先買東西的肯定是隊首的第一個,新來的人肯定是排隊在最後面。我們稱之爲先進先出(FIFO)原則。

隊列實現

隊列的數據結構實現起來比較簡單,用一個數組控制先進先出即可,.net平臺已經內置了這種數據結構,我們一起看一下萬能的微軟是如何實現隊列的。代碼較多,我們看一下關鍵部分入隊和出隊的代碼。

private T[] _array;

private int _head;

private int _tail;

private int _size;

private int _version;	
//出隊
public T Dequeue()
		{
			//判斷隊列中是否有元素,如果沒有拋出空隊列異常
			if (_size == 0)
			{
				System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource.InvalidOperation_EmptyQueue);
			}
			//取得隊首元素,作爲返回結果
			T result = _array[_head];
			//將隊首元素重置爲默認值
			_array[_head] = default(T);
			//_head指向隊首下一個元素
			_head = (_head + 1) % _array.Length;
			//隊列長度減一
			_size--;
			//版本更新
			_version++;
			return result;
		}
//入隊
public void Enqueue(T item)
		{
			//.net在初始化隊列的時候會給數組一個初始長度,當數組數量達到一定條件會擴容,每次增加4的長度
			if (_size == _array.Length)
			{
				int num = (int)((long)_array.Length * 200L / 100);
				if (num < _array.Length + 4)
				{
					num = _array.Length + 4;
				}
				SetCapacity(num);
			}
			//將新元素加到數組末尾
			_array[_tail] = item;
			//_tail指向末尾後一個索引
			_tail = (_tail + 1) % _array.Length;
			//隊列長度+1
			_size++;
			_version++;
		}		

棧簡介

棧和隊列類似,也是一種特殊的線性結構,同樣在尾部進行插入操作,但是刪除元素同樣是在隊尾,採用先進後出原則。假如我們往一個直徑只有乒乓球大小的水杯分別放入2,3,1號乒乓球,那麼我們想要拿出2號球就必須先分別拿出1號球和3號球,這裏的水杯就是典型的棧結構。同樣地我們看下微軟關於棧的關鍵代碼實現。

private T[] _array;

private int _size;

private int _version;
//出棧
public T Pop()
		{
			//判斷棧中是否還有元素可以出棧,沒有則拋出空棧異常
			if (_size == 0)
			{
				System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource.InvalidOperation_EmptyStack);
			}
			//版本+1
			_version++;
			//取的數組尾部元素作爲返回結果,並且長度記錄-1
			T result = _array[--_size];
			//將原來的尾部元素重置爲默認值
			_array[_size] = default(T);
			return result;
		}
//入棧
public void Push(T item)
		{
			//同隊列相似,如果數組容量達到條件就擴容
			if (_size == _array.Length)
			{
				T[] array = new T[(_array.Length == 0) ? 4 : (2 * _array.Length)];
				Array.Copy(_array, 0, array, 0, _size);
				_array = array;
			}
			//將新元素添加到數組尾部同時長度記錄+1
			_array[_size++] = item;
			_version++;
		}		

拖板車遊戲

遊戲玩法

將一副撲克牌隨機分爲兩份,小明和小紅各一份。小明先將手中的第一張撲克牌放在桌上,然後小紅也拿出手中的第一張牌放在小明剛打出的撲克牌上面,就像這樣兩人交替出牌。出牌時,如果某人打出的牌和桌上某張牌數字相同,即可將這兩張牌及中間所夾的牌全部收走並放到自己手牌的末尾。當其中任意一人手中牌爲空的時候,遊戲結束,對手獲勝。
在這裏插入圖片描述
我們分析下這個遊戲過程,兩個人的操作都是一樣的,手裏的牌相當於一個隊列,出牌是出隊,贏牌是入隊。而桌上的牌相當於一個棧,出牌的時候是入棧,贏牌的時候是出棧。這樣我們的實現思路就清晰了,下面我們用程序來模擬這個遊戲。

編碼實現

static void Main(string[] args)
        {
            var all = new List<string>();
            //首先將52張牌放入集合
            for (int i = 0; i < 4; i++) //每種牌四張,循環四次
            {
                for (int j = 2; j < 15; j++) //13種牌【2,3,4,5,6,7,8,9,10,J,Q,K,A】
                {
                    if (j <= 10)
                    {
                        all.Add(j.ToString());
                    }
                    else
                    {
                        switch (j)
                        {
                            case 11:
                                all.Add("J");
                                break;
                            case 12:
                                all.Add("Q");
                                break;
                            case 13:
                                all.Add("K");
                                break;
                            case 14:
                                all.Add("A");
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            //然後將54張牌隨機分到小明和小紅手裏
            //定義兩個隊列用來記錄小明和小紅的手牌
            Queue<string> xiaoming = new Queue<string>();
            Queue<string> xiaohong = new Queue<string>();
            var strming = "小明手上的牌依次爲:";
            var strhong = "小紅手上的牌依次爲:";
            for (int i = 0; i < 52; i++)
            {
                var random = new Random().Next(0, 51 - i);
                if (i%2==0)
                {
                    xiaoming.Enqueue(all[random]);
                    strming += all[random]+" ";
                }
                else
                {
                    xiaohong.Enqueue(all[random]);
                    strhong += all[random] + " ";
                }               
                all.RemoveAt(random);
            }
            Console.WriteLine(strming);
            Console.WriteLine(strhong);
            //開始遊戲
            //先定義一個棧來記錄桌面上的牌
            var stack = new Stack<string>();
            while (xiaoming.Count>0&&xiaohong.Count>0)
            {
                var first = xiaoming.Dequeue(); //小明出牌
                Console.WriteLine("小明出牌:"+first);
                //判斷桌面上是否有剛出的牌
                if (stack.Contains(first))
                {
                    //如果有那這兩張牌及中間的牌都入隊小明
                    xiaoming.Enqueue(first);         
                    while (stack.First()!=first)
                    {
                        xiaoming.Enqueue(stack.Pop());
                    }
                    xiaoming.Enqueue(stack.Pop());
                    Console.Write("小明贏得一輪,桌上剩餘牌爲:");
                    foreach (var item in stack)
                    {
                        Console.Write(item + " ");
                    }
                    Console.WriteLine("");
                }
                else
                {
                    //沒有的話放到桌面上
                    stack.Push(first);
                }
                first = xiaohong.Dequeue(); //小紅出牌
                Console.WriteLine("小紅出牌:" + first);
                //判斷桌面上是否有剛出的牌
                if (stack.Contains(first))
                {
                    //如果有那這兩張牌及中間的牌都入隊小紅
                    xiaohong.Enqueue(first);
                    while (stack.First() != first)
                    {
                        xiaohong.Enqueue(stack.Pop());
                    }
                    xiaohong.Enqueue(stack.Pop());
                    Console.Write("小紅贏得一輪,桌上剩餘牌爲:");
                    foreach (var item in stack)
                    {
                        Console.Write(item + " ");
                    }
                    Console.WriteLine("");
                }
                else
                {
                    //沒有的話放到桌面上
                    stack.Push(first);
                }
            }
            if (xiaoming.Count==0)
            {
                Console.WriteLine("小紅獲勝!");
            }
            else
            {
                Console.WriteLine("小明獲勝!");
            }
            
            Console.ReadKey();
        }

來看一下執行結果,輸出較多,只截了第一張和最後一張:
在這裏插入圖片描述
在這裏插入圖片描述
有趣的拖板車就到此爲止啦~
歡迎關注我的算法系列博客:
算法(https://blog.csdn.net/leaderxin/article/category/9434286)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章