线性结构是一个数据元素的有序(次序)集 。
线性结构的基本特征:
(1)存在惟一的一个被称作“第一个”的数据元素
(2)存在惟一的一个被称作“最后一个”的数据元素
(3)除第一个之外,集合中的每个数据元素均只有 一个直接前驱
(4)除最后一个之外,集合中的每个数据元素均只有一个直接后继.
线性表
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。
假设线性表的每个元素需占用l个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置。则线性表中第i+1个数据元素的存储位置LOC( a i+1)和第i个数据元素的存储位置LOC(a i )之间满足下列关系:
LOC(a i+1)=LOC(a i)+l
线性表的第i个数据元素ai的存储位置为:
LOC(ai)=LOC(a1)+(I-1)*l
线性表的基本操作:
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
1. 初始化线性表L
int InitList(SEQLIST *L)
{
L->elem=(Elemtype*)malloc(LIST_MAX_LENGTH *sizeof(Elemtype)); //分配空间
if (L->elem==NULL) return ERROR; //若分配空间不成功,返回ERROR
L->length=0; //将当前线性表长度置0
return OK; //成功返回OK
}
2.销毁线性表L
void DestroyList(SEQLIST *L)
{
if (L->elem) free(L->elem); //释放线性表占据的所有存储空间
}
3.清空线性表L
void ClearList(SEQLIST *L)
{
L->length=0; //将线性表的长度置为0
}
4.求线性表L的长度
int GetLength(SEQLIST L)
{
return (L.length);
}
5.判断线性表L是否为空
int IsEmpty(SEQLIST L)
{
if (L.length==0) return TRUE;
else return FALSE;
}
6.获取线性表L中的某个数据元素的内容
int GetElem(SEQLIST L,int i,Elemtype *e)
{
if (i<1||i>L.length) return ERROR; //判断i值是否合
// 理,若不合理,返回ERROR
*e=L.elem[i-1]; //数组中第i-1的单元存储着线性表中第
// i个数据元素的内容
return OK;
}
7.在线性表L中检索值为e的数据元素
int LocateELem(SEQLIST L,Elemtype e)
{
for (i=0;i< L.length;i++)
if (L.elem[i]==e) return i+1;
return 0;
}
8.在线性表L中第i个数据元素之前插入数据元素e
int ListInsert(SEQLIST *L,int i,Elemtype e)
{
if (L->length==LIST_MAX_LENGTH) return ERROR;
//检查是否有剩余空间
if (i<1||i>L->length+1) return ERROR; //检查i值是否合理
for (j=L->length-1;j>=i-1;i++)
//将线性表第i个元素之后的所有元素向后移动
L.->elem[j+1]=L->elem[j];
L->elem[i-1]=e; //将新元素的内容放入线性表的第i个位置
L->length++;
return OK;
}
顺序存储结构的优缺点
优点
逻辑相邻,物理相邻
可随机存取任一元素
存储空间使用紧凑
缺点
插入、删除操作需要移动大量的元素
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充
将线性表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。这样就形成的链表中有两个方向不同的链,故称为双向链表。
线性结构的基本特征:
(1)存在惟一的一个被称作“第一个”的数据元素
(2)存在惟一的一个被称作“最后一个”的数据元素
(3)除第一个之外,集合中的每个数据元素均只有 一个直接前驱
(4)除最后一个之外,集合中的每个数据元素均只有一个直接后继.
线性表
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。
假设线性表的每个元素需占用l个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置。则线性表中第i+1个数据元素的存储位置LOC( a i+1)和第i个数据元素的存储位置LOC(a i )之间满足下列关系:
LOC(a i+1)=LOC(a i)+l
线性表的第i个数据元素ai的存储位置为:
LOC(ai)=LOC(a1)+(I-1)*l
线性表的基本操作:
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
1. 初始化线性表L
int InitList(SEQLIST *L)
{
L->elem=(Elemtype*)malloc(LIST_MAX_LENGTH *sizeof(Elemtype)); //分配空间
if (L->elem==NULL) return ERROR; //若分配空间不成功,返回ERROR
L->length=0; //将当前线性表长度置0
return OK; //成功返回OK
}
2.销毁线性表L
void DestroyList(SEQLIST *L)
{
if (L->elem) free(L->elem); //释放线性表占据的所有存储空间
}
3.清空线性表L
void ClearList(SEQLIST *L)
{
L->length=0; //将线性表的长度置为0
}
4.求线性表L的长度
int GetLength(SEQLIST L)
{
return (L.length);
}
5.判断线性表L是否为空
int IsEmpty(SEQLIST L)
{
if (L.length==0) return TRUE;
else return FALSE;
}
6.获取线性表L中的某个数据元素的内容
int GetElem(SEQLIST L,int i,Elemtype *e)
{
if (i<1||i>L.length) return ERROR; //判断i值是否合
// 理,若不合理,返回ERROR
*e=L.elem[i-1]; //数组中第i-1的单元存储着线性表中第
// i个数据元素的内容
return OK;
}
7.在线性表L中检索值为e的数据元素
int LocateELem(SEQLIST L,Elemtype e)
{
for (i=0;i< L.length;i++)
if (L.elem[i]==e) return i+1;
return 0;
}
8.在线性表L中第i个数据元素之前插入数据元素e
int ListInsert(SEQLIST *L,int i,Elemtype e)
{
if (L->length==LIST_MAX_LENGTH) return ERROR;
//检查是否有剩余空间
if (i<1||i>L->length+1) return ERROR; //检查i值是否合理
for (j=L->length-1;j>=i-1;i++)
//将线性表第i个元素之后的所有元素向后移动
L.->elem[j+1]=L->elem[j];
L->elem[i-1]=e; //将新元素的内容放入线性表的第i个位置
L->length++;
return OK;
}
顺序存储结构的优缺点
优点
逻辑相邻,物理相邻
可随机存取任一元素
存储空间使用紧凑
缺点
插入、删除操作需要移动大量的元素
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充
将线性表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。这样就形成的链表中有两个方向不同的链,故称为双向链表。