在本章中,要讨论如何通过使用了指针的简单数据结构表示动态集合。有很多的复杂的数据结构可以用指针来构造,本章介绍几种基本数据结构,包括栈、队列、链表,以及有根树。

GitHub 第十章 程序实现代码

栈和队列都是动态集合,在这种结构中delete操作去掉的元素是预先规定好的。栈数据结构实现的是一种先进后出(FILO)的策略。作用于栈上的Insert操作称为压入Push,而无参数的Delete操作称为弹出Pop。

本章讨论的栈是用数组实现,数据结构定义为(CStack.h):

  1. #ifndef _CSTACK_H
  2. #define _CSTACK_H
  3. #include <iostream>
  4. class CStack{
  5. public:
  6. CStack();
  7. ~CStack();
  8. int getTop()
  9. {
  10. return data[top];
  11. }
  12. bool empty()
  13. {
  14. return top == -1;
  15. }
  16. bool full()
  17. {
  18. return top == (maxSize - 1);
  19. }
  20. void push(int x);
  21. void pop();
  22. void display();
  23. private:
  24. int *data;
  25. int top;
  26. const int maxSize = 100;
  27. };
  28. #endif

如上所示,类CStack表示一个自定义栈,其包含数据成员data这样一个动态数组存储元素,top为当前栈顶元素的下标,当top==-1时表示栈空,当top==maxSize-1时表示栈满。

类实现(CStack.cpp)如下:

  1. #include "CStack.h"
  2. #include <iostream>
  3. CStack::CStack()
  4. {
  5. top = -1;
  6. data = new int[maxSize];
  7. }
  8. CStack::~CStack()
  9. {
  10. delete data;
  11. }
  12. void CStack::push(int x)
  13. {
  14. if (full())
  15. {
  16. std::cout << "栈已满!" << std::endl;
  17. return;
  18. }
  19. else
  20. top = top + 1;
  21. data[top] = x;
  22. }
  23. void CStack::pop()
  24. {
  25. if (empty())
  26. {
  27. std::cout << "栈空!" << std::endl;
  28. return;
  29. }
  30. else
  31. std::cout << data[top--] << std::endl;
  32. }
  33. void CStack::display()
  34. {
  35. if (empty())
  36. {
  37. std::cout << "栈空!" << std::endl;
  38. return;
  39. }
  40. for (int i = 0; i <= top; i++)
  41. std::cout << data[i] << "\t";
  42. std::cout << std::endl;
  43. }

下面给出对以上自定义栈的测试程序(main.cpp)与测试结果:

  1. #include "CStack.h"
  2. #include <iostream>
  3. #include <cstdlib>
  4. using namespace std;
  5. int main()
  6. {
  7. CStack cs;
  8. //显示栈中元素
  9. cout << "当前栈中元素有:" << endl;
  10. cs.display();
  11. //添加元素入栈
  12. for (int i = 0; i < 10; i++)
  13. cs.push(i*i);
  14. //显示栈中元素
  15. cout << "当前栈中元素有:" << endl;
  16. cs.display();
  17. //显示栈顶元素
  18. cout << "栈顶元素为:" << endl;
  19. cout << cs.getTop() << endl;
  20. //栈顶元素出栈
  21. cout << "栈顶元素出栈:"<< endl;
  22. cs.pop();
  23. //重新打印栈元素
  24. cout << "当前栈中元素有:" << endl;
  25. cs.display();
  26. system("pause");
  27. return 0;
  28. }

测试结果为:

队列

类似于数据结构栈,队列实现了一种先进先出(FIFO)的策略,在队列中,可以去掉的那个元素总是在集合中存在时间最长的那个。

我们把作用于队列上的Insert操作称为入对EnQueue,把作用于队列上的Delete操作称为出队DeQueue。

本章讨论的队列也是用数组实现的,基本结构(CQueue.h)如下:

  1. #ifndef _CQUEUE_H_
  2. #define _CQUEUE_H_
  3. #include <iostream>
  4. class CQueue{
  5. public:
  6. CQueue();
  7. ~CQueue();
  8. bool empty()
  9. {
  10. return head == tail;
  11. }
  12. bool full()
  13. {
  14. return ((head == tail + 1) || (head == 0 && tail == maxSize-1));
  15. }
  16. void enQueue(int x);
  17. void deQueue();
  18. void display();
  19. private:
  20. int *data;
  21. int head;
  22. int tail;
  23. //队列最大容量,最多存储maxSize-1个元素
  24. const int maxSize = 10;
  25. };
  26. #endif

