第二章 线性表

news/2025/3/13 2:16:35/文章来源:https://www.cnblogs.com/eulbo-1018/p/18344328

线性表的定义和基本操作

线性表的定义

线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表,若当L命名线性表,则其一般表示为L=(a1,a2,.…,an)

ai 是线性表中“第i个”元素线性表中的位序

a1 是表头元素,an 是表尾元素

出第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

线性表的基本操作

InitList(&L):初始化表。构造一个空的线性表L,分配内存空间

DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间

Listlnsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e.

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元元素

GetElem(Li):按位查找操作。获取表L中第i个位置的元素的值。

Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。

PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。

Empty(L):判空操作。若L为空表,则返回true,否则返回false。

顺序表的定义

顺序表——用顺序存储的方式实现线性表

顺序表的特点

  1. 随机访问,可以在O(1)时间内找到第i个元素
  2. 存储密度高,每个节点只存储数据元素
  3. 拓展容量不方便,静态不可以拓展,动态分配拓展时间复杂度高
  4. 插入,删除操作不方便,需要移动大量元素

顺序表的实现

顺序表的实现—静态分配(存储空间是静态的,无法更改)

#include<stdio.h>
#define MaxSize 10 //定义最大长度
//创建静态顺序表的结构
typedef struct{int data[MaxSize];//用静态的数组存放数据元素int lengeh;//顺序表的长度
}SqList;//顺序表的类型定义
//初始化静态顺序表
void InitList(SqList &L){for(int i=0;i<MaxSize;i++){L.data[i]=0;}L.lengeh=0;
}
int main(){SqList L;InitList(L);return 0;
}

顺序表的实现—动态分配

//动态 顺序表
#include<stdio.h>
#include<stdlib.h>
#define InitSize 10//顺序表的初始长度
struct SqList {int* data;//只是动态分配数组的指针int MaxSize;//顺序表的最大容量int lengh;//顺序表的当前长度
};
void InitList(struct SqList* L) {L->data = (int*)malloc(sizeof(int) + InitSize);for (int i = 0; i < L->MaxSize; i++){L->data[i] = 0;}L->lengh = 0;L->MaxSize = InitSize;
}
//增加动态数组的长度
void IncreaseList(struct SqList* L, int len) {//用malloc函数申请一片连续的存储空间int* p = L->data;L->data = (int*)malloc(sizeof(int) * (L->MaxSize + len));//将数据复制到新区域中for (int i = 0; i < L->lengh; i++){L->data[i] = p[i];}L->MaxSize = L->MaxSize + len;//顺序表最长长度增加len
}
void ListInsert(struct SqList *L,int i ,int e) {for (int j = L->lengh; j >=i; j--){L->data[j] = L->data[j - 1];}L->data[i - 1] = e;L->lengh++;
}
int main() {struct SqList L = { 0 };InitList(&L);printf("%d\n", L.lengh);printf("%d\n", L.MaxSize);for (int i = 0; i < L.MaxSize; i++){(&L)->data[i] = i;}(&L)->lengh = L.MaxSize;for (int i = 0; i < L.lengh; i++){printf("%d ", L.data[i]);}printf("\n");IncreaseList(&L, 10);printf("%d\n", L.lengh);printf("%d\n", L.MaxSize);for (int i = 0; i < L.lengh; i++){printf("%d ", L.data[i]);}ListInsert(&L, 2, 2);printf("%d\n", L.lengh);printf("%d\n", L.MaxSize);for (int i = 0; i < L.lengh; i++){printf("%d ", L.data[i]);}return 0;
}

顺序表的基本操作

顺序表元素的插入

void ListInsert(struct SqList *L,int i ,int e) {for (int j = L->lengh; j >=i; j--){L->data[j] = L->data[j - 1];}L->data[i - 1] = e;L->lengh++;
}

最好情况 :新元素插入到表尾,不需要移动其他元素,循环0次,时间复杂度=O(1)

最坏情况 :新元素插入到表头,需要将原有的n个元素全部向后移动,循环n次,最坏时间复杂度=O(n)

平均情况 :假设新元素插入到任何一个位置的概率相同,即i=1,2,3,...length的概率都是p=1/(n+1),则平均循环次数=np+(n-1)p+....+p=n/2,平均时间复杂度=O(n)

