【线性表,线性表中的顺序表和链表】
目录
- 1、线性表的定义和基本操作
- 1.1、线性表的定义
- 1.2、线性表的基本操作
- 2、顺序表和链表的比较
- 2.1、顺序表
- 2.1.1、顺序表的定义和特点
- 2.1.2、顺序表的实现
- (1)顺序表的静态分配:
- (2)顺序表的动态分配
- 2.1.3、顺序表的基本操作:
- (1)插入操作
- (2)删除操作
- (3)按位查找
- (4)按值查找
- 2.2、链表
- 2.2.1、链表的定义及特点
- 2.2.2、链表的实现方式
- (1)带头结点
- (2)不带头结点:
- 2.2.3、单链表的基本操作
- (1)单链表的插入
- (2)指定结点的后插操作
- (3)指定结点的前插操作
- (4)单链表的删除
- 1)按位序删除结点
- 2)指定结点的删除
- (5)单链表的查找
- 1)按位查找
- 2)按值查找
- (6)求单链表的长度
- (7)单链表的建立
- 1)尾插法建立单链表
- 2)头插法建立单链表
- 3)链表的逆置
- 2.2.4、双链表
- 2.2.4.1、双链表的初始化(带头结点)
- 2.2.4.2、双链表的插入(后插)
- 2.2.4.3、双链表的删除(后删)和销毁
- 2.2.4.4、双链表的遍历
- 1)前向遍历
- 2)后向遍历
- 2.2.5、循环链表
- 2.2.5.1、循环单链表
- 2.2.5.2、循环双链表
- 2.2.5.3、循环链表的插入
- 2.2.5.4、循环链表的删除
- 2.2.6、静态链表
- 2.2.6.1、静态链表的定义
- 2.3、顺序表和链表的比较
1、线性表的定义和基本操作
1.1、线性表的定义
线性表是具有相同数据类型的n(n>0)个数据元素的有限序列。
其中n为表长,当n=0时,线性表是一个空表,若用L命名线性表,则其一般表示为:
特点:
- 存在唯一的第一个元素;
- 存在唯一的最后一个元素;
- 除第一个元素外,每个元素均只有一个直接前驱;
- 除最后一个元素外,每个元素均只有一个直接后继;
【直接前驱】:指在该序列中位于其前面且紧邻的元素;
【直接后继】:指在该序列中位于其后面且紧邻的元素。
例如:在一个整数数组[1,5,8,10,49]中,元素8的直接前驱为5,直接后继为10。
1.2、线性表的基本操作
- 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个位置上的元素的值;
- Length(L):求表长,返回线性表L的长度,即L中数据元素的个数;
- PrintList(L):输出操作,按照前后顺序输出线性表L中的所有元素值;
- Empty(L):判空操作,若L为空表,则返回为true,否则返回false。
发现在上述操作中,创销增删时传入的是参数的引用,其余操作传入的是参数,涉及到了传值调用和传址调用,如下:
1.传值调用
#include<stdio.h>
void test(int x) {x = 1024;printf("test函数内部 x = %d\n",x);
}int main() {int x = 1;printf("调用test前 x = %d\n", x);test(x);printf("调用test后 x = %d\n", x);return 0;
}
程序输出结果为:
2.传址调用
#include<stdio.h>
void test(int &x) {x = 1024;printf("test函数内部 x = %d\n",x);
}int main() {int x = 1;printf("调用test前 x = %d\n", x);test(x);printf("调用test后 x = %d\n", x);return 0;
}
程序输出结果为:
2、顺序表和链表的比较
2.1、顺序表
2.1.1、顺序表的定义和特点
顺序表: 用顺序存储的方式实现线性表顺序存储,把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
顺序表的特点:
- 随机访问,即可以在O(1)时间内找到第i个元素;
- 存储密度高,每个节点只存储数据元素;
- 拓展容量不方便,即使使用动态分配的方式实现,拓展长的的时间复杂度也比较高,因为需要把数据复制到新的区域;
- 插入删除操作不方便,需要移动大量的元素,时间复杂度为:O(n)。
2.1.2、顺序表的实现
顺序表的实现:顺序表的实现有静态分配和动态分配。
(1)顺序表的静态分配:
- 使用静态数组实现;
- 顺序表的表长刚开始确定后就无法再更改(存储空间是静态的)。
#include<stdio.h>
#define MaxSize 10 //定义表长最大值为10using namespace std;typedef struct {int data[MaxSize];//用静态数组存放数据元素int length;//顺序表的当前长度
}ArrayList;//顺序表的定义类型(静态分配方式)void initList(ArrayList &L) {for (int i = 0; i < MaxSize;i++) {L.data[i] = 0;//将所有数据元素设置为默认初始值}L.length = 0;
}
void insertList(ArrayList& L,int i,int e) {}int main() {ArrayList L;//声明一个顺序表initList(L);//初始化一个顺序表for (int i =0; i < MaxSize; i++) {printf("data[%d] = %d\n", i, L.data[i]);//顺序表的打印}return 0;
}
(2)顺序表的动态分配
#include<stdio.h>
#include<stdlib.h>//malloc,free的头文件
#define initsize 10 //默认是初始值typedef struct{int* data;int MaxSize;//顺序表的最大容量int length;//顺序表的当前长度
}Seqlist;void initlist(Seqlist& L) {//用malloc函数申请一片连续的存储的空间L.data = (int*)malloc(initsize * sizeof(int));L.length = 0;L.MaxSize = initsize;
}void increaseSize(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;//顺序表的最大长度增加lenfree(p);//释放原来的内存空间}int main() {Seqlist L;initlist(L);increaseSize(L,5);return 0;
}
2.1.3、顺序表的基本操作:
(1)插入操作
ListInsert(&L,i,e):在第i个位置处插入指定元素e,平均时间复杂度为:O(n)
#define MaxSize 10 //定义最大长度
typedef struct{int data[MaxSize]; //用静态的数组存放数据int length; //顺序表的当前长度
}SqList; //顺序表的类型定义 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处放入eL.length++; //长度加1return true;
}int main(){ SqList L; //声明一个顺序表InitList(L);//初始化顺序表//...此处省略一些代码;插入几个元素ListInsert(L,3,3); //再顺序表L的第三行插入3return 0;
}
(2)删除操作
ListDelete(&L,i,&e):删除表L中第i个位置处的元素,并用e返回删除元素的值,平均时间复杂度为:O(n)
#define MaxSize 10typedef struct {int data[MaxSize];int length;
} SqList;// 删除顺序表i位置的数据并存入e
bool ListDelete(SqList &L, int i, int &e) {if (i < 1 || i > L.length) // 判断i的范围是否有效return false;e = L.data[i-1]; // 将被删除的元素赋值给e for (int j = i; j < L.length; j++) //将第i个位置后的元素前移 L.data[j-1] = L.data[j];L.length--;return true;
}int main() {SqList L;InitList(L);int e = -1;if (ListDelete(L, 3, e))printf("已删除第3个元素,删除元素值为%d\n", e);elseprintf("位序i不合法,删除失败\n"); return 0;
}
(3)按位查找
GetElem(L,i):获取顺序表L中第i个位置上的元素,平均时间复杂度为:O(1)
// 静态分配的按位查找
#define MaxSize 10typedef struct {ElemType data[MaxSize]; int length;
}SqList;ElemType GetElem(SqList L, int i) {return L.data[i-1];
}
// 动态分配的按位查找
#define InitSize 10typedef struct {ElemType *data;int MaxSize;int length;
}SeqList;ElemType GetElem(SeqList L, int i) {return L.data[i-1];
}
(4)按值查找
LocateElem(L,e):在表L中查找具有给的那个关键字值的元素,平均时间复杂度为:O(n)
#define InitSize 10 //定义最大长度
typedef struct{ElemTyp *data; //用静态的“数组”存放数据元素 int Length; //顺序表的当前长度
}SqList; //在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SqList L, ElemType e){for(int i=0; i<L.lengthl i++)if(L.data[i] == e) return i+1; //数组下标为i的元素值等于e,返回其位序i+1return 0; //推出循环,说明查找失败
}
//调用LocateElem(L,9)
2.2、链表
2.2.1、链表的定义及特点
单链表: 用链式存储实现了线性结构,一个结点存储一个数据元素,各结点间的前后关系用一个指针表示。
特点:
- 优点:不要求大片连续空间,改变容量方便
- 缺点:不可随机存取,要耗费一定空间存放指针
//定义单链表结点类型
typedef struct LNode{ElemType data;//数据域struct LNode *next;//指针域
}LNode, *LinkList;
强调这是一个结点的时候用LNode*;
强调这是一个单链表的时候用LinkList。
2.2.2、链表的实现方式
(1)带头结点
写代码更方便,头结点不存储数据,头结点指向的下一个结点才存放实际数据;
#include<stdlib.h>
#include<stdio.h>typedef int ElemType;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;
}void test() {LinkList L;initList(L);//...
}//判断单链表是否为空(带头结点)
bool Empty(LinkList L) {if (L->next == NULL)return true;else return false;
}
(2)不带头结点:
麻烦,对第一个数据结点与后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用到不同的代码逻辑。
typedef struct LNode{ElemType data;struct LNode *next;
}LNode, *LinkList;//初始化一个空的单链表
bool InitList(LinkList &L){L = NULL; //空表,暂时还没有任何结点return true;
}void test(){LinkList L; //声明一个指向单链表的头指针//初始化一个空表InitList(L);...
}//判断单链表是否为空
bool Empty(LinkList L){return (L==NULL)
}
2.2.3、单链表的基本操作
(1)单链表的插入
ListInsert(&L,i,e): 在表L中第i个位置处插入一个元素e。找到第i-1个结点(前驱结点),将新结点插入其后,其中头结点可以看做第0个结点,故i=1时也适用。
平均时间复杂度为:O(n)
//带头结点的插入
typedef struct LNode
{ElemType data;struct LNode *next;
}LNode, *LinkList;//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e)
{ //判断i的合法性, i是位序号(从1开始)if(i<1)return False;LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点p = L; //L指向头结点,头结点是第0个结点(不存数据)//循环找到第i-1个结点while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULLp = p->next; //p指向下一个结点j++;}if (p==NULL) //如果p指针知道最后再往后就是NULLreturn false;//在第i-1个结点后插入新结点LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个结点s->data = e;s->next = p->next;p->next = s; //将结点s连到p后,后两步千万不能颠倒return true;
}
不带头结点插入时,不存在第0个结点,因此!i = 1时,需要特殊处理:(插入删除)第1个元素时,需要更改头指针L;
typedef struct LNode
{ElemType data;struct LNode *next;
}LNode, *LinkList;bool ListInsert(LinkList &L, int i, ElemType e)
{if(i<1)return false;//插入到第1个位置时的操作有所不同!if(i==1){LNode *s = (LNode *)malloc(size of(LNode));s->data =e;s->next =L;L=s; //头指针指向新结点return true;}//i>1的情况与带头结点一样!唯一区别是j的初始值为1LNode *p; //指针p指向当前扫描到的结点 int j=1; //当前p指向的是第几个结点p = L; //L指向头结点,头结点是第0个结点(不存数据)//循环找到第i-1个结点while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULLp = p->next; //p指向下一个结点j++;}if (p==NULL) //i值不合法return false;//在第i-1个结点后插入新结点LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个结点s->data = e;s->next = p->next;p->next = s; return true;}
(2)指定结点的后插操作
InsertNextNode(LNode *p, ElemType e);
给定一个结点p,在其后插入元素e,根据单链表的链表指针只能往后查找的逻辑关系,故给定一个结点p,p之后的结点我们可以知道,之前的就无法得知了。
typedef struct LNode
{ElemType data;struct LNode *next;
}LNode, *LinkList;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保存数据元素e s->next = p->next;p->next = s; //将结点s连到p之后return true;
} //平均时间复杂度 = O(1)//有了后插操作,那么在第i个位置上插入指定元素e的代码可以改成:
bool ListInsert(LinkList &L, int i, ElemType e)
{ if(i<1)return False;LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点p = L; //L指向头结点,头结点是第0个结点(不存数据)//循环找到第i-1个结点while(p!=NULL && j<i-1){ //如果i>lengh, p最后4鸟会等于NULLp = p->next; //p指向下一个结点j++;}return InsertNextNode(p, e)
}
(3)指定结点的前插操作
设待插入结点为s,将s插入到结点p的前面,我们仍然可以将结点s插入到结点p之后,然后将p->data和s->data进行交换,这样既满足了逻辑关系,又能使得时间复杂度为O(n)。
//前插操作:在p结点之前插入元素e
bool InsertPriorNode(LNode *p, ElenType 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连到p之后s->data = p->data; //将p中元素复制到sp->data = e; //p中元素覆盖为ereturn true;
}
(4)单链表的删除
1)按位序删除结点
ListDelete(&L,i,&e):删除表L中第i个位置上的元素,并用e来返回删除元素的值;头结点视为第0个结点;
思路:找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点
typedef struct LNode{ElemType data;struct LNode *next;
}LNode, *LinkList;bool ListDelete(LinkList &L, int i, ElenType &e){if(i<1) return false;LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点p = L; //L指向头结点,头结点是第0个结点(不存数据)//循环找到第i-1个结点while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULLp = p->next; //p指向下一个结点j++;}if(p==NULL) return false;if(p->next == NULL) //第i-1个结点之后已无其他结点return false;LNode *q = p->next; //令q指向被删除的结点e = q->data; //用e返回被删除元素的值p->next = q->next; //将*q结点从链中“断开”free(q) //释放结点的存储空间return true;
}
2)指定结点的删除
bool DeleteNode(LNode *p){if(p==NULL)return false;LNode *q = p->next; //令q指向*p的后继结点p->data = p->next->data; //让p和后继结点交换数据域p->next = q->next; //将*q结点从链中“断开”free(q);return true;
} //时间复杂度 = O(1)
(5)单链表的查找
1)按位查找
GetElem(L,i):按位查找操作,获取表L中第i个位置上的元素
平均时间复杂度:O(n)
LNode * GetElem(LinkList L, int i){if(i<0) return NULL;LNode *p; //指针p指向当前扫描到的结点int j=0; //当前p指向的是第几个结点p = L; //L指向头结点,头结点是第0个结点(不存数据)while(p!=NULL && j<i){ //循环找到第i个结点p = p->next;j++;}return p; //返回p指针指向的值
}
2)按值查找
GetElem(L,e):按值查找操作,在表L中查找具有关键字值e的元素;
平均时间复杂度:O(n)
LNode * LocateElem(LinkList L, ElemType e){LNode *P = L->next; //p指向第一个结点//从第一个结点开始查找数据域为e的结点while(p!=NULL && p->data != e){p = p->next;}return p; //找到后返回该结点指针,否则返回NULL
}
(6)求单链表的长度
Length(LinkList L):计算单链表中数据结点的个数(不含头结点),需要从第一个结点开始顺序依次访问表中的每个结点;
平均时间复杂度:O(n)
int Length(LinkList L){int len=0; //统计表长LNode *p = L;while(p->next != NULL){p = p->next;len++;}return len;
}
(7)单链表的建立
- 初始化一个单链表
- 每次去一个数据元素,插入到表尾或表头
1)尾插法建立单链表
平均时间复杂度:O(n)
思路:每次都将新结点插入到当前链表的末尾,所以必须增加一个尾指针r,使其始终指向当前链表的为结点
优点:生成的链表中结点的次序和输入数据的顺序会一致
// 使用尾插法建立单链表L
LinkList List_TailInsert(LinkList &L){ int x; //设ElemType为整型int L = (LinkList)malloc(sizeof(LNode)); //建立头结点(初始化空表) LNode *s, *r = L; //r为表尾指针 scanf("%d", &x); //输入要插入的结点的值 while(x!=9999){ //输入9999表示结束 s = (LNode *)malloc(sizeof(LNode)); s->data = x; r->next = s; r = s; //r指针指向新的表尾结点 scanf("%d", &x); } r->next = NULL; //尾结点指针置空 return L;
}
2)头插法建立单链表
平均时间复杂度:O(n)
LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表LNode *s;int x;L = (LinkList)malloc(sizeof(LNode)); //建立头结点L->next = NULL; //初始为空链表,这步不能少!scanf("%d", &x); //输入要插入的结点的值while(x!=9999){ //输入9999表结束s = (LNode *)malloc(sizeof(LNode)); //创建新结点s->data = x;s->next = L->next;L->next = s; //将新结点插入表中,L为头指针scanf("%d", &x); }return L;}
3)链表的逆置
算法思想:逆置链表初始为空,原表结点从原链表中依次删除,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的新的第一个结点,如此循环,直至原链表为空;
LNode *Inverse(LNode *L)
{LNode *p, *q;p = L->next; //p指针指向第一个结点L->next = NULL; //头结点指向NULLwhile (p != NULL){q = p;p = p->next;q->next = L->next; L->next = q;}return L;
2.2.4、双链表
对双链表中结点的描述:
//定义双链表结点类型
typedef struct DNode{ElemType data;//定义数据域struct DNode *prior, *next;//前驱指针和后继指针
}DNode,*DLinkList;
2.2.4.1、双链表的初始化(带头结点)
#include<stdlib.h>typedef struct DNode {ElemType data;struct DNode* prior, * next;
}DNode, *DLinkList;bool initlist(DLinkList &DL) {DL = (DNode*)malloc(sizeof(DNode));//分配一个结点if (DL == NULL)//内存不足,分配失败return false;DL->prior = NULL;//头结点的prior永远指向NULLDL->next = NULL;//头结点之后还没有结点return true;
}void testDLinkList() {//初始化双链表DLinkList DL;initlist(DL);}
//判断双链表是否为空
bool Empty(DLinkList DL) {if (DL->next == NULL)//判断头结点的next指针是否为空return false;else return true;
}
2.2.4.2、双链表的插入(后插)
//将结点p插到结点s之后
bool InsertNextDNode(DNode *s, DNode *p){if(p == NULL || s == NULL)return false;p->next = s->next;if(s->next != NULL)//s不是最后一个结点(s有后继结点)s->next->prior = p;p->prior = s;s->next = p;
}
2.2.4.3、双链表的删除(后删)和销毁
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;
}
//销毁一个双链表
bool DestroyDLinkLIst(DLinkList &DL){//循环释放每一个结点while(DL->next !=NULL){DeleteNextDNode(DL);//删除头结点的后继节点free(DL);//释放头结点DL = NULL;//头结点指向空}
}
2.2.4.4、双链表的遍历
1)前向遍历
while(P != NULL){//对结点p做相应处理,eg.打印p = p->prior;
}
2)后向遍历
while(P != NULL){//对结点p做相应处理,eg.打印p = p->next;
}
注意:双链表不可以随机存取,按位查找和按值查找都只能用遍历的方式实现,时间复杂度为O(n)。
2.2.5、循环链表
2.2.5.1、循环单链表
最后一个结点的指针不是指向NULL而是指向头结点
typedef struct LNode{ ElemType data; struct LNode *next;
}DNode, *Linklist;/初始化一个循环单链表
bool InitList(LinkList &L){L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点if(L==NULL) //内存不足,分配失败return false;L->next = L; //头结点next指针指向头结点return true;
}//判断循环单链表是否为空(终止条件为p或p->next是否等于头指针)
bool Empty(LinkList L){if(L->next == L)return true; //为空elsereturn false;
}//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p){if(p->next == L)return true;elsereturn false;
}
单链表和循环单链表的比较:
1. 单链表: 从一个结点出发,只能找到该结点后面的各个结点;对链表的操作大多都在头部或尾部;设立头指针,从头结点找到尾部得到时间复杂度为O(n),即对表尾操作需要O(n)的时间复杂度。
2. 循环单链表: 从表中任一个结点出发,可以找到该表中所有其他结点;设立尾指针,从尾部找到头部的时间复杂度为O(1),即对表头和表尾操作的时间复杂度都只需要O(1)的时间复杂度。
3. 循环单链表的优点: 从表中任一个结点出发,可以找到该表中所有其他结点;
2.2.5.2、循环双链表
表头结点的前驱结点prior指向表尾结点,表尾结点的后继结点next指向表头结点。
typedef struct DNode{ ElemType data; struct DNode *prior, *next;
}DNode, *DLinklist;//初始化空的循环双链表
bool InitDLinkList(DLinklist &L){L = (DNode *) malloc(sizeof(DNode)); //分配一个头结点if(L==NULL) //内存不足,分配失败return false; L->prior = L; //头结点的prior指向头结点L->next = L; //头结点的next指向头结点
}void testDLinkList(){//初始化循环单链表DLinklist L;InitDLinkList(L);//...
}//判断循环双链表是否为空
bool Empty(DLinklist L){if(L->next == L)return true;elsereturn false;
}//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinklist L, DNode *p){if(p->next == L)return true;elsereturn false;
}
2.2.5.3、循环链表的插入
bool InsertNextDNode(DNode *p, DNode *s){ s->next = p->next;p->next->prior = s;s->prior = p;p->next = s;
2.2.5.4、循环链表的删除
//删除p的后继结点q
p->next = q->next;
q->next->prior = p;
free(q);
2.2.6、静态链表
2.2.6.1、静态链表的定义
用数组的方式来描述线性表的链式存储结构:分配一整片连续的内存空间,各个结点集中安置,包括了数据元素和下一个结点的数组下标(游标)。
#define MaxSize 10 //静态链表的最大长度struct Node{ //静态链表结构类型的定义ElemType data; //存储数据元素int next; //下一个元素的数组下标(游标)
};//用数组定义多个连续存放的结点
//相当于typedef struct Node SLinkList[MaxSize]; 重命名struct Node,用SLinkList定义“一个长度为MaxSize的Node型数组;
void testSLinkList(){struct Node a[MaxSize]; //数组a作为静态链表, 每一个数组元素的类型都是struct Node//...
}
或者是
#define MaxSize 10 //静态链表的最大长度typedef struct{ //静态链表结构类型的定义ELemType data; //存储数据元素int next; //下一个元素的数组下标
}SLinkList[MaxSize];void testSLinkList(){SLinkList a;
}
2.3、顺序表和链表的比较
逻辑结构 | 存储结构 | 创建 | 销毁 | 增/删 | 查 | |
---|---|---|---|---|---|---|
顺序表 | 线性表 | 顺序存储 优点:支持随机存取,存储密度高 缺点:大片连续空间分配不方便,改变容量不方便 | 静态分配:需要预分配大片连续空间,若空间太小,拓展容量不方便,若太大又浪费内存资源;动态分配:可以改变容量,但是需要移动大量元素,时间代价高 | 对于静态数组,系统会自动回收;对于动态分配,需要手动进行free() | 插入/删除元素需要将后续元素进行后移/前移,时间复杂度为O(n),时间开销主要来自移动元素 | 按位查找:O(1);按值查找:O(n),若表内元素有序,则可以在O(log2n)时间内找到 |
链表 | 线性表 | 链式存储 优点:离散的小空间分配方便,改变容量方便 缺点:不可随机存取,存储密度低 | 只需要分配一个头结点或者声明一个头指针 | 利用后删从头结点依次删除后续结点,最后释放头结点 | 插入/删除元素只需要修改指针,时间复杂度为O(n),时间开销主要来自查找目标元素 | 按位查找:O(n);按值查找:O(n) |
顺序表 | 链表 | |
---|---|---|
弹性(可扩容) | 不嘻嘻 | 嘻嘻 |
增/删 | 不嘻嘻 | 嘻嘻 |
查 | 嘻嘻 | 不嘻嘻 |
相关文章:

