Java集合中的List代表一种集合元素允许重复,允许有序的集合。因此,可以通过下标索引来访问List集合中的元素。
那么在什么情况下需要使用List集合?
如果有一组数据节点,其中多个数据节点之间有松散的一对一关系,类似于A数据节点之后是B数据节点的这种关系,我们应该使用List的集合来保存。换一个角度来看,存在这种松散的,一对一关系的多个数据节点就是典型的线性结构,应该使用线性表来保存它们。Java的List集合本身就是线性表。其中ArrayList是线性表的顺序存储实现。而LinkedList则是线性表的链式存储实现。
线性表概述
线性表(linear list)是具有相同类型的n(n≥0)个数据元素a0,a1,…an-1组成的有限序列。其中n 称为线性表的长度,当n=0时称为空线性表,n>0时称为非空表。线性表也是最基本、最简单、也是最常用的一种数据结构。 线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的,循环链表除外(循环链表逻辑层次上也是一种线性表)。
线性表的第一种存储结构:顺序
线性表的顺序存储结构是指用一组地址连续的存储单元依次存放线性表的元素。顺序线性表中相邻元素,两个元素a和a+1对应的存储地址loc(a)和loc(a+1)也是相邻的。也就是说:在顺序结构线性表中数据元素的物理关系和逻辑关系是一致的。
获取顺序存储线性表中每个元素的存储起始地址的时间相同,读取表中数据元素的时间也相同。而且顺序表中每个元素都给随机存取,因此顺序存储的线性表表示一种随机存取数据结构。所以顺序存储一般使用数组实现。
基本操作
借助数组来描述线性表。除了用数组来存储线性表的元素之外,线性表还应该有表示线性表的长度属性,所以用结构类型来定义线性表类型。
#define OK 1
#define ERROR -1
#define MAX_SIZE 100
typedef int Status ;
typedef int ElemType ;
typedef struct sqlist
{ ElemType Elem_array[MAX_SIZE] ;
int length ;
} SqList ;
1.顺序线性表初始化
Status Init_SqList( SqList *L )
{ L->elem_array=( ElemType * )malloc(MAX_SIZE*sizeof( ElemType ) ) ;
if ( !L -> elem_array ) return ERROR ;
else { L->length= 0 ; return OK ; }
}
2 顺序线性表的插入
在线性表 L= (a1,…a i-1,ai, ai+1,…,an) 中的第i(1≦i≦n)个位置上插入一个新结点e,使其成为线性表:
L=(a1,…a i-1,e,ai,ai+1,…,an)
实现步骤
(1) 将线性表L中的第i个至第n个结点后移一个位置。
(2) 将结点e插入到结点ai-1之后。
(3) 线性表长度加1。
算法描述
Status Insert_SqList(Sqlist *L,int i ,ElemType e)
{ int j ;
if ( i<0||i>L->length-1) return ERROR ;
if (L->length>=MAX_SIZE)
{ printf(“线性表溢出!\n”); return ERROR ; }
for ( j=L->length–1; j>=i-1; --j )
L->Elem_array[j+1]=L->Elem_array[j];
/* i-1位置以后的所有结点后移 */
L->Elem_array[i-1]=e; /* 在i-1位置插入结点 */
L->length++ ;
return OK ;
}
3 顺序线性表的删除
在线性表 L=(a1,…a i-1,ai, ai+1,…,an) 中删除结点ai(1≦i≦n),使其成为线性表:
L= (a1,…ai-1,ai+1,…,an)
实现步骤
(1) 将线性表L中的第i+1个至第n个结点依此向前移动一个位置。
(2) 线性表长度减1。
ElemType Delete_SqList(Sqlist *L,int i)
{ int k ; ElemType x ;
if (L->length==0)
{ printf(“线性表L为空!\n”); return ERROR; }
else if ( i<1||i>L->length )
{ printf(“要删除的数据元素不存在!\n”) ;
return ERROR ; }
else { x=L->Elem_array[i-1] ; /*保存结点的值*/
for ( k=i ; k<L->length ; k++)
L->Elem_array[k-1]=L->Elem_array[k];
/* i位置以后的所有结点前移 */
L->length--; return (x);
}
}
4 查找定位删除
在线性表 L= (a1,a2,…,an) 中删除值为x的第一个结点。
实现步骤
(1) 在线性表L查找值为x的第一个数据元素。
(2) 将从找到的位置至最后一个结点依次向前移动一个位置。
(3) 线性表长度减1。
算法描述
Status Locate_Delete_SqList(Sqlist *L,ElemType x)
/* 删除线性表L中值为x的第一个结点 */
{ int i=0 , k ;
while (i<L->length) /*查找值为x的第一个结点*/
{ if (L->Elem_array[i]!=x ) i++ ;
else
{ for ( k=i+1; k< L->length; k++)
L->Elem_array[k-1]=L->Elem_array[k];
L->length--; break ;
}
}
if (i>L->length)
{ printf(“要删除的数据元素不存在!\n”) ;
return ERROR ; }
return OK;
}
线性表的第二种存储结构:链式
链式存储结构的线性表(链表),将采用一组地址任意的存储单元存放信息表中的数据元素,他不会按照线性的逻辑顺序来保存数据,它需要在每一个数据元素中保存一个引用下一个数据引用(也叫指针)。
由于不必须按照顺序存储。链表的插入删除数据元素时比顺序线性表快的多,但是查到一个节点或者访问特定编号的节点则比顺序链表慢得多。
链表可以充分利用计算机内存空间,实现灵活的内存动态管理。但链表失去了数组随机存储的优点,同时链表多了指针域,增加了空间开销。
对于链式存储的线性表而言,它的每个节点都必须包含数据元素本身和一个引用指向下一个节点的引用。并且有一种结构存在两个引用,分别指向上一个节点和下一个节点,这种结构我们称呼其为双向链表。
单向链表
指每个节点只有一个指向其他节点的引用,并且该节点指向当前节点的下一节点。没有节点指向头节点,尾节点指向null。
对于单链表的节点添加,有两种方法: 头插法,尾插法。
头插法: 添加的新节点做为头节点,并让新添加的头节点指向原头节点。
尾插法: 添加的新节点做为尾节点,并让原尾节点指向新节点。因此该方法需要定义一个引用变量来保存链表最后一个元素。
头插法虽然简单,但生成的链表中节点的次序和输入的次序却相反。如果希望二者次序一致,则应该采用尾插法建立链表。
1.单链表的查找
查找分为查询第index个节点以及查找element元素:
1)按序号查找index个节点:从header节点依次向下搜索第index。
设header为头,current为当前节点(初始时从header开始)。0为header序列,i为计数器。使current依次向下寻找节点,同时i计数,知道返回指定节点。
2)查找是否有等于制定的element的节点。若有,返回首此找到其指定的节点的索引,否则返回-1。查找过程从开始节点出发,顺着列表逐个将节点的值和给定值作比较。
2.单链表的插入
插入运算是将值为element的新结点插入到表的第index个结点的位置上,即插入到index-1与index之间。因此,必须首先找到index-1所在的结点p,然后生成一个数据域为element的新结点newNode。newNode结点作为p的直接后继next结点,newNode引用指向原来index处都节点。
3.单链表的删除
删除是将第index处元素节点删去。在单链表中,由于第index处元素由index-1引用。因此,让index-1引用到index+1处都节点,并释放index即可。
线性表的链式存储和顺序存储基本相同,因为他们都是线性表。只是底层实现不同而已。因此链式和顺序在性能上有所差异,顺序表再随机存取时性能很好,但插入删除时性能就不如链表。链表在删除,插入时性能很好,但随机存取的性能就不如顺序表。
循环链表
循环链表是一种特殊的链表,是一种首尾相接的链表。将单链表的尾指针next指向改用引用单链表header节点,这个单链表就成了循环链表。
循环链表无头无尾,从任意一个节点出发都能找到其所有节点。
循环链表在一些方法上比单链表更简单,例如,对于插入,则不用考虑是头插法还是尾插入。实现起来都差不多。
对于循环链表的实现,只有保证在单链表中tail.next = header即可。
双向链表
如果在每个节点中保留两个引用,next,prev。next指向下一个节点,儿prev指向上一个节点。这时链表即可以通过当前节点访问上一个节点也可以访问下一个节点。这种链表就是双向链表。
查找
双向链表既可以通过next来遍历,又能通过prev来遍历,所有在查找时需要判断要查找的index位元素,既可以从header开始又能从tail开始。因此,需要判断出离哪个更近。如果index<size/2,则更靠近header,应该从header开始搜索。
插入
双向链表插入比较复杂,插入一个新节点需要同时修改两个方向的指针。
删除
双向链表删除一个节点也需要修改两个方向的指针
总结
线性表的实现性能分析:
空间性能:
顺序表的存储空间是静态分布的,需要一个长度固定的数组,因此总有部分数组元素被浪费。
链表的存储空间是动态分布的,因此不会有空间被浪费。但由于链表需要额外的空间来为每个节点保存指针,因此也要牺牲一部分空间。
时间性能
顺序表中元素的逻辑顺序与物理存储顺序保持一致,而且支持随机存取。因此数据表在查找,读取时性能很好。
链表采用链式结构来保存表内元素,因此在插入删除元素时性能较好。
对于大部分java程序员来说,经常使用的List就是线性表,它的两种实现ArrayList是顺序存储结构(底层是数组),而LinkedList是链式存储结构。并且LinkedList是个双向链表。

本文详细介绍了线性表的存储结构,包括顺序存储和链式存储。顺序存储使用数组实现,支持随机访问,但在插入和删除时效率较低;链式存储包括单链表、循环链表和双向链表,插入和删除效率高,但访问速度较慢。线性表在Java中主要体现在List集合,ArrayList对应顺序存储,LinkedList对应链式存储。

1225

被折叠的 条评论
为什么被折叠?