顺序表元素的删除

int ListDelete(struct SqList *L,int i,int *e) {//判断if (i<1||i>L->lengh)//判断i的值是否有效{return 0;}else{*e = L->data[i - 1];//将删除的元素赋值给efor (int j = i; j < L->lengh; j++)//将第i个位置的元素前移{L->data[j - 1] = L->data[j];}L->lengh--;//有效长度减一return 1;}
}

最好情况 :删除表尾元素,不需要移动其他元素,循环0次,时间复杂度=O(1)

最坏情况 :删除表头元素,需要将后续的n-1个元素全部向前移动,循环n-1次,最坏时间复杂度=O(n)

平均情况 :假设删除任何一个元素的概率相同,即i=1,2,3,...length的概率都是p=1/n,则平均循环次数=(n-1)p+(n-2)p+....+p=(n-1)/2,平均时间复杂度=O(n)

顺序表的按位查找

GetElem(L,i)按位查找操作,获取表L中的第i位序的元素的值

ElemType GetElem(SeqList L,int i){return L.data[i - 1];
}

顺序表的按值查找

Locate(SeqList L, ElemType e) 按值查找操作,在表L中查找具有给定关键字的元素

ElemType LocateElem(SeqList L, ElemType e) {for (int i = 0; i <= L.length; i++) {if (L.data[i] == e) {return i + 1;}}return -1;
}

在注重考察语法的时候,基本数据类型可以用“==”比较

时间复杂度O(n)

单链表的定义

每个结点除了数据元素外,还可以存储指向下一个结点的指针

头结点和头指针的关系: 不管带不带头结点,头指针都始终指向链表的第一个结点,而头结点是带头结点的链表的第一个结点,结点内通常不存储信息。

引入头结点后,可以带来两个优点:

  1. 由于第一个数据节点的位置被存放在头结点的指针域中,因此在链表的第一个位置上的操作在表的其他位置上的操作一致,无须进行特殊处理
  2. 无论链表是否为空,其头指针都是指向头结点的非空指针(空表中头结点的指针域为空),因此空表和费控表的处理也就得到了统一

单链表的特点

优点:不要求大片连续空间,改变容量方方便

缺点:不可随机存取,要耗费一定空间存放指针

单链表的实现

点击查看代码
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LNode {ElemType data;struct LNode *next;
} LNode, *LinkList;
bool InitList(LinkList &L) {L = NULL;return true;
}
bool InitList_h(LinkList &L) {L = (LNode*)malloc(sizeof(LNode));if (L == NULL) {return false;}L->next = NULL;return true;
}
bool ListInsert_h(LinkList &L, int i, ElemType e) {if (i < 1) {return false;}LNode *p;int j = 0;p = 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;
}
bool ListInsert(LinkList &L, int i, ElemType e) {if (i < 1) {return false;}if (i == 1) {LNode *s = (LNode*)malloc(sizeof(LNode));s->data = e;s->next = L;L = s;return true;}LNode *p;int j = 1;p = 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;
}
bool InsertNextNode(LNode *p, ElemType 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;
}
bool InsertPriorNode(LNode *p, ElemType 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;
}
//按位序删除(带头节点)
bool ListDelete(LinkList &L, int i, ElemType &e) {if (i < 1) {return false;}LNode *p;int j = 0;p = 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;e = q->data;p->next = q->next;free(q);return true;
}
bool DeleteNode(LNode *p) {if (p == NULL) {return false;}LNode *q = p->next;p->data = p->next->data;p->next = q->next;free(q);return true;
}
LNode* GetElem_h(LinkList L, int i) {if (i < 0) {return NULL;}LNode *p;int j = 0;p = L;while (p != NULL && j < i) {p = p->next;j++;}return p;
}
LNode* LocateElem(LinkList L, ElemType e) {LNode *p = L->next; //指向头结点的下一个节点while (p != NULL && p->data != e) {p = p->next;}return p;
}
int Length_h(LinkList L) {int len = 0;LNode *p = L;while (p->next != NULL) {p = p->next;len++;}return len;
}
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);}
}
LinkList List_TailInsert(LinkList &L) {int x;L = (LNode*)malloc(sizeof(LNode));LNode *s, *r = L;scanf("%d", &x);while (x != 9999) {s = (LNode*)malloc(sizeof(LNode));s->data = x;r->next = s;r = s;scanf("%d", &x);}r->next = NULL;return L;
}int main() {LinkList L;return 0;
}