如上所示,队列使用数据成员data这样一个动态数组存储元素,另外还包含的队头,队尾两个成员变量,在本程序实现的是一个循环队列,当head == tail时队列为空,当((head == tail + 1) || (head == 0 && tail == maxSize-1))队列为满,整个队列中最多存储了maxSize-1个元素。

类实现(CQueue.cpp)如下:

  1. #include "CQueue.h"
  2. #include <iostream>
  3. using namespace std;
  4. CQueue::CQueue()
  5. {
  6. head = 0;
  7. tail = 0;
  8. data = new int[maxSize];
  9. }
  10. CQueue::~CQueue()
  11. {
  12. delete data;
  13. }
  14. void CQueue::enQueue(int x)
  15. {
  16. if (full())
  17. {
  18. cout << "队列已满" << endl;
  19. return;
  20. }
  21. data[tail] = x;
  22. if (tail == maxSize - 1)
  23. tail = 0;
  24. else tail += 1;
  25. }
  26. void CQueue::deQueue()
  27. {
  28. if (empty())
  29. {
  30. cout << "队列空!" << endl;
  31. return;
  32. }
  33. cout << data[head] << endl;
  34. if (head == maxSize - 1)
  35. head = 0;
  36. else
  37. head += 1;
  38. }
  39. void CQueue::display()
  40. {
  41. if (empty())
  42. {
  43. cout << "队列空!" << endl;
  44. return;
  45. }
  46. if (head > tail)
  47. {
  48. for (int i = head; i < maxSize-1; i++)
  49. cout << data[i] << "\t";
  50. for (int j = 0; j < tail; j++)
  51. cout << data[j] << "\t";
  52. }
  53. else{
  54. for (int i = head; i < tail; i++)
  55. cout << data[i] << "\t";
  56. }
  57. cout << endl;
  58. }

下面给出对以上自定义队列的测试程序(main.cpp)与测试结果:

  1. #include "CQueue.h"
  2. #include <iostream>
  3. #include <cstdlib>
  4. using namespace std;
  5. int main()
  6. {
  7. CQueue cq;
  8. cout << "此时队的状态是:" << endl;
  9. cq.display();
  10. for (int i = 0; i < 9; i++)
  11. cq.enQueue(i + 1);
  12. //显示队列中所有元素
  13. cout << "显示队中所有元素" << endl;
  14. cq.display();
  15. //再次入队一个元素
  16. cout << "元素11入队!";
  17. cq.enQueue(11);
  18. //队头依次出队
  19. cout << "队头元素:" << endl;
  20. cq.deQueue();
  21. cout << "队头元素:" << endl;
  22. cq.deQueue();
  23. cout << "队头元素:" << endl;
  24. cq.deQueue();
  25. //此时入队新元素
  26. cout << "元素11入队!"<<endl;
  27. cq.enQueue(11);
  28. cout << "元素12入队!"<<endl;
  29. cq.enQueue(12);
  30. //再次显示队列中所有元素
  31. cout << "显示队中所有元素" << endl;
  32. cq.display();
  33. system("pause");
  34. return 0;
  35. }

测试结果:

双向链表

在链表这种数据结构中,各个对象按照线性顺序排序。链表与数组不同,数组的线性顺序是由数组下标决定的,而链表中的顺序是由各个对象的指针决定的。

对于本节讨论的双向链表,定义结构(CList.h)如下:

  1. #ifndef _CLIST_H_
  2. #define _CLIST_H_
  3. #include <iostream>
  4. //双向链表结构体
  5. typedef struct Node{
  6. Node *prev;
  7. Node *next;
  8. int key;
  9. Node(int x) :prev(NULL), next(NULL), key(x){}
  10. };
  11. class CList{
  12. public:
  13. CList();
  14. ~CList();
  15. void Insert(int x);
  16. Node *Search(int value);
  17. void Delete(int x);
  18. void Display();
  19. private:
  20. Node *head;
  21. };
  22. #endif

