C/C++ 数据结构与算法【线性表】 顺序表+链表详细解析【日常学习,考研必备】带图+详细代码
1)线性表的定义
线性表(List):零个或多个数据元素的有限序列。
线性表的数据集合为{a1,a2,…,an},假设每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
在较复杂的线性表中,一个数据元素可以由若干个数据项组成。在这种情况下,常把数据元素称为记录,含有大量记录的线性表又称为文件
2)线性表的顺序存储结构
一、顺序表
1、顺序表的基本概念
概念:用一组地址连续的存储单元依次存储线性表的数据元素,这种存储结构的线性表称为顺序表。
特点:逻辑上相邻的数据元素,物理次序也是相邻的。
只要确定好了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的储存结构,因为高级语言中的数组类型也是有随机存取的特性,所以通常我们都使用数组来描述数据结构中的顺序储存结构,用动态分配的一维数组表示线性表。
2、顺序表的存储结构
普通变量用.来引用
指针变量用->来引用
模板:
#define LIST_INIT_SIZE 100 // 线性表存储空间的初始分配量//顺序表数据结构
typedef struct
{ElemType *elem[LIST_INIT_SIZE];int length; // 当前长度
}SqList;
静态数组定义:
#define MAXSIZE 100
typedef struct {ElemType elem [MAXSIZE];int length;
}SqList;
动态数组定义:
typedef struct {ElemType *elem;int length;
}Sqlist;L.elem = (ElemType*)malloc(sizeof(ElemType)*MAXSIZE);
常见基本操作:
InitList(&L) // 初始化操作,建立一个空的线性表L
DestroyList(&L) // 销毁已存在的线性表L
ClearList(&L) // 将线性表清空
Listlnsert(&L, i, e) // 在线性表L中第i个位置插入新元素e
ListDelete(&L,i, &e) // 删除线性表L中第i个位置元素,用e返回
IsEmpty(L) // 若线性表为空,返回true,否则false
ListLength(L) // 返回线性表L的元素个数
LocateElem(L,e) // L中查找与给定值e相等的元素,若成功返回该元素在表中的序号否则返回 0
GetElem(L, i, &e) // 将线性表L中的第i个位置元素返回给e
二、代码具体实现:
1、定义顺序表结构
#include<iostream>
#include<malloc.h>
using namespace std;// 函数结果状态码
#define OK 1 //成功标识
#define ERROR 0 //失败标识#define MAXSIZE 100 //线性表存储空间的初始分配量typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int//顺序表数据结构
typedef struct
{ElemType *elem;int length;
}SqList;
2、顺序表的初始化
Status InitList(SqList &L){ // 构造一个空的顺序表L// 构造一个空的线性表LL.elem = (ElemType *)malloc(MAXSIZE*sizeof(ElemType));if(!L.elem){return ERROR;}L.length = 0;return OK;
}
3、顺序表的清空与销毁
// 顺序表L的销毁
void DestroyList(SqList &L){if(L.elem) {delete L.elem;// 释放存储空间 }
} // 顺序表L的清空
void ClearList(SqList &L){L.length = 0;// 将线性表的长度置为 0 ,元素个数为0,存储空间还在
}
4、求顺序表长度和判断是否为空
// 求顺序表L的长度
int GetLength(SqList L){ // 这里不需要改变原值,不要需要引用 return (L.length);
} // 判断线性表L是否为空
int IsEmpty(SqList &L){if(L.length == 0){return 1;}else{return 0;}
}
5、顺序表的插入
// 顺序表L的插入
Status ListInsert(SqList &L,int i,ElemType e){if(i < 1 || i > L.length + 1) return ERROR; // i值不合法if(L.length == MAXSIZE) return ERROR; // 当前存储空间已满int j;for(j = L.length - 1;j >= i - 1;j--){L.elem[j + 1] = L.elem[j]; // 插入位置及之后的元素后移} L.elem[i - 1] = e; // 将新元素e放入第i个位置L.length++; // 表长增 1return OK;
}
6、顺序表的删除
// 顺序表L的删除
Status ListDelete(SqList &L,int i){if(i < 1 || i > L.length) return ERROR; // i值不合法int j;for (j = i;j <= L.length - 1;j++){L.elem[j - 1] = L.elem[j]; // 被删除元素之后的元素前移}L.length--; // 表长减 1return OK;
}
7、顺序表的查找
7.1 按位查找
Status GetElem(SqList &L,int i,ElemType &e){if (L.length == 0 || i < 1 || i > L.length) return ERROR;// 判断i值是否合理,若不合理,返回ERRORe = L.elem[i - 1]; //第i-1的单元存储着第i个数据return OK;
} // 复杂度 O(1)
7.2 按值查找
Status LocateELem(SqList &L, ElemType &e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)for (int i = 0;i < L.length;i++){if(L.elem[i] == e) {return i + 1;}//查找成功,返回序号}return ERROR; //查找失败,返回0
}
8、顺序表的输入与输出
// 顺序表的输入
void ListCreate(SqList& L)
{cout << "输入线性表中的元素:(以-1作为结束标志)" << endl;int i = 0; //线性表数组元素的下标cin >> L.elem[i];while (L.elem[i] != -1 ) //线性表结束输入的标志{L.length++;i++;cin >> L.elem[i];}
}//顺序表的输出
void PrintSqList(SqList &L)
{int i;for (i = 0; i < L.length; i++) {cout << L.elem[i] << ' ';}
}
9、运行测试
int main(){SqList L; //定义线性表LInitList(L); //初始化线性表LListCreate(L); //线性表的输入cout << "线性表为:" << endl;PrintSqList(L); //线性表的输出cout << endl << "此时线性表中的元素个数为:" << L.length;cout << endl << "---------------------------------------";int a1,x1 = 0;cout << endl << "要获取第几个元素" << endl;cin >> x1;GetElem(L, x1,a1);cout << "获取第"<<x1<<"个元素为:" <<a1<<endl;int a;cout << "请输入要查找的元素:" << endl;cin >> a;LocateELem(L, a);int x2, b;cout << "要插入的位置 ";cin >> x2;cout<< "要插入的元素 ";cin >> b;ListInsert(L, x2, b);cout << "线性表为:" << endl;PrintSqList(L); //线性表的输出int x3;cout <<endl<< "要删除第几个元素?";cin >> x3;ListDelete(L, x3);cout << "线性表为:" << endl;PrintSqList(L); //线性表的输出system("pause");return 0;
}
10、总体代码
// 函数结果状态码
#define OK 1 //成功标识
#define ERROR 0 //失败标识#define MAXSIZE 100 //线性表存储空间的初始分配量typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int//顺序表数据结构
typedef struct
{ElemType *elem;int length;
}SqList;// 顺序表L的初始化
Status InitList(SqList &L){ // 构造一个空的顺序表L// 构造一个空的线性表LL.elem = (ElemType *)malloc(MAXSIZE*sizeof(ElemType));if(!L.elem){return ERROR;}L.length = 0;return OK;
} // 顺序表L的销毁
void DestroyList(SqList &L){if(L.elem) {delete L.elem;// 释放存储空间 }
} // 顺序表L的清空
void ClearList(SqList &L){L.length = 0;// 将线性表的长度置为 0 ,元素个数为0,存储空间还在
}// 求顺序表L的长度
int GetLength(SqList L){ // 这里不需要改变原值,不要需要引用 return (L.length);
} // 判断线性表L是否为空
int IsEmpty(SqList &L){if(L.length == 0){return 1;}else{return 0;}
} // 顺序表L的插入
Status ListInsert(SqList &L,int i,ElemType e){if(i < 1 || i > L.length + 1) return ERROR; // i值不合法if(L.length == MAXSIZE) return ERROR; // 当前存储空间已满int j;for(j = L.length - 1;j >= i - 1;j--){L.elem[j + 1] = L.elem[j]; // 插入位置及之后的元素后移} L.elem[i - 1] = e; // 将新元素e放入第i个位置L.length++; // 表长增 1return OK;
}// 顺序表L的删除
Status ListDelete(SqList &L,int i){if(i < 1 || i > L.length) return ERROR; // i值不合法int j;for (j = i;j <= L.length - 1;j++){L.elem[j - 1] = L.elem[j]; // 被删除元素之后的元素前移}L.length--; // 表长减 1return OK;
}// 顺序表L的查找
// 按位查找(根据位置i获取相应位置数据元素的内容)
Status GetElem(SqList &L,int i,ElemType &e){if (L.length == 0 || i < 1 || i > L.length) return ERROR;// 判断i值是否合理,若不合理,返回ERRORe = L.elem[i - 1]; //第i-1的单元存储着第i个数据return OK;
} // 复杂度 O(1) // 按值查找 (查找与指定值e相同元素的位置)
Status LocateELem(SqList &L, ElemType &e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)for (int i = 0;i < L.length;i++){if(L.elem[i] == e) {return i + 1;}//查找成功,返回序号}return ERROR; //查找失败,返回0
}// 顺序表的输入
void ListCreate(SqList& L)
{cout << "输入线性表中的元素:(以-1作为结束标志)" << endl;int i = 0; //线性表数组元素的下标cin >> L.elem[i];while (L.elem[i] != -1 ) //线性表结束输入的标志{L.length++;i++;cin >> L.elem[i];}
}//顺序表的输出
void PrintSqList(SqList &L)
{int i;for (i = 0; i < L.length; i++) {cout << L.elem[i] << ' ';}
}int main(){SqList L; //定义线性表LInitList(L); //初始化线性表LListCreate(L); //线性表的输入cout << "线性表为:" << endl;PrintSqList(L); //线性表的输出cout << endl << "此时线性表中的元素个数为:" << L.length;cout << endl << "---------------------------------------";int a1,x1 = 0;cout << endl << "要获取第几个元素" << endl;cin >> x1;GetElem(L, x1,a1);cout << "获取第"<<x1<<"个元素为:" <<a1<<endl;int a;cout << "请输入要查找的元素:" << endl;cin >> a;LocateELem(L, a);int x2, b;cout << "要插入的位置 ";cin >> x2;cout<< "要插入的元素 ";cin >> b;ListInsert(L, x2, b);cout << "线性表为:" << endl;PrintSqList(L); //线性表的输出int x3;cout <<endl<< "要删除第几个元素?";cin >> x3;ListDelete(L, x3);cout << "线性表为:" << endl;PrintSqList(L); //线性表的输出system("pause");return 0;
}
11、运行结果
12、小结
12.1、顺序表的操作算法分析
- 时间复杂度
查找、插入、删除算法的平均时间复杂度 O(n)
- 空间复杂度
显然,顺序表操作算法的空间复杂度 S(n) = O(1)(没有占用辅助空间)
12.2、优缺点
- 优点:
存储密度大(结点本身所占存储量 / 结点结构所占存储量)可以随机存取表中任一元素
- 缺点:
在插入、删除某一元素时,需要移动大量元素
浪费存储空间
属于静态存储形式,数据元素的个数不能自由扩充
3)线性表的链式存储结构
介绍:
- 用一组物理位置任意的存储单元来存放线性表的数据元素。
- 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
- 链表中元素的逻辑次序和物理次序不一定相同。
在链式结构中,除了要存储数据元素的信息外,还要存储它的后继元素的存储地址。因此,为了表示每个数据元素ai与其直接后继元素ai + 1之间的逻辑关系,对数据ai来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域(存的是下一结点的地址)。
特点:
(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
(2)访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等。
结点;
指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。
分类:
头指针、头节点、首元结点:
头节点的作用:
1 便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其它位一一致,无须进行特殊处理。
2 便于空表和非空表的统一处理
无论链表是否为空,头指针都是指向头结点的非空指针因此空表和非空表的处理也就统一了。
头结点的数据域内装的是什么:
头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值。
空链表,头结点的直接后继为空。
假设p是指向线性表第i个数据元素的指针,p -> data表示第 i 个位置的数据域,
p -> next表示第 i + 1 个位置的指针域,
则第p+i个数据元素可表示为:
一、单链表
1、单链表的基本概念
n个结点(ai的存储映像)链结成一个链表,即为线性表(a1, a2, …, an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
单链表是由表头唯一确定的,因此单链表可以用头指针的名字来命名,若指针名是L,则把链表称为表L。
2、单链表的存储结构
模板:
typedef struct Lnode{ // 声明结点的类型和指向结点的指针类型ElemType data; // 结点的数据域struct Lnode *next; // 结点的指针域
}Lnode,*Linklist; // LinkList 为指向结构体 Lnode 的指针类型
定义链表:
LinkList L;定义结点指针p:
LNode *p; 或者 LinkList p;
二、代码总体实现
1、定义单链表结构
#include<iostream>
#include<malloc.h>
using namespace std;// 函数结果状态码
#define OK 1 //成功标识
#define ERROR 0 //失败标识#define MAXSIZE 100 //线性表存储空间的初始分配量typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为inttypedef struct LNode { // 声明结点的类型和指向结点的指针类型ElemType data; // 结点的数据域struct LNode *next; // 结点的指针域
}LNode, *LinkList; // LinkList 为指向结构体 Lnode 的指针类型
2、单链表的初始化
Status InitList(LinkList &L) {// 从内存中获得一块空间,将这块空间的地址赋给 L L = new LNode; // 或者 L = (LinkList) malloc (sizeof(LNode));if (!L) {return ERROR;}L->next = NULL;return OK;
}
3、单链表的创建
3.1 头插法
元素插在链表头部,也叫前插法
// 头插法
void ListCreateT(LinkList &L, int n) {LinkList p;L = new LNode;L->next = NULL; // 先建立一个带头结点的单链表for (int i = n; i > 0; --i) {p = new LNode; // 生成新结点 p=(LNode*)malloc(sizeof(LNode))cin >> p->data; // 输入元素值 scanf(&p-> data);p->next = L->next; // 插入到表头L->next = p;}
}
3.2 尾插法:
元素插入到链表尾部,也叫后插法
// 尾插法 比较常用
// 正位序输入n个元素的值,建立带表头结点的单链表
void ListCreateW(LinkList &L, int n) {L = new LNode;L->next = NULL;LinkList p, r = L; // 尾指针r指向头结点for (int i = 0; i < n; ++i) {p = new LNode; cin >> p->data; // 生成新结点 ,输入元素值p->next = NULL;r->next = p; // 插入到表尾r = p;//r指向新的尾结点}
}
4、单链表的清空与销毁
// 单链表的销毁
Status DestroyList(LinkList &L) {LNode *p;// 或者LinkList p;while (L) {p = L;L = L->next;delete p;}return OK;
}// 单链表的清空
Status ClearList(LinkList &L) { // 将L重置为空表 LNode *p, *q; // 或者LinkList p,q;p = L->next;while (p) { // 没到表尾 q = p->next;delete p;p = q;}L->next = NULL; // 头结点指针域为空 return OK;
}
5、求单链表长度和判断是否为空
// 求单链表的表长
int ListLength(LinkList L) {LinkList p;p = L->next;int i = 0;while (p) {i++;p = p->next;}return i;
}// 判断链表是否为空(判断头节点指针域是否为空)
int ListEmpty(LinkList L) { // 若L为空表,则返回1,否则返回0 if (L->next) { // 非空 return 0;}else {return 1;}
}
6、单链表的插入
若将将节点s插入到节点p和几点 p -> next之间,如下图所示,其核心操作是:
s -> next = p -> next;
p -> next = s;
具体代码如下:
//在L中第i个元素之前插入数据元素
Status ListInsert(LinkList& L, int i, ElemType e) {LinkList p = L,s;int j = 0;while (p && j < i - 1) {p = p->next; // 寻找第i-1个结点,p指向i-1结点++j;}if (!p || j > i - 1) { // i大子表长 + 1或者小于1,插入位置非法return ERROR;}s = new LNode;s->data = e; // 生成新结点s,将结点s的数据域置为es->next = p->next; //将 结点s插入L中 p->next = s;return OK;
}
7、单链表的删除
// 将线性表L中第i个数据元素删除
Status ListDelete(LinkList &L, int i){LinkList p = L,q;int j = 0;while (p->next && j < i - 1) {p = p->next; ++j;}// 寻找第i个结点,并令p指向其前驱if (!(p->next) || j > i - 1) {return ERROR; // 删除位置不合理}q = p -> next; // 临时保存被删结点的地址以备释放p->next = q->next; // 改变删除结点前驱结点的指针域delete q; // 释放删除结点的空间return OK;
}
8、单链表的查找
常见取值:
// 单链表的取值
Status GetElem(LinkList L, int i, ElemType &e) { // 获取单链表中的某个数据元素的内容,通过变量e返回// 初始化 LinkList p;p = L->next;int j = 1;while (p && j < i) { // 向后扫描,直到p指向第i个元素或p为空 p = p->next;++j;}if (!p || j > i) {return ERROR; // 第i个元素不存在 }e = p->data; // 取第i个元素 return OK;
}
按位查询:
// 按位查找(根据指定数据获取该数据所在的位置)(地址)
LNode *LocateELem(LinkList L, ElemType e) {// 在单链表中查找值为 e 的数据元素// 找到,返回值为 e 数据元素的地址,查找失败返回NULL LinkList p;p = L->next;while (p && p->data != e) {p = p->next;}return p;
}
按值查询:
int GetELem(LinkList L, ElemType e) {// 返回L中值为e的数据元素的位置序号,查找失败返回0LinkList p;p = L->next;int j = 1;while (p && p->data != e) {p = p->next;j++;}if (p) {return j;}else {return 0;}
}
9、单链表的打印
// 单链表的遍历
void PrintSqList(LinkList &L)
{LinkList p = L->next;while (p){printf("%d -> ", p->data);p = p->next;}printf("NULL");printf("\n");//最后打印出来的效果就是 1->2->3->4->NULL
}
10、运行测试
int main() {LinkList L;int i;InitList(L); //初始化单链表Lprintf("请输入单链表的数据个数 \n");cin >> i;printf("请输入单链表数据 \n");ListCreateW(L,i);printf("成功创建链表:\n");PrintSqList(L);int option;ElemType x;do {printf("请输入选项:");cin >> option;switch (option){case 1:{int i;printf("请输入插入数据的位置:");cin >> i;printf("请输入插入数据的值:");cin >> x;ListInsert(L, i, x);printf("插入后的链表为:");//打印链表 PrintSqList(L);break;}case 2:{int i;printf("请输入删除的位置\n");cin >> i;ListDelete(L, i);printf("删除后的链表为:");PrintSqList(L);break;}case 3: {int i;printf("请输入查找的数据:");cin >> i;int j = GetELem(L,i);printf("该数据的位置为 %d \n", j);break;}case 0:break;default:printf("输出错误!\n"); break;}} while (option > 0);return 0;
}
11、总体代码
#include<iostream>
#include<malloc.h>
using namespace std;// 函数结果状态码
#define OK 1 //成功标识
#define ERROR 0 //失败标识#define MAXSIZE 100 //线性表存储空间的初始分配量typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为inttypedef struct LNode { // 声明结点的类型和指向结点的指针类型ElemType data; // 结点的数据域struct LNode *next; // 结点的指针域
}LNode, *LinkList; // LinkList 为指向结构体 Lnode 的指针类型// 单链表初始化
Status InitList(LinkList &L) {// 从内存中获得一块空间,将这块空间的地址赋给 L L = new LNode; // 或者 L = (LinkList) malloc (sizeof(LNode));if (!L) {return ERROR;}L->next = NULL;return OK;
}// 头插法
void ListCreateT(LinkList &L, int n) {LinkList p;L = new LNode;L->next = NULL; // 先建立一个带头结点的单链表for (int i = n; i > 0; --i) {p = new LNode; // 生成新结点 p=(LNode*)malloc(sizeof(LNode))cin >> p->data; // 输入元素值 scanf(&p-> data);p->next = L->next; // 插入到表头L->next = p;}
}// 尾插法 比较常用
// 正位序输入n个元素的值,建立带表头结点的单链表
void ListCreateW(LinkList &L, int n) {L = new LNode;L->next = NULL;LinkList p, r = L; // 尾指针r指向头结点for (int i = 0; i < n; ++i) {p = new LNode; cin >> p->data; // 生成新结点 ,输入元素值p->next = NULL;r->next = p; // 插入到表尾r = p;//r指向新的尾结点}
}// 单链表的销毁
Status DestroyList(LinkList &L) {LNode *p;// 或者LinkList p;while (L) {p = L;L = L->next;delete p;}return OK;
}// 单链表的清空
Status ClearList(LinkList &L) { // 将L重置为空表 LNode *p, *q; // 或者LinkList p,q;p = L->next;while (p) { // 没到表尾 q = p->next;delete p;p = q;}L->next = NULL; // 头结点指针域为空 return OK;
}// 求单链表的表长
int ListLength(LinkList L) {LinkList p;p = L->next;int i = 0;while (p) {i++;p = p->next;}return i;
}// 判断链表是否为空(判断头节点指针域是否为空)
int ListEmpty(LinkList L) { // 若L为空表,则返回1,否则返回0 if (L->next) { // 非空 return 0;}else {return 1;}
}//在L中第i个元素之前插入数据元素
Status ListInsert(LinkList& L, int i, ElemType e) {LinkList p = L,s;int j = 0;while (p && j < i - 1) {p = p->next; // 寻找第i-1个结点,p指向i-1结点++j;}if (!p || j > i - 1) { // i大子表长 + 1或者小于1,插入位置非法return ERROR;}s = new LNode;s->data = e; // 生成新结点s,将结点s的数据域置为es->next = p->next; //将 结点s插入L中 p->next = s;return OK;
}// 将线性表L中第i个数据元素删除
Status ListDelete(LinkList &L, int i){LinkList p = L,q;int j = 0;while (p->next && j < i - 1) {p = p->next; ++j;}// 寻找第i个结点,并令p指向其前驱if (!(p->next) || j > i - 1) {return ERROR; // 删除位置不合理}q = p -> next; // 临时保存被删结点的地址以备释放p->next = q->next; // 改变删除结点前驱结点的指针域delete q; // 释放删除结点的空间return OK;
}// 单链表的取值
//Status GetElem(LinkList L, int i, ElemType &e) { // 获取单链表中的某个数据元素的内容,通过变量e返回
// // 初始化
// LinkList p;
// p = L->next;
// int j = 1;
//
// while (p && j < i) { // 向后扫描,直到p指向第i个元素或p为空
// p = p->next;
// ++j;
// }
//
// if (!p || j > i) {
// return ERROR; // 第i个元素不存在
// }
// e = p->data; // 取第i个元素
// return OK;
//}// 单链表的查找
// 按位查找(根据指定数据获取该数据所在的位置)(地址)
LNode *LocateELem(LinkList L, ElemType e) {// 在单链表中查找值为 e 的数据元素// 找到,返回值为 e 数据元素的地址,查找失败返回NULL LinkList p;p = L->next;while (p && p->data != e) {p = p->next;}return p;
}// 按值查找( 根据指定数据获取该数据位置序号)
// 在线性表L中查找值为e的数据元素的位置序号
int GetELem(LinkList L, ElemType e) {// 返回L中值为e的数据元素的位置序号,查找失败返回0LinkList p;p = L->next;int j = 1;while (p && p->data != e) {p = p->next;j++;}if (p) {return j;}else {return 0;}
}// 单链表的遍历
void PrintSqList(LinkList &L)
{LinkList p = L->next;while (p){printf("%d -> ", p->data);p = p->next;}printf("NULL");printf("\n");//最后打印出来的效果就是 1->2->3->4->NULL
}int main() {LinkList L;int i;InitList(L); //初始化单链表Lprintf("请输入单链表的数据个数 \n");cin >> i;printf("请输入单链表数据 \n");ListCreateW(L,i);printf("成功创建链表:\n");PrintSqList(L);int option;ElemType x;do {printf("请输入选项:");cin >> option;switch (option){case 1:{int i;printf("请输入插入数据的位置:");cin >> i;printf("请输入插入数据的值:");cin >> x;ListInsert(L, i, x);printf("插入后的链表为:");//打印链表 PrintSqList(L);break;}case 2:{int i;printf("请输入删除的位置\n");cin >> i;ListDelete(L, i);printf("删除后的链表为:");PrintSqList(L);break;}case 3: {int i;printf("请输入查找的数据:");cin >> i;int j = GetELem(L,i);printf("该数据的位置为 %d \n", j);break;}case 0:break;default:printf("输出错误!\n"); break;}} while (option > 0);return 0;
}
12、运行结果:
13、小结:
单链表的算法时间效率分析
1.查找:
因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为 O(n)。
2.插入和删除:
因线性链表不需要移动元素,只要修改指针一般情况下时间复杂度为 O(1)。
但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为 O(n)。
三、静态链表
1、静态链表的基本概念
静态链表,使用数组连描述指针,首先我们让数组的元素都是由两个数据域组成,data和cur。数据域data,用来存放数据元素;游标cur相当于单链表的next指针,存放该元素的后继在数组中的下标。
为了方便插入数据,我们通常会把数组建立得大一些,以便有一些空闲空间可以便于插入时不至于溢出。
#define MAXSIZE 1000 //假设链表的最大长度是1000
typedef struct{ElemType data;int cur; //游标(Cursor),为0时表示无指向
} Component,StaticLinkList[MAXSIZE];
另外我们对数组的第一个和最后一个元素作为特殊元素处理,不存数据。通产把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0的元素的cur存放备用链表的第一个结点的下标;而数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点的作用,当整个链表为空时,则为0。
此时图示相当于初始化的数组状态,见下面代码:
/**将一维数组space中各分量链成一备用链表space[0].cur为头指针。“0”表示空指针
*/
Status InitList(Component *space){int i;for(i=0; i<MAXSIZE; i++){space[i].cur = i+1;}space[MAXSIZE-1].cur = 0; //目前静态链表为空,最后一个元素的cur为0return OK;
}
在前面的动态链表中,节点的申请和释放分别借用malloc()和free()两个函数来实现。在静态链表中,我们需要自己实现这两个函数。
为了辨明数组中哪些分量未被使用,解决的办法是将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取得第一个结点作为待插入的新节点。
/**申请下一个分量的资源,返回下标
*/
int Malloc_SLL(StaticLinkList space){int i = space[0].cur; //当前数组第一个元素的cur存的值,就是要返回的第一个备用空间的下标if(space[0].cur){space[0].cur = space[i].cur; //把下一个分量用来做备用}return i;
}/**将下标为k的空闲节点收回到备用链表
*/
void Free_SSL(Component *space, int k){space[k].cur = space[0].cur; //把第一个元素cur值赋值给要删除的分量curspace[0].cur = k; //把要删除的分量下标赋值给第一个元素的cur
}
2、静态链表的插入操作
例如如果我们需要在“乙”和“丁”之间,插入一个“丙”,操作如图所示:
用代码实现如下:
/**得到静态列表的长度初始条件:静态列表L已存在。操作结果:返回L中数据元素的个数
*/
int ListLength(StaticLinkList L){int j = 0;int i = L[MAXSIZE-1].cur;while(i){i = L[i].cur;j++;}return j;
}/**在L中第i个元素之前插入新的元素e
*/
Status ListInsert(Component *L, int i, ElemType e){int j,k,l;k = MAXSIZE - 1; //注意k首先是最后一个元素的下标if(i<1 || i>ListLength(L) + 1){return ERROR;}j = Malloc_SLL(L);if(j){L[j].data = e; //将数据赋值给此分量的datafor(l=1; l<= i-1; l++){ k = L[k].cur; //找到第i个元素之前的位置}L[j].cur = L[k].cur; //把第i个元素之前的cur赋值给新元素的curL[k].cur = j; //把新元素的下标赋值给第i个元素之前元素的curreturn OK;}return ERROR;
}
3、静态链表的删除操作
例如如果要删除“甲”元素,如图所示:
用代码实现如下:
/**删除在L中第i个数据元素e
*/
Status ListDelete(Component *L, int i){int j,k;if(i<1 || i>ListLength(L)+1){return ERROR;}k = MAXSIZE - 1;for(j=1; j<=i-1; j++){k = L[k].cur; //找到第i个元素之前的位置}j = L[k].cur;L[k].cur = L[j].cur;OUTPUT(L);Free_SSL(&L, j);return OK;
}
四、循环链表
1、循环链表的基本概念
将单链表中终端节点的指针端由空指针改为指向头结点(头尾相连的链表),就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
**优点:**从表中任意结点出发均可找到表中其他结点。
循环链表带有头结点的空链表如下图所示:
对于非空的循环链表则如下图所示:
注意:
由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断 p 或p->next 是否为空,而是判断它们是否等于头指针。
循环条件:
单链表
p != NULL
p->next != NULL单循环链表
p != L
p->next != L
2、仅设尾指针的循环链表
上述仅设头指针的循环链表有一个弊端,我们可以用O(1)的时间访问第一个节点,但对于最后一个节点,却需要O(n)的时间,于是就有了仅设尾指针的循环链表。
如下图所示:
从上图可以看到,终端节点用尾指针 R 指示,则查找终端节点是O(1),而开始节点,其实就是 R ->next->next,其时间复杂度也是O(1)。
举个程序的例子,要将两个循环链表合成一个表时,有了尾指针就非常简单了。比如下面的这两个循环链表,它们的尾指针分别是Ta和Tb。
要想把它们合并,只需要如下操作即可:
Ta的最后一个元素连接到Tb的第一个元素
释放Tb的表头结点,将Tb的最后一个元素与Ta的表头相连
// 第一步:保存A的头结点
p = Ta -> next; // 第二步:将本是指向B表的第一个元素的指向赋值给 Ta->next,
// 再次注意Tb是尾指针,所以是Tb -> next -> next指向第一个元素
Ta -> next = Tb -> next -> next;// 第三步:释放Tb表头结点
delete Tb->next;// 第四步:将原A表的头结点赋值给Tb -> next
// 将Tb的最后一个元素与Ta的表头相连
Tb -> next = p;
伪代码实现:
LinkList Connect(LinkList Ta, LinkList Tb) {//假设Ta、Tb都是非空的单循环链表p = Ta->next; // ①p存表头结点Ta->next = Tb->next->next; // ②Tb表头连结Ta表尾delete Tb->next; // ③释放Tb表头结点 或者 free(Tb->next)Tb->next = p; // ④修改指针 return Tb;
}
五、双向链表
1、双向链表的基本概念
双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
/*双向链表存储结构*/
typedef struct DulNodse{ElemType data;struct DulNode *prior; //直接前驱指针struct DulNode *next; //直接后继指针
} DulNode, *DuLinkList;
双链表示意图如下所示:
双向链表的对称性,对于链表中的某一个结点p,它的后继的前驱以及它的前驱的后继都是它自己,即:
p->prior->next = p = p->next->prior
2、双向链表的插入操作
在双链表中p所指的结点之后插入结点*s,其指针的变化过程如下图所示:
插入操作的代码片段如下:
//第一步:把p的前驱赋值给s的前驱
s->prior = p -> prior;
//第二步:把a的后继指向给s
p->prior->next = s;
//第三步:把s的后继指向给p
s->next = p;
//第四步:把p的前驱指向s
p->prior = s;注意:体会赋值与指向的含义
伪代码实现:
void Listinsert(DuLinkList& L, Int i, ElemType e) {//在带头结点的双向循环链表L中第i个位置之前插入元素 eif (!(p = GetElemP(L, i))) {return ERROR;}s = new DuLNode;s->date = e; // 赋值插入的元素p->prior->next = s;s->prior = p->prior;s->next = p;p->prior = s;return OK;
}
3、双向链表的删除操作
如果要删除q结点,只需下面两步:
代码片段如下:
//第一步
p->prior->next = p->next;
//第二步
p->next->prior = p->prior;
伪代码实现:
void ListDelete(DuLink& L, Int i, ElemType& e) {// 删除带头结点的双向循环链表L的第i个元素,并用 e 返回。if (!(p = GetElemP(L, i))) {return ERROR;}e = p->data;p->prior->next = p->next; p->next->prior = p->prior; free(p);return OK;
}
六、单链表、循环链表和双向链表的时间效率比较
4)总结
一、顺序表和链表的比较
(1)顺序存储结构的优缺点:
顺序存储结构优点:
- 存储密度大(结点本身所占存储量 / 结点结构所占存储量)可以随机存取表中任一元素
顺序存储结构缺点:
- 在插入、删除某一元素时,需要移动大量元素
- 浪费存储空间
- 属于静态存储形式,数据元素的个数不能自由扩充
(2)链式存储结构的优缺点:
链式存储结构的优点:
- 结点空间可以动态申请和释放;
- 数据元素的逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素
链式存储结构的缺点:
-
存储密度小,每个结点的指针域需额外占用存储空间。当每个结点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。
-
链式存储结构是非随机存取结构。对任一结点的操作都要从头指针依指针链查找到该结点,这增加了算法的复杂度。
存储密度概念:
指结点数据本身所占的数据量和整个结点结构中所占的存储量之比。
一般地,存储密度越大,存储空间的利用率就越高。显然,顺序表的存储密度为1(100%),而链表的存储密度小于1。
(3)比较
二、在实际中应该怎样选取存储结构呢?
1、基于存储的考虑
难以估计线性表的长度或存储规模时,不宜采用顺序表;链表不用事先估计存储规模,但链表的存储密度较低,显然链式存储结构的存储密度是小于1的。
2、基于运算的考虑
在顺序表中按序号访问a1的时间复杂度为O(1),而链表中按序号访问的时间复杂度为O(n),因此若经常做的运算是按序号访问数据元素,则显然顺序表优于链表。
在顺序表中进行插入、删除操作时,平均移动表中一半的元素,当数据元素的信息量较大且表较长时,这一点是不应忽视的;在链表中进行插入、删除操作时,虽然也要找插入位置,但操作主要是比较操作,从这个角度考虑显然后者优于前者。
3、基于环境的考虑
通常较稳定的线性表选择顺序存储
而频繁进行插入、删除操作的线性表(即动态性较强)宜选择链式存储。
5)线性表的应用
一、线性表的合并
伪代码实现:
void union(List &La, List Lb) { // 合并后的结果用La来返回La_len = ListLength(La); Lb_len = ListLength(Lb); for(i = 1; i <= Lb_len; i + +) {GetElem(Lb, i, e); // 获取元素操作if (!LocateElem(La, e)) { // 查找元素操作Listinsert(&La, ++La_len, e); // 插入元素操作}}// 上述几个操作我前面已经实现过,这里就不再显示
}
二、有序表的合并
顺序表实现:
伪代码实现:
void MergeList(SqList LA, SqList LB, SqList& LC) {pa = LA.elem;pb = LB.elem; // 指针pa和pb的初值分别指向两个表的第一个元素LC.length = LA.length + LB.length; // 新表长度为待合并两表的长度之和LC.elem = new ElemType[LC.length]; // 为合并后的新表分配一个数组空间pc = LC.elem; // 指针pc指向新表的第一个元素pa_last = LA.elem + LA.length - 1; // 指针pa_last指向LA表的最后一个元素pb_last = LB.elem + LB.length - 1; // 指针pb_last指向LB表的最后一个元素while(pa <= pa_last && pb <= pb_last){ // 两个表都非空if (*pa <= *pb) {*pc++ = *pa++; // 依次“摘取”两表中值较小的结点}else {*pc++ = *pb++;}while (pa <= pa_last) { // LB表已到达表尾,将LA中剩余元素加入LC*pc++ = *pa++;}while (pb <= pb_last) { // LA表已到达表尾,将LB中剩余元素加入LC*pc++ = *pb++;}}
链表实现:
伪代码实现:
void MergeList(LinkList& La, LinkList& Lb, LinkList& Lc) {pa = La->next; pb = Lb->next;pc = Lc = la; // 用La的头结点作为Lc的头结点while (pa && pb) {if (pa->data <= pb->data) {pc->next = pa;pc = pa;pa = pa->next;}else {pc->next = pb;pc = pb;pb = pb->next;}}pc->next = pa ? pa : pb; // 插入剩余段delete Lb; // 释放Lb的头结点
}
相关文章:
C/C++ 数据结构与算法【线性表】 顺序表+链表详细解析【日常学习,考研必备】带图+详细代码
1)线性表的定义 线性表(List):零个或多个数据元素的有限序列。 线性表的数据集合为{a1,a2,…,an},假设每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素…...
详细说说 JDK 的线程池的创建参数
文章目录 1. 概要2. 线程的核心参数2.1.核心线程和最大线程2.2.任务队列2.2.1.ArrayBlockingQueue2.2.2.LinkedBlockingQueue2.2.3.SynchronousQueue2.2.4.PriorityBlockingQueue2.2.5.DelayQueue2.2.7.LinkedBlockingDeque 2.3 keepAliveTime2.4 ThreadFactory2.5 拒绝策略 3.…...
惠普电脑切换默认F1至F12快捷键,FN切换
发现新买的惠普电脑,按F1至F12发现是快捷功能键,而按fnF1至F12才是windows的功能键和正常我自己使用的电脑刚好相反,实在太不方便了。 解决办法需要进入biso里面去把功能键模式选中给关掉,才能恢复回来...
RabbitMQ在手动消费的模式下设置失败重新投递策略
最近在写RabbitMQ的消费者,因为业务需求,希望失败后重试一定次数,超过之后就不处理了,或者放入死信队列。我这里就达到重试次数后就不处理了。本来以为很简单的,问了kimi,按它的方法配置之后,发…...
[巅峰极客 2021]签到
[巅峰极客 2021]签到 给了我们好多表情,真的是一脸懵逼 注意给我们的关键词 GAME 现在还不知道是什么意思我们去试着解开一下 用这个emoji表情解密器,这里我找了好久才找到一个 emoji-aes 这里的Key值就是GAME 运行后出现flag NSSCTF{10ve_4nd_Peace…...
CrystalDiskInfo:硬盘健康监测工具简介和下载
原论坛给你更好的阅读体验:CrystalDiskInfo:硬盘健康监测工具简介和下载 | 波波论坛 引言 在日常使用电脑时,硬盘的健康状态对于系统的稳定性和数据的安全性至关重要。硬盘出现故障可能会导致数据丢失,严重时甚至会使整个系统无…...
循环神经网络(RNN)详解
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
vue基础之3:模板语法、数据绑定
欢迎来到“雪碧聊技术”CSDN博客! 在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将…...
Ubuntu通过脚本启动多个可执行文件
Ubuntu通过脚本启动多个可执行文件 在 Ubuntu 中,可以通过一个脚本启动多个可执行文件,同时支持顺序执行、并行执行或特定条件下的执行。以下是实现的详细方法: 1. 创建脚本文件 首先,创建一个脚本文件,例如 start_p…...
【C++】LeetCode:LCR 026. 重排链表
题干 LCR 026. 重排链表 给定一个单链表 L 的头节点 head ,单链表 L 表示为: L0 → L1 → … → Ln-1 → Ln 请将其重新排列后变为: L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → … 不能只是单纯的改变节点内部的值,而是需要实…...
文档加密怎么做才安全?
公司的文档包含很多机密文件,这些文件不仅关乎公司的核心竞争力,还涉及到客户隐私、商业策略等敏感信息。因此,文档的保管和传递一直是我们工作的重中之重。 为了确保机密文件的安全,公司需要制定了一系列严格的保密措施。从文件的…...
CTF之WEB(php弱类型绕过)
PHP 的弱类型特性有时会导致意外的行为,特别是在类型比较时。这些特性可以被利用来绕过一些预期的安全检查。以下是一些常见的 PHP 弱类型绕过技巧及其解释: 类型介绍 1. 类型比较 ( vs ) 在 PHP 中, 是松散比较,而 是严格比较…...
Java ConcurrentHashMap
Java Map本质不是线程安全的,HashTable和Collections同步包装器(Synchronized Wrapper)在并发场景下性能低。Java还为实现 Map 的线程安全提供了并发包,保证线程安全的方式从synchronize简单方式到精细化,比如Concurre…...
力扣162:寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。 你可以假设 nums[-1] nums[n] -∞ 。 你必须实现时间复杂度为 O(…...
网络设备配置指南:交换机、路由器与防火墙的基础配置与管理
在现代网络管理中,交换机、路由器和防火墙是不可或缺的关键设备。掌握这些设备的基本配置与管理,对于确保网络的稳定性、安全性和高效性至关重要。本文将详细介绍交换机、路由器和防火墙的基础配置与管理,并通过代码示例和图示来帮助读者更好地理解和应用。 一、交换机的基…...
parallelStream并行流使用踩坑,集合安全
parallelStream并行流使用踩坑 parallelStream介绍 parallelStream实现的是多线程处理从而实现并行流,相较于stream的单行流处理数据的速度更快,看一下其源码会发现parallelStream是使用线程池ForkJoin来调度的。 而ForkJoinPool的默认线程数是CPU核数 …...
清远榉之乡托养机构探讨:自闭症的本质辨析
当人们谈及自闭症时,常常会产生一个疑问:自闭症是精神类疾病吗?今天,清远榉之乡托养机构就来为大家解开这个疑惑。 榉之乡大龄自闭症托养机构在江苏、广东、江西等地都有分校,一直致力于为大龄自闭症患者提供专业的支持…...
音视频入门基础:MPEG2-TS专题(10)——PAT简介
一、引言 当某个transport packet的TS Header中的PID属性的值为0x0000时,该transport packet的payload为Program association table ,即 PAT表。PAT表包含所有PMT表的目录列表,将program_number和PMT表的PID相关联,获取数据的起始…...
wordpress网站首页底部栏显示网站备案信息
一、页脚文件footer.php 例如,wordpress主题使用的是simple-life主题,服务器IP为192.168.68.89,在wordpress主题文件中有个页脚文件footer.php,这是一个包含网站页脚代码的文件。 footer.php 路径如下: /www/wwwroot/192.168.68…...
SOLIDWORKS英文,怎么修改成中文
SOLIDWORKS英文,怎么修改成中文 打开控制面板里的程序 选择程序与功能 找到SOLIDWORKS,选择并点击上方 “更改” 在跳出来的更改页面,选择“简体中文” 点击SOLIDWORKS界面上小齿轮,进入设置 取消勾选English两个相关设置 重启SO…...
简单搭建qiankun的主应用和子应用并且用Docker进行服务器部署
在node18环境下,用react18创建qiankun主应用和两个子应用,react路由用V6版本,都在/main路由下访问子应用,用Dockerfile部署到腾讯云CentOS7.6服务器的8000端口进行访问,且在部署过程中进行nginx配置以进行合理的路由访…...
等保三级安全架构设计方案
一、概述 等保三级,全称为“信息系统安全等级保护三级”,是根据信息安全保护的需求,将系统的安全保护划分为五个等级中的第三级,主要针对相对重要的信息系统进行保护。根据《信息系统安全等级保护基本要求》(GB/T 222…...
【Stable Diffusion】安装教程
目录 一、python 安装教程 二、windows cuda安装教程 三、Stable Diffusion下载 四、Stable Diffusion部署(重点) 一、python 安装教程 (1)第一步下载 打开python下载页面,找到python3.10.9,点击右边…...
05—如何设计和仿真阻抗匹配网络
如何设计和仿真阻抗匹配网络 1. 介绍 在设计电路时,大部分同学只是想把布局布置的更专业,可能没有考虑串扰、电源完整性或阻抗匹配等问题。当了解天线和其他射频设备的匹配网络后,才会意识到阻抗匹配在高速和高频电路中的重要性。 但是,什么时候应该使用阻抗匹配网络?哪…...
Trimble X12助力电力管廊数据采集,为机器人巡视系统提供精准导航支持
地下电缆是一个城市重要的基础设施,它不仅具有规模大、范围广、空间分布复杂等特点,更重要的是它还承担着信息传输、能源输送等与人们生活息息相关的重要功能,也是一个城市赖以生存和发展的物质基础。 01、项目概述 本次项目是对某区域2公里左…...
新质驱动·科东软件受邀出席2024智能网联+低空经济暨第二届湾区汽车T9+N闭门会议
为推进广东省加快发展新质生产力,贯彻落实“百县千镇万村高质量发展工程”,推动韶关市新丰县智能网联新能源汽车、低空经济与数字技术的创新与发展,充分发挥湾区汽车产业链头部企业的带动作用。韶关市指导、珠三角湾区智能网联新能源汽车产业…...
UE5_建立自己的资产库
资产库需要用到一个插件: UAsset Browser - 直接在当前项目预览其他UE项目资产(.uasset 文件) - 直接迁移其他UE项目资产到当前项目 - 不用另外打开资产项目查看资产,迁移资产(麻烦) 插件官网插件文档插…...
Matlab搜索路径添加不上
发现无论是右键文件夹添加到路径,还是在“设置路径”中专门添加,我的路径始终添加不上,导致代码运行始终报错,后来将路径中的“”加号去掉后,就添加成功了,经过测试,路径中含有中文也可以添加成…...
跨UI发送信号
如何自定义信号 1.使用signals声明 2.返回值是void 3.在需要发送的地方使用 emit 信号名字(参数); 进行发送 4.在需要链接的地方使用connect进行链4 接 signals:void sig_addOne(int value); connect(&dlg,&SetDialog::sig_addOne,[](int value){ui->lineEdit…...
宠物领养平台构建:SpringBoot技术路线图
摘 要 如今社会上各行各业,都在用属于自己专用的软件来进行工作,互联网发展到这个时候,人们已经发现离不开了互联网。互联网的发展,离不开一些新的技术,而新技术的产生往往是为了解决现有问题而产生的。针对于宠物领养…...
uniapp App端在renderjs层渲染echarts获取不到service层id的问题
报错信息:Cannot read properties of undefined (reading id) at app-view.js 这样的写法App端有时在renderjs视图层获取不到server逻辑层的数据 server层 renderjs层 解决方法:需要把数据(id)通过server层向renderjs层传值 server层 renderjs层...
标准输入输出函数scanf()/gets()/printf()/puts()的功能和区别
前言: 这两个函数都是用来从标准输入设备(通常是键盘)读取字符串的,但是它们有一些区别和注意事项。 scanf函数 scanf函数是C语言中的一个输入函数,它可以按照指定的格式从标准输入设备(通常是键盘&#…...
JavaScript 中的原型和原型链
JavaScript 中的原型和原型链也是一个相对较难理解透彻的知识点,下面结合详细例子来进行说明: 一、原型的概念 在 JavaScript 中,每个函数都有一个 prototype 属性,这个属性指向一个对象,这个对象就是所谓的 “原型对…...
tensorflow.python.framework.errors_impl.FailedPreconditionError
以下是我的报错 Traceback (most recent call last):File "e:\tool\anaconda\envs\openmmlab\lib\runpy.py", line 194, in _run_module_as_mainreturn _run_code(code, main_globals, None,File "e:\tool\anaconda\envs\openmmlab\lib\runpy.py", line 8…...
lua-cjson 例子
apt install -y lua-cjson 安装 编辑 tmp.lua cjson require "cjson" p 666 d "23.42" payload{"d":[{"pres":..(p)..,"temp":"..(d).."}]} print("payload " .. payload) j cjson.decode(payloa…...
《白帽子讲Web安全》15-16章
《白帽子讲Web安全》15-16章 《白帽子讲Web安全》15章15、Web Server配置安全15.1、Apache安全15.2、Nginx安全15.3、jBoss远程命令执行15.4、Tomcat远程命令执行15.5、HTTP Parameter Pollution15.6、小结 第四篇 互联网公司运营安全《白帽子讲Web安全》16章16、互联网业务安全…...
挑战用React封装100个组件【001】
项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于需要展示图文信息的场景,比如产品介绍、用户卡片或任何带有标题、描述和可选图片的内容展示 样式展示 代码展示 InfoCard.tsx import ./InfoCard.cssinterface InfoCardProps {ti…...
在 macOS 上安装 MongoDB Community Edition
https://www.mongodb.com/zh-cn/docs/manual/tutorial/install-mongodb-on-os-x/...
网络安全运行与维护高级 - 题库汇总百题
1. 单选题 内部信息安全管理组织中的()担负保护系统安全的责任,但工作重点偏向于监视系统的运行情况,并且对安全管理制度的贯彻执行情况进行监督和检查。 A. 安全审查和决策机构 B. 安全主管机构 C. 安全运行维护机构 D. 安全审计机构 正确答案:D 2. 单选题 下列那…...
在html页面显示一个变量,而这个变量中有xss脚本,如何安全的把这个变量原样展示出来
当你想要在HTML页面安全地展示一个可能包含XSS(跨站脚本攻击)脚本的变量原样内容时,可以通过以下几种常见的方式来实现安全展示: 方法一:使用文本节点 在JavaScript中,当你要将变量插入到HTML页面的某个元…...
【Linux】TCP网络编程
目录 V1_Echo_Server V2_Echo_Server多进程版本 V3_Echo_Server多线程版本 V3-1_多线程远程命令执行 V4_Echo_Server线程池版本 V1_Echo_Server TcpServer的上层调用如下,和UdpServer几乎一样: 而在InitServer中,大部分也和UDP那里一样&…...
openGauss你计算的表大小,有包含toast表么?
openGauss你计算的表大小,有包含toast表么? 最近有一个同事问我说“openGauss中pg_relation_size函数在计算表的大小时是否包含了大字段的大小?”,经过思考后,自己觉得表的大小是不包含大字段的大小的,然后…...
Python字典的用法(定义、增加、删除、修改、查询、遍历)
一.字典的介绍 dictionary(字典)是除了列表以外的 Python 中最灵活的数据类型。dict(字典)可以采用多个数据,通常用于存储描述一个物体的相关信息。 字典和列表最主要的区别是,字典是无序的对象集合&#x…...
分布式锁的实现原理
作者:来自 vivo 互联网服务器团队- Xu Yaoming 介绍分布式锁的实现原理。 一、分布式锁概述 分布式锁,顾名思义,就是在分布式环境下使用的锁。众所周知,在并发编程中,我们经常需要借助并发控制工具,如 mute…...
linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)
目录 1.安装JDK (非docker)1.1 将文件放在目录下: /usr/local/jdk1.2 解压至当前目录1.3 配置环境变量 2.安装docker2.1 验证centos内核2.2 安装软件工具包2.3 设置yum源2.4 查看仓库中所有docker版本,按需选择安装2.5 安装docker2.6 启动docker 并 开机…...
批量生成不同用户的pdf 文件(html样式)
技术 selenium thymeleaf itextpdf chromedriver 使用thymeleaf 将动态数据替换 使用selenium chromedriver 进行js ,css等逻辑运算后渲染视图 使用itextpdf 将html 转为pdf 文件 html模板 <!DOCTYPE html> <html xmlns:th"http://www.thymeleaf…...
常见的排序算法
一、基于比较的排序算法 基于比较的排序算法通过比较元素之间的大小来完成排序。 1.1 冒泡排序(Bubble Sort) 特点:通过多次交换相邻元素,将最大(或最小)元素“冒泡”到序列末端。时间复杂度:…...
从语法、功能、社区和使用场景来比较 Sass 和 LESS
一:可以从语法、功能、社区和使用场景来比较 Sass 和 LESS: 1:语法 原始的 Sass 采用的是缩进而不是大括号,后续的 Sass 版本与 LESS 一样使用与 CSS 类似的语法: address {.fa.fa-mobile-phone {margin: 0 3px 0 2…...
hdlbits系列verilog解答(Exams/m2014 q4b)-87
文章目录 一、问题描述二、verilog源码三、仿真结果一、问题描述 本节学习如何实现下图中的电路。 模块声明 module top_module ( input clk, input d, input ar, // asynchronous reset output q); 思路: 只是实现一种带异步复位的D触发器。 时钟边沿两种触发方式的关键字…...
Python 和 Pyecharts 对Taptap相关数据可视化分析
结果展示: 数据来源: Python爬取TapTap 热门游戏信息并存储到数据库(详细版) 目录 结果展示: 数据来源: Python爬取TapTap 热门游戏信息并存储到数据库(详细版 一、引言 二、准备工作 三、…...