【线性表,线性表中的顺序表和链表】
目录 1、线性表的定义和基本操作1.1、线性表的定义1.2、线性表的基本操作 2、顺序表和链表的比较2.1、顺序表2.1.1、顺序表的定义和特点2.1.2、顺序表的实现(1)顺序表的静态分配:(2)顺序表的动态分配 2.1.3、顺序表的基…...

46 mysql 客户端拿不到具体的错误信息
前言 这是最近碰到的一个问题 同样的一个 环境的问题, 在正常的 mysql 环境会返回 具体的错误信息, 然后 在我的另外一个环境里面 只能返回一些 unknown error 之类的 十分抽象的环境 然后 我们这里 来看一下 具体的情况 我们这里从 错误的环境 往前推导 来查看 并解决这个…...

Java语言程序设计——篇三(2)
循环结构 概述1️⃣while循环例题讲解 2️⃣do-while循环例题讲解 🚩while循环与do-while循环区别3️⃣for循环例题讲解 4️⃣循环的嵌套🏮例题讲解 概述 ⭐️Java语言提供了4种循环结构: (1) while循环 (2) do-while循环 (3) for循环 (4)增…...

如何实现一个分布式锁
如何实现一个分布式锁 本篇内容主要介绍如何使用 Java 语言实现一个注解式的分布式锁,主要是通过注解AOP 环绕通知来实现。 1. 锁注解 我们首先写一个锁的注解 /*** 分布式锁注解*/ Retention(RetentionPolicy.RUNTIME) Target({ElementType.METHOD}) Documente…...

