第二章 线性表

来源:互联网 发布:软件售后工程师发展 编辑:程序博客网 时间:2024/05/18 03:13

2.1线性表的逻辑结构

2.1.1线性表的定义

n个有相同类型的数据结构元素的有限序列。

线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。

长度:线性表中数据元素的个数。

2.1.2 线性表的抽象数据类型定义

对于不同的应用,线性表的基本操作不同

线性表的基本操作:
1.InitList(&L)  初始化:构造一个空的线性表L。
2.DestroyList(&L)  销毁:销毁一个业已存在的线性表L。
3.ClearList(&L)  清空:将一业已存在的线性表L重置为空表。
4.ListEmpty(L)  判表空:若L为空表,则返回TRUE;否则返回FALSE 。
5.ListLength(L)  求长度:对给定的线性表L,返回线性表L的数据元素的个数。
6.GetElem(L,i,&e) 对给定的线性表L,取第i个数据元素。0≤i≤Length(L)-1),用e返回L中第i个数据元素的值。
或 GetElem(L,I)  , 1≤i≤Length(L),正确返回值,否则出错。
7.LocateElem(L,e)  e为线性表中的同型元素,定位 返回L中第一个与e满足相等关系数据元素的位序,  若这种数据元素不存在,  则返回0 。
8.PriorElem(L,cur_e,&pre_e)  求前驱:若cur_e是L的数据元素,  且不是第一个,  则用pre_e返回它的前驱,  否则操作失败, pre_e无定义。
  或PriorElem(L,e)  求前驱: e是L表中同质元素,求出e的前驱元素并用e返回其值。若有值,函数返回真,否则返回假。
9.NextElem(L,cur_e,&next_e)求后继   若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败, next_e无定义。或用NextElem(L,e) 求后继 : e是L表中同质元素求出e的后继元素并用e返回其值。若有值,函数返回真,否则返回假。 
10.ListInsert(&L,i,e)  插入  在L中第i个位置之前插入新的数据元素e,L的长度加1 。
11.ListDelete(&L,i,&e)  删除  删除L的第i个数据元素,并用e返回其值,L的长度减1 。(i的选择要合法) 
12.ListTraverse(L,visit())  遍历   对给定的线性表L,依次输出L的每一个数据元素。(不允许重复)
13.Copy(L,C)  复制   将给定的线性表L复制到线性表C中。
14.Merge(A,B,C)  合并   将给定的线性表A和B合并为线性表C。}ADT  List

2.2 线性表的顺序存储结构及实现

2.2.1线性表的顺序结构——顺序表

线性表有两种实现,一种是顺序实现,一种是链式实现。上述中我们已经定义了顺序表SqList那么我们现在先用顺序实现。

顺序实现的几个例子:注意:在上述定义顺序表SqList时,我们用了两种方法(非指针和指针),那么在实现顺序结构时,也用两种方法进行实现,分别对应两种定义。

初始化将表中的元素进行初始化。 

  1. Status InitList_Sq( SqList& L )   
  2. {    // 构造一个空的线性表   
  3.     L.length = 0; //表长度为0,即元素个数设置为0  
  4.     return OK;  
  5. } // InitList_Sq  
  6. 指针实现  
  7. Status InitList_Sq( SqList& L )   
  8. {    // 构造一个空的线性表   
  9.     //用动态内存分配表的空间  
  10.   L.elem=(ElemType *)malloc(MAXSIZE *sizeof(ElemType));  
  11.   If(!L.elem)exit(OVERFLOW); //分配失败  
  12.   L.length = 0;  //表长度为0,即元素个数设置为0
  13.   L.listsize=MAXSIZE ;//初始存储容量,也就上定义中的10  
  14.     return OK;  
  15. } // InitList_Sq 

查找定位

while( i<=L.length && !(*compare)(*p++,e))中 compare是一个函数,*p++和e 都是这个函数的参数。这句代码理解为:先是p指针指向的值加1,得到的值和e变量作为实际参数传递给函数compare进行处理,然后根据compare函数处理完的结果进行真假判断(真为1,假为0),然后!取反

插入:在顺序表中指定位置插入一个已知元素

newbase=(ElemType*)realloc(L.elem,(L.listsize+MAXSIZE)*sizeof(ElemType));这句代码实际上是从新分配了一个空间,然后把原来空间复制到新空间,然后又增加了一个新的空间,这个新的空间大小为LISTINCREMENT.
删除:将表中指定位置的元素删除

  1. Status ListDelete (SqList &L, int i, ElemType &e)  
  2. {   // 在顺序表L中删除第i个元素,用e返回删除的值 
  3.     // i 的合法范围为  1≤i≤L.length+1 
  4.      if (i < 1 || i > L.length)   return ERROR;  // 插入位置不合法 
  5.      e=L.elem[i-1]; 将被删除的元素赋给e 
  6.      for(j=i+1;j<=length;j++) 
  7.          L.elem[j-2]=L.elem[j-1]; 
  8.      L.length--; 
  9.      return OK; 
  10. } // ListDelete_Sq 
  11. 指针 
  12. Status ListDelete (SqList &L, int i, ElemType &e)  
  13. {   // 在顺序表L中删除第i个元素,用e返回删除的值 
  14.     // i 的合法范围为  1≤i≤L.length+1 
  15.      if (i < 1 || i > L.length)   return ERROR;  // 插入位置不合法 
  16.      p=&(L.elem[i-1]); 
  17.      e=*p; 
  18.      q=L.elem+L.length-1; 
  19.      for(++p;p<=q;++p)*(p-1)=*p; 
  20.       --L.length; 
  21.      return OK; 
  22. } // ListDelete_Sq 