如上所示,结构体Node定义了链表中结点的基本内容,包括prev,next两个指针来实现双向性,以及一个key存储结点元素。在CList类中定义了4中基本操作,本处对于增、查、删操作函数参数均为基本元素而不是链表结点(书中是以结点作为参数的,本处认为以元素值作为参数封装性更好)。

类实现(CList.cpp)如下:

  1. #include "CList.h"
  2. #include <iostream>
  3. #include <cstdlib>
  4. using namespace std;
  5. CList::CList()
  6. {
  7. head = NULL;
  8. }
  9. CList::~CList()
  10. {
  11. delete head;
  12. }
  13. void CList::Insert(int x)
  14. {
  15. //新建一个结点,其值为x
  16. Node *node = new Node(x);
  17. node->next = head;
  18. if (head != NULL)
  19. {
  20. head->prev = node;
  21. }
  22. head = node;
  23. head->prev = NULL;
  24. }
  25. Node * CList::Search(int value)
  26. {
  27. Node *node = head;
  28. while (node != NULL && node->key != value)
  29. {
  30. node = node->next;
  31. }
  32. //如果找到相应结点
  33. if (node)
  34. return node;
  35. else
  36. return NULL;
  37. }
  38. //删除值为x的结点
  39. void CList::Delete(int x)
  40. {
  41. if (head == NULL)
  42. {
  43. std::cout << "链表为空!" << endl;
  44. return;
  45. }
  46. Node *node = Search(x);
  47. if (node)
  48. {
  49. if (node->prev != NULL)
  50. node->prev->next = node->next;
  51. else
  52. head = node->next;
  53. if (node->next != NULL)
  54. node->next->prev = node->prev;
  55. delete node;
  56. return;
  57. }
  58. else{
  59. std::cout << "链表中无值为"<<x<<"的元素!" << endl;
  60. return;
  61. }
  62. }
  63. void CList::Display()
  64. {
  65. if (head == NULL)
  66. {
  67. std::cout << "链表为空!" << std::endl;
  68. return;
  69. }
  70. Node *node = head;
  71. while (node)
  72. {
  73. cout << node->key << "\t";
  74. node = node->next;
  75. }
  76. cout << endl;
  77. }

代码说明不再赘述,下面给出自定义双向链表的测试程序(main.cpp)以及测试结果:

  1. #include "CList.h"
  2. #include <iostream>
  3. #include <cstdlib>
  4. using namespace std;
  5. int main()
  6. {
  7. CList list;
  8. //打印链表
  9. list.Display();
  10. //向链表插入元素
  11. for (int i = 0; i < 10; i++)
  12. list.Insert(i*i);
  13. cout << "打印链表:" << endl;
  14. list.Display();
  15. //查找元素为9的结点
  16. Node *node = list.Search(9);
  17. cout << "查找元素为9的结点" << node->key << endl;
  18. //删除元素为10的结点
  19. list.Delete(10);
  20. cout << "打印链表:" << endl;
  21. list.Display();
  22. //删除元素为36的结点
  23. list.Delete(36);
  24. cout << "打印链表:" << endl;
  25. list.Display();
  26. system("pause");
  27. return 0;
  28. }

测试结果:

带哨兵的循环双向链表

我们在处理边界条件时为了方便而且减少错误,经常使用哨兵(Sentinel),它是一个哑对象,可以简化边界条件。此处,在链表的实现中实现哨兵,不仅可以减少因边界处理不当而引起的指针异常,还可以实现循环的双向链表。基本操作与上类似,下面给出具体实现:

数据结构(SList.h)如下:

  1. #ifndef _SLIST_H_
  2. #define _SLIST_H_
  3. #include <iostream>
  4. /**
  5. * 带哨兵的环形双向链表结构体
  6. */
  7. typedef struct SNode{
  8. SNode *prev;
  9. SNode *next;
  10. int key;
  11. SNode(int x) :prev(NULL), next(NULL), key(x){}
  12. };
  13. class SList{
  14. public:
  15. SList();
  16. ~SList();
  17. void Insert(int x);
  18. SNode *Search(int value);
  19. void Delete(int x);
  20. void Display();
  21. private:
  22. SNode *nil;
  23. };
  24. #endif