Ajax从零到实战
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…...

编程参考 - 在C++移动构造函数声明中使用noexcept
在 C 中,noexcept 是用于表示函数不抛出异常的指定符。它既可用于常规函数,也可用于特殊成员函数,包括构造函数和析构函数。使用 noexcept 可以帮助编译器进行优化,提高代码的安全性和正确性。 In C, noexcept is a specifier use…...

Vue2/Vue3实现全局/局部添加防篡改水印的效果。删除元素无效!更改元素属性无效!支持图片、元素、视频等等。
水印目的 版权保护:水印可以在图片、文档或视频中嵌入作者、品牌或版权所有者的信息,以防止未经授权的复制、传播或使用。当其他人使用带有水印的内容时,可以追溯到原始作者或版权所有者,从而加强版权保护。 身份识别:水印可以用作作者或品牌的标识符,使观众能够轻松识…...

GuLi商城-商品服务-API-属性分组-获取分类属性分组
获取分类属性分组接口开发 操作的是这张表 造数据: 后台代码: @Override public PageUtils queryPage(Map<String, Object> params, Long catelogId) {//select * from pms_attr_group where catelog_id=? and (attr_group_id=key or attr_group_name like %key%)Stri…...

安全测试理论
安全测试理论 什么是安全测试? 安全测试:发现系统安全隐患的过程安全测试与传统测试区别 传统测试:发现bug为目的 安全测试:发现系统安全隐患什么是渗透测试 渗透测试:已成功入侵系统为目标的的攻击过程渗透测试与安全…...

序列化和反序列化
面试题:对序列化和反序列化的理解? 我们之所以需要序列化,它核心的目的是为了解决网络通信之间的对象传输的问题,也就是说,如何把当前JVM进程的一个对象,通过跨网络传输到另一个JVM进程里面,而序…...

OpenCV中使用Canny算法在图像中查找边缘
操作系统:ubuntu22.04OpenCV版本:OpenCV4.9IDE:Visual Studio Code编程语言:C11 算法描述 Canny算法是一种广泛应用于计算机视觉和图像处理领域中的边缘检测算法。它由John F. Canny在1986年提出,旨在寻找给定噪声条件下的最佳边…...

基于springboot+vue+uniapp的机电公司管理信息系统
开发语言:Java框架:springbootuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包&#…...

电子期刊制作实战教程:从零开始制作
随着互联网的普及,电子期刊已经成为了信息传递的重要载体。它以便捷、环保、互动性强等特点受到了越来越多人的青睐。那么,如何从零开始制作一份吸引人的电子期刊呢? 1.要制作电子杂志,首先需要选择一款适合自己的软件。比如FLBOOK在线制作…...

11.FreeRTOS_事件组
事件组概述 事件组的作用: 可以等待某一个事件发生可以等待若干个事件发生可以等待若干个事件中的某一个事件发生 同步点是事件组的另一个使用方式,它可以让多个任务进行阻塞等待,当全部事件完成后,再一起解除任务的阻塞。常常…...

Python爬虫-爬取三国演义文本数据-bs4
bs4进行数据解析 -数据解析的原理: - 1.标签定位 -2.提取标签、标签属性中存储的数据值 - bs4数据解析的原理: - 1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中 -2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取 - 环境安装: - pi…...

html5——列表、表格
目录 列表 无序列表 有序列表 自定义列表 表格 基本结构 示例 表格的跨列 表格的跨行 列表 无序列表 <ul>【声明无序列表】 <li>河间驴肉火烧</li>【声明列表项】 <li>唐山棋子烧饼</li> <li>邯郸豆沫</li> <l…...

【Python字符串攻略】:玩转文字,编织程序的叙事艺术
文章目录 🚀一.字符串基础🌈二.查看数据类型⭐三.转化❤️四.字符串索引🚲五.字符串切片🎬六.字符串切片-步长☔七.反向切片注意事项🚲八.字符串💥查💥改💥删 ❤️九.字符串拼接&…...

element form表单中密码框被自动赋值,并默认背景色为白色,手动输值后背景色才是自己配置的背景色,与表单的自动填充有关
事件背景: 一个表单,有两组需要输入密码的地方,两组都被填充用户名密码,其中一组是其他信息,不是用户名密码,也被填充了,且input背景色是白色,表单中的input已经手动配置为无背景色&…...

【UE5.1 角色练习】15-枪械射击——子弹发射物
目录 效果 步骤 一、创建并发射子弹 二、优化子弹 效果 步骤 一、创建并发射子弹 1. 在前面的文章中(【UE5.1 角色练习】06-角色发射火球-part1)我们创建了蓝图“BP_Skill_FireBall” 这里我们复制一份命名为“BP_Ammo_5mm”,用于表示…...

Zynq7000系列FPGA中的DMA控制器的编程限制
有关DMAC编程时适用的限制信息,有四个考虑因素: 固定非对齐突发Endian swap size restrictions:在数据传输或处理过程中,不同字节序(Endian)之间的转换和对应的限制在DMA周期内更新通道控制寄存器当MFIFO满…...

超简易高效的 AI绘图工具—与sd-webui一致界面,6G显存最高提升75%出图速率!(附安装包)
大家好,我是灵魂画师向阳 今天给大家分享一个基于Stable Diffusion WebUI 构建的AI绘图工具—sd-webui-forge,该工具的目标在于简化插件开发,优化资源管理,加速推理。 Forge承诺永远不会对Stable Diffusion WebUI用户界面添加不…...

ArduPilot开源代码之OpticalFlow_backend
ArduPilot开源代码之OpticalFlow_backend 1. 源由2. Library设计3. 重要例程3.1 OpticalFlow_backend::_update_frontend3.2 OpticalFlow_backend::_applyYaw 4. 总结5. 参考资料 1. 源由 光流计是一种低成本定位传感器,所有的光流计设备传感驱动代码抽象公共部分统…...

设计模式探索:适配器模式
1. 适配器模式介绍 1.1 适配器模式介绍 适配器模式(adapter pattern)的原始定义是:将一个类的接口转换为客户期望的另一个接口,适配器可以让不兼容的两个类一起协同工作。 适配器模式的主要作用是把原本不兼容的接口,…...

OpenCV 寻找棋盘格角点及绘制
目录 一、概念 二、代码 2.1实现步骤 2.2完整代码 三、实现效果 一、概念 寻找棋盘格角点(Checkerboard Corners)是计算机视觉中相机标定(Camera Calibration)过程的重要步骤。 OpenCV 提供了函数 cv2.findChessboardCorners…...

【深度学习】PyTorch深度学习笔记02-线性模型
1. 监督学习 2. 数据集的划分 3. 平均平方误差MSE 4. 线性模型Linear Model - y x * w 用穷举法确定线性模型的参数 import numpy as np import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0] y_data [2.0, 4.0, 6.0]def forward(x):return x * wdef loss(x, y):y_pred…...

