做网站项目计划书,网站建设与运营公司部门结构,wap端网站建设,wordpress 邮箱配置目录 1、线性表的定义和基本操作1.1、线性表的定义1.2、线性表的基本操作 2、顺序表和链表的比较2.1、顺序表2.1.1、顺序表的定义和特点2.1.2、顺序表的实现#xff08;1#xff09;顺序表的静态分配#xff1a;#xff08;2#xff09;顺序表的动态分配 2.1.3、顺序表的基… 目录 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、线性表的定义
线性表是具有相同数据类型的nn0个数据元素的有限序列。 其中n为表长当n0时线性表是一个空表若用L命名线性表则其一般表示为 特点
存在唯一的第一个元素存在唯一的最后一个元素除第一个元素外每个元素均只有一个直接前驱除最后一个元素外每个元素均只有一个直接后继
【直接前驱】指在该序列中位于其前面且紧邻的元素 【直接后继】指在该序列中位于其后面且紧邻的元素。 例如在一个整数数组[1581049]中元素8的直接前驱为5直接后继为10。
1.2、线性表的基本操作
InitList(L)初始化表构造一个空的线性表L分配内存空间DestroyList(L)销毁操作销毁线性表并释放线性表L所占用的空间ListInsert(L;i,e)插入操作在线性表L中的第i个位置上插入指定元素eListDelete(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.传值调用
#includestdio.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.传址调用
#includestdio.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顺序表的静态分配
使用静态数组实现顺序表的表长刚开始确定后就无法再更改存储空间是静态的。
#includestdio.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顺序表的动态分配
#includestdio.h
#includestdlib.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(i1||iL.length1) //判断i的范围是否有效return false;if(L.lengthMaxSize) //当前存储空间已满不能插入 return false;for(int jL.length; ji; 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(Li)获取顺序表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按值查找
LocateElemLe在表L中查找具有给的那个关键字值的元素平均时间复杂度为O(n)
#define InitSize 10 //定义最大长度
typedef struct{ElemTyp *data; //用静态的“数组”存放数据元素 int Length; //顺序表的当前长度
}SqList; //在顺序表L中查找第一个元素值等于e的元素并返回其位序
int LocateElem(SqList L, ElemType e){for(int i0; iL.lengthl i)if(L.data[i] e) return i1; //数组下标为i的元素值等于e返回其位序i1return 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带头结点
写代码更方便头结点不存储数据头结点指向的下一个结点才存放实际数据
#includestdlib.h
#includestdio.htypedef 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 (LNULL)
}2.2.3、单链表的基本操作
1单链表的插入
ListInsert(L,i,e) 在表L中第i个位置处插入一个元素e。找到第i-1个结点前驱结点将新结点插入其后其中头结点可以看做第0个结点故i1时也适用。 平均时间复杂度为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(i1)return False;LNode *p; //指针p指向当前扫描到的结点 int j0; //当前p指向的是第几个结点p L; //L指向头结点头结点是第0个结点不存数据//循环找到第i-1个结点while(p!NULL ji-1){ //如果ilengh, p最后会等于NULLp p-next; //p指向下一个结点j;}if (pNULL) //如果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(i1)return false;//插入到第1个位置时的操作有所不同if(i1){LNode *s (LNode *)malloc(size of(LNode));s-data e;s-next L;Ls; //头指针指向新结点return true;}//i1的情况与带头结点一样唯一区别是j的初始值为1LNode *p; //指针p指向当前扫描到的结点 int j1; //当前p指向的是第几个结点p L; //L指向头结点头结点是第0个结点不存数据//循环找到第i-1个结点while(p!NULL ji-1){ //如果ilengh, p最后会等于NULLp p-next; //p指向下一个结点j;}if (pNULL) //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根据单链表的链表指针只能往后查找的逻辑关系故给定一个结点pp之后的结点我们可以知道之前的就无法得知了。
typedef struct LNode
{ElemType data;struct LNode *next;
}LNode, *LinkList;bool InsertNextNode(LNode *p, ElemType e)
{if(pNULL){return false;}LNode *s (LNode *)malloc(sizeof(LNode));//某些情况下分配失败比如内存不足if(sNULL)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(i1)return False;LNode *p; //指针p指向当前扫描到的结点 int j0; //当前p指向的是第几个结点p L; //L指向头结点头结点是第0个结点不存数据//循环找到第i-1个结点while(p!NULL ji-1){ //如果ilengh, 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(pNULL)return false;LNode *s (LNode *)malloc(sizeof(LNode));if(sNULL) //内存分配失败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个结点将其指针指向第i1个结点并释放第i个结点
typedef struct LNode{ElemType data;struct LNode *next;
}LNode, *LinkList;bool ListDelete(LinkList L, int i, ElenType e){if(i1) return false;LNode *p; //指针p指向当前扫描到的结点 int j0; //当前p指向的是第几个结点p L; //L指向头结点头结点是第0个结点不存数据//循环找到第i-1个结点while(p!NULL ji-1){ //如果ilengh, p最后会等于NULLp p-next; //p指向下一个结点j;}if(pNULL) 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(pNULL)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(i0) return NULL;LNode *p; //指针p指向当前扫描到的结点int j0; //当前p指向的是第几个结点p L; //L指向头结点,头结点是第0个结点(不存数据)while(p!NULL ji){ //循环找到第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 len0; //统计表长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、双链表的初始化带头结点
#includestdlib.htypedef 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(LNULL) //内存不足分配失败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(LNULL) //内存不足分配失败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)
顺序表链表弹性可扩容不嘻嘻嘻嘻增/删不嘻嘻嘻嘻查嘻嘻不嘻嘻