取元素

  1. Status GetElem (SqList L, int i, ElemType &e) { 
  2.      e=L.elem[i-1]; 
  3.      return OK; 
  4. }//GetElem 


2.3线性表的链接存储结构及实现

优点
逻辑相邻,物理相邻
可随机存取任一元素
存储空间使用紧凑
缺点
插入、删除操作需要移动大量的元素
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充
将线性表L中第i个数据元素删除 
int ListDelete(SEQLIST *L,int i,Elemtype *e)
{
if (IsEmpty(L)) return ERROR;     //检测线性表是否为空
if (i<1||i>L->length) return ERROR;   //检查i值是否合理
*e=L->elem[i-1];   
//将欲删除的数据元素内容保留在e所指示的存储单元中
for (j=i;j<=L->length-1;j++) 
 //将线性表第i+1个元素之后的所有元素向前移动
L->elem[j-1]=L->elem[j];
L->length--;
return OK;
}
C语言中的数组下标从“0”开始,因此,若L是Sqlist类型的顺序表,则表中第i个元素是L.data[I-1]。


         线性表的顺序表示的特点是用物理位置上的邻接关系来表示结点间的逻辑关系,这一特点使我们可以随机存取表中的任一结点,但它也使得插入和删除操作会移动大量的结点.为避免大量结点的移动,我们介绍线性表的另一种存储方式,
   链式存储结构,简称为链表(Linked List)。


线性链表
        链表是指用一组任意的存储单元来依次存放线性表的结点,这组存储单元既
可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。因此,链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息,这个信息称为指针(pointer)或链(link)。这两部分组成了链表中的结点结构:
其中:data域是数据域,用来存放结点的值。next是指针域(亦称链域),用来存放结点的直接后继的地址(或位置)。
     链表正是通过每个结点的链域将线性表的n个结点按其逻辑次序链接在一起的。由于上述链表的每一个结只有一个链域,故将这种链表称为单链表(Single Linked)。
单链表又有带头结点结构和不带头结点结构两种。头指针(设为head)所指的不存放数据元素的第一个结点称为头结点。存放第一个数据元素的结点称为首元结点或第一个结点。首元结点在带头结点的单链表中是链表的第二个结点,在不带头结点的单链表中是链表的第一个结点。
带头结点的链式结构的优点:
在首元结点前插入头结点与在其他结点前插入结点一样,不会改变head的值,改变的是head->next的值,删除首元结点也一样。
一个单链表是否带头结点是由初始化操作决定的,由初始化操作定义空的单链表头指针指向头结点时,则单链表带头结点;由初始化操作定义空的单链表头指针指向NULL时,则单链表不带头结点。
在链表中,即使知道被访问结点的序号i,也不能象顺序表中那样直接按序号i访问结点,而只能从链表的头指针出发,顺链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。
     设单链表的长度为n,要查找表中第i个结点,仅当1≦i≦n时,i的值是合法的。但有时需要找头结点的位置,故我们将头结点看做是第0 个结点
按值查找是在链表中,查找是否有结点值等于给定值key的结点,若有的话,则返回首次找到的其值为key的结点的存储位置;否则返回NULL。查找过程从开始结点出发,顺着链表逐个将结点的值和给定值key作比较。
插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到
  ai-1与ai之间。因此,我们必须首先找到ai-1的存储位置p,然后生成一个数据域为x的新结点*p,并令结点*p的指针域指向新结点,新结点的指针域指向结点ai。从而实现三个结点ai-1,x和ai之间的逻辑关系的变化
删除运算是将表的第i个结点删去。因为在单链表中结点ai的存储地址是在其直接前趋结点a a i-1的指针域next中,所以我们必须首先找到    
    a i-1的存储位置p。然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点
    ai的空间,将其归还给“存储池”。
循环链表是一种头尾相接的链表。其特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点,就得到了单链形式的循环链表,并简单称为单循环链表。
       为了使空表和非空表的处理一致,循环链表中也可设置一个头结点。这样,空循环链表仅有一个自成循环的头结点表示。
在很多实际问题中,表的操作常常是在表的首尾位置上进行,此时头指针表示的单循环链表就显得不够方便.如果改用尾指针rear来表示单循环链表,则查找开始结点a1和终端结点an都很方便,它们的存储位置分别是(rear–>next) —>next和rear,显然,查找时间都是O(1)。因此,实际中多采用尾指针表示单循环链表。
       由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p—>next是否为空,而是判断它们是否等于某一指定指针,如头指针或尾指针等。
双向链表(Double linked list):在单链表的每个结点里再增加一个指向其直接前趋的指针域prior。这样就形成的链表中有两个方向不同的链,故称为双向链表。

2.4 线性表结构特点

线性表具有如下的结构特点:

1.均匀性:虽然不同数据表的数据元素可以是各种各样的,但对于同一线性表的各数据元素必定具有相同的数据类型和长度。

2.有序性:各数据元素在线性表中的位置只取决于它们的序号,数据元素之前的相对位置是线性的,即存在唯一的“第一个“和“最后一个”的数据元素,除了第一个和最后一个外,其它元素前面均只有一个数据元素直接前驱和后面均只有一个数据元素(直接后继)。

在实现线性表数据元素的存储方面,一般可用顺序存储结构和链式存储结构两种方法。链式存储结构将在本网站线性链表中介绍,本章主要介绍用数组实现线性表数据元素的顺序存储及其应用。另外栈、队列和串也是线性表的特殊情况,又称为受限的线性结构。

0 0
原创粉丝点击