第一章P1——P11:
数据元素:是数据中具有独立意义的个体,是数据的基本单位。(又称:元素、结点或记录)
数据项:有时,一个数据元素可以划分为若干个数据线(也称字段、域),数据项是数据不可分割的最小单位。
数据对象(data object):性质相同的数据元素的集合,是数据的一个子集。
数据类型(data type):是计算机程序中的数据对象以及定义在这个数据对象集合上的一组操作的总称。
数据类型可以分为:原子数据类型和结构数据类型。
原子数据类型:由计算机语言说提供的,如C语言中的整形、实型、字符型
结构数据类型:利用计算机语言提供的一种描述数据元素之间逻辑关系的机制,由用户自己定义而成,如C语言中的数组类型、结构类型等。
数据结构(data structure):指数据对象以及该数据对象集合中的数据元素之间的相互关系(数据元素的组织形式)。
逻辑结构:数据元素之间的逻辑关系有4类
1)集合:其中的数据元素之间除了“属于同一个集合”的关系以外,别无其他关系。
2)线性结构:数据元素之间存在一对一的关系。
3)树结构:数据元素之间存在一对多的关系。
4)图结构:数据元素之间存在多对多的关系。
数据的存储结构可采用以下4中基本的存储方法得到:
1)顺序存储:
此方法时间逻辑上相邻的节点存储在物理位置相邻的存储单元中,结点之间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储结构称为顺序存储结构。
通常顺序结构使用计算机高级语言中的数组来描述
此方法主要用于线性数据结构,非线性数据结构可以通过某种线性话的方法来实现顺序存储。
2)链接存储:
此方法不要求逻辑上相邻的几点在物理位置上也相邻,结点之间的逻辑关系是由附加的指针来表示的。由此得到的存储结构称为链式存储结构。
通常链式存储结构使用计算机高级语言中的指针来描述。
3)索引存储:
此方法通常实在存储结点信息的同时,建立附加的索引表。索引表中的每一项称为索引项。索引项的一般形式是“(关键字,地址)”。
若每个结点在索引表中 都有一个索引项,则该索引称为稠密索引。稠密索引中的索引项地址指出了结点所在的存储位置。
若一组结点在索引表中只对应一个索引项,则该索引称为稀疏索引。稀疏索引中的索引项地址则指出了一组结点的起始存储位置。
4)散列存储:
此方法的基本思想是根据结点的关键字直接计算出该结点的存储地址。
如何选择存储结构:主要考虑运算便捷和算法的时间、空间需求。
将同一逻辑结构的不同存储结构分别冠以不同的数据结构名称来标识:
线性表是一种逻辑结构,
若采用顺序存储方法表示,则称为顺序表;若菜用链式存储方法表示,则称为链表;若菜用散列存储方法表示,则称为散列表。
在给定数据的逻辑结构和存储结构之后,按定义的运算集合及其运算性质的不同,也可以导出完全不同的数据结构:
若将线性表上的插入、删除运算限制在表的固定一端进行,则称该线性表就称为栈;
若将插入限制在表的一端进行,而将删除运算限制在表的另一端进行,则该线性表称为队列
若线性表采用顺序表或链表作为存储结构,则对插入、删除运算做了以上的限制以后,可以分别得到顺序栈或链栈、顺序队列或链队列。
算法+数据结构=程序
数据结构指:数据的逻辑结构和存储结构,算法指:数据运算的描述。
算法的5个特性:
1)有穷性:一个算法必须在执行有穷步之后结束,即算法必须在有限时间内完成。
2)确定性:算法中每一步必须有确切的含义,不会产生二义性;并且,在任何条件下,算法只有唯一的一条执行路径,及对于相同的输入只能得出相同的输出。
3)可行性:一个算法是可行的,即算法中的每一步都可以通过已实现的基本运算执行又有限次得以实现。
4)输入:一个算法由零个或多个输入,它们是算法开始时对算法给出的初始量。
5)输出:一个算法有零个或多个输出,它们是与输入有特定关系的量。
设计一个“好”的算法应考虑以下几点:
1)正确性
2)健壮性(当输入数据非法时,算法也能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果或出错信息,并终止程序的执行。)
3)可读性(算法主要是为了方便人们的阅读和交流,其次才是机器执行。)
4)执行算法所耗费的时间
5)执行算法所耗费的存储空间,其中主要考虑辅助存储空间。
评价一个好的程序主要考虑:时间资源和空间资源。时间复杂度(时间代价)和空间复杂度(空间代价)
通常,一个算法是由控制结构(顺序、选择和循环)和“原操作”(固有数据类型的操作)构成的,而算法时间取决于二者的综合效果。为了比较同一问题的不同算法,一般是从算法中选取一种对于所研究的问题(或算法类型)来说是基本操作的“原操作”,以该原操作重复执行的次数作为算法的时间度量。被称为问题的原操作应该是其重复执行次数和算法的执行时间成正比的原操作,大部分情况下是最深层循环内的语句中的原操作,它的执行次数和包含它的语句的频度相同。
所谓语句的频度,指的是该语句重复执行的次数。
常见时间复杂度:
1.常数阶:O(1)——C
2.对数阶:O(log以2为底n的对数)
3.线性阶:O(n)
4.线性对数阶O(n的平方乘以log以2为底n的对数)
5.平方阶O(n^2)
6.立方阶O(n^3)
7.指数阶O(2^n)
8.阶乘阶O(n!)
C<log2n<n<nlog2n<n^2<n^3<10的n次方
时间都复杂度从上往下越来越高,执行效率越来越低。
第二章P12——P43:
线性表的基本概念:
线性结构是指结构中的数据元素之间存在着一对一的关系。
线性结构的基本特征:
¥有且只有一个”第一元素“;
¥有且只有一个”最后元素“;
¥除第一元素之外,其他元素都由唯一的直接前驱;
¥除最后元素之外,其他元素都由唯一的直接后继。
线性表(linear list)是具有相同数据类型的n个数据元素的有限序列,通常记为:(a1,a2,a3....an)
数据元素的个数n称为线性表的长度。当n=0时称为空表。
线性表的逻辑特征:在非空的线性表中,有且只有一个起始结点(第一元素)a1,它没有直接前趋,只有一个直接后继a2;有且只有一个终端结点(最后元素)an,它没有直接后继,只有一个直接前趋an-1;而除了a1和an外,其他的每一个结点ai(2<=i<=n-1)都有且只有一个直接前趋ai-1和一个直接后继ai+1。
线性表是一种常用的数据结构。在线性表中,除表头结点外的其他结点有且仅有一个直接前趋;除末尾结点外的其他结点有且仅有一个直接后继。
线性表的顺序存储:
(特点:逻辑上相邻的两个数据元素,在存储的物理位置上也是相邻的。)
线性表的顺序存储方式,是指利用一段连续的内存地址来存储线性表的数据元素。在C语言中,是用一个数组来实现的。
在线性表中的第一个数据元素的存储位置,通常称为线性表的起始位置或基地址。
定义线性表的存储结构:
#define int datatype; /*定义数据元素的类型,这里将int重新命名为datatype*/
#define maxsize 1024; /**/
typedef struct{
datatype elem[maxsize];
int length; /*表长*/
}sequenlist;
其中,数据域elem是存放线性表结点的一个数组,数组下标范围为0~maxsize-1;length是线性表的长度。
1.顺序表的初始化
void InitList(sequenlist *L){ /*构造一个空顺序表,只需要把表长度置为0*/
L->length=0; /*空表,长度为0*/
}
2.清除一个线性表的内容
要清除一个已经存在的线性表的内容,只需要把该线性表设置为空表,也就是把表长置为0.
void clearList(sequenlist *L){
L->lenngth=0;
}
此算法的时间复杂度为O(1)。
3.定位(按值查找)
要查找一个值,只需要从头到尾遍历线性表,如果找到了,则返回找到的位置,否则继续;如果一直到最后一个位置都没有找到,则返回-1.
int Loc(sequenlist L,datatype Item){
int i,j;
j=L.length; /*取出线性表的长度*/
if(j==0) /*空表*/
return FALSE;
for(i=0;i<j;i++){
if(L.elem[i]==Item) /*找到Item*/
return i; /*返回找到的位置*/
}
printf("找不到该值!");
return 0; /*没找到,返回0*/
}
本算法中,有可能找到Item,这时候,平均比较次数为O(n/2),其中,n是线性表的长度;也有可能找不到Item,这时候算法的比较次数为O(n)。因此,总结起来,本算法的时间复杂度为O(n)。
4.插入数据
插入一个数据元素,需考虑以下因素:
i是否在1和L.length之间:如果在1和L.lenght之间,则把Item插入到第i个位置,原来的第i个位置及其以后的数据元素向后依次移动一个位置,然后线性表长度加上1,返回TRUE;如果不在1和L.length之间,则说明插入位置不合适,返回FALSE。
int Ins(sequenlist *L,int i, datatype b){
int j;
if(i<1||i>L->length)
return FALSE; /*位置不合适*/
for(j=L->length;j>i;j--)
L->elem[j]=L->elem[j-1]; /*第i个位置之后的数据均向后移一个位置*/
L->elem[i-1]=b; /*位置i-1就是第i个位置,进行插入*/
L->length++; /*表长加1*/
return TRUE;
}
本算法的时间主要花在移动数据上,由于插入位置是随机的,因此,移动的平均次数为(n/2),其中,n是线性表的长度。由此可知,本算法的时间复杂度为O(n)。
5.删除数据
删除数据和插入数据很相似,也要求判断i的值是否合适,如果合适,则把后面的数据向前移动,删除成功,表长减1,返回TRUE;否则,返回FALSE。
int Del(sequenlist *L,int i){
/*删除顺序表L的第i个结点*/
int j;
if(i<1||i>L->length) /*位置不合适*/
return FLASE;
for(j=i;j<L->length;j++)
L->elem[j]=L->elem[j+1]; /*结点前移*/
L->length--; /*表长减1*/
return TRUE;
}
注意:线性表的顺序存储方式是用一段连续的内存空间来保存线性表结点的值。由于存储空间是连续的,因此能够根据线性表的首地址直接计算出任意一个结点的存储位置。在顺序存储中,能够方便地查找指定位置的结点值;当时插入或者删除结点比较麻烦。
线性表的链式存储:
以链式结构存储的线性表称为链表(linked list)。链式存储结构使用一组任意的存储单元爱存储线性表的结点。(也就是说,链式存储结构中,存储单元可以是相邻的,也可以是不相邻的;同时,相邻的存储单元中的数据不一定是相邻的结点。)
单链表的结点结构:数据域(data)和指针域(next)。 这两部分信息组成链表中的结点结构。
链表的每一个结点只包含一个指针域,称为单链表。链表正是通过每个结点的地址域中的指针,才将线性表的n个结点(1<=i<=n)按其逻辑顺序(a1,a2,a3...an)连接在一起。
由于单链表由头指针唯一确定,因此单链表可以用头指针的名字来命名。
在C语言中,单链表可以描述如下:
typedef struct LNode{ /*结点类型说明*/
ElemType data; /*数据域*/
Struct LNode *next; /*指针域*/
}LinkList;
LinkList *L,*head; /*指针类型说明*/
这里的L(或head)可以作为单链表的头指针,它指向该链表的第1个结点。
如果L=NULL,则表示该单链表为一个空表,长度为0.
有时,为了更方便地判断空表、插入和删除结点,在单链表的第1个结点前面加上一个附设的结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储一些附加信息;而头结点的指针域存储表第1个结点的地址。
单链表是一种非随机存取的存储结构。
1.清除链表的内容
设L是链表的头结点。
要请清除链表的内容,就必须从头结点开始,依次释放每一个结点所占有的存储空间,然后把表长设置为0,把头结点的next域设置为NULL。
int ClearList(LinkList *L){
LinkList *temp;
while(L-next!=NULL){ /*当表还没有空时*/
temp=L->next;
L->next=L->next->next; /*表头指向下一个结点*/
free(temp); /*释放当前结点*/
}
return TRUE;
}
本算法首先把头结点指向第2个结点,然后释放第1一个结点的存储空间,依次类推。
要清除表的内容,就必须对链表进行一次遍历,因此,时间复杂度为O(n),其中,n表示链表的长度。
注意:在释放完毕后,不需要进行L->next=NULL操作,因为循环结束后,L->next已经等于NULL了。
2.求表长
由于在结点中没有保存链表的长度,因此,要获得表长,就必须对链表进行完整的遍历。
int ListLength(LinkList *L){
int len=0;
LinkList *temp;
temp=L;
while(temp->next!=NULL){
len++;
temp=temp->next;
}
return len;
}
本算法时间复杂度为O(n),其中n是链表的长度。
注意:定义临时变量temp是必须的,否则会导致链表丢失。
3.定位
int Loc(LinkList *L,ElemType Item){
int i=1;
LinkList *temp;
temp=L->next;
while(temp!=NULL && temp->data!=Item){
i++;
temp=temp->next;
}
if(temp==NULL)
return 0;
else
return i;
}
采用本算法进行遍历时,如果找到数据,则退出,在能找到的情况下,所需要的时间为(n/2);如果找不到数据,则必须遍历整个链表,此时所需要的时间为(n)。因此,本算法的时间复杂度为O(n),其中,n是链表的长度。
注意:在链表中插入数据比较方便,不需要进行大量的数据移动,只需找到插入点即可。这里给出的是一个后面插入的算法,也就是在插入点的后面添加结点。
插入点前面添加结点,则是前插入方式,与后面插入相比稍有不同。
int Ins(LinkList *L,int i,ElemType Item){
/*在单链表L的第i个位置上后插入值为Item的结点*/
int j=1;
LinkList *node,*temp;
node=(LinkList)malloc(sizeof(LinkList));
if(node==NULL) /*存储空间分配不成功*/
return FALSE;
node->data=Item; /*生成要添加的结点*/
temp=L->next;
if(temp==NULL){ /*空表*/
if(i==0){ /*插入作为第1个结点*/
L->next=node;
Node->next=NULL;
return TRUE;
}
else{
return FALSE; /*没有合适的插入点*/
}
while(j<i && temp!=NULL){ /*寻找第i-1个家电,使temp指向该结点*/
temp=temp->next;
j++;
}
if(temp==NULL) /*没有合适的插入位置*/
return FALSE;
node->next=temp->next; /*插入结点*/
temp->next=node;
return TRUE;
}
}
本算法判断较多,要考虑空表、有无合适的插入点等问题,但是时间复杂度不高,只需要进行一个链表的遍历,而不需要进行大量的数据移动。因此,时间复杂度为O(n),其中,n使链表的长度。
注意:还有一种比较简单的添加结点的方式,就是把新结点加到链表的末端。
5.删除数据
在链表中,删除数据和插入数据很相似,先寻找删除点,然后进行指针赋值操作,
int Del(LinkList *L,int i){
/*在带表头结点的单链表L中,删除第i个结点*/
LinkList *temp, *p;
int j=1;
temp=L;
if(temp=NULL) /*空表,不能删除*/
return FALSE;
while(j<i-1 &&temp!=NULL){ /*寻找删除结点*/
j++;
temp=temp->next;
}
if(temp==NULL||temp->next==NULL) /*第i-1个结点或第i个结点不存在*/
return FALSE;
/*删除结点*/
p=temp->next;
temp->next=p->next /*或者temp->next=temp->next->next*/
free(p);
return TRUE;
}
本算法的时间复杂度也是O(n)
在链式存储中,插入或者删除结点不需要大量地移动结点;但是在定位时,却需要遍历整个链表。
循环链表:
循环链表(cricular link list)是一种首尾相接的链表。
在单链表中,最后一个结点的指针域为空(NULL),如果把该指针与指向链表的第1个结点(头结点),则能构成一个单链形式的循环链表,简称单循环链表。(类似的还有多重链的循环链表。)
1.循环链表的建立
循环链表是对单向链表的一种改善。单向链表中末尾结点的next=NULL,只需要把这个结点的next域的值设置为头结点的地址,就能够获得一个循环链表。
int InitCList(LinkList *L){
/*创建一个头指针为L的循环链表,若成功,则返回TRUE;否则,返回FALSE*/
L=(LinkList *)malloc(sizeof(LinkList)); /* 申请一个表结点空间*/
if(L==NULL) /*申请不成功*/
return FALSE;
L->next=L;
return TRUE; /*申请成功,创建一个空循环链表*/
}
本算法的时间复杂度为O(1),和创建单向空链表相同,唯一的区别是:单项空链表中,L->next=NULL;而在空循环链表中,L->next=L,也就是指向了自身。
2.空循环链表的判断
在单向链表中,判断一个链表是否为空,只需要看头结点的next域是否为NULL;相似地,在循环链表中,要判断一个链表是否为空,只需要看头结点的next域是否等于自身。
int isemty(LinkList *L)
{
if(L->next==L)
return TRUE;
else
return FALSE;
}
本算法的时间复杂度为O(1)。
3.插入结点和删除结点
int InsertCList(LinkList *L,int i,ElemType e){
/*本算法把数据e插入以L作为表头的循环链表中的第i个位置,若成功,返回TRUE;若失败,则返回FALSE*/
int j;
LinkList *temp,*node;
temp=L->next;
j=1;
while(j<i&&temp!=L){
j++;
temp=temp->next;
}
if(j<i && temp->next==L) /*没有合适的插入位置*/
return FALSE;
node=(LinkList *)malloc(sizeof(LinkList));
if(node==NULL) /*申请结点不成功*/
return FALSE;
node->next=temp->next; /*插入数据*/
temp->next=node;
return TRUE;
}
本算法的时间复杂度为O(n),其中n是链表的长度。
注意:在链表中插入数据,关键是对于在空表中插入数据以及在末尾插入数据这两种情况大的判断。但是,对于带表头的链表来说,这两个操作都可以归并到一般情况下来处理,因此不需要更多的判断。
4.删除结点
int DelCList(LinkList *L,int i){
/*从以L作为表头的循环链表中删除第i个数据元素,如果成功,返回TRUE;不成功,返回FALSE*/
int j;
LinkList *t1,*t2;
j=1;
t1=L->next;
t2=L; /*用t2保存要删除系欸但的前一个结点*/
while(j<i && t1!=L)
{
j++;
t1=t1->next;
t2=t2->next;
}
if(j>i) /*找不到要删除的结点*/
return FALSE;
/*有适合的删除位置*/
t2->next=t1->next;
free(t1); /* 释放存储空间*/
return TRUE;
}
在删除结点时,需要判断i是否大于表长,如果大于,则不能删除;如果能够删除,则要注意删除点为第1个或者最后一个结点的情况,但是,在带有头结点的链表中,操作时一致的。本算法的时间复杂度为O(n),其中,n是表长。
从上面的几个例子可以看出,带有头结点的链表虽然占据了一个冗余的存储空间,但是简化了很多判断。
双向链表
单链表都只有一个指向其后继结点的指针,只能进行顺序的向后查找。
如果需要寻找某个结点的前趋,则需要从链表的头指针开始,对链表进行遍历。最坏的情况下,需要遍历整个链表,才能确定结点的前趋。
双向链表可克服单链表这种单向性的缺点。
在C语言中,双向链表的存储结构可定义为:
typedef struct DNode{
struct DNode *prior; /*指向前趋的指针域*/
ElemType data; /*数据域*/
struct DNode *next; /*指向后继的指针域*/
}DLinkList;
DLinkList *DL,*p; /*指针类型说明*/
注意:结点*p的后继的前趋指向该结点本身,同理,结点*p的前趋的后继也指向该结点本身。
双链表的这一特性可称为双向链表结构的对称性。
p->next->prior=p
p->prior->next=p
1.双向链表的建立
由于双向链表具有指向前趋和指向后继的两个指针,因此,在创建空双向链表的时候,需要减这两个指针赋值为NULL。
int InitDList(DLinkList *DL)
{/*创建一个空双向链表DL,若成功,则返回TRUE;否则,返回FALSE*/
DL=(DNode *)malloc(sizeof(DNode));
if(DL==NULL) /*存储空间申请不成功*/
return FALSE;
DL->proir=DL->next=NULL;
return TRUE;
}
本算法和单向链表的创建几乎完全一样,只是需要为两个指针域赋值。
本算法的时间复杂度为O(1)。
2.双向链表为空表的判断
双向链表是否为空表,要看两个指针域是否同时为NULL。
int DLEmpty(DLinkList *DL)
{
if (DL->prior==DL->next &&DL->prior==NULL)
return TRUE;
else
return FALSE;
}
3.在双向链表内插入结点
如果要在双向链表中插入新的结点,则必须修改插入位置的前趋和后继。
int DLInsert(DLinkList *DL,int i,ElemType e)
{
/*在双向链表DL的第i个位置插入一个新的结点e,若成功,则返回TRUE;否则,返回FALSE*/
int j;
DLinkList *temp,*node;
j=1;
temp=DL;
while(j<i && temp->next!=NULL){
j++;
temp=temp->next;
}
if(j>i) /*第i个结点不存在*/
return FALSE;
node=(DLinkList *)malloc(sizeof(DLinkList));
/*建立新节点*/
if(node==NULL)
return FALSE;
node->data=e;
node->prior=temp; /*修改后指针*/
node->next=temp->next; /*修改前指针*/
if(temp->next!=NULL) /*不在表尾插入*/
temp->next->prior=node;
temp->next=node;
return TRUE;
}
本算法的时间复杂度为O(n),其中,n是链表长度。
4.在双向链表内删除结点
P32
注意指针的修改。
int DLDelete(DLinkList *DL,int i)
{
/*在双向链表DL中删除第i个结点,若成功,则返回TRUE;否则,返回FALSE*/
int j=1;
DLinkList *temp;
temp=DL->next; /*工作指针temp指向被删结点*/
while(j<i && temp!=NULL)
{/*在双向链表DL中找第i个结点,使指针temp指向第i个结点*/
j++;
temp=temp->next;
}
if((j<i)||temp==NULL){ /*第i个结点不存在*/
return FALSE;}
temp->prior->next=temp->next; /*删除第i个结点*/
if(temp->next!=NULL) /*表示被删结点不是表尾*/
temp->next->prior=temp->prior;
free(temp);
return TRUE;
}
本算法的时间复杂度为O(n),其中,n使链表的长度。
双向循环链表:在双向循环链表中,是将头结点的前趋指针指向尾结点,而将尾结点的后继指针指向头结点。
对于空双向循环链表来说,头结点的前趋指针和后继指针都指向了头结点自身,这也是判断双向循环链表是否为空表的依据。
双向循环链表的的实质是把双向链表和循环链表结合起来进行。
在双向循环链表中删除结点
int DLClist(DLinkList *DL,ElemType e)
{/*从双向循环链表DL中删除值为e的结点,若成功,则返回TRUE;否则,返回FALSE*/
DLinkList *temp;
temp=DL->next;
if(temp->next==temp) /*空表*/
return FALSE;
while(temp->data!=e && temp!=DL)
temp=temp->next;
if(temp!=DL) /*找到需要删除的结点,而且该结点不是表头*/
{temp->prior->next=temp->next; /*删除结点*/
temp->next->prior=temp->prior;
free(temp); /*释放空间*/
return TRUE;
}
else return FALSE;
}
本算法和循环链表中的删除结点很相似,只是寻找删除点的方式不同。本算法的时间复杂度为O(n)。
静态链表
链表中的结点空间的分配和释放都是由系统提供的标准函数malloc和free动态执行,因此称之为动态链表(dynamic linked list)。
程序设计语言不支持动态分配地址的方式,所以必须预先分配好存储空间。
也就是说,先顶一个规模较大的数组,作为备用结点空间。当申请结点时,从备用结点空间内取出一个结点;当释放结点时,就将结点归还给备用结点空间。用这种方式实现的单链表,每个结点(结构数组的一个分量)含有两个域:data域和next域。data域用来存放结点数据;next域用来存放模拟指针的”游标“(cursor),它指示了其后继结点在数组中的位置。把这种用数组描述的链表称为静态链表(static linked list)。
线性表的静态链表存储结构定义如下:
#define MAXSIZE 1000 /*链表的最大长度*/
typedef int ElemType;
typedef struct SList{ /*结点类型*/
ElemType data; /*数据域*/
int next; /*游标域*/
}node;
node SLinkList[MAXSIZE]; /*备用结点空间*/
int av; /*游标变量*/
线性表顺序存储与链式存储的比较
时间角度:按位置查找数据时,查找元素的前趋和后继等方面,顺序存储有较大的优势(在数据有序的情况下)。
但在插入数据、删除数据时,链式存储就有较大的优势,是由于在链表中只要修改指针就可实现插入、删除;而在顺序表中进行插入和删除,平均要移动表中将近一半的几点。
因此,当线性表的操作主要是进行查找,而很少进行插入和删除操作时,应当采用顺序表作为存储结构;而对于频繁进行插入和删除操作的线性表,则应采用链表作为存储结构。
空间角度:顺序表的存储空间时静态分配的,在程序执行之前必须规定其存储规模。若预估过大则造成空间的浪费;若预估过小则易发生空间的溢出。
动态链表的存储空间是动态分配的,只要内存空间有空闲,就不会产生溢出。
当线性表的长度变化较大时,应该采用动态链表作为存储结构。
第二章小结P42
1)顺序表时采用数组来实现,链表时采用指针或游标来实现。
2)用指针来实现的链表,由于其结点空间是动态分配的,因此称为动态链表;
而用游标模拟指针来实现的链表,因其结点空间是预先静态分配的,故称为静态链表。
3)这两种链表可按结点指针域个数的多少、链接形式的不同,区分为单链表、双向链表和循环链表等。
4)算法主要考虑求解算法的时间复杂度和空间复杂度。
引用在校学习书