[C++]数据结构:线性表的公式化描述和链式描述的结构特点与基本操作

来源:互联网 发布:决裂电影知乎 编辑:程序博客网 时间:2024/05/20 16:35

线性表的公式化描述的基本特点:

关于线性表的概念,可以简单的理解成按照一定顺序排列的列表。但是这里的顺序并不是指元素内容的有序,而是指元素的位置的有序。就好像是有一个队伍,不一定按照高矮胖瘦进行排列,但是在队伍中每个人按照前后站着谁,都有自己相对固定的位置。

再介绍一下公式化描述的概念。公式化描述是采用数组来表示一个对象的实例,数组中的每个位置为单元或者节点,每个数组单元需要足够大以便能容纳数据对象实例中的任意一个元素。在公式化描述中,可以用一个数学公式来确定元素的位置。一个简单的映射公式如下:location(i)=i-1。

上面的公式表明,第i个元素在数组的i-1位置处。由此可以看出,公式化描述的一个优点便是,访问数据极为便捷,直接调用对应的映射函数便可以知道元素的位置并且进行访问。

但是既然有好处,相比也是有缺点的,下面我们来客观的考察一下线性表的公式化描述的优缺点。

优点是不言而喻了,正如上面所谈到的那样,我们可以非常轻松的使用C++的函数对线性表里的内容进行访问,并且执行查找,删除,修改的函数都有一个最差的、与表的大小呈线性关系的时间复杂性。

但是公式化描述的一个明显的缺点就是空间的低效使用。

举个例子:如果你要测试输入数据的平均值,使用公式化描述的线性表来存储输入,那么你将这个线性表的长度设置为多少比较合适呢?这显然是一个棘手的问题。也就是说,公式化描述线性表需要预测线性表最大的可能尺寸。解决的方案之一就是初始化的时候置MaxSize=1,然后在后面插入的时候如果表中已经有MaxSize个元素则将MaxSize翻倍按照这个新尺寸重新分配数组,并将老数组复制过去再删除老数组。光听这过程就知道,动态调整分配的空间并不是公式化描述的强项。

公式化描述比较适用于已知尺寸的数据存储,比如一个猴子的坐标,不管他如何腾挪闪转七十二变,还是只需要三个数字就可以将他的坐标表示出来。


链表描述的基本特点:

链表描述人如其名,也就是使用链状的列表来存储数据。每个链表使用节点Node来存储数据,在通过一个link的链将彼此连接起来。

在链表中,我们把数据存放在节点(Node)里,然后再把节点通过链(link)串起来。

每个节点至少需要存储两个内容,一个是该节点要储存的数据,另一个则是指向下一个节点的链。

链表的优点可以说刚好是公式化描述的缺点,也就是它比较擅长于动态修改列表的大小,比如添加删除等操作,然后它的缺点刚好是公式化描述的优点:链表访问元素需要遍历到那个节点而不是像公式化描述那样直接可以访问。而这可谓各有千秋,术业有专攻。

链表的基本操作:

析构函数:析构函数在代码中是这样实现的:先声明一个next指针,然后每次遍历的时候将next指针指向first的link指针指向的内容。相当于是一份存档,避免删了first节点后找不到后面的入口。然后再将first指向next指针指向的节点,重复以上操作。

C++代码如下:

template<class T>Chain<T>::~Chain(){    ChainNode*<T>next;    while(head)    {        next = head->link;        delete head;        head = next;    }}

下面来看看如何确定链表的长度。

确定链表长度的基本思想是,从头结点开始往下走依次访问每个节点,并且将计数器加一,直到访问的对象为空为止。在代码中这样实现:

//确认链表的长度  template<class T>  int Chain<T>::Length()const{      ChainNode<T>*current = first;      int length = 0;      while(current){          length++;          current = current->link;      }      return length;  }  


链表的查找也需要进行遍历操作,区别就是跳出循环的条件不一样。我们用k来表示想要访问的元素的下标,用x来存储查找的结果。

