【学习笔记】数据结构 第二章 线性表

2.1 线性表的存储结构 

顺序表:静态存储分配,编译时确定容量 ,用一段地址连续的存储单元依次存储线性表的数据,逻辑关系通过存储位置来实现

链表: 动态存储分配,运行时分配空间,用一组任意的存储单元存放线性表的数据元素,用指针来反应数据元素之间的逻辑关系。

2.1.1 顺序表与链表的比较

顺序表 Array

特点:

1.元素在内存中连续存储。可以通过元素的索引快速访问任意的元素。

2.插入和删除操作相对复杂,要移动大量元素。

3.占用连续内存空间,大小固定,诺空间不足要扩容。

4.适合元素数量固定 或 较少的场景

如:静态数据 或 需要随机访问的情况。

单链表 Linked List

特点:

1.元素在内存中不连续,每个节点除了存储的数据外还要存:指向下一个节点的指针。

2.插入 和 删除 简单高效 ,只需要修改指针,不用大量移动元素。

3.空间利用灵活,可以动态分配内存。

4.不支持随机访问,要从头遍历到目标位置。

5.适合频繁 插入 ,删除 操作

如:实现栈 ,队列, 或者 动态管理内存。

双链表

通过引入额外的指针 提供了更多操作。

特点:

1.每个节点除了存储数据外,还包含两个指针,分别指向前一个节点和后一个节点,这使得在双链表中可以双向遍历和访问节点。(双向访问)

2.双链表相比单链表会占用更多的空间,因为每个节点需要存储两个指针。

3.如需要频繁地从 头尾两端 插入或删除节点的情况下,双链表比单链表更加高效。

 

循环链表

特点:

1.与普通链表不同之处在于,其尾节点的指针不是指向 null,而是指向链表的头节点或其他节点,形成一个循环。

2.可以从任何节点开始遍历整个链表,不需要像普通链表那样特别处理尾节点。

3.循环链表常用于需要循环访问数据的场景

如:循环队列的实现,或者需要按照某种周期性顺序处理数据的情况。

 

静态链表:

数组来实现的链式存储结构 ,解决链表在存储分配上的灵活性不足的问题。

特点:

1.使用数组实现

2.预先分配内存

3.使用游标实现指针:模拟指针功能,每个节点会有一个指向下一个节点的索引 称为 游标

4.不支持动态扩展,由于空间是静态分配的,一旦分配完是固定的

单链表的实现

const int N =10000;
int head,  //head 表示头节点的下标
    e[N],  //e[i] 表示节点i 的值
    ne[N], //ne[i]表示节点 i的next 指针是多少
    idx;   //idx 存储当前已经用到了哪个点

//初始化
void init(){
     head = -1; idx =0;
}
//将x插入到头节点
void add_to_head(int x){
e[idx]=x , ne[idx]=head , head=idx++
}
//将x插到 下标为 k的点后面
void add(int k,int x){
e[idx]=x , ne[idx]=ne[k] , ne[k]=idx++;
}
//将下标是 k的点后面的点删掉
void remove(int k){
ne[k] = ne[ne[k]];
}

注:在删除中,要注意删除头节点:

if(!k) head = ne[head]
else remove(k-1,x);

输出:

for(int i=head;i!=-1;i=ne[i])
cout<<e[i]<<' ';

双链表的实现

 插入:

在K右边插入:调用add(k,x)     

在K左边插入:调用add(l[k],x)就行了。

删除 (删除第k个点)

实现代码: 

const int N =10000;
int idx;
int e[N],l[N],r[N];
//初始化
void int(){
l[i]=0,r[0]=1;//初始化 第一个点的右边是1 ,第二个点的左边是0
idx=2;  //idx此时已经用掉两个点
}
//在第k个点右边插一个x
void add(int k,int x){
e[idx] =x; l[idx]=k; r[idx]=r[k]; l[r[k]]=idx; r[k]=idx;
}
//删除第k个点
void remove(int k){
r[l[k]] =r[k];
l[r[k]] =l[k];
}

输出:

for(int i =r[0]; i!=1; i=r[i])
  cout<<e[i]<<' ';

2.2栈

栈定义:限定仅在一段(栈顶)进行 插入和删除操作的线性表  后进先出

栈的基本操作:

入栈(push),出栈(Pop),取栈顶元素(Top) 判空(IsEmpty)

除了 遍历栈中的元素的操作时间复杂度为O(n)外,其余:入栈,出栈,取栈顶元素,判断栈是否为空时间复杂度为O(1);

const int N =10000;
int st[N];
int top =-1;
int main(){
//push 放入操作 ,栈顶所在索引往后移动一格,然后放入x
if(s=="push"){
int a;
cin>>a;
st[++top]=a;
}
//弹出操作:往前移动一格
if(s=="pop"){
top--;
}
//返回栈顶元素
if(s=="query"){
cout<<st[top]<<endl;
}
//大于等于0 栈非空,小于0 栈空
if(s=="empty"){
cout<<(top== -1 ?"YES":"NO")<<endl;
}
}

2.3队列

定义:只允许在一端插入 , 在另一端删除 ,具有 先进先出 的特点

队列示意图:

双端队列

只允许两端插入 , 删除 的线性表。

输入受限的双端队列,只允许一端插入,可以两端删除

删除受限的双端队列只允许一端删除,可以两端插入

队列的应用:

1.树的层次遍历

2.图的广度优先遍历

模拟队列

const int N =10000;
int q[N];
//[hh,tt]之间为队列
int hh =0; //队头位置
int tt =-1;//队尾位置
//入队:队尾先往后移动一格,再放入要插入的数据
void push(int x){
q[++tt] =x;
}
//出队:队头往后移动一格
void pop(){
hh++;
}
//判断是否为空
void empty(){
if(tt>=hh) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
//访问队头元素
void query(){
cout<<q[hh]<<endl;
}

2.4 串

即字符串 ,由0 或多个字符组成的有限序列 。串是一种特殊的线性表,数据元素之间呈线性关系

字符串匹配模式:

1.朴素模式 匹配输出算法

2.KMP 算法

KMP算法

S 为模式串  ; P为匹配串

next 数组实现

自己与自己进行匹配操作。

for(int i=2,j=0;i<=m;i++){
//从模式串P的第二个字符开始到最后一个字符结束
while(j&& p[i]!=p[j+1]) j=next[j];/ /如果j大于0 且p[i]不等于p[j+1] 则将j更新为next[j] 向前回溯
if(p[i]==p[j+1]) j++; //如果p[i]等于p[j+1] 则说明找到了一个更长的相同前缀,此时j可以增加
next[i]=j; //最后将计算得到的值进行赋值

KMP算法的实现:

const int N=10000,M=1000;//N为模式串长度 ,M为匹配串长度
int n,m;  //n为模式串长度   m为匹配串长度
int ne[M]; //next[]数组,避免和头文件冲突
char S[N],P[M];//S为模式串  P为匹配串
int mian(){
cin>>n>>s+1>>m>>p+1;//下标从1开始
// 求next[]数组
for(int i=2,j=0;i<=m;i++){
while(j &&p[i]!=p[j+1]) j =ne[j];
if(p[i]==p[j+1]) j++;
ne[i]= j;
}
//匹配操作
for(int i=1,j=0;i<n;i++){
while(j &&s[i]!=p[j+1]) j= ne[j];
if(s[i] == p[j+1]) j++;
if(j==m){ //满足匹配条件
//执行具体操作 如打印从0开始的匹配子串的首字母下标
j =ne[j]; //继续匹配
}
}
return0;}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值