• 数据结构初阶--栈和队列(讲解+类模板实现)


    栈的概念和结构

    栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)加粗样式的原则。
    入栈:从栈顶放入数据的操作。
    出栈:从栈顶取出元素的操作。

    栈的实现

    栈用链表和顺序表两种数据结构都可以实现,我们要确定选择哪一种更优,我们来分析一下。
    链表栈:如果选择单链表的话,我们应该选择头当栈顶,尾当栈底,不然的话,每次存取数据都要遍历一遍链表。所以选双链表会比较好一点。
    数组栈:访问栈顶的时间复杂度为O(1),相比链表栈比较优。
    所以下面我们用顺序表来实现栈的这种数据结构。
    结构如下:初始化栈的大小为5

    1. #define InitSize 5
    2. template <class DateType>
    3. class Stack
    4. {
    5. public:
    6. private:
    7. DateType* data;//栈空间指针
    8. int size;//栈容量
    9. int top;//栈顶,栈中元素个数
    10. };

    栈的接口

    栈要实现的接口有以下几个:

    1. Stack();//初始化栈,初始化的大小是5
    2. Stack(const Stack& stack);//拷贝构造函数
    3. Stack& operator=(const Stack& stack);//等号运算符的重载
    4. ~Stack();//销毁栈
    5. bool ifFull();//判断栈是否满了
    6. bool isEmpty();//判断栈是否为空
    7. void Push(const DateType& val);//入栈
    8. bool Pop(DateType &item);//出栈,并获取出栈元素
    9. void ExpandStack();//扩容
    10. void PrintStack();//打印

    栈的初始化

    初始化栈就是把结构体中的成员都初始化一下,方便后续的扩容等操作。
    具体实现如下:

    1. //初始化栈,初始化的大小是5
    2. Stack()
    3. {
    4. data = new DateType[InitSize];
    5. if (data == NULL)
    6. {
    7. cout << "内存分配失败" << endl;
    8. exit(-1);
    9. }
    10. size = InitSize;
    11. top = 0;
    12. }

    拷贝构造

    1. //拷贝构造函数
    2. Stack(const Stack& stack)
    3. {
    4. this->data = new DateType[stack.size];
    5. if (data == NULL)
    6. {
    7. cout << "内存分配失败" << endl;
    8. exit(-1);
    9. }
    10. for (int i = 0; i < stack.size; i++)
    11. {
    12. this->data[i] = stack.data[i];
    13. }
    14. this->size = stack.size;
    15. this->top = stack.top;
    16. }

    判断栈满

    1. //判断栈是否满了
    2. bool ifFull()
    3. {
    4. if (top == size)
    5. {
    6. return true;
    7. }
    8. return false;
    9. }

    扩容

    1. //扩容
    2. void ExpandStack()
    3. {
    4. this->size = this->size == 0 ? 4 : 2 * this->size;
    5. DateType* tmp = new DateType[this->size];
    6. if (tmp == NULL)
    7. {
    8. cout << "内存分配失败" << endl;
    9. exit(-1);
    10. }
    11. for (int i = 0; i < top; i++)
    12. {
    13. tmp[i] = data[i];
    14. }
    15. delete[] data;
    16. data = tmp;
    17. }

    判断栈空

    1. //判断栈是否为空
    2. bool isEmpty()
    3. {
    4. if (top == 0)
    5. {
    6. return true;
    7. }
    8. return false;
    9. }

    入栈

    压栈就是在栈顶插入元素,其中是肯定要考虑到扩容的问题,当this->top == this->size时,就要考虑到扩容了,扩容也是像之前顺序表那样每次扩一倍,这样可以一定程度地减少扩容次数,但同时是会带来一定的空间消耗的。

    1. //入栈
    2. void Push(const DateType& val)
    3. {
    4. if (ifFull())
    5. {
    6. ExpandStack();
    7. }
    8. data[top++] = val;
    9. }

    出栈

    出栈就是在栈顶pop掉一个元素,也就是top-1指向的位置,只需要将top进行一个减1的操作即可。
    与此同时,我们要确保此次栈不为空,所以要进行判断栈空的操作,防止程序崩溃。

    1. //出栈,并获取出栈元素
    2. bool Pop(DateType &item)
    3. {
    4. if (isEmpty())
    5. {
    6. cout << "栈为空,无法出栈" << endl;
    7. return false;
    8. }
    9. item = data[--top];
    10. return true;
    11. }

    赋值运算符重载

    运算符重载的注意事项在前面的博客我已经介绍过了,尤其是赋值运算符,感兴趣的小伙伴可以去看看,这里要注意几点

    • 返回的是*this,对象本身
    • 不要自己给自己赋值
    • 要防止内存泄漏
    • 防止浅拷贝的发生
    1. //等号运算符的重载
    2. Stack& operator=(const Stack& stack)
    3. {
    4. //防止自赋值
    5. if (this == &stack)
    6. {
    7. return *this;
    8. }
    9. //防止内存泄漏
    10. if (data != NULL)
    11. {
    12. delete[] data;
    13. }
    14. //防止浅拷贝
    15. this->data = new DateType[stack.size];
    16. if (data == NULL)
    17. {
    18. cout << "内存分配失败" << endl;
    19. exit(-1);
    20. }
    21. for (int i = 0; i < stack.top; i++)
    22. {
    23. this->data[i] = stack.data[i];
    24. }
    25. this->size = stack.size;
    26. this->top = stack.top;
    27. return *this;
    28. }

    打印

    1. //打印
    2. void PrintStack()
    3. {
    4. for (int i = 0; i < top; i++)
    5. {
    6. cout << data[i] << " ";
    7. }
    8. cout << endl;
    9. }

    整体代码以及测试

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include<iostream> //引入头文件
    3. #include<string>//C++中的字符串
    4. using namespace std; //标准命名空间
    5. #define InitSize 5
    6. /*
    7. 栈,利用顺序表实现
    8. */
    9. template <class DateType>
    10. class Stack
    11. {
    12. public:
    13. //初始化栈,初始化的大小是5
    14. Stack()
    15. {
    16. data = new DateType[InitSize];
    17. if (data == NULL)
    18. {
    19. cout << "内存分配失败" << endl;
    20. exit(-1);
    21. }
    22. size = InitSize;
    23. top = 0;
    24. }
    25. //拷贝构造函数
    26. Stack(const Stack& stack)
    27. {
    28. this->data = new DateType[stack.size];
    29. if (data == NULL)
    30. {
    31. cout << "内存分配失败" << endl;
    32. exit(-1);
    33. }
    34. for (int i = 0; i < stack.size; i++)
    35. {
    36. this->data[i] = stack.data[i];
    37. }
    38. this->size = stack.size;
    39. this->top = stack.top;
    40. }
    41. //等号运算符的重载
    42. Stack& operator=(const Stack& stack)
    43. {
    44. //防止自赋值
    45. if (this == &stack)
    46. {
    47. return *this;
    48. }
    49. //防止内存泄漏
    50. if (data != NULL)
    51. {
    52. delete[] data;
    53. }
    54. //防止浅拷贝
    55. this->data = new DateType[stack.size];
    56. if (data == NULL)
    57. {
    58. cout << "内存分配失败" << endl;
    59. exit(-1);
    60. }
    61. for (int i = 0; i < stack.top; i++)
    62. {
    63. this->data[i] = stack.data[i];
    64. }
    65. this->size = stack.size;
    66. this->top = stack.top;
    67. return *this;
    68. }
    69. //销毁栈
    70. ~Stack()
    71. {
    72. if (data != NULL)
    73. {
    74. delete[] data;
    75. data = NULL;
    76. }
    77. }
    78. //判断栈是否满了
    79. bool ifFull()
    80. {
    81. if (top == size)
    82. {
    83. return true;
    84. }
    85. return false;
    86. }
    87. //判断栈是否为空
    88. bool isEmpty()
    89. {
    90. if (top == 0)
    91. {
    92. return true;
    93. }
    94. return false;
    95. }
    96. //入栈
    97. void Push(const DateType& val)
    98. {
    99. if (ifFull())
    100. {
    101. ExpandStack();
    102. }
    103. data[top++] = val;
    104. }
    105. //出栈,并获取出栈元素
    106. bool Pop(DateType &item)
    107. {
    108. if (isEmpty())
    109. {
    110. cout << "栈为空,无法出栈" << endl;
    111. return false;
    112. }
    113. item = data[--top];
    114. return true;
    115. }
    116. //扩容
    117. void ExpandStack()
    118. {
    119. this->size = this->size == 0 ? 4 : 2 * this->size;
    120. DateType* tmp = new DateType[this->size];
    121. if (tmp == NULL)
    122. {
    123. cout << "内存分配失败" << endl;
    124. exit(-1);
    125. }
    126. for (int i = 0; i < top; i++)
    127. {
    128. tmp[i] = data[i];
    129. }
    130. delete[] data;
    131. data = tmp;
    132. }
    133. //打印
    134. void PrintStack()
    135. {
    136. for (int i = 0; i < top; i++)
    137. {
    138. cout << data[i] << " ";
    139. }
    140. cout << endl;
    141. }
    142. private:
    143. DateType* data;//栈空间指针
    144. int size;//栈容量
    145. int top;//栈顶,栈中元素个数
    146. };
    147. int main()
    148. {
    149. Stack<int> stack;
    150. stack.Push(1);
    151. stack.Push(2);
    152. stack.Push(3);
    153. stack.Push(4);
    154. stack.Push(5);
    155. stack.PrintStack();
    156. cout << "-------------------------" << endl;
    157. int b = 0;
    158. stack.Pop(b);
    159. cout << b << endl;
    160. stack.Pop(b);
    161. cout << b << endl;
    162. stack.PrintStack();
    163. cout << "-------------------------" << endl;
    164. Stack<int> stack2(stack);
    165. stack2.PrintStack();
    166. cout << "-------------------------" << endl;
    167. Stack<int> stack3;
    168. stack3 = stack2;
    169. stack3.PrintStack();
    170. cout << "-------------------------" << endl;
    171. system("pause");
    172. return EXIT_SUCCESS;
    173. }

    队列

    队列的概念和结构

    队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。

    队列的结构,我们选取单链表来实现,秩序进行头删和为插的不足即可。如果选数组,那么每一次删头我们都要挪动一遍数据,这种方式不优,所以我们还是选取用单链表来实现。
    定义的结构如下:

    1. template<class DateType>
    2. //链队的结点类型
    3. struct Node
    4. {
    5. DateType data;
    6. Node<DateType> *next;
    7. Node(Node<DateType>* p = NULL)
    8. {
    9. next = p;
    10. }
    11. //构造函数
    12. Node(DateType val, Node<DateType>* p = NULL)
    13. {
    14. data = val;
    15. next = p;
    16. }
    17. };
    1. template <class DateType>
    2. class Queue
    3. {
    4. public:
    5. private:
    6. //声明,也是定义,只不过定义的是指针类型,保存的应该是地址,未初始化
    7. //队头指针
    8. Node<DateType>* front;
    9. //队尾指针
    10. Node<DateType>* rear;
    11. };

    队列的实现

    队列的接口

    1. Queue();//构造函数,初始化队列
    2. ~Queue();//析构函数
    3. bool QueuePush(const DateType& val);//队尾入队
    4. bool QueuePop(DateType& val);//对头出队列
    5. bool getFront(DateType& val) const;//获取对头元素的值
    6. bool getRear(DateType& val);//获取队尾元素的值
    7. void MakeEmpty();//将队列清空
    8. bool isEmpty() const;//判断队列是否为NULL
    9. int getSize() const;//返回队列元素的个数
    10. void PrintQueue();//输出队列元素

    队列的初始化

    初始化很简单,只要将头指针和尾指针都置空。

    1. //构造函数
    2. Queue()
    3. {
    4. front = NULL;
    5. rear = NULL;
    6. }

    判断队列是否为空

    1. //判断队列是否为NULL
    2. bool isEmpty() const
    3. {
    4. if (front == NULL)
    5. {
    6. return true;
    7. }
    8. else
    9. {
    10. return false;
    11. }
    12. }

    入队

    出队就是进行单链表尾删的操作,要考虑链表为空时不能进行删除,还要注意的是只有一个节点进行删除是要改变尾指针的指向。

    1. //队尾入队列
    2. bool QueuePush(const DateType& val)
    3. {
    4. if (front == NULL)
    5. {
    6. //如果队列为空,直接用指针开辟结点
    7. front = rear = new Node<DateType>(val);
    8. if (front == NULL)
    9. {
    10. cout << "内存分配失败" << endl;
    11. return false;
    12. }
    13. }
    14. else
    15. {
    16. Node<DateType>* p = new Node<DateType>(val);
    17. rear->next = p;
    18. if (rear->next == NULL)
    19. {
    20. cout << "内存分配失败" << endl;
    21. return false;
    22. }
    23. //更新尾结点
    24. rear = rear->next;
    25. }
    26. return true;
    27. }

    出队

    出队就是进行单链表尾删的操作,要考虑链表为空时不能进行删除,还要注意的是只有一个节点进行删除是要改变尾指针的指向。

    1. bool QueuePop(DateType& val)
    2. {
    3. //如果队列为空,不允许出列
    4. if (isEmpty())
    5. {
    6. return false;
    7. }
    8. else
    9. {
    10. Node<DateType>* p = front;
    11. val = front->data;
    12. front = front->next;
    13. delete p;
    14. return true;
    15. }
    16. }

    获取队头元素和队尾元素

    首先要确保链表不为空,对头就是返回头节点的大小,队尾就是返回尾节点的大小。
    具体实现如下:

    1. //获取对头元素的值
    2. bool getFront(DateType& val) const
    3. {
    4. if (isEmpty())
    5. {
    6. return false;
    7. }
    8. val = front->data;
    9. return true;
    10. }
    11. //获取队尾元素的值
    12. bool getRear(DateType& val) {
    13. if (isEmpty())
    14. {
    15. return false;
    16. }
    17. val = rear->data;
    18. return true;
    19. }

    获取队列元素个数

    遍历一遍链表,同时进行计数操作,最后返回计数结果即可。

    1. //返回队列元素的个数
    2. int getSize() const
    3. {
    4. //函数返回队列元素的个数
    5. Node<DateType>* p = front;
    6. int count = 0;
    7. while (p != NULL)
    8. {
    9. ++count;
    10. p = p->next;
    11. }
    12. return count;
    13. }

    队列的销毁

    为了防止内存泄漏,动态内存申请的空间一定要我们自己手动释放,养成一个良好的习惯。所以要将链表的空间逐个释放。

    1. //将队列清空
    2. void MakeEmpty()
    3. {
    4. //置空队列,释放链表中所有的结点
    5. Node<DateType>* current;
    6. if (front != NULL)
    7. {
    8. current = front;
    9. front = front->next;
    10. delete current;
    11. }
    12. }

    打印队列

    1. //输出队列元素
    2. void PrintQueue()
    3. {
    4. Node<DateType>* p = front;
    5. while (p != NULL)
    6. {
    7. cout << p->data << " ";
    8. p = p->next;
    9. }
    10. cout << endl;
    11. }

    整体代码以及测试

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include<iostream> //引入头文件
    3. #include<string>//C++中的字符串
    4. using namespace std; //标准命名空间
    5. /*
    6. 队列,单链表实现
    7. */
    8. template<class DateType>
    9. //链队的结点类型
    10. struct Node
    11. {
    12. DateType data;
    13. Node<DateType> *next;
    14. Node(Node<DateType>* p = NULL)
    15. {
    16. next = p;
    17. }
    18. //构造函数
    19. Node(DateType val, Node<DateType>* p = NULL)
    20. {
    21. data = val;
    22. next = p;
    23. }
    24. };
    25. template <class DateType>
    26. class Queue
    27. {
    28. public:
    29. //构造函数
    30. Queue()
    31. {
    32. front = NULL;
    33. rear = NULL;
    34. }
    35. //析构函数
    36. ~Queue()
    37. {
    38. MakeEmpty();
    39. }
    40. //队尾入队列
    41. bool QueuePush(const DateType& val)
    42. {
    43. if (front == NULL)
    44. {
    45. //如果队列为空,直接用指针开辟结点
    46. front = rear = new Node<DateType>(val);
    47. if (front == NULL)
    48. {
    49. cout << "内存分配失败" << endl;
    50. return false;
    51. }
    52. }
    53. else
    54. {
    55. Node<DateType>* p = new Node<DateType>(val);
    56. rear->next = p;
    57. if (rear->next == NULL)
    58. {
    59. cout << "内存分配失败" << endl;
    60. return false;
    61. }
    62. //更新尾结点
    63. rear = rear->next;
    64. }
    65. return true;
    66. }
    67. //对头出队列
    68. bool QueuePop(DateType& val)
    69. {
    70. //如果队列为空,不允许出列
    71. if (isEmpty())
    72. {
    73. return false;
    74. }
    75. else
    76. {
    77. Node<DateType>* p = front;
    78. val = front->data;
    79. front = front->next;
    80. delete p;
    81. return true;
    82. }
    83. }
    84. //获取对头元素的值
    85. bool getFront(DateType& val) const
    86. {
    87. if (isEmpty())
    88. {
    89. return false;
    90. }
    91. val = front->data;
    92. return true;
    93. }
    94. //获取队尾元素的值
    95. bool getRear(DateType& val) {
    96. if (isEmpty())
    97. {
    98. return false;
    99. }
    100. val = rear->data;
    101. return true;
    102. }
    103. //将队列清空
    104. void MakeEmpty()
    105. {
    106. //置空队列,释放链表中所有的结点
    107. Node<DateType>* current;
    108. if (front != NULL)
    109. {
    110. current = front;
    111. front = front->next;
    112. delete current;
    113. }
    114. }
    115. //判断队列是否为NULL
    116. bool isEmpty() const
    117. {
    118. if (front == NULL)
    119. {
    120. return true;
    121. }
    122. else
    123. {
    124. return false;
    125. }
    126. }
    127. //返回队列元素的个数
    128. int getSize() const
    129. {
    130. //函数返回队列元素的个数
    131. Node<DateType>* p = front;
    132. int count = 0;
    133. while (p != NULL)
    134. {
    135. ++count;
    136. p = p->next;
    137. }
    138. return count;
    139. }
    140. //输出队列元素
    141. void PrintQueue()
    142. {
    143. Node<DateType>* p = front;
    144. while (p != NULL)
    145. {
    146. cout << p->data << " ";
    147. p = p->next;
    148. }
    149. cout << endl;
    150. }
    151. private:
    152. //声明,也是定义,只不过定义的是指针类型,保存的应该是地址,未初始化
    153. //队头指针
    154. Node<DateType>* front;
    155. //队尾指针
    156. Node<DateType>* rear;
    157. };
    158. int main()
    159. {
    160. Queue<int> que;
    161. que.QueuePush(1);
    162. que.QueuePush(2);
    163. que.QueuePush(3);
    164. que.QueuePush(4);
    165. que.PrintQueue();
    166. cout << "----------------------" << endl;
    167. int b = 0;
    168. que.QueuePop(b);
    169. cout << b << endl;
    170. que.QueuePop(b);
    171. cout << b << endl;
    172. que.PrintQueue();
    173. cout << "----------------------" << endl;
    174. int c = 0;
    175. que.getFront(c);
    176. cout << c << endl;
    177. que.PrintQueue();
    178. cout << que.getSize() << endl;
    179. cout << "----------------------" << endl;
    180. system("pause");
    181. return EXIT_SUCCESS;
    182. }
  • 相关阅读:
    图论_3。
    ES6新增循环对象的四种方法(通俗易懂)
    基于springboot的支教系统-计算机毕业设计源码
    FastApi+Vue实现WebSockets服务端推送
    C语言变量的定义和赋值
    基于Java+SpringBoot+小程序实现的小说阅读管理系统
    【GitHub免密拉取代码】
    tesseract-ocr-w64-setup-5.3.3.20231005.exe 百度网盘下载
    centos7安装后的基础配置
    wordpress遇到的问题
  • 原文地址:https://blog.csdn.net/jh035/article/details/128110023