源码如下:

//在链表中查找第k个元素  //存在就储存到x中  //不存在则返回false,否则返回true  template<class T>  bool Chain<T>::Find(int k,T&x)const{      if(k<1)          return false;      ChainNode<T>*current = first;      int index = 1;      while(index<k&¤t){          current = current->link;          index++;      }      if(current){          x = current->data;          return true;      }      return false;  }  


链表的搜索操作和确定长度的流程基本相似,唯一的不同就是遍历的时候将计数器加一改成了比较元素大小。如果大小相等,则说明找到了想要找的元素。如果遍历到最后还是没找到,只能说明链表中没有想要找的元素。

//在链表中搜索  //查找x,如果发现则返回x的下标  //如果x不存在则返回0  template<class T>  int Chain<T>::Search(const T&x)const{      ChainNode<T>*current = first;      int index = 1;      while(current&¤t->data!=x){          current = current->link;          index++;      }      if(current){          return index;      }      return 0;  }  


删除操作可能是链表中略微复杂的一项操作,当然只是相比于其他操作而言。

基本的流程是先找到想要删除的元素,然后再将它前面的节点的link指向欲删除节点的link节点。然后再删除节点即可

代码如下:

//从链表中删除一个元素  //将第k个元素取至x  //然后从链表中删除第k个元素  //如果不存在则引发异常OutOfBounds  template<class T>  Chain<T>& Chain<T>::Delete(int k,T& x){      if(k<1||!first){          throw OutOfBounds();      }          ChainNode<T>*p = first;      if(k==1){          first = first->link;      }else{          ChainNode<T>*q = first;          for(int index = 1;index<k-1&&q;index++){              q = q->link;              //此时q指向要删除的前一个节点          }          if(!q||!q->link){              throw OutOfBounds();          }          p = q->link;          if(p==last)              last=q;          q->link=p->link;          //从链表中删除该节点          x = p->data;          delete p;          return *this;      }  }  


插入操作则和删除操作基本类似,关键在于那两个指针的调整,代码中有所体现

  //在第k个位置之后插入元素  //不存在则报OutOfBounds异常  //没有足够内存则报NoMem异常  template<class T>  Chain<T>& Chain<T>::Insert(int k,const T&x){      if(k<0){          throw OutOfBounds();      }          ChainNode<T>*p = first;        for(int index = 1;index<k && p;index++){          p = p->link;      }      if(k>0 && !p){          throw OutOfBounds();      }      ChainNode<T>*y = new ChainNode<T>;      y->data = x;      if(k){          y->link=p->link;          p->link=y;      }else{          //作为第一个元素插入          y->link = first;          first = y;      }      if(!y->link)          last=y;      return *this;  }  


采用下面的几条策略,可以使链表的应用代码更为简洁:

1.把线性表描述成一个单向循环链表而不是一个单向链表

2.在链表的前面加上一个附加的结点,称之为头结点。

通过把单项链表的最后一个元素的链接指针改为指向这个头结点,就可以把一个单向链表改造成循环链表了。在进行查找操作的时候,我们可以把需要查找的元素放在头结点,然后开始遍历,这样可以是的程序更为简练。

在带有头结点的循环链表中进行查找的方法如下:

template<class T>int Chain<T>::Search(const T&x){ //返回元素x的下标    ChainNode*<T>current=first->link;//和前面不同,此时从head的下一个节点开始遍历    int index = 1; //标记当前访问的是第几个节点    first->data=x;//把待搜索元素放到头结点    while(current->data!=x )//{        current= current->link;//current指向下一个节点        index++;//    }    if(current==first)    {        return 0;        //说明遍历了整整一圈又回到了头结点,也就是没有找到该元素    }else{        return index;    }}

完整的链表操作代码传送:

[C++]数据结构实验03:链式结构线性表的基本操作



原创粉丝点击