结构体创建

typedef struct LNode {ElemType data;struct LNode *next;
} LNode, *LinkList;typedef 

初始化

带头结点

bool InitList_h(LinkList &L) {L = (LNode*)malloc(sizeof(LNode));if (L == NULL) {return false;}L->next = NULL;return true;
}

带头结点的单链表初始化时,需要创建一个头结点,并让头指针指向头结点。头结点的next域初始化为NULL

不带头结点

bool InitList(LinkList &L) {L = NULL;return true;
}

不带头结点的单链表初始化时,只需将头指针L初始化为NULL

单链表的基本操作

按位序插入(带头节点)

bool ListInsert_h(LinkList &L, int i, ElemType e) {if (i < 1) {return false;}LNode *p;int j = 0;p = 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;
}

本算法主要的时间开销在于查找第i-1个元素,,时间复杂度为O(n)

按位序插入(不带头结点)

bool ListInsert(LinkList &L, int i, ElemType e) {if (i < 1) {return false;}if (i == 1) {LNode *s = (LNode*)malloc(sizeof(LNode));s->data = e;s->next = L;L = s;return true;}LNode *p;int j = 1;p = 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;
}

当链表不带头结点时,需要判断插入位置i是否为1,若是,则要做特殊处理,将头指针L指向新的首结点。当链表带头结点时,插入位置i为1时不做特殊处理。

指定结点的后插操作

