线性表是具有相同数据类型的n(n>=0)个数据元素的有限 序列,其中n为表长,当n=0时线性表是一个空表。
若用L命名线性表,则一般表示为 L= (a1,a2,…,ai,ai+1,…an)
总结一下:线性表的逻辑结构只需要掌握以下几点:
1、数据元素同类型、有限、有序
2、表长为0则是一个空表
3、数据元素的位序(从1开始),数组的位序(从0开始)
InitList(&L)
初始化表:构造一个空的线性表L,分配内存空间
DestroyList(&L)
销毁操作:销毁线性表,并释放线性表L所占用的内存空间
ListInsert(&L,i,e)
插入操作:在表L中的第i个位置上插入指定元素e
ListDelete(&L,i,&e)
删除操作:删除表L中的第i个位置的元素,并用e返回删除元素的值
LocateElem(L,e)
按值查找:在表L中查找具有给定关键字值的元素
GetElem(L,i)
按位查找:获取表L中第i个位置的元素的值
顺序表——用顺序存储的方式实现线性表,把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中
顺序表的实现——静态分配
#define MaxSize 10
typedef stuct{
int data[MaxSize];
int length;
}SqList;
初始化静态分配顺序表
void InitList(SqList &L){
for(int i=0; i < MaxSize; i++){
L.data[i] = 0;
}
L.length = 0;
}
顺序表的实现——动态分配
#define InitSize 10
typedef struct{
int *data;
int MaxSize;
int length;
}SeqList;
初始化动态分配顺序表
void InitList(SeqList &L){
L.data = (int *) malloc(InitSize * (sizeof(int));
L.Maxzise = InitSize;
L.length = 0;
}
增加动态数组的长度
void IncreseSize(SeqList &L, int len){
int *p = L.data;
L.data = (int *) malloc((L.Maxsize + len) * sizeof(int));
for(int i = 0; i< L.length; i++){
L.data[i] = p[i]; // 将数据复制到新的区域
}
L.Maxsize = L.Maxsize + len;
free(p);
}
1、随机访问,即可以在O(1)时间内找到第i个元素。
2、存储密度高,每个节点只存储数据元素。
3、拓展容量不方便(即便采用动态分配方式实现,拓展长度的时间复杂度也比较高)。
4、插入、删除操作不方便,需要移动大量的元素。
插入操作—— 时间复杂度 O(n)
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
void InitList(SqList &L){
for(int i = 0; i < MaxSize; i++){
L.data[i] = 0;
}
L.length = 0;
}
bool ListInsert(SqList &L, int i, int e){
if(i < 1 || i > L.length+1 ) // 判断i是否有效
return false;
if(L.length >= MaxSize) // 当前存储空间已满,不能插入
return false;
for(int j = L.length; j>=i; j--){ // 将第i个元素及之后的元素后移
L.data[j] = L.data[j-1];
}
L.data[i-1] = e; // 在位置i处放入e
L.length ++; // 长度加1
return true;
}
int main(){
SqList L;
InitList(L);
ListInsert(L,3,9);
}
删除操作 —— 时间复杂度 O(n)
bool ListDelete(SqList &L, int i, &e){
if(i < 1 || i > L.length)
return false;
e = L.data[i-1];
for(int j = i; j < L.length; j++){
L.data[j-1] = L.data[j];
}
L.length --;
return true;
}
int main(){
SqList L;
InitList(L);
...
int e = -1;
ListDelete(L,3,e);
}
按位查找 —— 时间复杂度 O(1)
int GetElem(SqList L, int i){
return L.data[i-1];
}
按值查找 —— 时间复杂度 O(n)
int LocateElem(SqList L, int e){
for(let i = 0; i < L.length; i++){
if(L.data[i] == e){
return i+1; // 返回的是位序,需要加一
}
}
return 0;
}
typedef struct LNode{
ElmeType data; // 每个节点存放一个数据元素
struct LNode *next; // 指针指向下一个节点
}LNode, *LinkList;
不带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
// 初始化一个空的单链表
bool initList(LinkList &L){
L = NULL; // 空表,暂时还没有任何结点
return true;
}
// 判断单链表是否为空
bool Empty(LinkList L){
return (L == NULL)
}
void mian(){
LinkList L;
InitList(L);
}
带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L){
L = (LNode *) malloc(sizeof(LNode)); // 分配一个头结点
if(L == NULL) // 内存不足,分配失败
return false;
L->next = NULL; // 头结点之后暂时还没有结点
return true;
}
// 判断单链表是否为空
bool Empty(LinkList L){
return (L->next == NULL)
}
void mian(){
LinkList L;
InitList(L);
}
值得注意的点是:
LinkList 等价于 LNode *
LinkList 强调的是链表
LNode * 强调的是结点
按位序插入(带头结点) —— 时间复杂度 O(n)
bool ListInsert(LinkList &L, int i, int e){
if(i < 1)
return false;
LNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // L指向头结点
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p == NUll)
return false;
LNode *s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
按位序插入(不带头结点) —— 时间复杂度 O(n)
bool ListInsert(LinkList &L, int i, int e){
if(i < 1)
return false;
if(i == 1){
LNode *s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next=> L;
s = L;
return true;
}
LNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // L指向头结点
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p == NUll)
return false;
LNode *s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
以上代码都有一段重复逻辑:在指定结点的后插操作,因此可以将重复逻辑封装起来
指定结点的后插操作 —— 时间复杂度 O(1)
bool InsertNextNode(LNode *p, int e){
if(p == NUll)
return false;
LNode *s = (LNode *) malloc(sizeof(LNode));
if(s == NULL)
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
指定结点的前插操作—— 时间复杂度 O(1)
bool InsertNextNode(LNode *p, int e){
if(p == NUll)
return false;
LNode *s = (LNode *) malloc(sizeof(LNode));
if(s == NULL)
return false;
s->next = p->next;
p->next = s;
s->data = p->data; // 同样是后插操作,改变数据的位置,完美实现前插操作
p->data = e;
return true;
}
按位序删除(带头结点) —— 时间复杂度 O(n)
bool ListInsert(LinkList &L, int i, int e){
if(i < 1)
return false;
LNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // L指向头结点
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p == NUll)
return false;
if(p->next == NULL)
return false;
LNode *q = p->next; // 令q指向被删除结点
e = q->data;
p->next = q->next;
free(q);
return true;
}
按位序查找(带头结点) —— 时间复杂度 O(n)
LNode * GetElem(LinkList L, int i){
if(i < 0)
return false;
LNode *p;
int j = 0;
p = L;
while(p != NULL && j < i){
p = p->next;
j++;
}
return p;
}
按值查找(带头结点) —— 时间复杂度 O(n)
LNode * LocateElem(LinkList L, int e){
LNode *p = L->next; // 从第一个结点开始寻找
while(p != NULL && p->data != e){
p = p->next;
}
return p;
}
LinkList List_TailInsert(LinkList &L){
int x;
L = (LNode *) malloc(sizeof(LNode)); // 建立头结点
LNode *s,*r=L; // r为表尾指针
scanf("%d",&x);
while(x!=9999){ // 表示结束
s = (LNode *) malloc(sizeof(LNode));
s=>data = x;
r->next = s;
r = s; // r指向新的表尾结点
scanf("%d",&x);
}
r->next = NULL;
return L;
}
LinkList List_HeadInsert(LinkList &L){
LNode *s;
int x;
L = (LNode *) malloc(sizeof(LNode)); // 建立头结点
L->next = NULL;
scanf("%d",&x);
while(x!=9999){ // 表示结束
s = (LNode *) malloc(sizeof(LNode));
s=>data = x;
s->next = L->next;
L->next = s;
scanf("%d",&x);
}
return L;
}
typedef struct DNode{
int data;
struct DNode *prior,*next;
}DNode,*DLinkList;
双链表的初始化(带头结点)
bool InitDLinkList(DLinkList &L){
L = (DNode *) malloc(sizeof(DNode));
if(L == NULL)
return false;
L->prior = NULL; // 头结点的 prior 永远指向null
L->next = NULL;
return true;
}
双链表的插入
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
if(p == NULL || s == NULL)
return false;
s->next = p->next;
if(p->next != NULL)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
双链表的删除
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
if(p = NULL)
return false;
DNode *q = p->next;
if(q == NULL)
return false;
p->next = q->next;
if(q->next != NULL)
q->next->prior = p;
free(q);
return true;
}
双链表的销毁
void DestroyList(DLinkList &L){
while(L->next != NULL){
DeleteNextDNode(L);
}
free(L);
L = NULL;
}
双链表的遍历 —— 时间复杂度 O(n)
// 后向遍历
while(p != NULL)
p = p->next;
//前向遍历
while(p != NULL)
p = p->prior;
//前向遍历(跳过头结点)
while(p->prior != NULL)
p = p->prior;
初始化循环单链表
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L){
L = (LNode *) malloc(sizeof(LNode));
if(L == NULL)
retun false;
L->next = L; // 头结点的next指向头结点
return true;
}
判断循环单链表是否为空
bool Empty(LinkList L){
if(L->next == L)
return true;
else
return false;
}
判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p){
if(p->next == L)
return true;
else
return false;
}
初始化循环单链表
typedef struct DNode{
int data;
struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinkList &L){
L = (DNode *) malloc(sizeof(DNode));
if(L == NULL)
return false;
L->prior = L;
L->next = L;
return true;
}
判断循环双链表是否为空
bool Empty(DLinkList L){
if(L->next == L)
return true;
else
return false;
}
判断结点p是否为循环双链表的表尾结点
bool isTail(DLinkList L, LNode *p){
if(p->next == L)
return true;
else
return false;
}
判断结点p是否为循环双链表的表尾结点
bool isTail(DLinkList L, LNode *p){
if(p->next == L)
return true;
else
return false;
}
双链表的插入
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
}
静态链表:分配一整片连续的内存空间,各个结点集中安置
定义静态链表
#define MaxSize 10
typedef struct{
ElemType data; // 存储数据元素
int next; // 下一个元素的数组下标
}SLinkList[MaxSize];
优点:增、删操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查找,容量固定不变
1、逻辑结构
都是属于线性表,都是线性结构
2、存储结构
顺序表:
优点:支持随机存取,存储密度高
缺点:大片连续空间分配不方便,改变容量不方便
链式表
优点:离散的小的空间分配方便,改变容量方便
缺点:不可以随机存取,存储密度低
用顺序表 or 链表?
表长难以预估、经常要增加、删除元素————链表
表长可预估,查询(搜索)操作比较多————顺序表