线性表
概念
线性表是一种逻辑结构,表示元素直接一对一的相邻关系。
顺序表和链表是存储结构,两者属于不同层面的概念,不要混淆。
如何实现线性表
用顺序存储实现(顺序表)
特点:逻辑上相邻的两个元素在物理位置上也相邻
定义:
#define Maxsize 50; //定义线性表的长度
// typedef 的 作用 就是 起别名
typedef struct{
ElemType data[Maxsize]; //顺序表的元素
int len; //顺序表的当前长度
}SqlList;//顺序表的类型定义
优点
- 可以随机存取(根据表头元素地址和元素序号)表中任意一个元素。
- 存储密度高,每个结点只存储数据元素。
缺点
- 插入与删除操作需要移动大量元素。
- 线性表变化较大时,难以确定存储空间的容量。
- 存储分配需要一整段的存储空间,不够灵活。
插入操作
- 最好情况:在表尾插入元素,不需要移动元素,时间复杂度为O(1)
- 最坏情况:在表头插入元素,所有元素依次后移,时间复杂度为O(n)
- 平均情况:在插入位置概率均等的情况下,平均移动元素的次数为n/2,时间复杂度为O(n)
//i代表插入的位置,从1开始,e要插入的元素
bool ListInsit(SqlList& L, int i, ElemType e) {
//判断要插入的位置是否合法
if (i > L.len + 1 || i < 1) {
return false;
}
//元素存储满了,不能再插入元素
if (L.len >= MaxSize) {
return false;
}
for (int j = L.len;j>=i; j--) { //移动顺序表中的元素,依次往后移
L.data[j] = L.data[j-1];
}
L.data[i - 1] = e; //数组下标从0开始,插入第一个位置,访问的下标为0
L.len++;
return true; //走到这里代表插入成功,返回true
}
删除操作
- 最好情况:删除表尾元素,不需要移动元素,时间复杂度为O(1)
- 最坏情况:删除表头元素,所有元素依次前移,时间复杂度为O(n)
- 平均情况:在删除位置概率均等的情况下,平均移动元素的次数为(n-1)/2,时间复杂度为O(n)
//删除元素使用元素e引用的目的是拿出对应的值
bool ListDelete(SqlList &L, int i, ElemType &e) {
if (i < 1 || i > L.len + 1) {
return false;
}
//顺序表没有元素,无需删除
if (L.len == MaxSize) {
return false;
}
e = L.data[i - 1];//获取顺序表中对应的元素,赋值给e
for (int j = i;j<L.len; j++) {//从i的位置依次把元素往前覆盖
L.data[j - 1] = L.data[j];
}
L.len--;//删除一个元素,顺序表长度减一
return true;
}
动态分配的数组还属于顺序存储结构吗?
动态分配并不是链式存储,同样还是属于顺序存储结构,其物理结构没有变化,依次是随机存取的方式,只是分配的空间可以在运行时决定。
顺序表增删查操作代码(用c++实现)
#include <stdio.h>
#include <stdlib.h>
#define MaxSize 50 //定义线性表的长度
typedef int ElemType; //顺序表中元素的元素
//静态分配;定义结构体
typedef struct{
ElemType data[MaxSize]; //定义的数组,用来存元素
int len; //当前顺序表有多少个
}SqlList;//顺序表的类型定义
//输出元素
void PrintList(SqlList L) {
for (int i = 0; i < L.len; i++) {
printf("%4d", L.data[i]);//并排输出
}
printf("\n");
}
//i代表插入的位置,从1开始,e要插入的元素
bool ListInsit(SqlList& L, int i, ElemType e) {
//判断要插入的位置是否合法
if (i > L.len + 1 || i < 1) {
return false;
}
//元素存储满了,不能再插入元素
if (L.len >= MaxSize) {
return false;
}
for (int j = L.len;j>=i; j--) { //移动顺序表中的元素,依次往后移
L.data[j] = L.data[j-1];
}
L.data[i - 1] = e; //数组下标从0开始,插入第一个位置,访问的下标为0
L.len++;
return true; //走到这里代表插入成功,返回true
}
//删除元素使用元素e引用的目的是拿出对应的值
bool ListDelete(SqlList &L, int i, ElemType &e) {
if (i < 1 || i > L.len + 1) {
return false;
}
//顺序表没有元素,无需删除
if (L.len == MaxSize) {
return false;
}
e = L.data[i - 1];//获取顺序表中对应的元素,赋值给e
for (int j = i;j<L.len; j++) {//从i的位置依次把元素往前覆盖
L.data[j - 1] = L.data[j];
}
L.len--;//删除一个元素,顺序表长度减一
return true;
}
//查找成功,返回位置,位置从一开始,查找失败,返回0
int LocateElem(SqlList L, ElemType e) {
for (int i = 0; i < L.len; i++) {
if (L.data[i] == e) {
return i + 1;//加一就是元素在顺序表中的位置
}
}
return 0;
}
int main() {
SqlList L;//顺序表名称
bool ret;//查看返回值,布尔型是True,或者False
ElemType del;//要来存要删除的元素
//首先手动在顺序表中赋值
L.data[0] = 1;
L.data[1] = 2;
L.data[2] = 3;
L.len = 3;//总计三个元素
//插入元素
ret = ListInsit(L,2,60); // 往第二个位置插入60这个元素
if (ret) {
printf("插入成功\n");
PrintList(L);
}
else {
printf("插入失败\n");
}
//删除元素
ret = ListDelete(L, 2, del); // 删除第二个位置的元素,并把元素值输出
if (ret) {
printf("删除成功\n");
printf("删除元素值为%d\n",del);
PrintList(L);
}
else {
printf("删除失败\n");
}
//查找元素
int ret1 = LocateElem(L,3); // 查找元素3的位置,并把位置值输出
if (ret1) {
printf("查找成功\n");
printf("元素位置为%d\n", ret1);
PrintList(L);
}
else {
printf("查找失败\n");
}
return 0;
}
线性表的链式表示
单链表
单链表结点的定义:
typedef struct LNode{ //单链表结点类型
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
头指针:链表中第一个结点的存储位置,用来标识单链表。(链表为空时,L也不是空指针)
头结点:在单链表第一个结点之前附加的一个结点,为了操作上的方便
(头结点里无数据,数据在后面一个。)
若链表有头结点,则头指针永远指向头结点,不论链表是否为空,头指针均不为空,头指针是链表的必须元素,他标识一个链表。
头结点是为了操作的方便而设立的,其数据域一般为空,或者存放链表的长度。有头结点后,对第一结点前插入和删除第一结点的操作就统一了,不需要频繁重置头指针。但头结点不是必须的。
单链表的头插法与尾插法,以及增删查找
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct LNode { //单链表结点类型
ElemType data; //数据域
struct LNode* next; //指针域
}LNode, * LinkList; //LinkList等价于struct LNode *
//头插法新建链表
LinkList GreatList1(LinkList& L) {
LNode* s; int x;
L = (LinkList)malloc(sizeof(LNode));//带头结点的链表
L->next = NULL;//L->data里面不放东西
scanf("%d", &x);//从标准输入读取数据
// 3 4 5 6 7 999
while(x != 999) {
s = (LinkList)malloc(sizeof(LNode)); //申请一个新空间给s,强制类型转换
s->data = x; // 把读取到的值,给新空间的data成员
s->next = L->next;//让新结点的next指针指向链表的第一个元素(第一个元素放我们数据的元素)
L->next = s; //让s作为第一个元素
scanf("%d", &x);
}
return L;
}
//尾插法新建链表
LinkList GreatList2(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//带头结点的链表
//r代表链表表尾结点,指向链表尾部
LNode* r=L, * s;//LinkList s,r=L也可以
scanf("%d", &x);
while (x != 999) {
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;//让尾部结点指向新结点
r=s;//r指向新的表尾结点
scanf("%d", &x);
}
r->next = NULL;//尾结点的next指针赋值为NULL
return L;
}
//打印链表
void PrintList(LinkList L) {
L = L->next;
while (L != NULL) {
printf("%3d", L->data);//打印当前结点数据
L = L->next;//指向下一个结点
}
printf("\n");
}
//查找第几个结点的值
LinkList getElem(LinkList L,int i) {
LNode *p = L->next;
int j = 1;
if (i == 0) {
return L; // i是0就返回头结点
}
if (i < 1) {
return NULL; //i是负值就返回空
}
while (p&&j<i) {
p = p->next;// 让指向下一个结点
j++;
}
return p;
}
// 按值查找
LinkList LocateElem(LinkList L, ElemType e) {
LNode* p = L->next;
while (p != NULL && p->data != e)
p = p->next;
return p;
}
//往第i个位置插入元素
bool ListFrontInsert(LinkList &L,int i, ElemType e) {
LNode* p = getElem(L,i-1);//拿到要插入位置的前一个位置的地址值
if (p == NULL) {
return false;// i不对
}
LinkList s = (LNode*)malloc(sizeof(LNode));
s->data = e;//要插入的值放入对应空间
s->next = p->next;
p->next = s;
return true;
}
//删除第i个位置的元素
bool ListDelete(LinkList& L, int i) {
LinkList p = getElem(L, i - 1);//查找删除位置的前驱元素
if (p == NULL) {
return false;
}
LinkList q = p->next;
if (q == NULL) {
return false;//要删除的位置不存在
}
p->next = q->next;//断链
free(q);//释放对应结点的空间
q = NULL;//为了避免野指针
return true;
}
int main() {
LinkList L;//链接头,是结构体指针类型
LinkList search; //用来存储拿到的某一个节点
//头插法插入元素
//GreatList1(L);//输入数据可以为3 4 5 6 7 9999代表结束
//尾插法插入元素
GreatList2(L);//输入数据可以为3 4 5 6 7 9999代表结束
PrintList(L);
//查找元素值
search = getElem(L, 2);//查找链表第二个位置的元素
if (search != NULL) {
printf("按序号查找成功\n");
printf("%d\n", search->data);
}
//按值查询
search = LocateElem(L, 6);
if (search != NULL) {
printf("按值查找成功\n");
printf("%d\n", search->data);
}
//新结点插入第i个位置
ListFrontInsert(L,2,99);
PrintList(L);
//删除第四个结点
ListDelete(L, 4);
PrintList(L);
return 0;
}
双链表的头插法与尾插法,以及增删查找
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
//定义双链表结点类型
typedef struct DNode {
ElemType data; //数据域
struct DNode* prior,*next;//前驱和后驱指针
}DNode,*DLinkList;
//打印双向链表
void PrintDList(DLinkList DL) {
DL = DL->next;
while (DL != NULL) {
printf("%3d", DL->data);
DL = DL->next;
}
printf("\n");
}
//双向链表头插法
DLinkList Dlist_head_insert(DLinkList& DL) {
DL = (DLinkList)malloc(sizeof(DNode));//带头结点的链表,DL是头结点
DL->prior = NULL;//前驱指针和后驱指针为NULL
DL->next = NULL;
int x;
DNode* s;
scanf("%d", &x);//从标准输入读取数据
while (x!=9999)
{
s = (DLinkList)malloc(sizeof(DNode));
s->data = x;
s->next = DL->next;
if (DL->next != NULL) {
DL->next->prior = s;
}
s->prior = DL; //要插入的结点指向头结点
DL->next = s;
scanf("%d", &x);
}
return DL;
}
//双向链表尾插法
DLinkList Dlist_tail_insert(DLinkList& DL) {
DL = (DLinkList)malloc(sizeof(DNode));//带头结点的链表,DL是头结点
DL->prior = NULL;//前驱指针为NULL
int x;
DNode* s,*r=DL;// r代表尾指针
scanf("%d", &x);//从标准输入读取数据
while (x != 9999) {
s = (DLinkList)malloc(sizeof(DNode));
s->data = x;
r->next = s;
s->prior = r;
r = s;//r指向新的表尾结点
scanf("%d", &x);
}
r->next = NULL; //尾结点的next指针赋值为0
return DL;
}
//查找第几个结点的值
DLinkList getElem(DLinkList DL, int i) {
DNode* p = DL->next;
int j = 1;
if (i == 0) {
return DL; // i是0就返回头结点
}
if (i < 1) {
return NULL; //i是负值就返回空
}
while (p && j < i) {
p = p->next;// 让指向下一个结点
j++;
}
return p;
}
//往第i个位置插入元素
bool DListFrontInsert(DLinkList& DL, int i, ElemType e) {
DNode* p = getElem(DL, i - 1);//拿到要插入位置的前一个位置的地址值
if (p == NULL) {
return false;// i不对
}
DLinkList s = (DNode*)malloc(sizeof(DNode));
s->data = e;//要插入的值放入对应空间
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
//删除结点
bool DListDelete(DLinkList& DL, int i) {
DLinkList p = getElem(DL, i - 1);
if (NULL == p) {
return false;
}
DNode* q;
q = p->next;
if (q == NULL) {
return false;//要删除的位置不存在
}
p->next = q->next;//断链
if (q->next != NULL) {//q->next为NULL删除的是最后一个结点
q->next->prior = p;
}
free(q);
return true;
}
int main() {
DLinkList DL;//数据 3 4 5 6 7 8 9999
DLinkList search; //用来存储拿到的某一个节点
Dlist_head_insert(DL);//头部插入
PrintDList(DL);
Dlist_tail_insert(DL);//尾部插入
PrintDList(DL);
//查找元素值
search = getElem(DL, 2);//查找链表第二个位置的元素
if (search != NULL) {
printf("按序号查找成功\n");
printf("%d\n", search->data);
}
//新结点插入第i个位置
DListFrontInsert(DL, 2, 99);
PrintDList(DL);
//删除结点
DListDelete(DL, 2);
PrintDList(DL);
return 0;
}