bool InsertNextNode(LNode *p, ElemType 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 InsertPriorNode(LNode *p, ElemType 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(1)

按位序删除(带头节点)

bool ListDelete(LinkList &L,int i,ElemType &e){if(i<1){return false;}LNode *p;int j=0;p=L;while(p!=NULL&&j<i-1){p=p->next;j++;}if(p==NULL){return false;}if(p->next==NULL){//确定第i个节点存在return false;}LNode *q=p->next;e=q->data;p->next=q->next;free(q);return true;
}

时间复杂度为O(n)

指定结点的删除

bool DeleteNode(LNode *p){if(p==NULL){return false;}LNode *q=p->next;p->data=p->next->data;p->next=q->next;free(q);return true;
}

实质就是将其后继的值赋予其自身,然后再删除后继,也能使得时间复杂度为O(1).

这种算法存在缺陷,当要删除的结点是最后一个结点的时候,无法顺利删除,因为无法找到尾结点的后继

按位查找

LNode* GetElem_h(LinkList L, int i) {if (i < 0) {return NULL;}LNode *p;int j = 0;   //头结点是第0个结点p = L;while (p != NULL && j < i) {p = p->next;j++;}return p;
}

时间复杂度O(n)

按值查找

LNode* LocateElem(LinkList L,ElemType e){LNode *p=L->next;//指向头结点的下一个节点while(p!=NULL&&p->date!=e){p=p->next;}return p;
}

时间复杂度O(n)

求表的长度

int Length_h(LinkList L) {int len = 0;LNode *p = L;while (p->next != NULL) {p = p->next;len++;}return len;
}

带头结点的单链表中求表长len的初始长度为0,因为头结点是第0个结点。不带头结点的单链表中求表长len的初始长度为1,因为直接从第一个结点开始计数。

求表长操作的时间复杂度为O(n)

单链表的建立

尾插法

该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点

LinkList List_TailInsert(LinkList &L){int x;L=(LNode*)malloc(sizeof(LNode));LNode *s,*r=L;scanf("%d",&x);while(x!9999){s=(LNode*)malloc(sizeof(LNode));s->data=x;r->next=s;r=s;scanf("%d",&x);}r->next=NULL;return L;
}

头插法

该方法从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后

LinkList List_TailInsert(LinkList &L){int x;L=(LNode*)malloc(sizeof(LNode));LNode *s,*r=L;scanf("%d",&x);while(x!=9999){s=(LNode*)malloc(sizeof(LNode));s->data=x;r->next=s;r=s;scanf("%d",&x);}r->next=NULL;return L;
}

采用头插法建立单链表时,读入数据的顺序与生成的链表中元素的顺序是相反的,可用来实现链表的逆置

双链表

双链表结点中有两个指priornext,分别指向其直接前驱和直接后继

双链表的特点

双链表在单链表结点中增加了一个指向前驱的指针prior,因此双链表的按值查找和按位查找的操作与单链表相同。但双链表在插入和删除操作的实现上,与单链表有着较大的不同,这是因为“链”变化时也需要对指针prior做出修改,其关键是保证在修改的过程中不断链

双链表的实现

结构体创建

typedef struct DNode {ElemType data;struct DNode *prior, *next;
} DNode, *DLinkList;

初始化

带头结点

bool InitLinkList_h(DLinkList &L){L=(DNode*)malloc(sizeof(DNode));if(L==NULL){return false;}L->prior=NULL;L->next=NULL;
}

不带头结点

bool IinitLinkList(DLinkList &L){L=NULL;return true;
}

双链表的基本操作

给定结点的后插操作

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;
}

给定结点的前插操作

bool InsertPriorDNode(DNode *p, DNode *s) {if (p == NULL || s == NULL) {return false;}s->next = p;s->prior = p->prior;if (p->prior != NULL) {p->prior->next = s;}p->prior = s;return true;
}

删除给定结点的后继结点

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;
}

循环链表

循环单链表

在循环单链表中,表尾结点*r的next指针域指向L,故表中没有指针域为NULL的结点,因此,循环单链表的判空条件不是头结点的指针是否为空,而是它是否等于头指针L

初始化

bool InitList(LinkList &L) {L = (LNode*)malloc(sizeof(LNode));if (L == NULL) {return false;}L->next = L;//头结点的next指向头结点return true;
}

判断给定结点是否为循环单链表的表尾结点

bool isTail(LinkList L, LNode *p) {if (p->next == L) {return true;} else {return false;}
}

循环双链表

在循环双链表中,头结点的prior指针还要指向表尾结点,当某结点*p为尾结点时,p->next==L;当循环双链表为空时,其头结点的prior域和next域都等于L

初始化

bool InitDLinkList(DLinkList &L) {L = (DNode*)malloc(sizeof(DNode));if (L == NULL) {return false;}L->next = L;L->prior = L;return true;
}

静态链表

静态链表是用数组来描述线性表的链式存储结构,结点也有数据域和指针域,这里的指针是结点在数组中的相对位置,又称游标,指针指向下一个结点的位置。
静态链表也要预先分配一块连续的内存空间。
image

typedef struct {ElemType data;int next;
}SLinkList[MaxSize];//一个长度为MaxSize的数组

插入位序为i的结点

  1. 找到一个空的结点,存入输入元素
  2. 从头结点出发找到位序为i-1的结点
  3. 修改新结点的next
  4. 修改i-1号结点的next

优点:增删操作不需要大量移动元素

缺点:不能随机存取,只能从头结点开始一次往后查找,容量固定不可变

顺序表和链表的比较

逻辑结构

都属于线性表,都是线性结构

存储结构

顺序表

优点:支持随机存取,存储密度高

缺点:大片连续空间分配不方便,改变容量不方便

链表

优点:离散的小空间分配方便,改变容量方便

缺点:不可随机存取,存储密度小

基本操作

顺序表

创建:需要预分配大片连续空间,若分配空间过小,则之后不方便拓展容量;若分配空间过大,则浪费内存空间

插入、删除:要将后续元素都后移或前移,时间复杂度为O(n),主要开销是移动元素,若移动元素过大,则移动的时间代价很高

查找:按位查找:O(1) 按值查找O(n) 若表中元素有序,可在O(ln(n))时间内找到

链表

创建:只需分配一个头结点(也可以不用头结点,只声明一个头指针),之后方便拓展

插入,删除:只需修改指针,时间复杂度为O(n),时间开销主要来自查找目标元素,查找元素的时间代价更低

查找:按位查找O(n) 按值查找O(n)

表长难以估计,经常需要增加、删除元素 ——链表

表长可预估,查询操作较多 ——顺序表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/778724.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

zabbix“专家坐诊”第250期问答

问题一 Q:乐维监控社区版监控交换机,能统计出端口的IP流量排名吗? A:社区版没有这个功能 ,正式版,流量报表可以实现端口IP流量排行。问题二 Q:我看了一下乐维有事件平台汇总的功能,可以接第三方的告警吗? A:可以的。 Q:有没有操作手册我看看,或者你们是基于什么做的…

uniapp中的websocket的研究,以及相关的封装

官方文档---官方文档写的跟屎一样 https://uniapp.dcloud.net.cn/api/request/websocket.html相关博客 https://www.cnblogs.com/sunnyeve/p/16757633.html

aspnet .core 网站默认不支持文件下载

将txt文件和rar文件都放到 wwwroot 目录下,会发现前者可以正常下载而后者不行。其实是需要在初始化的地方进行设置: builder.WebHost.UseKestrel(option =>{ option.ListenAnyIP(config.Port);}); WebApplication app = builder.Build(); var httpContextAccessor = ap…

2024FIC线上初赛WP

感觉需要总结一些技巧了2024FIC线上初赛WP 挖个坑,哪天想起来了补一下艰难困苦,玉汝于成

部署CPU与GPU通用的tensorflow:Anaconda环境

本文介绍在Anaconda环境中,下载并配置Python中机器学习、深度学习常用的新版tensorflow库的方法~本文介绍在Anaconda环境中,下载并配置Python中机器学习、深度学习常用的新版tensorflow库的方法。在之前的两篇文章Python TensorFlow深度学习回归代码:DNNRegressor与Python T…

采购订单列表根据条件设置行背景色

方式一:通过Python脚本实现 import clr clr.AddReference(System) clr.AddReference(Kingdee.BOS) clr.AddReference(Kingdee.BOS.Core) clr.AddReference(System.Drawing) clr.AddReference(System.Collections)from System import * from System.Collections import * from…

采购订单打开单据时表格行设置颜色

列表根据条件设置背景色import clr clr.AddReference(System) clr.AddReference(Kingdee.BOS) clr.AddReference(Kingdee.BOS.Core) clr.AddReference(System.Drawing) clr.AddReference(System.Collections)from System import * from System.Collections import * from Syst…

ArgoWorkflow 教程(一)--DevOps 另一选择?云原生 CICD 初体验

本文主要记录了如何使用 ArgoWorkflow 构建流水线,以及 ArgoWorkflow 中 的 Workflow、Template 等概念模型。本文主要分析以下问题:1)如何创建流水线 2)Workflow、Template、template 自己的引用关系 3)Workflow 和 Template 之间的参数传递问题 4)ArgoWorkflow 流水线…

