一、线性表的两种存储结构
1.顺序存储
线性表的顺序表示:用一组地址连续的存储单元依次存储线性表的数据元素。用顺序存储结构表示的线性表又称为顺序表。
顺序表特点
- 元素的存储位置能反映元素间的逻辑关系,逻辑上相邻的数据元素在物理位置上也相邻。
- 只要确定顺序表在内存中的起始位置,表中的任何元素都可随机存取,顺序表是一种随机存取的存储结构
顺序表优点
可以随机存取表中任一元素; 大部分操作都比较容易实现;存储利用率高(关系隐含在存储结构里,不需显式表示)
顺序表缺点
不知道表的规模的情况下,2个常量的大小不太好确定;顺序表在进行插入和删除操作时,需要移动大量元素,浪费大量时间。
适用场合:表中元素变动不大,但需要快速存取元素;或存储空间需求预先知道。
顺序表的实现
静态分配存储结构
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int length;
}SqList;
动态分配存储结构
#define LIST_INIT_SIZE 100 //顺序表存储空间的初始分配量
#define LISTINCREMENT 10 //顺序表存储空间的分配增量
typedef struct {
ElemType *elem; //存储空间的基地址
int length; //顺序表的当前长度
int listsize; //数组存储空间的长度
}SqList;
2.链式存储
用一组任意的存储单元(可连续可不连续)存储线性表中的数据元素。
链式表特点:
逻辑关系上相邻的两个元素在物理位置上不一定相邻,元素之间的关系由指针指示
优点:
链表在进行插入和删除操作时,不再移动大量元素,仅需修改指针;不再需要定义2个常量
缺点:
元素只能顺序存取;大部分操作的时间复杂度都为O(n);存储利用率较低(存储元素时要附带存储指针)
适用场合: 表中元素频繁插入和删除;或存储空间需求不定的应用。
单链表存储结构
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
二、基本操作
1.顺序表
初始化
void InitList(List *L) {
(*L).data = (ElementType *)malloc(InitSize * sizeof(ElementType));
(*L).length = 0;
(*L).listsize = InitSize;
}
插入
时间复杂度 O(n)
//类C语言
Status ListInsert(SqList &L,int i,ElemType e){
//插入元素e到顺序表L的第i个位置之前
if(i<1 ||i>L.length+1) return ERROR;
if(L.length==L.listize)
{
L.elem=(ElemType *) realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
if(!L.elem) exit(OVERFLOW);
L.Listsize += LISTINCREMENT;
}//重新分配空间
q=&L.elem[i-1];
for(p=&(L.elem[L.length-1];p>=q;--p)
*(p+1)=*p;
*q=e;
++L.length;
return OK;
}
删除
时间复杂度O(n)
//类C语言
Status ListDelete(SqList &L,int i,ElemType &e){
//删除顺序表L中的第i个元素,其值由e返回;
if(i<1 ||i>L.length) return ERROR;
p=&L.elem[i-1];
e=*p;
q=L.elem+L.length-1;//表尾元素的位置
for(++p;p<=q;++p)
*(p-1)=*p;//元素依次前移
--L.length;
return OK;
}
遍历
Status ListTraverse(List L)
{
for(i=0;i<L.length;i++)
printf("%d",L.data[i]);
}
2.单链表
初始化
//类C语言
Status InitList(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode));
if (!L) exit(OVERFLOW);
L->next=NULL;
return OK;
}
插入
时间复杂度O(n)
//在带头结点的单链表L中在第i个位置前插入(类C)
Status Insert(LinkList &L,int i,ElemType e)
{
while(p&&j<i-1)
{
p=p->next;++j;
}
if(!p||j>i-1)
return ERROR;
s=(LinkList )malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return ok;
删除
时间复杂度O(n)
//删除第i个元素
ListDelete(&L, i ,&e)
{
p=L; j=0;
while(p->next&&j<i-1) {p=p->next;++j;}
if(!(p->next)||j>i-1) return ERROR;
q=p->next;//q指向被删除结点
p->next=q->next;
e=q->data;
free(q);
}
遍历
时间复杂度O(n)
Status ListTraverse(LinkList L,Status (*visit)(ElemType))
{
for( p=L->next; p; p=p->next )
if( !visit(p->data) ) return ERROR;
return OK;
}
三、循环、双向链表
循环链表
带头结点的循环链表:尾结点的指针指向头结点
优点:从表中任一结点出发都可找到表中其他结点。
操作:和线性链表基本一致,差别在于判空和判表尾条件。
判空:L->next=L;判表尾:p->next=L;
线性单链表和循环链表的缺点:寻找结点后继和前驱的可能性及复杂度不同。
双向链表:
在单链表的每个结点里增加一个指向其直接前趋的指针域prior。方便查找结点的前驱结点。
双向链表C语言描述
typedef struct DulNode{
ElemType data;
Struct DulNode *prior;//指向其直接前驱的指针域
Struct DulNode *next; //指向其直接后继的指针域
}DulNode,*DuLinkList;
//本文中仅仅写出了自己老师所点的复习重点,不是很完善,假期会完善。