【考纲内容】
(一)、 线性表的定义和基本操作
(二)、线性表的实现
顺序存储;链式存储;线性表的应用
【知识框架】
【复习提示】
线性表是考研命题的重点 ;
这类算法题实现起来比较容易而且代码量较少,但却要求具有最优的性能(时间 、空间复杂度),才能获得满分;
因此,需要牢固掌握线性表的各种操作(基于两者存储结构),在平时的学习中多注重培养动手能力;
另外,需要提醒的是,算法最重要的是思想!考场上的时间有限,在试卷上不一定要求代码具有实际的可执行性,因此应尽力表达出算法的思想和步骤,而不必过于拘泥于每个细节!注意算法题只能用 C/C++ 语言实现
线性表的定义和基本操作 | |
################################################################################## | |
2.1.1 线性表的定义 | |
线性表的定义 | 线性表是具有相同数据类型的 ( 0)个数据元素的有限序列 |
表长 | 线性表中数据元素的个数 |
空表 | 当数据元素个数 为 0 时,称为 "空表" |
线性表的一般表示 | 用 L 命名线性表,其一般表示为 = ( , ,... , , ,... ,) |
线性表的逻辑特性 | (i). 是唯一的 "第一个" 数据元素,也称表头元素 (ii). 是唯一的 "最后一个" 数据元素,也称表尾元素 (iii). 除表头元素外,每个元素有且仅有一个直接前驱 (iv). 除表尾元素外,每个元素有且仅有一个直接后继 |
线性表的特点 | (i). 表中元素的个数有限 (ii). 表中元素具有逻辑上的顺序性,表中元素有其先后次序 (iii). 表中元素都是数据元素,每个元素都是单个元素 (iv). 表中元素的数据类型都相同,即每个元素占有相同大小的存储空间 (v). 表中元素具有抽象性,仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容 |
注意 | 线性表是一种逻辑结构,表示元素之间一对一的相邻关系; 顺序表和链表是指存储结构,两者属于不同层面的概念,不要将其混淆 |
################################################################################## | |
2.1.2 线性表的基本操作 | |
################################################################################## | |
Init(&L) | 初始化表:构造一个空的线性表 |
Length(L) | 求表长:返回线性表 L 的长度,即 L 中数据元素的个数 |
Empty(L) | 判空操作:若 L 为空表,则返回 true ,否则返回 false |
LocateElem(L,e) | 按值查找操作:在表 L 中查找给定关键字值的元素 |
GetElem(L,i) | 按位查找操作:获取表 L 中第 i 个位置上的元素的值 |
Insert(&L,i,e) | 插入操作:在表 L 中的第 i 个位置插入指定元素 e |
Delete(&L,i,&e) | 删除操作:删除表 L 中第 i 个位置上的元素,并用 e 返回被删除元素的值 |
PrintList(L) | 输出操作:按先后顺序输出线性表 L 的所有元素的值 |
Destroy(&L) | 销毁操作:销毁线性表,并释放线性表 L 所占用的内存空间 |
注意 | (i). 基本操作的实现取决于采用哪种存储结构(顺序存储还是链式存储),存储结构不同,算法的实现也不同 (ii). "&" 表示 C++ 中的引用调用,若传入的变量是指针型变量,且在函数体内要对传入的指针进行修改,则会用到指针变量的引用型;在 C 语言中采用指针的指针也可以达到同样的效果 |
#include
#include
#define MAXSIZE 20
#define OK 1 // success
#define ERROR 0 // failure
typedef int Status; // function return type
typedef int ElemType; // simple data type
typedef struct {
ElemType *elem;
int cap
int len;
}SeqList;
Status InitList(SeqList* L)
{
L->elem = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
if (L->elem == NULL)
{
printf("Out of memory!\n");
return ERROR;
}
L->cap = MAXSIZE;
L->len = 0;
return OK;
}
int Length(SeqList *L)
{
return L->len;
}
int LocateElem(SeqList *L, ElemType e)
{
for (int k = 0; k < L->len; k++)
{
if (L->elem[k] == e)
{
return k + 1;
}
}
return -1;
}
按值查找 | |
概念 | 在顺序表 中查找第一个元素值等于 的元素, 并返回该元素的位序 |
时间复杂度分析 | |
最好情况 | 查找的元素就在表头,仅需比较一次,时间复杂度为 |
最坏情况 | 查找的元素在表尾(或不存在)时,需要比较 次, 时间复杂度为 |
平均情况 | 假设 ( = )是查找的元素在第 (1 <= <= L.len)个位置上的概率, 则在长度为 线性表中查找值为 的元素所需比较的平均次数为 |
= = = | |
因此,线性表按值查找算法的平均时间复杂度为 |
Status GetElem(SeqList *L, int i, ElemType *e)
{
if (i < 1 || i > L->len)
{
printf("Invalid index !\n");
return ERROR;
}
if (L->len == 0)
{
printf("Empty List !\n");
return ERROR;
}
*e = L->elem[i-1];
return OK;
}
Status Insert(SeqList *L, int i, ElemType e)
{
if (i < 1 || i > L->len + 1)
{
printf("Invalid index !\n");
return ERROR;
}
if (L->len == L->cap)
{
L->elem = (ElemType*)realloc(L->elem, 2 * L->cap);
if (L->elem == NULL)
{
printf("Out of memory !\n");
return ERROR;
}
}
for (int k = L->len; k >= i; k--)
{
L->elem[k] = L->elem[k-1];
}
L->elem[i-1] = e;
L->len++;
return OK;
}
插入操作 | |
概念 | 在顺序表 的第 (1 <= <= L.len+1) 个位置插入新元素 ; 若 的输入不合法,则返回 false ,表示插入失败;否则, 将顺序表的第 个元素及其后的所有元素均右移一个位置, 腾出一个空位置插入新元素 ,顺序表的长度增加 1 , 插入成功,返回 true |
注意 | 区别顺序表的位序和数组下标 |
时间复杂度分析 | |
最好情况 | 在表尾插入(即 ), 不需要后移元素,时间复杂度为 |
最坏情况 | 在表头插入(即 ),元素后移语句将执行 次, 时间复杂度为 |
平均情况 | 假设 ( = )是在第 个位置上插入一个结点的概率, 则在长度为 的线性表中插入一个结点时,所需移动结点的 平均次数为 |
因此,线性表插入算法的平均时间复杂度为 |
Status Delete(SeqList *L, int i, ElemType *e)
{
if(L->len == 0)
{
printf("Empty List !\n");
return ERROR;
}
if(i < 1 || i > L->len)
{
printf("Invalid index !\n");
return ERROR;
}
*e = L->elem[i-1];
for(int k = i; k < L->len; k++)
{
L->elem[k-1] = L->elem[k];
}
L->len--;
return OK;
}
void PrintList(SeqList *L)
{
if (L->len == 0)
{
printf("Empty List !\n");
return ERROR;
}
for (int i = 0; i < L->len; i++)
{
printf("the %d th element is %d\n", i+1, L->elem[i]);
}
return OK;
}
bool Empty(SeqList *L)
{
if (L->len == 0)
return true;
else
return false;
}
void Destroy(SeqList *L)
{
free(L->elem);
L->elem = NULL;
L->len = 0;
L->cap = 0;
}
接口:
#include
#include
#define MAXSIZE 100
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct {
ElemType *elem;
int cap;
int len;
} SeqList;
Status InitList(SeqList *L);
void Destroy(SeqList *L);
Status Empty(SeqList *L);
int Length(SeqList *L);
int LocateElem(SeqList *L, ElemType e);
Status GetElem(SeqList *L, int i, ElemType *e);
Status Insert(SeqList *L, int index, ElemType e);
Status Delete(SeqList *L, int index, ElemType *e);
Status PrintList(SeqList* L);
int main(int argc, char* argv[])
{
SeqList L;
ElemType e = 0;
InitList(&L);
printf("Just Init!\n");
if (Empty(&L) == OK)
printf("Empty\n");
else
printf("Not Empty\n");
printf("len of list is %d\n", Length(&L));
Insert(&L, 1, 5);
Insert(&L, 2, 7);
Insert(&L, 3, 11);
Insert(&L, 4, 13);
Insert(&L, 5, 17);
printf("the index of 13 is %d\n", LocateElem(&L, 13));
PrintList(&L);
Delete(&L, 3, &e);
if (Empty(&L) == OK)
printf("Empty\n");
else
printf("Not Empty\n");
printf("len of list is %d\n", Length(&L));
Destroy(&L);
return 0;
}
Status InitList(SeqList *L)
{
L->elem = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
if (L->elem == NULL)
{
printf("Out of memory!\n");
return ERROR;
}
L->cap = MAXSIZE;
L->len = 0;
return OK;
}
void Destroy(SeqList *L)
{
free(L->elem);
L->elem = NULL;
L->len = 0;
L->cap = 0;
}
Status Empty(SeqList *L)
{
if (L->len == 0)
return OK;
else
return ERROR;
}
int Length(SeqList *L)
{
return L->len;
}
int LocateElem(SeqList *L, ElemType e)
{
for (int k = 0; k < L->len; k++)
{
if (L->elem[k] == e)
{
return k + 1;
}
}
return -1;
}
Status GetElem(SeqList *L, int i, ElemType *e)
{
if (i < 1 || i > L->len)
{
printf("Invalid index !\n");
return ERROR;
}
if (L->len == 0)
{
printf("Empty List !\n");
return ERROR;
}
*e = L->elem[i - 1];
return OK;
}
Status Insert(SeqList *L, int index, ElemType e)
{
if (index < 1 || index > L->len + 1)
{
printf("Invalid index !\n");
return ERROR;
}
if (L->len == L->cap)
{
L->elem = (ElemType*)realloc(L->elem, 2 * L->cap);
if (L->elem == NULL)
{
printf("Out of memory !\n");
return ERROR;
}
}
for (int k = L->len; k >= index; k--)
{
L->elem[k+1] = L->elem[k];
}
L->elem[index-1] = e;
L->len++;
printf("the %dth inserted elem is %d\n", index, e);
return OK;
}
Status Delete(SeqList *L, int index, ElemType *e)
{
if(L->len == 0)
{
printf("Empty List !\n");
return ERROR;
}
if(index < 1 || index > L->len)
{
printf("Invalid index !\n");
return ERROR;
}
*e = L->elem[index - 1];
if(index < L->len)
{
for(int k = index; k < L->len; k++)
{
L->elem[k-1] = L->elem[k];
}
}
L->len--;
printf("the %dth deleted elem is %d\n", index, *e);
return OK;
}
Status PrintList(SeqList* L)
{
if (L->len == 0)
{
printf("Empty List !\n");
return ERROR;
}
for (int i = 0; i < L->len; i++)
{
printf("the %d th element is %d\n", i+1, L->elem[i]);
}
return OK;
}
/*
* 从顺序表中删除具有最小值的元素(假设唯一)并由函数返回背包删除元素的值,
* 空出的位置由最后一个元素填补,若顺序表为空则显示出错信息并退出运行
*/
void DeleteMin(SqList *L, ElemType *e)
{
if (L->len == 0)
{
printf("Empty List, can not delete any element !\n");
return ERROR;
}
int minIndex = 0;
for (int i = 1; i < L->len; i++)
{
if (L->elem[i] < L->elem[i-1])
{
minIndex = i;
}
}
*e = L->elem[minIndex];
if (minIndex != L->len - 1)
{
L->elem[minIndex] = L->elem[L->len-1];
L->elem[L->len-1] = 0;
}
L->len--;
return;
}
顺序表的优点 | 可以随时存取表中的任意一个元素,存储位置可以用简单 、直观的公式表示 |
顺序表的缺点 | 插入和删除操作需要移动大量元素 |
链表 | |
链表的特点 | 不需要使用地址连续的存储单元, 即,不要求逻辑上相邻的元素在物理位置上也相邻, 通过 "链" 来建立起数据元素之间的逻辑关系 |
链表的优点 | 插入和删除操作不需要移动元素,只需修改指针 |
链表的缺点 | 无法像顺序表那样随机存取元素 |
#include
#include
typedef int ElemType;
struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;
struct Node
{
ElemType Elem;
Position Next;
};
List MakeEmpty(List L);
int IsEmpty(List L);
int IsLast(Position P);
Position FindPrevious(List L, ElemType E);
Position Find(List L, ElemType E);
void HeadInsert(Position Head, ElemType E);
Position TailInsert(Position Tail, ElemType E);
void Delete(List L, ElemType E);
void DeleteList(List L);
Position Header(List L);
Position First(List L);
Position Advance(Position P);
ElemType Retrieve(Position P);
void PrintList(List L);
void PrintEmpty(List L);
int main(int argc, char* argv[])
{
List Lh, Lt;
Position Head, Tail;
Lh = Lt = Head = Tail = NULL;
Lh = MakeEmpty(Lh);
Lt = MakeEmpty(Lt);
Head = Lh->Next;
Tail = Lt->Next;
for (int i = 1; i <= 5; i++)
{
HeadInsert(Head, i*i+3); // 4 7 12 19 28
Tail = TailInsert(Tail, i*i+3);
}
printf("\n");
printf("HeadInsert : \n");
PrintList(Lh);
printf("\n");
printf("TailInsert : \n");
PrintList(Lt);
printf("\n");
DeleteList(Lh);
DeleteList(Lt);
return 0;
}
List MakeEmpty(List L)
{
L = (List)malloc(sizeof(struct Node));
if (!L)
return NULL;
Position header = (Position)malloc(sizeof(struct Node));
if (!header)
return NULL;
header->Next = NULL;
L->Next = header;
return L;
}
int IsEmpty(List L)
{
return L->Next == NULL;
}
int IsLast(Position P)
{
return P->Next == NULL;
}
Position FindPrevious(List L, ElemType E)
{
Position P;
P = L;
while (P->Next != NULL && P->Next->Elem != E)
P = P->Next;
return P;
}
Position Find(List L, ElemType E)
{
Position P;
P = L->Next;
while (P != NULL && P->Elem != E)
P = P->Next;
return P;
}
void HeadInsert(Position Head, ElemType E)
{
Position NewCell;
NewCell = (Position)malloc(sizeof(struct Node));
if (NewCell == NULL)
{
printf("Out of memory!\n");
exit(0);
}
NewCell->Elem = E;
NewCell->Next = Head->Next;
Head->Next = NewCell;
}
Position TailInsert(Position Tail, ElemType E)
{
Position NewCell;
NewCell = (Position)malloc(sizeof(struct Node));
if (NewCell == NULL)
{
printf("Out of memory!\n");
exit(0);
}
NewCell->Elem = E;
NewCell->Next = NULL;
Tail->Next = NewCell;
return NewCell;
}
void Delete(List L, ElemType E)
{
Position P, TmpCell;
P = FindPrevious(L, E);
if (!IsLast(P))
{
TmpCell = P->Next;
P->Next = TmpCell->Next;
free(TmpCell);
}
}
void DeleteList(List L)
{
Position P, TmpCell;
P = L->Next;
L->Next = NULL;
while (P != NULL)
{
TmpCell = P->Next;
free(P);
P = TmpCell;
}
}
Position Header(List L)
{
return L;
}
Position First(List L)
{
return L->Next;
}
Position Advance(Position P)
{
return P->Next;
}
ElemType Retrieve(Position P)
{
return P->Elem;
}
void PrintList(List L)
{
Position Head, Cur;
int count = 1;
Head = L->Next;
Cur = Advance(Head);
while (Cur != NULL)
{
printf("%dth elem is %d\n", count, Retrieve(Cur));
count++;
Cur = Advance(Cur);
}
}
void PrintEmpty(List L)
{
if (IsEmpty(L) != 0)
printf("Empty\n");
else
printf("Not Empty\n");
}
#include
#include
typedef int ElemType;
struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;
struct Node
{
ElemType Elem;
Position Prior, Next;
};
List MakeEmpty(List L);
int IsEmpty(List L);
int IsLast(Position P);
Position FindPrevious(List L, ElemType E);
Position Find(List L, ElemType E);
void HeadInsert(Position Head, ElemType E);
void TailInsert(List L, ElemType E);
void DeleteElem(List L, ElemType E);
void DeletePos(List L, Position Pos);
void DeleteList(List L);
Position Header(List L);
Position First(List L);
Position Advance(Position P);
ElemType Retrieve(Position P);
void PrintList(List L);
void PrintEmpty(List L);
int main(int argc, char* argv[])
{
List L;
Position Cur, Pre, H;
L = MakeEmpty(L);
PrintList(L);
H = L->Next;
HeadInsert(L, 2);
printf("L insert elem 2\n");
HeadInsert(L, 3);
printf("L insert elem 3\n");
HeadInsert(L, 5);
printf("L insert elem 5\n");
HeadInsert(L, 7);
printf("L insert elem 7\n");
HeadInsert(L, 11);
printf("L insert elem 11\n");
HeadInsert(L, 13);
printf("L insert elem 13\n");
printf("\n\n");
printf("Head Insert !!!\n");
PrintList(L);
printf("\n\n");
//printf("Del Elem = 7\n");
//DeleteElem(L, 7);
printf("Find Elem 11 and 5\n\n");
Pre = FindPrevious(L, 11);
printf("Pre's Next Elem is %d\n", Pre->Next->Elem);
Cur = Find(L, 5);
printf("Cur's Elem is %d\n", Cur->Elem);
printf("Del Elem = 7\n");
DeleteElem(L, 7);
printf("Current L is like below:\n");
PrintList(L);
printf("\n\n");
DeletePos(L, L->Next);
PrintList(L);
printf("Del First Node and Current L is like below\n");
DeletePos(L, L->Next);
PrintList(L);
printf("Del First Node and Current L is like below\n");
DeletePos(L, L->Next);
PrintList(L);
DeleteList(L);
printf("\n\n Current L is like below:\n");
PrintList(L);
return 0;
}
List MakeEmpty(List L)
{
L = (List)malloc(sizeof(struct Node));
if (!L)
return NULL;
Position header = (Position)malloc(sizeof(struct Node));
if (!header)
return NULL;
header->Next = NULL;
header->Prior = L;
L->Next = header;
L->Prior = NULL;
return L;
}
int IsEmpty(List L)
{
return L->Next->Next == NULL;
}
int IsLast(Position P)
{
return P->Next == NULL;
}
Position FindPrevious(List L, ElemType E)
{
Position P;
P = L;
while (P->Next != NULL && P->Next->Elem != E)
P = P->Next;
return P;
}
Position Find(List L, ElemType E)
{
Position P;
P = L->Next;
while (P != NULL && P->Elem != E)
P = P->Next;
return P;
}
void HeadInsert(Position Head, ElemType E)
{
Position NewCell;
NewCell = (Position)malloc(sizeof(struct Node));
if (NewCell == NULL)
{
printf("Out of memory!\n");
exit(0);
}
NewCell->Elem = E;
NewCell->Next = Head->Next;
Head->Next->Prior = NewCell;
NewCell->Prior = Head;
Head->Next = NewCell;
}
void TailInsert(List L, ElemType E)
{
Position NewCell, Tail;
Tail = L->Next;
while (Tail->Next != NULL)
Tail = Tail->Next;
NewCell = (Position)malloc(sizeof(struct Node));
if (NewCell == NULL)
{
printf("Out of memory!\n");
exit(0);
}
NewCell->Elem = E;
NewCell->Next = Tail->Next;
Tail->Next->Prior = NewCell;
NewCell->Prior = Tail;
Tail->Next = NewCell;
}
void DeleteElem(List L, ElemType E)
{
Position Pre, Cur;
Pre = FindPrevious(L, E);
if (!IsLast(Pre))
{
Cur = Pre->Next;
Pre->Next = Cur->Next;
Cur->Next->Prior = Pre;
free(Cur);
}
}
void DeletePos(List L, Position Pos)
{
Position Pre, Cur;
Pre = FindPrevious(L, Pos->Elem);
if (!IsLast(Pre))
{
Cur = Pre->Next;
Pre->Next = Cur->Next;
Cur->Next->Prior = Pre;
free(Cur);
}
}
void DeleteList(List L)
{
Position P, TmpCell;
P = L->Next;
L->Next = NULL;
while (P != NULL)
{
TmpCell = P->Next;
free(P);
P = TmpCell;
}
}
Position Header(List L)
{
return L;
}
Position First(List L)
{
return L->Next;
}
Position Advance(Position P)
{
return P->Next;
}
ElemType Retrieve(Position P)
{
return P->Elem;
}
void PrintList(List L)
{
Position Cur;
int count = 1;
Cur = L->Next;
if (Cur->Next == NULL)
{
printf("Empty List!\n");
return;
}
while (Cur->Next != NULL)
{
printf("%dth elem is %d\n", count, Retrieve(Cur));
count++;
Cur = Cur->Next;
}
}
void PrintEmpty(List L)
{
if (IsEmpty(L) != 0)
printf("Empty\n");
else
printf("Not Empty\n");
}
比较\类型 | 顺序表 | 链表 |
存取方式 | 可以顺序存取, 也可以随机存取 |
只能从表头顺序存取元素 |
逻辑结构与 物理结构 |
顺序存储,逻辑上相邻的元素, 对应的物理存储位置也相邻 |
链式存储,逻辑上相邻的元素, 物理存储位置不一定相邻, 对应的逻辑关系是通过指针链接表示 |
按值查找 | 顺序表无序时, 时间复杂度为 |
顺序查找,时间复杂度为 |
顺序表有序时,采用折半查找, 时间复杂度为 |
顺序查找,时间复杂度为 | |
按序号查找 | 顺序表支持随机访问, 时间复杂度为 |
链表依旧只能从头开始顺序查找, 时间复杂度为 |
插入与删除 | 平均需要移动半个表长的元素 | 只需修改相关结点的指针域即可 |
每个结点都带有指针域,故存储密度不大 | ||
空间分配 | 静态分配: (i). 一旦存储空间装满就不能扩充,若再假如新元素,则内存溢出,因此需要预先分配足够大的存储空间 (ii). 预先分配过大,可能会导致顺序表后半部分空间大量闲置 (iii). 预先分配过小,会造成溢出 |
链式存储的结点空间只在需要时申请分配,只要内存有空间就可以分配,操作灵活 、高效 |
动态分配: (i). 当存储空间不够时,可以扩充存储空间 (ii). 扩充存储空间需要移动大量元素,导致操作效率低下 (iii). 若内存中没有更大块的连续存储空间, 则会导致分配失败 |
如何选取存储结构 | |
基于存储考虑 | 难以估计线性表的长度或存储规模时,不宜采用顺序表; 链表不用事先估计存储规模,但链表的存储密度较低, 显然链式存储结构的存储密度是小于 1 的 |
基于运算考虑 | 在顺序表中按序号访问 的时间复杂度为 ; 而链表中按序号访问的时间复杂度为 ; 若常用的运算是按序号访问数据元素,则顺序表优于链表 |
在顺序表中进行插入 、删除操作时,平均移动表中一半的元素, 当数据元素的信息量较大且表较长时,必须考虑顺序表移动元素的特性; 在链表中进行插入 、删除操作时,虽然也要找插入位置,但操作主要是 比较操作; 若常用的运算是插入以及删除结点,则链表优于顺序表 |
|
基于环境考虑 | 顺序表容易实现,任何高级语言中都有数组类型; 链表的操作是基于指针; 相对而言,顺序表相较于链表,实现起来更简单 |
小结 | 两种存储结构各有长短,选择哪一种需要由实际问题的主要因素来决定; 通常较稳定的线性表选择顺序存储; 频繁进行插入 、删除操作的线性表(动态性较强)则应选择链式存储 |
注意 | 只有熟练掌握顺序存储和链式存储,才能深刻理解它们各自的优缺点 |