利用miniprogram-ci工具实现一键上传微信小程序代码

本文由 ChatMoney团队出品利用miniprogram-ci工具在后台实现一键上传微信小程序代码,避免了微信开发者工具的繁琐。 一、部署node环境 我用的是宝塔,可以直接在宝塔上安装Node.js版本管理器二、安装miniprogram-ci npm install miniprogram-ci --save安装在指定文件夹里,这个…

AI宝宝辅食助手,你的私人营养师!

本文由 ChatMoney团队出品介绍说明 亲爱的家长们,尤其是那些新手爸爸妈妈们,你们是否在为宝宝的辅食问题而烦恼?不知道该什么时候开始添加辅食,不知道哪些食物适合宝宝,担心营养不够或者过敏问题?别担心,今天我要介绍一个超级贴心的小帮手——AI宝宝辅食助手!🤖 🌟…

automa工作流RPA - 抖音个人号网页版主页私信自动回复

工作流插件实现抖音个人号的私信自动回复 利用打开的网页版主页,插件自动发送固定话语或图片 automa自动化工作流插件 将下面的扩展压缩包文件解压缩,按上面的流程【加载解压缩的扩展】,选中解压缩的目录 暂时无法在飞书文档外展示此内容 浏览器插件支持edge和chrome浏览器,…

AndroidStdio编译冲突解决

1、JDK版本选择11 2、降低material版本号至1.6.1