当前位置:   article > 正文

C++基础语法:STL之容器(5)--序列容器中的list(二)

C++基础语法:STL之容器(5)--序列容器中的list(二)

前言
     

         "打牢基础,万事不愁" .C++的基础语法的学习

引入

        序列容器的学习.以<C++ Prime Plus> 6th Edition(以下称"本书")内容理解

        本书中容器内容不多只有几页.最好是有数据结构方面的知识积累,如果没有在学的同时补上

         接上一篇C++基础语法:STL之容器(4)--序列容器中的list(一)-CSDN博客

list(双向链表)  

         本书内容解读  

         第3部分除序列和可反转容器的函数外,list模板类还包含了链表专用的成员函数。表16.9列出了其中一些(有关STL方法和函数的完整列表,请参见附录G)。通常不必担心Alloc模板参数,因为它有默认值。

          ----看见了STL中的list,发现和自己写的不太一样,他有两个参数,表示为list<T,Alloc>,不过感觉问题也不是很大,代码主要说的是思路.

         照着成员函数的说明当成需求,前面是给出的函数原型,试着写一写算法.

        merge()合并算法看起来有点复杂,不写;remove前面有个相同名称的函数,因为想要调用之前的remove()函数,所以把他改为removeValue;splice()要用到迭代器,不写.把前面的容器类定义拿过来

  1. template<class T>
  2. class list{
  3. enum{MAX=10}
  4. int lsize; //list最大元素数量
  5. int items; //list内当前的元素个数
  6. class Node{ //声明结点类
  7. public: //结点数据向外部类公开
  8. T t;
  9. Node *front;
  10. Node *next;
  11. Node(T val):t(val),front(0),next(0){}
  12. Node(){} //默认构造函数,为初始化时使用
  13. }
  14. Node* first;
  15. Node* last;
  16. public:
  17. list(int num=MAX); //构造函数
  18. void add(Node* n,T& t); //添加元素t到结点n后面
  19. T remove(Node* n); //删除地址为n的结点
  20. //下面三个是将要实现的函数
  21. void removeValue(const T& val); //删除val的所有实例
  22. void sort(); //使用<运算符对链表进行排序
  23. Node* findNode(const T& t); //排序中用到的函数
  24. void unique(); //将连续的相同元素压缩成单个元素
  25. }

         注意:下列代码为了练手,试图重现逻辑,不保证准确.  

        1>删除val的所有实例

        思路:遍历list,找到一个删一个.因为已定义了remove(Node* n),所以省事一些.

  1. template<class T>
  2. void list<T>::removeValue(const T& val){
  3. if(items==0){ //空链表直接返回
  4. std::cout<<"链表内没有数据"<<std::endl;
  5. return;
  6. }
  7. list<T>::Node* p=first; //声明指针指向链表头结点
  8. bool flag=false; //声明标志位;
  9. while(p){ //遍历链表
  10. if(p->t==val){
  11. remove(p); //调用删除结点函数
  12. flag=true; //标志位改变
  13. }
  14. p=p->next; //指针指向下一个结点
  15. }
  16. if(flag==true)
  17. std::cout<<"数据已全部删除"<<std::endl;
  18. else
  19. std::cout<<"链表内没有您想删除的数据"<<std::endl;
  20. }

          2>sort排序,从小到大排序,最小的放链表前面

           思路:要排序,我只知道冒泡排序,链表排序可以仿照冒泡排序算法吗?

            把链表"打散"成数组,然后给数组排序,接着把排好序后的结点重新找出来,依次挂到first后面.

             问题来了,现在的函数只能从结点访问到数据t,还没有从数据t访问到结点的函数,加一个呗    

  1. template<class T>
  2. Node* list<T>::findNode(const T& t){ //结点查找函数
  3. list<T>::Node * p=first;
  4. while(p){
  5. if(p->t==t)
  6. std::cout<<"您查找的数据已找到"<<std::endl;
  7. return p;
  8. p=p->next; //指向下一个结点
  9. }
  10. std::cout<<"您查找的数据不存在"<<std::endl;
  11. return nullptr;
  12. }

         接下来按照冒泡排序的思路,把数据从小到大排列

  1. template<class T>
  2. void list<T>::sort(){ //排序算法
  3. vector<T> a;
  4. List<T>::Node* p=first;
  5. while(p){
  6. a.push_back(p->t); //元素取出来放进vector里
  7. p=p->next; //指针指向下一个结点
  8. }
  9. /*以下是冒泡排序*/
  10. for(int i=0;i<items-1;i++){
  11. for(int j=0;j<items-i-1;j++){
  12. if(a[j]>=a[j+1]){
  13. T tmp;
  14. tmp=a[j+1];
  15. a[j+1]=a[j];
  16. a[j]=tmp;
  17. }
  18. }
  19. }//到这里排序完成,从小到大,从a[0]到a[items-1]
  20. /*还原成结点,并依次挂到first后面*/
  21. Node* tmp=first; //声明临时结点tmp帮first整理,用上面的p也行
  22. for(int i=0;i<items;i++){
  23. //两句说明结点后面是tmp->next
  24. findNode(a[i])->next=tmp->next;
  25. tmp->next->front=findNode(a[i]);
  26. //两句说明结点前面是tmp;
  27. tmp->next=findNode(a[i]);
  28. findNode(a[i])->front=tmp;
  29. //tmp结点指向新结点,为下次整理做准备;
  30. tmp=findNode(a[i]);
  31. }
  32. last=tmp; //最后让last指向tmp;
  33. }

        ----然而问题又来了,list并不检查两个相同的值,上面的findNode函数有bug该怎么办?想解决肯定是有办法的,比如给Node结点来个编号(题外话:黑皮书上有讲,那是本好书但是挺难)

  1. class Node{ //声明结点类
  2. static int num; //静态变量,给对象编号用
  3. public: //结点数据向外部类公开
  4. int id;
  5. T t;
  6. Node *front;
  7. Node *next;
  8. Node(T val):t(val),front(0),next(0),id(num++){}
  9. Node():id(num++){} //默认构造函数,为初始化时使用
  10. }

        但这样要耗费空间,查找结点代码也要改动,比较麻烦.所以我建议在排序之前先把多余的重复值去掉,也就是先调用下面要写的的unique()函数.

        ----除了这个问题还有个问题:让类型T的元素怎么比较?重载运算符吗?

  1. /*???????????*/
  2. bool operator<(T& t){ //起不了作用
  3. return *this < t
  4. }

        如果从完全泛型的角度来讲,不可能成立.只能在调用时做出约束,如int,char,double等类型自带大小判断的才可以调用sort().

        如果对象中有部分元素是可以排序的,如有一个int属性,那么按照前面的思路,让其反向查找排序,这不是容器的事,应该在外面定义函数来解决.        

  1. class Person{
  2. int age; //这个可以排序
  3. string name;
  4. }

        3>压缩链表,去掉多余元素

        思路:用一个集合a存储链表里的数据,遍历链表的同时,遍历集合a.当新元素在集合a里找到时,删除,当没有找到时,加入集合a.

  1. template<class T>
  2. void list<T>::unique(){   //将连续的相同元素压缩成单个元素
  3. list<T>::Node* p=first;
  4. vector<T> a;
  5. while(p){
  6. for(pt=a.begin();pt!=a.end();pt++){
  7. if(p->t==*pt){
  8. remove(p); //调用删除结点函数
  9. }
  10. a.push_back(p->t); //元素取出来放进vector里
  11. }
  12. p=p->next; //指针指向下一个结点
  13. }
  14. }                 

        此外本书P699举了个例子,讲几种api的使用,看看即可.

链表梳理

        链表的定义和使用流程:数据→结点→链表.1.把数据放入结点;2.结点指针生成链表.

        链表是一个结点指针的集合,指针元素之间用结点指针相连接.头结点是链表的入口.

        链表的难点是区分指针变量和指针常量.

        链表里的元素是结点指针,他们是常量,要访问他们或者修改他们用的是指针变量.常见的指针变量有头结点,尾结点.他们时刻要保证逻辑正确,也是算法的保证. 

后记

        一天更了两篇,大热天还觉得挺兴奋.数据结构应该算是编程的分水岭,如果真的喜欢会享受代码和写作的过程.如果一时学不进去也挺难受,但也不要着急慢慢来,许多人也经历过那个过程.一起加油. 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号