第二周学习心得
单链表与双链表
一,单链表
(1)定义
单链表是由一系列节点组成的线性数据结构,每个节点包含数据部分和一个指向下一个节点的指针。链表的起始节点称为头节点,末尾节点的指针指向null,表示链表的结束。
(2)特点
动态大小:单链表可以根据需要动态地添加或删除节点。
高效的插入和删除:相较于数组,链表在插入和删除数据时无需移动其他元素,仅需修改指针即可。
顺序访问:单链表不支持随机访问,数据的访问必须从头节点开始,顺序进行。
(3)优缺点
优点
动态分配内存,内存利用率高。
插入和删除操作简单,时间复杂度通常为O(1)。
缺点
查找操作效率低,最坏情况下时间复杂度为O(n)。
只能单向遍历,某些情况下操作不够灵活。
(4)代码展示
//单链表的插入操作
void init(){
head = -1;//头结点下标
idx = 0;//存储当前已经用到了哪个点
}
//头部插入
void add_head(int x){
e[idx]=x;
ne[idx]=head;
head=idx;
idx++;
}
void add(int k,int x){
d[idx]=x;
ne[idx]=ne[k];
ne[k]=idx;
idx++;
}
//单链表的删除操作
void remove(int k){
ne[k]=ne[ne[k]];
}
二,双链表
(1)定义
双链表在单链表的基础上,每个节点除了包含数据部分和指向下一个节点的指针外,还增加了一个指向前一个节点的指针。这种结构允许链表从两个方向进行遍历。
(2)特点
双向遍历:既可以向前查找也可以向后查找,提高了遍历效率。
灵活的节点操作:在任何位置插入或删除节点都非常方便。
(3)优缺点
优点
双向遍历,使用更灵活。
对于链表中间的插入和删除操作更高效。
缺点
每个节点需要额外空间存储另一个指针,增加了内存开销。
实现比单链表复杂,维护成本较高。
(4)代码展示
//双链表的插入操作
void init(){
r[0]=1;
l[1]=0;
idx=2;
}
void add(int k,int x){
e[idx]=x;
r[idx]=r[k];
l[idx]=k;
l[r[k]]=idx;
r[k]=idx;
}
//双链表的删除操作
void remove(int k){
r[l[k]]=r[k];
l[r[k]]=l[k];
}
这是两道经典的题目,感兴趣的小伙伴可以尝试AC一下
三,栈
(1)定义:栈是计算机科学中一种非常基础且广泛使用的数据结构,遵循
*后进先出
的原则。
这意味着最后被添加到栈中的元素将是第一个被移除的元素。
(2)代码展示
//栈操作
int stk[n],tt;
//插入
stk[++k]=x;
//弹出
tt--;
//判断栈是否为空
if(tt>0)
no empty
else
empty
//栈顶
stk[tt];
这是栈的题目,感兴趣的小伙伴可以尝试AC一下
四,队列
(1)定义:队列是计算机科学中一种基本的数据结构,遵循
先进先出
的原则。
这意味着最先被添加到队列中的元素将是第一个被移除的元素。
(2)代码展示
//队列操作
//在队尾插入元素,在队头弹出
int q[n],hh,tt=-1;
//插入
q[++tt]=x;
//弹出
hh++;
//判断队列是否为空
if(hh<=tt)
no empty
else
empty
//取出队头元素
q[hh];
这是队列的题目,感兴趣的小伙伴可以尝试AC一下
五,kmp字符串
(1)定义:KMP算法是一种高效的字符串匹配算法,旨在减少在主串中查找模式串时的回溯次数,以提高匹配效率。
(2)时间复杂度O(n).
(3)kmp算法过程:
1---- 预处理模式串
首先,KMP算法通过预处理模式串来生成一个部分匹配表该数组用于在发生不匹配时指示模式串应该如何移动。
2----搜索
在主串和模式串匹配过程中,我们同时遍历主串(text)和模式串(pattern):
当当前字符匹配成功(即text[i] == pattern[j]),则i和j都自增1,继续比较下一个字符。
当当前字符匹配失败(即text[i] != pattern[j]),如果j不在模式串的开头(j != 0),我们将j移动到LPS[j-1]的位置,这是基于之前已经匹配成功的“最长相同前后缀”的长度。如过j已经在模式串的开头,那么i就自增1,继续比较下一个字符。
3----匹配成功或者失败
(4)代码实现
//求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;
}
// kmp匹配
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)
{
j = ne[j];
}
}
这是kmp的题目,感兴趣的小伙伴可以尝试AC一下
六,堆排序
(1)定义
堆排序是一种基于比较的排序算法,和二叉树的结构比较相似。
(2)步骤
步骤一:建立堆
从最后一个非叶子节点开始,向上构造最大堆(或最小堆)。最后一个非叶子节点可以通过数组的长度计算得出,位置为(arr.length - 1) / 2。
步骤二:堆调整
移除位于根节点的最大(或最小)元素,与堆的最后一个元素交换。
将剩余的堆继续调整为最大堆(或最小堆)。
重复步骤直到堆的大小为1。
(3)优势与劣势
优势:堆排序在最好、最坏和平均情况下的时间复杂度均为O(n log n)
,空间复杂度为O(1),非常省空间,不像归并排序需要开临时数组。
劣势:它是一种不稳定的排序方法,因为在堆顶元素被取出,与堆的最后一个元素交换后,相等元素的相对顺序可能会发生改变。
(4)代码实现
//堆排序
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;//左儿子
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//右儿子
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
//交换函数
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
这是堆排序的题目,感兴趣的小伙伴可以尝试AC一下