类实现(SList.cpp)如下:

  1. #include "SList.h"
  2. #include <iostream>
  3. using namespace std;
  4. SList::SList()
  5. {
  6. nil = new SNode(0);
  7. nil->prev = nil;
  8. nil->next = nil;
  9. }
  10. SList::~SList()
  11. {
  12. delete nil;
  13. }
  14. void SList::Insert(int x)
  15. {
  16. //新建一个结点,其值为x
  17. SNode *node = new SNode(x);
  18. node->next = nil->next;
  19. nil->next->prev = node;
  20. nil->next = node;
  21. node->prev = nil;
  22. }
  23. SNode * SList::Search(int value)
  24. {
  25. SNode *node = nil->next;
  26. while (node != nil && node->key != value)
  27. {
  28. node = node->next;
  29. }
  30. //如果找到相应结点
  31. if (node != nil)
  32. return node;
  33. else
  34. return NULL;
  35. }
  36. //删除值为x的结点
  37. void SList::Delete(int x)
  38. {
  39. if (nil->next == nil)
  40. {
  41. std::cout << "链表为空!" << endl;
  42. return;
  43. }
  44. SNode *node = Search(x);
  45. if (node)
  46. {
  47. node->prev->next = node->next;
  48. node->next->prev = node->prev;
  49. delete node;
  50. return;
  51. }
  52. else{
  53. std::cout << "链表中无值为" << x << "的元素!" << endl;
  54. return;
  55. }
  56. }
  57. void SList::Display()
  58. {
  59. if (nil == nil)
  60. {
  61. std::cout << "链表为空!" << std::endl;
  62. return;
  63. }
  64. SNode *node = nil->next;
  65. while (node->next != nil)
  66. {
  67. cout << node->key << "\t";
  68. node = node->next;
  69. }
  70. cout << endl;
  71. }

下面给出测试程序(main.cpp)和测试结果:

  1. #include "SList.h"
  2. #include <iostream>
  3. #include <cstdlib>
  4. using namespace std;
  5. int main()
  6. {
  7. SList list;
  8. //打印链表
  9. list.Display();
  10. //向链表插入元素
  11. for (int i = 0; i < 10; i++)
  12. list.Insert(i*i);
  13. cout << "打印链表:" << endl;
  14. list.Display();
  15. //查找元素为9的结点
  16. SNode *node = list.Search(9);
  17. cout << "查找元素为9的结点" << node->key << endl;
  18. //删除元素为10的结点
  19. list.Delete(10);
  20. cout << "打印链表:" << endl;
  21. list.Display();
  22. //删除元素为36的结点
  23. list.Delete(36);
  24. cout << "打印链表:" << endl;
  25. list.Display();
  26. system("pause");
  27. return 0;
  28. }

测试结果:

