算法系列15天速成——第九天 队列
可能大家都知道,线性表的变种非常非常多,比如今天讲的“队列”,灰常有意思啊。
一:概念
队列是一个”先进先出“的线性表,牛X的名字就是“First in First Out(FIFO)”,
生活中有很多这样的场景,比如读书的时候去食堂打饭时的”排队“。当然我们拒绝插队。
二:存储结构
前几天也说过,线性表有两种”存储结构“,① 顺序存储,②链式存储。当然“队列”也脱离
不了这两种服务,这里我就分享一下“顺序存储”。
顺序存储时,我们会维护一个叫做”head头指针“和”tail尾指针“,分别指向队列的开头和结尾。
代码段如下:
1 #region 队列的数据结构
2 /// <summary>
3 /// 队列的数据结构
4 /// </summary>
5 /// <typeparam name="T"></typeparam>
6 public class SeqQueue<T>
7 {
8 private const int maxSize = 100;
9
10 public int MaxSize
11 {
12 get { return maxSize; }
13 }
14
15 /// <summary>
16 /// 顺序队列的存储长度
17 /// </summary>
18 public T[] data = new T[maxSize];
19
20 //头指针
21 public int head;
22
23 //尾指针
24 public int tail;
25
26 }
27 #endregion
三:常用操作
队列的操作一般分为:
①: 初始化队列。
②: 出队。
③: 入队。
④: 获取队头。
⑤: 获取队长。
1:初始化队列
这个很简单,刚才也说过了,队列是用一个head和tail的指针来维护。分别设置为0即可。
2:出队
看着“队列”的结构图,大家都知道,出队肯定跟head指针有关,需要做两件事情,
第一: 判断队列是否为空,这个我想大家都知道。
第二: 将head头指针向后移动一位,返回head移动前的元素,时间复杂度为O(1)。
代码段如下:
1 #region 队列元素出队
2 /// <summary>
3 /// 队列元素出队
4 /// </summary>
5 /// <typeparam name="T"></typeparam>
6 /// <param name="seqQueue"></param>
7 /// <returns></returns>
8 public T SeqQueueOut<T>(SeqQueue<T> seqQueue)
9 {
10 if (SeqQueueIsEmpty(seqQueue))
11 throw new Exception("队列已空,不能进行出队操作");
12
13 var single = seqQueue.data[seqQueue.head];
14
15 //head指针自增
16 seqQueue.data[seqQueue.head++] = default(T);
17
18 return single;
19
20 }
21 #endregion
3:入队
这个跟”出队“的思想相反,同样也是需要做两件事情。
第一:判断队列是否已满。
第二:将tail指针向后移动一位,时间复杂度为O(1)。
代码段如下:
1 #region 队列元素入队
2 /// <summary>
3 /// 队列元素入队
4 /// </summary>
5 /// <typeparam name="T"></typeparam>
6 /// <param name="seqQueue"></param>
7 /// <param name="data"></param>
8 /// <returns></returns>
9 public SeqQueue<T> SeqQueueIn<T>(SeqQueue<T> seqQueue, T data)
10 {
11 //如果队列已满,则不能进行入队操作
12 if (SeqQueueIsFull(seqQueue))
13 throw new Exception("队列已满,不能入队操作");
14
15 //入队操作
16 seqQueue.data[seqQueue.tail++] = data;
17
18 return seqQueue;
19 }
20 #endregion
4: 获取队头
知道”出队“和”入队“的原理,相信大家都懂的如何进行”获取队头“操作,唯一不一样的就是
他是只读操作,不会破坏”队列“结构,时间复杂度为O(1)。
代码段如下:
#region 获取队头元素
/// <summary>
/// 获取队头元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="seqQueue"></param>
/// <returns></returns>
public T SeqQueuePeek<T>(SeqQueue<T> seqQueue)
{
if (SeqQueueIsEmpty(seqQueue))
throw new Exception("队列已空,不能进行出队操作");
return seqQueue.data[seqQueue.head];
}
#endregion
5: 获取队长
大家都知道,我们是用数组来实现队列,所以千万不要想当然的认为数组长度是XXX,
我们维护的是一个head和tail的指针,所以长度自然就是tail-head咯,时间复杂度为O(1)。
代码段如下:
1 /// <summary>
2 /// 获取队列长度
3 /// </summary>
4 /// <typeparam name="T"></typeparam>
5 /// <param name="seqQueue"></param>
6 /// <returns></returns>
7 public int SeqQueueLen<T>(SeqQueue<T> seqQueue)
8 {
9 return seqQueue.tail - seqQueue.head;
10 }
然后上一下总的运行代码:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace SeqQueue
7 {
8 public class Program
9 {
10 static void Main(string[] args)
11 {
12 SeqQueue<Student> seqQueue = new SeqQueue<Student>();
13
14 SeqQueueClass queueManage = new SeqQueueClass();
15
16 Console.WriteLine("目前队列是否为空:" + queueManage.SeqQueueIsEmpty(seqQueue) + "\n");
17
18 Console.WriteLine("将ID=1和ID=2的实体加入队列");
19 queueManage.SeqQueueIn(seqQueue, new Student() { ID = 1, Name = "hxc520", Age = 23 });
20 queueManage.SeqQueueIn(seqQueue, new Student() { ID = 2, Name = "一线码农", Age = 23 });
21
22 Display(seqQueue);
23
24 Console.WriteLine("将队头出队");
25 //将队头出队
26 var student = queueManage.SeqQueueOut(seqQueue);
27
28 Display(seqQueue);
29
30 //获取队顶元素
31 student = queueManage.SeqQueuePeek(seqQueue);
32
33 Console.Read();
34 }
35 //展示队列元素
36 static void Display(SeqQueue<Student> seqQueue)
37 {
38 Console.WriteLine("******************* 链表数据如下 *******************");
39
40 for (int i = seqQueue.head; i < seqQueue.tail; i++)
41 Console.WriteLine("ID:" + seqQueue.data[i].ID +
42 ",Name:" + seqQueue.data[i].Name +
43 ",Age:" + seqQueue.data[i].Age);
44
45 Console.WriteLine("******************* 链表数据展示完毕 *******************\n");
46 }
47 }
48
49 #region 学生数据实体
50 /// <summary>
51 /// 学生数据实体
52 /// </summary>
53 public class Student
54 {
55 public int ID { get; set; }
56
57 public string Name { get; set; }
58
59 public int Age { get; set; }
60 }
61 #endregion
62
63 #region 队列的数据结构
64 /// <summary>
65 /// 队列的数据结构
66 /// </summary>
67 /// <typeparam name="T"></typeparam>
68 public class SeqQueue<T>
69 {
70 private const int maxSize = 100;
71
72 public int MaxSize
73 {
74 get { return maxSize; }
75 }
76
77 /// <summary>
78 /// 顺序队列的存储长度
79 /// </summary>
80 public T[] data = new T[maxSize];
81
82 //头指针
83 public int head;
84
85 //尾指针
86 public int tail;
87
88 }
89 #endregion
90
91 #region 队列的基本操作
92 /// <summary>
93 /// 队列的基本操作
94 /// </summary>
95 public class SeqQueueClass
96 {
97 #region 队列的初始化操作
98 /// <summary>
99 /// 队列的初始化操作
100 /// </summary>
101 /// <typeparam name="T"></typeparam>
102 /// <param name="seqQueue"></param>
103 public SeqQueue<T> SeqQueueInit<T>(SeqQueue<T> seqQueue)
104 {
105 seqQueue.head = 0;
106 seqQueue.tail = 0;
107
108 return seqQueue;
109 }
110 #endregion
111
112 #region 队列是否为空
113 /// <summary>
114 /// 队列是否为空
115 /// </summary>
116 /// <typeparam name="T"></typeparam>
117 /// <param name="seqQueue"></param>
118 /// <returns></returns>
119 public bool SeqQueueIsEmpty<T>(SeqQueue<T> seqQueue)
120 {
121 //如果两指针重合,说明队列已经清空
122 if (seqQueue.head == seqQueue.tail)
123 return true;
124 return false;
125 }
126 #endregion
127
128 #region 队列是否已满
129 /// <summary>
130 /// 队列是否已满
131 /// </summary>
132 /// <typeparam name="T"></typeparam>
133 /// <param name="seqQueue"></param>
134 /// <returns></returns>
135 public bool SeqQueueIsFull<T>(SeqQueue<T> seqQueue)
136 {
137 //如果尾指针到达数组末尾,说明队列已经满
138 if (seqQueue.tail == seqQueue.MaxSize)
139 return true;
140 return false;
141 }
142 #endregion
143
144 #region 队列元素入队
145 /// <summary>
146 /// 队列元素入队
147 /// </summary>
148 /// <typeparam name="T"></typeparam>
149 /// <param name="seqQueue"></param>
150 /// <param name="data"></param>
151 /// <returns></returns>
152 public SeqQueue<T> SeqQueueIn<T>(SeqQueue<T> seqQueue, T data)
153 {
154 //如果队列已满,则不能进行入队操作
155 if (SeqQueueIsFull(seqQueue))
156 throw new Exception("队列已满,不能入队操作");
157
158 //入队操作
159 seqQueue.data[seqQueue.tail++] = data;
160
161 return seqQueue;
162 }
163 #endregion
164
165 #region 队列元素出队
166 /// <summary>
167 /// 队列元素出队
168 /// </summary>
169 /// <typeparam name="T"></typeparam>
170 /// <param name="seqQueue"></param>
171 /// <returns></returns>
172 public T SeqQueueOut<T>(SeqQueue<T> seqQueue)
173 {
174 if (SeqQueueIsEmpty(seqQueue))
175 throw new Exception("队列已空,不能进行出队操作");
176
177 var single = seqQueue.data[seqQueue.head];
178
179 //head指针自增
180 seqQueue.data[seqQueue.head++] = default(T);
181
182 return single;
183
184 }
185 #endregion
186
187 #region 获取队头元素
188 /// <summary>
189 /// 获取队头元素
190 /// </summary>
191 /// <typeparam name="T"></typeparam>
192 /// <param name="seqQueue"></param>
193 /// <returns></returns>
194 public T SeqQueuePeek<T>(SeqQueue<T> seqQueue)
195 {
196 if (SeqQueueIsEmpty(seqQueue))
197 throw new Exception("队列已空,不能进行出队操作");
198
199 return seqQueue.data[seqQueue.head];
200 }
201 #endregion
202
203 /// <summary>
204 /// 获取队列长度
205 /// </summary>
206 /// <typeparam name="T"></typeparam>
207 /// <param name="seqQueue"></param>
208 /// <returns></returns>
209 public int SeqQueueLen<T>(SeqQueue<T> seqQueue)
210 {
211 return seqQueue.tail - seqQueue.head;
212 }
213 }
214 #endregion
215 }
三:顺序队列的缺陷
大家看这张图,不知道可有什么异样的感觉,在这种状态下,我入队操作,发现程序提示队列
已满,但是tnd我这个数组还有一个空间啊,是的,这就是所谓的“假溢出”。
四:循环队列
俗话说的好啊,“没有跨不过的坎”。
1: 概念
之所以叫“循环”,得益于神奇的“%”。他让队列的首位进行相连,形成了一个我们思维中的
“圈圈”。
2:循环公式
tail=(tail+1)%array.Length;
多看几眼,大家就看通了其中循环的道理,我要做成如下的图:
3:对循环的改造
先前看了一些资料,有的压根就是错的,有的说想要循环,就要牺牲一个单位的空间。
我觉得没必要。我既要循环又不牺牲空间,所以反射了一下framework中的Queue类。
改造后代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace SeqQueue
7 {
8 public class Program
9 {
10 static void Main(string[] args)
11 {
12 SeqQueue<Student> seqQueue = new SeqQueue<Student>();
13
14 SeqQueueClass queueManage = new SeqQueueClass();
15
16 Console.WriteLine("目前队列是否为空:" + queueManage.SeqQueueIsEmpty(seqQueue) + "\n");
17
18 Console.WriteLine("将ID=1,2,3的实体加入队列\n");
19 queueManage.SeqQueueIn(seqQueue, new Student() { ID = 1, Name = "hxc520", Age = 23 });
20 queueManage.SeqQueueIn(seqQueue, new Student() { ID = 2, Name = "一线码农", Age = 23 });
21 queueManage.SeqQueueIn(seqQueue, new Student() { ID = 3, Name = "51cto", Age = 23 });
22
23 Console.WriteLine("\n当前队列个数:" + queueManage.SeqQueueLen(seqQueue) + "");
24
25 Console.WriteLine("\n*********************************************\n");
26
27 Console.WriteLine("我要出队了\n");
28 queueManage.SeqQueueOut(seqQueue);
29
30 Console.WriteLine("哈哈,看看跟顺序队列异样之处,我再入队,看是否溢出\n");
31 queueManage.SeqQueueIn(seqQueue, new Student() { ID = 4, Name = "博客园", Age = 23 });
32 Console.WriteLine("\n....一切正常,入队成功");
33
34 Console.WriteLine("\n当前队列个数:" + queueManage.SeqQueueLen(seqQueue) + "");
35
36 Console.Read();
37 }
38 }
39
40 #region 学生数据实体
41 /// <summary>
42 /// 学生数据实体
43 /// </summary>
44 public class Student
45 {
46 public int ID { get; set; }
47
48 public string Name { get; set; }
49
50 public int Age { get; set; }
51 }
52 #endregion
53
54 #region 队列的数据结构
55 /// <summary>
56 /// 队列的数据结构
57 /// </summary>
58 /// <typeparam name="T"></typeparam>
59 public class SeqQueue<T>
60 {
61 private const int maxSize = 3;
62
63 public int MaxSize
64 {
65 get { return maxSize; }
66 }
67
68 /// <summary>
69 /// 顺序队列的存储长度
70 /// </summary>
71 public T[] data = new T[maxSize];
72
73 //头指针
74 public int head;
75
76 //尾指针
77 public int tail;
78
79 //队列中有效的数字个数
80 public int size;
81 }
82 #endregion
83
84 #region 队列的基本操作
85 /// <summary>
86 /// 队列的基本操作
87 /// </summary>
88 public class SeqQueueClass
89 {
90 #region 队列的初始化操作
91 /// <summary>
92 /// 队列的初始化操作
93 /// </summary>
94 /// <typeparam name="T"></typeparam>
95 /// <param name="seqQueue"></param>
96 public SeqQueue<T> SeqQueueInit<T>(SeqQueue<T> seqQueue)
97 {
98 seqQueue.size = seqQueue.head = seqQueue.tail = 0;
99
100 return seqQueue;
101 }
102 #endregion
103
104 #region 队列是否为空
105 /// <summary>
106 /// 队列是否为空
107 /// </summary>
108 /// <typeparam name="T"></typeparam>
109 /// <param name="seqQueue"></param>
110 /// <returns></returns>
111 public bool SeqQueueIsEmpty<T>(SeqQueue<T> seqQueue)
112 {
113 //如果两指针重合,说明队列已经清空
114 if (seqQueue.size == 0)
115 return true;
116 return false;
117 }
118 #endregion
119
120 #region 队列是否已满
121 /// <summary>
122 /// 队列是否已满
123 /// </summary>
124 /// <typeparam name="T"></typeparam>
125 /// <param name="seqQueue"></param>
126 /// <returns></returns>
127 public bool SeqQueueIsFull<T>(SeqQueue<T> seqQueue)
128 {
129 //采用循环队列后,头指针
130 if (seqQueue.size == seqQueue.MaxSize)
131 return true;
132 return false;
133 }
134 #endregion
135
136 #region 队列元素入队
137 /// <summary>
138 /// 队列元素入队
139 /// </summary>
140 /// <typeparam name="T"></typeparam>
141 /// <param name="seqQueue"></param>
142 /// <param name="data"></param>
143 /// <returns></returns>
144 public SeqQueue<T> SeqQueueIn<T>(SeqQueue<T> seqQueue, T data)
145 {
146 //如果队列已满,则不能进行入队操作
147 if (SeqQueueIsFull(seqQueue))
148 throw new Exception("队列已满,还入啥队列啊!");
149
150 //采用循环队列,必须先赋值,在自增tail指针
151 seqQueue.data[seqQueue.tail] = data;
152 seqQueue.tail = (seqQueue.tail + 1) % seqQueue.MaxSize;
153
154 //队列实际元素增加
155 seqQueue.size++;
156
157 return seqQueue;
158 }
159 #endregion
160
161 #region 队列元素出队
162 /// <summary>
163 /// 队列元素出队
164 /// </summary>
165 /// <typeparam name="T"></typeparam>
166 /// <param name="seqQueue"></param>
167 /// <returns></returns>
168 public T SeqQueueOut<T>(SeqQueue<T> seqQueue)
169 {
170 if (SeqQueueIsEmpty(seqQueue))
171 throw new Exception("队列已空,大哥,不要在出队了!");
172
173 //循环队列出队,展现的是head的灵活性
174 seqQueue.head = (seqQueue.head + 1) % seqQueue.MaxSize;
175
176 //队列实际元素递减
177 seqQueue.size--;
178
179 return seqQueue.data[seqQueue.head];
180 }
181 #endregion
182
183 #region 获取队头元素
184 /// <summary>
185 /// 获取队头元素
186 /// </summary>
187 /// <typeparam name="T"></typeparam>
188 /// <param name="seqQueue"></param>
189 /// <returns></returns>
190 public T SeqQueuePeek<T>(SeqQueue<T> seqQueue)
191 {
192 if (SeqQueueIsEmpty(seqQueue))
193 throw new Exception("队列已空,不能进行出队操作");
194
195 return seqQueue.data[seqQueue.head];
196 }
197 #endregion
198
199 #region 获取队列长度
200 /// <summary>
201 /// 获取队列长度
202 /// </summary>
203 /// <typeparam name="T"></typeparam>
204 /// <param name="seqQueue"></param>
205 /// <returns></returns>
206 public int SeqQueueLen<T>(SeqQueue<T> seqQueue)
207 {
208 return seqQueue.size;
209 }
210 #endregion
211 }
212 #endregion
213 }