一、ADT
具有相同数据类型的n个数据元素的有限序列。除第一个元素之外每个元素都有唯一的直接前驱,除最后一个元素之外都有唯一的直接后继。
L(a1,a2,...,an)
操作:
InitList(&L)
Length(L)
LocateElem(L,e)
GetElem(L,i)
ListInsert(&L,i,e)
ListDelete(&L,i,&e)
PrintList(L)
Empty(L)
DestoryList(&L)
二、顺序存储
特点:逻辑顺序和物理顺序相同,支持随机访问,存储密度高
优点:
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
缺点:
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间的“碎片”
#define Maxsize 100
typedef struct {
int data[Maxsize]; //ElemType data[Maxsize];
int length;
}SqList;
void InitList(SqList *&L) {
L = (SqList *)malloc(sizeof(SqList));
//L = new SqList();
L->length = 0;
}
int ListLength(SqList *L) {
return L->length;
}
bool ListInsert(SqList *&L, int i, int e) {
if (i<0 || i>L->length || L->length>=Maxsize) return false;
for (int j = L->length; j > i; j--) {
L->data[j] = L->data[j - 1];
}
L->data[i] = e;
L->length++;
return true;
}
bool ListDelete(SqList *&L, int i, int &e) {
if (i<0 || i>L->length - 1) return false;
e = L->data[i];
for (int j = i + 1; j < L->length; j++) {
L->data[j - 1] = L->data[j];
}
L->length--;
return true;
}
int GetElem(SqList *L, int i) {
if (i<0 || i>L->length - 1) return false;
return L->data[i];
}
int LocateElem(SqList *L, int e) {
for (int i = 0; i < L->length; i++) {
if (L->data[i] == e) {
return i;
}
}
return -1;
}
void PrintList(SqList *L) {
for (int i = 0; i < L->length;i++) {
printf("%d ", L->data[i]);
}
printf("\n");
}
bool Empty(SqList *L) {
if (L->length == 0) return true;
return false;
}
void DestroyList(SqList *&L) {
free(L);
}
三、链式存储
对于每个节点通过增加指针来表示数据元素之间的关系。
特点:
- 解决了顺序表需要大量连续存储单元的缺点,但单链表附加指针域,也存在浪费存储空间的缺点。
- 非随机存取
1.单链表
增加一个后继指针
typedef struct LNode {
int data; //ElemType data;
struct LNode *next;
}LNode, *LinkList;
void InitList(LinkList &L, int data) {
L = new LNode();
L->data = data;
L->next = nullptr;
}
int ListLength(LinkList L) {
int i = 0;
while (L->next != nullptr) {
i++;
L = L->next;
}
return i;
}
LNode* GetElem(LinkList L, int i) {
if (i < 0) return NULL;
LNode *p = L;
while (p != nullptr && i > 0) {
p = p->next;
i--;
}
if (i > 0) {
return NULL;
}
return p;
}
bool ListInsert(LinkList L, int i, int e) {
LNode *p = GetElem(L, i);
if (p == NULL) return false;
LNode *q;
InitList(q, e);
q->next = p->next;
p->next = q;
return true;
}
bool ListDelete(LinkList L, int i, LNode *&e) {
if (i <= 0) return false;
LNode *p = GetElem(L, i - 1);
if (p == NULL) return false;
e = p->next;
p->next = e->next;
}
LNode* LocateElem(LinkList L, int e) {
LNode *p = L->next;
while (p != NULL&&p->data!=e) {
p = p->next;
}
return p;
}
void PrintList(LinkList L) {
LNode *p = L->next;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void DestroyList(LinkList &L) {
LNode* p = L->next;
LNode* q = p->next;
while (q != NULL) {
delete(p);
p = q;
q = q->next;
}
delete(p);
delete(L);
}
2.双链表
在单链表的基础上增加了一个指向其前驱的prior指针
typedef struct LNode {
int data; //ElemType data;
struct LNode *prior;
struct LNode *next;
}LNode, *LinkList;
void InitList(LinkList &L, int data) {
L = new LNode();
L->data = data;
L->prior = nullptr;
L->next = nullptr;
}
int ListLength(LinkList L) {
int i = 0;
while (L->next != nullptr) {
i++;
L = L->next;
}
return i;
}
LNode* GetElem(LinkList L, int i) {
if (i < 0) return NULL;
LNode *p = L;
while (p != nullptr && i > 0) {
p = p->next;
i--;
}
if (i > 0) {
return NULL;
}
return p;
}
bool ListInsert(LinkList L, int i, int e) {
LNode *p = GetElem(L, i);
if (p == NULL) return false;
LNode *q;
InitList(q, e);
if (p->next != NULL) {
p->next->prior = q;
q->next = p->next;
p->next = q;
q->prior = p;
}
else {
q->prior = p;
p->next = q;
}
return true;
}
bool ListDelete(LinkList L, int i, LNode *&e) {
if (i <= 0) return false;
LNode *p = GetElem(L, i - 1);
if (p == NULL) return false;
e = p->next;
if (e->next != NULL) {
e->next->prior = p;
}
p->next = e->next;
}
LNode* LocateElem(LinkList L, int e) {
LNode *p = L->next;
while (p != NULL&&p->data!=e) {
p = p->next;
}
return p;
}
void PrintList(LinkList L) {
LNode *p = L->next;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void DestroyList(LinkList &L) {
LNode* p = L->next;
LNode* q = p->next;
while (q != NULL) {
delete(p);
p = q;
q = q->next;
}
delete(p);
delete(L);
}
3.循环链表
4.循环双链表
5.静态链表
#define Maxsize 50
typedef struct{
ElemType data;
int next;
} SLinkList[Maxsize];
静态链表依然属于链表,不能随机存取
四、顺序表和链表的比较
1.存取方式
顺序表可以顺序存取,也可以随机存取,链表只能从表头顺序存取元素
2.逻辑结构和物理结构
采用顺序存储时,逻辑上相邻的元素,对应的物理存储位置也相邻,而采用链式存储时,逻辑上相邻的元素,物理存储位置不一定相邻
3.查找,插入,删除操作
对于按值查找,顺序表无序时,两者的时间复杂度均为O(n);顺序表有序时,可采用折半查找,此时的时间复杂度为O(log2 n)
对于按序号查找,顺序表O(1),链表O(n)
插入删除,顺序表要移动半个表长的元素.链表只需修改指针域即可。链表的时间复杂度也为O(n),但花销在寻找节点上
4.空间分配
顺序表需要预先分配足够大的存储空间,动态存储分配实际上是数据的大量移动,并非是静态的增加空间,依旧需要连续的足够大的存储空间
- realloc:如果有足够空间用于扩大mem_address指向的内存块,则分配额外内存,并返回mem_address
- realloc是从堆上分配内存的,当扩大一块内存空间时, realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平。也就是说,如果原先的内存大小后面还有足够的空闲空间用来分配,加上原来的空间大小= newsize。那么就ok。得到的是一块连续的内存。
- 如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address指针。(数据被移动了)。
链表只在需要时申请分配,只要内存有空间就可以分配