线性表
1.1 线性表的定义
线性表(linear_list) 是最常用且最简单的一种数据结构。简言之,一个线性表是n个数据元素的有限 序列。至于每个数据元素的具体含义,在不同的情况下各不相同,它可以是一个数一个符号,也可以是一页书,甚至是其他更复杂的信息。
在稍复杂的线性表中,一个数据元素可以由若干个数据项(item) 组成。在这样情况下,常把数据元素称为记录 ,含有大量记录的线性表又称文件。
线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性,即属同一数据对象,相邻数据元素之间存在着序偶关系。若将线性表记为
(a1, … ,ai-1, ai, ai+1, an)
则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素。当i=1,2,···,n-1时,ai有且只有一个直接后继,当i=2,3,···,n时,ai有且仅有一个直接前驱。
1.2 线性表基本操作
InitList(&L)//构造一个空的线性表
DestroyList(&L)//如果线性表已存在,则摧毁线性表
ClearList(&L)//如果线性表已存在,则将L重置为空表
ListEmpty(L)//若线性表已存在,若L为空表,则返回True,否则返回False
ListLength(L)//若线性表已存在,返回线性表中元素的个数
GetElem(L, i, &e)//线性表已存在,用e返回L中第i个元素的值(1<=i<=ListLength(L))
LocateElem(L, e, compare())
//若线性表L已存在,compare()是数据元素判断的函数,返回L中第1个与e满足compare()的数据元素的位序,若不存在则返回0
PriorElem(L, cur_e, &pre_e)
//cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
NextElem(L, cur_e, &next_e)
//cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
ListInsert(&L, i, e)
//在L中第i个位置之前插入新的数据元素e,L的长度+1(1<=i<=ListLength(L)+1)
ListDelete(&L, i, &e)
//删除L的第i个数据元素,并且用e返回其值,L的长度-1(1<=i<=ListLength(L))
ListTraverse(L, visit())
//依次对L的每个数据元素调用函数visit(),一旦visit()失败,则操作失败。
1.3 线性表的顺序表示和实现
1.3.1 数据类型的定义
这里数组指针elem指示线性表的基地址,length表示线性表的当前长度。listsize指示顺序表当前分配的存储空间大小,一旦因为插入空间不足时,可以进行再分配,即增加一个大小为存储LISTINCREMENT个元素数据的空间。
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
typedef int ElemType;
typedef int Status;
typedef struct{
ElemType *elem;
int length;
int listsize;
}Sqlist;
1.3.2 创建一个空的顺序表(InitList)
Status InitList_Sq(SqList &L) {
//构造一个空的线性表
L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L.elem) exit(OVERFLOW);
L.length = 0;
L.listsize = LIST_INIT_SIZE;
printf("创建空链表成功\n");
return OK;
}
1.3.3 顺序表中数据的插入(ListInsert)
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
//在顺序线性表L中第i个位置之前插入新的元素e
//(1 <= i <= ListLength_Sq(L) + 1)
if (i < 1 || i > L.length + 1) return ERROR;
if (L.length >= L.listsize) {
//存储空间已满,增加分配
ElemType *newbase;
newbase = (ElemType *)realloc(
L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if (!newbase) exit(OVERFLOW); //分配失败
L.elem = newbase; //新基址
L.listsize = L.listsize + LISTINCREMENT; //增加存储容量
}
ElemType *q, *p;
q = &(L.elem[i - 1]); // q为插入位置
for (p = &(L.elem[L.length - 1]); p >= q; p--) {
*(p + 1) = *p; //插入之后的元素后移;
}
*q = e;
L.length++; //长度+1;
printf("插入成功,插入的数据为%d, 当前插入的位置为%d,目前表长为%d\n",
e, i, L.length);
return OK;
}
1.3.4 顺序表中的数据删除(ListDelete)
Status ListDelete_Sq(SqList &L, int i, ElemType &e) {
//在合法的位置删除第i个元素,并用e返回
if (i < 1 || i > L.length) return ERROR; // i的值不合法
ElemType *p, *q;
p = &(L.elem[i - 1]); // p为被删元素的位置
e = *p;
q = L.elem + L.length - 1; // q表示表尾元素的位置
for (p++; p <= q; p++) *(p - 1) = *p; //被删除元素之后的元素往左移动
L.length--; //表长-11
printf("删除成功,删除的数据为%d,当前删除的位置为%d,目前表长为%d", e, i,
L.length);
return OK;
}
1.3.5 顺序表中的数据查找(LocateElem)
这里我自己定义了一个compare函数,详见下面代码
int compare(ElemType a, ElemType b) {
if (a == b)
return TRUE;
else
return FALSE;
}
接下来这里时顺序查找的数据查找方式,查找方式可以根据不同的算法来进行优化,其中的 compare函数就是上方给出的compare函数
int LocateElem_Sq(SqList &L, ElemType e,
Status (*compare)(ElemType, ElemType)) {
//在顺序线性表中查找满足compare()元素的位序
//若找到返回位序,找不到返回0;
//这里以顺序查找为例
int i = 1;
ElemType *p;
p = L.elem;
while (i <= L.length && !(*compare)(*p++, e)) i++;
if (i <= L.length) {
printf("当前要查找的数据为%d, 在第%d个位置查找到符合compare()的数据\n",
e, i);
return i;
} else {
printf("没有查找到符合条件的数据\n");
return 0;
}
}
这里为了更直观的表现出每次插入或者删除操作后的元素变化,增加了该函数,来输出线性表中的元素
void PrintTheElem_Sq(SqList &L) {
int i = 1;
printf("当前线性表中的元素为:\n");
while (i <= L.length) {
printf("%d ", L.elem[i - 1]);
i++;
}
printf("\n");
}
1.3.6 顺序表操作代码的简单整合与验证
/*
* @Author: Henry
* @Date: 2020-07-04 14:48:11
* @LastEditors: Henry
* @LastEditTime: 2021-01-14 19:21:15
*/
#include <bits/stdc++.h>
using namespace std;
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
typedef int ElemType;
typedef int Status;
//这里数组指针elem指示线性表的基地址,
// length表示线性表的当前长度。
// listsize指示顺序表当前分配的存储空间大小,
//一旦因为插入空间不足时,可以进行再分配,即增加一个大小为存储LISTINCREMENT个元素数据的空间
typedef struct {
ElemType *elem;
int length;
int listsize;
} SqList;
Status InitList_Sq(SqList &L) {
//构造一个空的线性表
L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L.elem) exit(OVERFLOW);
L.length = 0;
L.listsize = LIST_INIT_SIZE;
printf("创建空链表成功\n");
return OK;
}
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
//在顺序线性表L中第i个位置之前插入新的元素e
//(1 <= i <= ListLength_Sq(L) + 1)
if (i < 1 || i > L.length + 1) return ERROR;
if (L.length >= L.listsize) {
//存储空间已满,增加分配
ElemType *newbase;
newbase = (ElemType *)realloc(
L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if (!newbase) exit(OVERFLOW); //分配失败
L.elem = newbase; //新基址
L.listsize = L.listsize + LISTINCREMENT; //增加存储容量
}
ElemType *q, *p;
q = &(L.elem[i - 1]); // q为插入位置
for (p = &(L.elem[L.length - 1]); p >= q; p--) {
*(p + 1) = *p; //插入之后的元素后移;
}
*q = e;
L.length++; //长度+1;
printf("插入成功,插入的数据为%d, 当前插入的位置为%d,目前表长为%d\n", e, i,
L.length);
return OK;
}
Status ListDelete_Sq(SqList &L, int i, ElemType &e) {
//在合法的位置删除第i个元素,并用e返回
if (i < 1 || i > L.length) return ERROR; // i的值不合法
ElemType *p, *q;
p = &(L.elem[i - 1]); // p为被删元素的位置
e = *p;
q = L.elem + L.length - 1; // q表示表尾元素的位置
for (p++; p <= q; p++) *(p - 1) = *p; //被删除元素之后的元素往左移动
L.length--; //表长-11
printf("删除成功,删除的数据为%d,当前删除的位置为%d,目前表长为%d\n", e, i,
L.length);
return OK;
}
int compare(ElemType a, ElemType b) {
if (a == b)
return TRUE;
else
return FALSE;
}
int LocateElem_Sq(SqList &L, ElemType e,
Status (*compare)(ElemType, ElemType)) {
//在顺序线性表中查找满足compare()元素的位序
//若找到返回位序,找不到返回0;
//这里以顺序查找为例
int i = 1;
ElemType *p;
p = L.elem;
while (i <= L.length && !(*compare)(*p++, e)) i++;
if (i <= L.length) {
printf("当前要查找的数据为%d, 在第%d个位置查找到符合compare()的数据\n",
e, i);
return i;
} else {
printf("没有查找到符合条件的数据\n");
return 0;
}
}
void PrintTheElem_Sq(SqList &L) {
int i = 1;
printf("当前线性表中的元素为:\n");
while (i <= L.length) {
printf("%d ", L.elem[i - 1]);
i++;
}
printf("\n");
}
int main() {
int i;
SqList L;
ElemType e;
//初始化线性表
InitList_Sq(L);
ListInsert_Sq(L, 1, 10);
ListInsert_Sq(L, 2, 10);
ListInsert_Sq(L, 1, 20);
ListInsert_Sq(L, 4, 20);
ListDelete_Sq(L, 1, e);
LocateElem_Sq(L, 10, compare);
PrintTheElem_Sq(L);
system("pause");
return 0;
}
输出结果表示:
1.3.7 两个顺序表的合并(MergeList)
根据上述的操作(尤其是ListInsert(&L, i, e)操作),我们不难可以写出两个顺序表的合并操作,时间复杂度为 O(La.length + Lb.length)。
void MergeList_Sq(Sqlist &La, Sqlist &Lb, Sqlist &Lc) {
//已知顺序线性表La和Lb的元素按值非递减的顺序排列
//归并La和Lb得到新的顺序线性表Lc。Lc也按值非递减的顺序排列
ElemType *pa;
ElemType *pb;
ElemType *pc;
pa = La.elem;
pb = Lb.elem;
Lc.listsize = Lc.length = La.length + Lb.length;
pc = Lc.elem = (ElemType *)malloc(Lc.listsize * sizeof(ElemType));
if (!Lc.elem) {
printf("存储分配失败\n");
exit(OVERFLOW);
}
ElemType *pa_last;
pa_last = La.elem + La.length - 1;
ElemType *pb_last;
pb_last = Lb.elem + Lb.length - 1;
while (pa <= pa_last && pb <= pb_last) {
//归并
if (*pa < *pb) {
*pc = *pa;
pa++;
pc++;
} else {
*pc = *pb;
pc++;
pb++;
}
}
while (pa <= pa_last) {
// Lb全部插入完全,La还有剩余
//将La剩下的全部插入Lc中
*pc = *pa;
pa++;
pc++;
}
while (pb <= pb_last) {
// La全部插入完全,Lb还有剩余
//将Lb剩下的全部插入Lc中
*pc = *pb;
pb++;
pc++;
}
}
所有代码展示:
/*
* @Author: Henry
* @Date: 2021-01-14 19:38:06
* @LastEditors: Henry
* @LastEditTime: 2021-01-14 21:57:19
*/
#include <bits/stdc++.h>
using namespace std;
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct {
ElemType *elem;
int length; // length表示线性表的当前长度。
int listsize; // listsize指示顺序表当前分配的存储空间大小,
} Sqlist;
Status InitList_Sq(Sqlist &L) {
L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L.elem) {
printf("申请失败");
exit(OVERFLOW);
}
L.length = 0;
L.listsize = LIST_INIT_SIZE;
printf("创建空链表成功\n");
return OK;
}
Status ListInsert_Sq(Sqlist &L, int i, ElemType e) {
if (i < 1 || i > L.length + 1) return ERROR;
if (L.length >= L.listsize) {
//存储空间已满,增加分配空间
ElemType *newbase;
newbase = (ElemType *)realloc(
L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if (!newbase) {
printf("分配内存失败\n");
return OVERFLOW;
}
L.elem = newbase;
L.listsize = L.listsize + LISTINCREMENT;
}
ElemType *p, *q;
q = &(L.elem[i - 1]);
for (p = &(L.elem[L.length - 1]); p >= q; p--) {
*(p + 1) = *p;
}
*q = e;
L.length++;
// printf("插入成功,插入的数据为%d, 当前插入的位置为%d,目前表长为%d\n", e,
// i,
// L.length);
return OK;
}
void MergeList_Sq(Sqlist &La, Sqlist &Lb, Sqlist &Lc) {
//已知顺序线性表La和Lb的元素按值非递减的顺序排列
//归并La和Lb得到新的顺序线性表Lc。Lc也按值非递减的顺序排列
ElemType *pa;
ElemType *pb;
ElemType *pc;
pa = La.elem;
pb = Lb.elem;
Lc.listsize = Lc.length = La.length + Lb.length;
pc = Lc.elem = (ElemType *)malloc(Lc.listsize * sizeof(ElemType));
if (!Lc.elem) {
printf("存储分配失败\n");
exit(OVERFLOW);
}
ElemType *pa_last;
pa_last = La.elem + La.length - 1;
ElemType *pb_last;
pb_last = Lb.elem + Lb.length - 1;
while (pa <= pa_last && pb <= pb_last) {
//归并
if (*pa < *pb) {
*pc = *pa;
pa++;
pc++;
} else {
*pc = *pb;
pc++;
pb++;
}
}
while (pa <= pa_last) {
// Lb全部插入完全,La还有剩余
//将La剩下的全部插入Lc中
*pc = *pa;
pa++;
pc++;
}
while (pb <= pb_last) {
// La全部插入完全,Lb还有剩余
//将Lb剩下的全部插入Lc中
*pc = *pb;
pb++;
pc++;
}
}
void PrintTheElem_Sq(Sqlist &L) {
int i = 1;
printf("当前线性表中的元素为:\n");
while (i <= L.length) {
printf("%d ", L.elem[i - 1]);
i++;
}
printf("\n");
}
int main() {
Sqlist La, Lb, Lc;
InitList_Sq(La);
for (int i = 1; i <= 10; i++) {
//往La中插入数据3,4,5...12
ListInsert_Sq(La, i, i + 2);
}
printf("La:");
PrintTheElem_Sq(La);
InitList_Sq(Lb);
for (int i = 1; i <= 20; i++) {
//往Lb中插入数据5,6,7...24
ListInsert_Sq(Lb, i, i + 4);
}
printf("Lb:");
PrintTheElem_Sq(Lb);
MergeList_Sq(La, Lb, Lc);
PrintTheElem_Sq(Lc);
system("pause");
return 0;
}
结果展示:
1.4 线性表的链式表示和实现
1.4.1线性链表
- 线性表的顺序存储结构的特点是用一组任意的存储单元存储线性表的元素(这些存储单元可以是连续的也可以是不连续的)。
- 除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像称为结点
- 它包括两个域:其中存储数据元素信息的域称为数据域 ,存储直接后继存储位置的域称为指针域 。指针域中存储的信息称作指针或链,又由于每一个结点中只包含一个指针域,故又称线性链表或者单链表。
1.4.2数据类型的定义
链式线性表的存储类型定义不像顺序存储链表一样复杂,只需要一个结构体中的一个数据类型存储数据,另一个存储地址即可,每次插入一个数据就申请一次空间。
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
typedef int ElemType;
typedef int Status;
typedef struct Node {
ElemType data;
struct Node *next;
}LNode, *LinkList;
1.4.3 建立带头结点的单链线性表(CreatList)
插入数据的方法有很多,这边主要介绍两种常见的插入方法,分别是头插法和尾插法
1.4.3.1尾插法
图解:
void CreatList_L_Tail(LinkList &L, int n) {
LinkList p, r;
L = (LinkList)malloc(sizeof(LNode));
r = L;
while (n--) {
p = (LinkList)malloc(sizeof(LNode));
scanf("%d", &p->data);
r->next = p;
r = p; //令r变为p,以便后续的链接;
}
r->next = NULL;
}
1.4.3.2头插法
图解:
void CreatList_L(LinkList &L, int n) {
//逆位序输入n个元素的值,建立带表头的结点的单链线性表L
LinkList p;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
while (n--) {
p = (LinkList)malloc(sizeof(LNode));
scanf("%d", &p->data);
p->next = L->next;
//先使p的next直线后面的一个值
L->next = p;
//然后让头指针指向p,实现插入到表头
printf("插入成功,当前插入的数值为%d\n", p->data);
}
}
1.4.4 查找单链线性表中第i个值(GetElem)
Status GetElem_L(LinkList &L, int i, ElemType &e) {
// L为带头结点的单链表头指针
//当第i个元素存在时,其赋值给e并返回OK,否则返回ERROR
LinkList p;
p = L->next;
int j = 1;
for (; p && j < i; j++) {
p = p->next;
}
if (!p || j > i) {
return ERROR;
} else {
e = p->data;
return OK;
}
}
1.4.4 在单链线性表中第i个位置插入值(ListInsert)
由上面的头插法创建链表不难看出这就是一种插入值的方式,因此我们只需要在这个基础上进行部分改动,增加一个查找第几个位置的过程即可。
Status ListInsert_L(LinkList &L, int i, ElemType e) {
//在带头结点的单链表中第i个元素插入e
LinkList p, s;
int j = 0;
for (; p && j < i - 1; i++) {
p = p->next;
}
if (!p || j > i) {
return ERROR;
} else {
s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
}
1.4.5 在单链线性表中第i个位置删除值(ListDelet)
我们可以很容易的发现,只要让p->next = q->next 就可以让链表跳过需要删除的数字,从而实现删除的目的,同时为了节约空间,我们可以释放掉q的空间。
Status ListDelete_L(LinkList &L, int i, ElemType &e) {
LinkList p, q;
p = L;
int j = 0;
//如果没有找到,或者没有找到最后一个,就继续往下找
while (p->next && j < i - 1) {
p = p->next;
j++;
}
if (!(p->next) || j > i - 1) {
return FALSE; //删除位置不合理
}
q = p->next;
p->next = q->next;
e = q->data; //删除的元素用e返回
free(q);
return OK;
}
为了更加直观的显示输出,这里增加了输出函数
void print_List(LinkList &L) {
printf("当前链表为:");
LinkList p;
p = L;
while (p->next) {
p = p->next;
printf("%d ", p->data);
}
printf("\n");
}
1.4.6 链表操作代码的简单整合与验证
/*
* @Author: Henry
* @Date: 2020-07-04 14:48:11
* @LastEditors: Henry
* @LastEditTime: 2021-04-03 15:13:58
*/
#include <bits/stdc++.h>
using namespace std;
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct Node {
ElemType data;
struct Node *next;
} LNode, *LinkList;
void CreatList_L_Tail(LinkList &L, int n) {
LinkList p, r;
L = (LinkList)malloc(sizeof(LNode));
r = L;
while (n--) {
p = (LinkList)malloc(sizeof(LNode));
scanf("%d", &p->data);
r->next = p;
r = p; //令r变为p,以便后续的链接;
}
r->next = NULL;
}
void CreatList_L(LinkList &L, int n) {
//逆位序输入n个元素的值,建立带表头的结点的单链线性表L
LinkList p;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
while (n--) {
p = (LinkList)malloc(sizeof(LNode));
scanf("%d", &p->data);
p->next = L->next;
//先使p的next直线后面的一个值
L->next = p;
//然后让头指针指向p,实现插入到表头
printf("插入成功,当前插入的数值为%d\n", p->data);
}
}
Status GetElem_L(LinkList &L, int i, ElemType &e) {
// L为带头结点的单链表头指针
//当第i个元素存在时,其赋值给e并返回OK,否则返回ERROR
LinkList p;
p = L->next;
int j = 1;
for (; p && j < i; j++) {
p = p->next;
}
if (!p || j > i) {
return ERROR;
} else {
e = p->data;
return OK;
}
}
Status ListInsert_L(LinkList &L, int i, ElemType &e) {
//在带头结点的单链表中第i个元素插入e
LinkList p, s;
p = L;
int j = 0;
for (; p && j < i - 1; j++) {
p = p->next;
}
if (!p || j > i) {
return ERROR;
} else {
s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
}
Status ListDelete_L(LinkList &L, int i, ElemType &e) {
LinkList p, q;
p = L;
int j = 0;
//如果没有找到,或者没有找到最后一个,就继续往下找
while (p->next && j < i - 1) {
p = p->next;
j++;
}
if (!(p->next) || j > i - 1) {
return FALSE; //删除位置不合理
}
q = p->next;
p->next = q->next;
e = q->data; //删除的元素用e返回
printf("删除成功,当前删除的数值为%d\n", p->data);
free(q);
return OK;
}
void print_List(LinkList &L) {
printf("当前链表为:");
LinkList p;
p = L;
while (p->next) {
p = p->next;
printf("%d ", p->data);
}
printf("\n");
}
int main() {
int n;
LinkList L;
cin >> n;
CreatList_L_Tail(L, n);
print_List(L);
ElemType e;
int k;
printf("请输入需要获取的第k个元素:\n");
cin >> k;
GetElem_L(L, k, e);
printf("第%d个元素为%d\n", k, e);
printf("请输入插入的第k个元素和需要插入的元素e:\n");
cin >> k >> e;
ListInsert_L(L, k, e);
printf("插入第%d个元素为%d\n当前链表为", k, e);
print_List(L);
printf("请输入删除的第k个元素:\n");
cin >> k;
ListDelete_L(L, k, e);
printf("删除的第%d个元素为%d\n当前链表为", k, e);
print_List(L);
system("pause");
return 0;
}