《算法导论》 — Chapter 10 基本数据结构的更多相关文章

  1. 算法导论——lec 10 图的基本算法及应用

    搜索一个图是有序地沿着图的边訪问全部定点, 图的搜索算法能够使我们发现非常多图的结构信息, 图的搜索技术是图算法邻域的核心. 一. 图的两种计算机表示 1. 邻接表: 这样的方法表示稀疏图比較简洁紧凑 ...

  2. 《算法导论》 — Chapter 7 高速排序

    序 高速排序(QuickSort)也是一种排序算法,对包括n个数组的输入数组.最坏情况执行时间为O(n^2). 尽管这个最坏情况执行时间比較差.可是高速排序一般是用于排序的最佳有用选择.这是由于其平均 ...

  3. 《算法导论》 — Chapter 7 快速排序

    序 快速排序(QuickSort)也是一种排序算法,对包含n个数组的输入数组,最坏情况运行时间为O(n^2).虽然这个最坏情况运行时间比较差,但是快速排序通常是用于排序的最佳实用选择,这是因为其平均性 ...

  4. 数据结构和算法(Golang实现)(10)基础知识-算法复杂度主方法

    算法复杂度主方法 有时候,我们要评估一个算法的复杂度,但是算法被分散为几个递归的子问题,这样评估起来很难,有一个数学公式可以很快地评估出来. 一.复杂度主方法 主方法,也可以叫主定理.对于那些用分治法 ...

  5. 基本数据结构(2)——算法导论(12)

    1. 引言     这一篇博文主要介绍链表(linked list),指针和对象的实现,以及有根树的表示. 2. 链表(linked list) (1) 链表介绍      我们在上一篇中提过,栈与队 ...

  6. (搬运)《算法导论》习题解答 Chapter 22.1-1(入度和出度)

    (搬运)<算法导论>习题解答 Chapter 22.1-1(入度和出度) 思路:遍历邻接列表即可; 伪代码: for u 属于 Vertex for v属于 Adj[u] outdegre ...

  7. 《算法导论》— Chapter 9 中位数和顺序统计学

    序 在算法导论的第二部分主要探讨了排序和顺序统计学,第六章~第八章讨论了堆排序.快速排序以及三种线性排序算法.该部分的最后一个章节,将讨论顺序统计方面的知识. 在一个由n个元素组成的集合中,第i个顺序 ...

  8. 《算法导论》 — Chapter 8 线性时间排序

    序 到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序.合并排序.堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势.它们都有一个相同的特点,以上所有排序的结果序列,各个 ...

  9. 《算法导论》— Chapter 15 动态规划

    序 算法导论一书的第四部分-高级设计和分析技术从本章开始讨论,主要分析高效算法的三种重要技术:动态规划.贪心算法以及平摊分析三种. 首先,本章讨论动态规划,它是通过组合子问题的解而解决整个问题的,通常 ...

随机推荐

  1. 洛谷 P3400 仓鼠窝

    卡常 #pragma GCC optimize(2) #include<cstdio> #include<algorithm> #include<cstring> ...

  2. 521 Longest Uncommon Subsequence I 最长特殊序列 Ⅰ

    给定两个字符串,你需要从这两个字符串中找出最长的特殊序列.最长特殊序列定义如下:该序列为某字符串独有的最长子序列(即不能是其他字符串的子序列).子序列可以通过删去字符串中的某些字符实现,但不能改变剩余 ...

  3. 分享一个实用任意路数PWM函数

    一.什么是PWM? 1.科普一下什么是PWM,嘿嘿,莫闲啰嗦,好好看看,可能大多数人听过,但可能没详细了解过,至此不妨花费几分钟,详细了解哈,PWM中文译名为:脉冲宽度调制,即控制电路在输出频率不变的 ...

  4. C. Molly's Chemicals 暴力 + 统计技巧

    http://codeforces.com/contest/776/problem/C 一开始做的时候,就发现是预处理前缀和,然后对于每一个前缀和,如果他能成为一个贡献,就是能和前面的某些段 组合成和 ...

  5. Dapper系列之二:Dapper的事务查询

    Dapepr讲解 上篇文章我们介绍了,什么是Dapepr,有什么好处,性能的对比,还有多表多数据添加操作(事务的封装)等等.本篇文章我们继续讲解.....如果本篇文章看不懂,请看我上一篇文章:Dape ...

  6. 释放资源的一般范式——更锋利的C#代码小记

    public class Photo : IDisposable { //在Finalize函数中调用内部的Dispose方法 ~Photo() { //被自动回收时仅释放托管资源,不释放非托管资源 ...

  7. SpringBoot项目不占用端口启动

    @EnableScheduling @SpringBootApplication public class Application { public static void main(String[] ...

  8. html5新增的主题结构元素

    article元素 article元素代表文档.页面或应用程序中独立的.完整的.可以独自被外部引用的内容. 它可以是一篇博客或者报刊中的文章,一篇论坛帖子.一段用户评论或独立的插件. 或其他任何独立的 ...

  9. Es6学习笔记(7)----数组的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 数组的扩展 1.扩展运算符:可以将数组转化成逗号隔离的单个参数...[1,2,3] //控制台运 ...

  10. pandas DataFrame 警告(SettingWithCopyWarning)

    转自:https://www.cnblogs.com/pig-fly/p/7875472.html 刚接触python不久,编程也是三脚猫,所以对常用的这几个工具还没有一个好的使用习惯,毕竟程序语言是 ...