启迪思维:循环顺序队列
前几天和女朋友一起参加一个技术沙龙,走到地铁又想到自己的疑问,为啥很大多数电梯只有向上的电梯,而没有向下的;以前想过各种解释(节约成本、基于安全考虑等等),女朋友说因为向上的电梯都离开地铁,设计有电梯可以让人快点离开,向下是进入地铁,没有电梯是让人慢点进入地铁,这样的设计一定程度缓解地铁人流量压力。觉得是目前我想过和听过最合理的解释,也许有一天会有更合理的解释,每一次的思考都会离真理更近,没事多思考,突然有天想通一个困扰你很久的问题,真的很幸福。
一:概念
队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。
队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加,还在需要考试的鞋童有事没事看看概念(免得被考试鄙视了),已经在写代码的鞋童多想想为什么下面代码会这样写,或者想想有没有更好实现方式。
二:示例图
三:栈的应用
1、操作系统底层消息实现
2、舞伴问题
3、迷宫问题
四:代码分析
顺序队列示例图所示,分为普通顺序存储和循环存储,普通顺序存储节点出队后,其所占空间无法再次利用,导致浪费空间,为充分利用存储空间,克服"假溢出"现象将存储空间想象为一个首尾相接的圆环。下面代码分析为循环存储
1、入队节点
入队列示例图如下:
代码分析如下:
1 /** 2 *元素入队列 3 */ 4 void EnQueue(const T &e){ 5 6 //判断队列是否已经满,如果慢,则自动增长空间为原来的2倍 7 if(IsFull()){ 8 //创建一个新的指针数组 9 T *newBase = new T[size*2]; 10 //复制原空间的数据到新创建空间 11 memcpy(newBase,base,sizeof(base)+1); 12 //这种删除内存很危险,更多请参考boost里shared_array 13 delete[] base; 14 //指向新空间 15 base = newBase; 16 17 //初始化队头下标 18 front = 0; 19 //初始化队尾下标 20 rear = size -1; 21 //更新队列大小 22 size = size * 2; 23 } 24 //元素入队尾 25 *(base + rear) = e; 26 //改变队尾下标,看不懂下面表达式,可以用特例带入推理下,或者多几个图试试 27 rear = ++rear % size; 28 }
2、出队节点
出队列示例图如下:
代码分析如下:
1 /** 2 *元素出队列 3 */ 4 bool DeQueue(T &e){ 5 //判断是否为空 6 if(IsEmpty()){ 7 return false; 8 } 9 //取出队头元素 10 e = *(base + front); 11 //改变队头下标 12 front = ++front % size; 13 14 return true; 15 }
3、清空队列
1 //清空队列 2 void Clear(){ 3 //如果不等空,则初始化front,rear 4 if(!IsEmpty()){ 5 //保证比较好缩进和空格,可以让代码跟美观和易阅读,慢慢就能玩好程序艺术 6 front = rear = 0; 7 } 8 }
4、判断是否为空
1 /** 2 *判断队列是否为空 3 */ 4 bool IsEmpty() const { 5 return front == rear; 6 }
5、判断队列是否满
1 /** 2 *判断队列是否已经满 3 *要是春节火车站有这个判断该有多好,不知道效率会高多少倍 4 */ 5 bool IsFull() const { 6 //关于(rear + 1) % size == front这个表达式看不懂没有关系, 7 //比如size = 5,front = 0,那么rear = 4,则队列满 8 //比如size = 5,front = 1,那么rear = 0(这个地方什么等于1,参考EnQueue方法),则队列满 9 //在很多看不懂算法,可以用带入特例,帮忙我们更好理解 10 return (rear + 1) % size == front; 11 }
6、获取队头元素
1 /** 2 *获取队列头元素 3 */ 4 bool GetFront(T &e) const { 5 //如果队列为空,则不处理 6 if(IsEmpty()){ 7 return false; 8 } 9 10 //直接获取队列头元素,如果对*(base + front);有疑惑请学习指针、数组、指针数组 11 e = *(base + front); 12 return true; 13 }
7、计算队列大小
1 /** 2 *计算队列大小 3 */ 4 int GetSize() const { 5 return rear - front; 6 }
8、完整代码
ArrayQueue.h:
1 /* 2 * ArrayQueue.h 3 * 4 * Created on: May 16, 2013 5 * Author: sunysen 6 */ 7 8 #ifndef ARRAYQUEUE_H_ 9 #define ARRAYQUEUE_H_ 10 11 /** 12 *循环顺序队列实现代码 13 */ 14 template <class T> 15 class ArrayQueue{ 16 private: 17 int size;//队列大小 18 int front;//队列下标 19 int rear;//队尾下标 20 T *base;//元素存储空间 21 public: 22 /** 23 * 构造函数和初始化列表 24 * explicit 只对构造函数起作用,用来抑制隐式转换 25 * 想要用C++写出高效的代码,需要我们了解编译器为我们写代码做哪些转换 26 * 比如(对象如何初始化,销毁,为啥不能根据返回值内型不通做重载, 27 * 一个cpp文件修改,包含这个cpp文件都需要重新编译等等) 28 * 在这方面我也是菜鸟,想了解跟多,可以读下<<深度探索C++对象模型>> 29 */ 30 explicit ArrayQueue(int k):size(k),front(0),rear(0),base(new T[k]){ 31 } 32 /** 33 *析构函数 34 */ 35 ~ArrayQueue(){ 36 //这种删除内存很危险,更多请参考boost里shared_array 37 //专家都建议,在实际开发中尽量少用数组,用vector代替 38 delete[] base; 39 } 40 /** 41 *判断队列是否为空 42 */ 43 bool IsEmpty() const { 44 return front == rear; 45 } 46 47 /** 48 *计算队列大小 49 */ 50 int GetSize() const { 51 return rear - front; 52 } 53 54 /** 55 *获取队列头元素 56 */ 57 bool GetFront(T &e) const { 58 //如果队列为空,则不处理 59 if(IsEmpty()){ 60 return false; 61 } 62 63 //直接获取队列头元素,如果对*(base + front);有疑惑请学习指针、数组、指针数组 64 e = *(base + front); 65 return true; 66 } 67 68 /** 69 *判断队列是否已经满 70 *要是春节火车站有这个判断该有多好,不知道效率会高多少倍 71 */ 72 bool IsFull() const { 73 //关于(rear + 1) % size == front这个表达式看不懂没有关系, 74 //比如size = 5,front = 0,那么real = 4,则队列满 75 //比如size = 5,front = 1,那么real = 0(这个地方什么等于1,参考EnQueue方法),则队列满 76 //在很多看不懂算法,可以用带入特例,帮忙我们更好理解 77 return (rear + 1) % size == front; 78 } 79 80 //清空队列 81 void Clear(){ 82 //如果不等空,则初始化front,rear 83 if(!IsEmpty()){ 84 //保证比较好缩进和空格,可以让代码跟美观和易阅读,慢慢就能玩好程序艺术 85 front = rear = 0; 86 } 87 } 88 89 /** 90 *元素入队列 91 */ 92 void EnQueue(const T &e){ 93 94 //判断队列是否已经满,如果慢,则自动增长空间为原来的2倍 95 if(IsFull()){ 96 //创建一个新的指针数组 97 T *newBase = new T[size*2]; 98 //复制原空间的数据到新创建空间 99 memcpy(newBase,base,sizeof(base)+1); 100 //这种删除内存很危险,更多请参考boost里shared_array 101 delete[] base; 102 //指向新空间 103 base = newBase; 104 105 //初始化队头下标 106 front = 0; 107 //初始化队尾下标 108 rear = size -1; 109 //更新队列大小 110 size = size * 2; 111 } 112 //元素入队尾 113 *(base + rear) = e; 114 //改变队尾下标,看不懂下面表达式,可以用特例带入推理下,或者多几个图试试 115 rear = ++rear % size; 116 } 117 /** 118 *元素出队列 119 */ 120 bool DeQueue(T &e){ 121 //判断是否为空 122 if(IsEmpty()){ 123 return false; 124 } 125 //取出队头元素 126 e = *(base + front); 127 //改变队头下标 128 front = ++front % size; 129 130 return true; 131 } 132 /** 133 * 测试队列所有的方法 134 */ 135 void test(){ 136 std::cout<<"-----------EnQueue queue begin------------"<<std::endl; 137 for(size_t i = 1; i < 5; ++i){ 138 EnQueue(i); 139 } 140 std::cout<<"-----------EnQueue queue end------------"<<std::endl; 141 142 std::cout<<"frist queue length="<<GetSize()<<std::endl; 143 144 std::cout<<"-----------GetFront queue begin------------"<<std::endl; 145 bool isFront = false; 146 T ee; 147 isFront = GetFront(ee); 148 if(isFront){ 149 std::cout<<"ee is value = "<<ee<<"\n"; 150 }else{ 151 std::cout<<"ee is value = empty"<<"\n"; 152 } 153 std::cout<<"-----------GetFront queue end------------"<<std::endl; 154 155 std::cout<<"-----------DeQueue queue begin------------"<<std::endl; 156 157 T e; 158 for(size_t i = 1; i < 5; ++i){ 159 DeQueue(e) ; 160 std::cout<<"e is value = "<<e<<"\n"; 161 } 162 std::cout<<"-----------DeQueue queue end------------"<<std::endl; 163 164 std::cout<<"secend queue size="<<GetSize()<<std::endl; 165 Clear(); 166 167 std::cout<<"-----------GetFront queue begin------------"<<std::endl; 168 T eee; 169 isFront = GetFront(eee); 170 if(isFront){ 171 std::cout<<"eee is value = "<<eee<<"\n"; 172 }else{ 173 std::cout<<"eee is value = empty"<<"\n"; 174 } 175 std::cout<<"-----------GetFront queue end------------"<<std::endl; 176 177 std::cout<<"third queue size="<<GetSize()<<std::endl; 178 } 179 180 }; 181 182 #endif /* ARRAYQUEUE_H_ */
Common.h:
1 /* 2 * Common.h 3 * 4 * Created on: May 17, 2012 5 * Author: sunysen 6 */ 7 8 #ifndef COMMON_H_ 9 #define COMMON_H_ 10 11 #include <iostream> 12 #include "memory" 13 #include "string" 14 #include "string.h" 15 #include <math.h> 16 #include "core/node/LNode.h" 17 18 19 using namespace std; 20 #endif /* COMMON_H_ */
9、运行结果
测试队列完整代码:
1 /** 2 * 测试队列所有的方法 3 */ 4 void test(){ 5 std::cout<<"-----------EnQueue queue begin------------"<<std::endl; 6 for(size_t i = 1; i < 5; ++i){ 7 EnQueue(i); 8 } 9 std::cout<<"-----------EnQueue queue end------------"<<std::endl; 10 11 std::cout<<"frist queue length="<<GetSize()<<std::endl; 12 13 std::cout<<"-----------GetFront queue begin------------"<<std::endl; 14 bool isFront = false; 15 T ee; 16 isFront = GetFront(ee); 17 if(isFront){ 18 std::cout<<"ee is value = "<<ee<<"\n"; 19 }else{ 20 std::cout<<"ee is value = empty"<<"\n"; 21 } 22 std::cout<<"-----------GetFront queue end------------"<<std::endl; 23 24 std::cout<<"-----------DeQueue queue begin------------"<<std::endl; 25 26 T e; 27 for(size_t i = 1; i < 5; ++i){ 28 DeQueue(e) ; 29 std::cout<<"e is value = "<<e<<"\n"; 30 } 31 std::cout<<"-----------DeQueue queue end------------"<<std::endl; 32 33 std::cout<<"secend queue size="<<GetSize()<<std::endl; 34 Clear(); 35 36 std::cout<<"-----------GetFront queue begin------------"<<std::endl; 37 T eee; 38 isFront = GetFront(eee); 39 if(isFront){ 40 std::cout<<"eee is value = "<<eee<<"\n"; 41 }else{ 42 std::cout<<"eee is value = empty"<<"\n"; 43 } 44 std::cout<<"-----------GetFront queue end------------"<<std::endl; 45 46 std::cout<<"third queue size="<<GetSize()<<std::endl; 47 }
运行结果如图:
五:环境
1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
2、开发工具:Eclipse+make
六:题记
1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;
2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;
3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";
4、运行上面的代码还需要在包含一个公共的头文件(Common.h);
欢迎继续阅读“启迪思维:数据结构和算法”系列