10.FreeRTOS_互斥量
互斥量概述 在博文“ FreeRTOS_信号量 ”中,使用了二进制信号量实现了互斥,保护了串口资源。博文链接如下: FreeRTOS_信号量-CSDN博客 但还是要引入互斥量的概念。互斥量与二进制信号量相比,能够多实现如下两个功能:…...

EtherCAT总线冗余让制造更安全更可靠更智能
冗余定义 什么是总线冗余功能?我们都知道,EtherCAT现场总线具有灵活的拓扑结构,设备间支持线型、星型、树型的连接方式,其中线型结构简单、传输效率高,大多数的现场应用中也是使用这种连接方式,如下图所示…...

Android IdleHandler源码分析
文章目录 Android IdleHandler源码分析概述前提基本用法源码分析添加和删除任务执行任务 应用场景 Android IdleHandler源码分析 概述 IdleHandler是一个接口,它定义在MessageQueue类中,用于在主线程的消息队列空闲时执行一些轻量级的任务。IdleHandle…...

Mac安装stable diffusion 工具
文章目录 1.安装 Homebrew2.安装 stable diffusion webui 的依赖3.下载 stable diffusion webui 代码4.启动 stable diffusion webui 本体5.下载模型6.这里可能会遇到一个clip-vit-large-patch14报错 参考:https://brew.idayer.com/install/stable-diffusion-webui/…...

CVE-2024-6387Open SSH漏洞彻底解决举措(含踩坑内容)
一、漏洞名称 OpenSSH 远程代码执行漏洞(CVE-2024-6387) 二、漏洞概述 Open SSH是基于SSH协议的安全网络通信工具,广泛应用于远程服务器管理、加密文件传输、端口转发、远程控制等多个领域。近日被爆出存在一个远程代码执行漏洞,由于Open SSH服务器端…...