数组模拟单链表(静态链表)
链表
数据对象的存储结构可以分为两种:0顺序存储结构和链式存储结构
顺序存储结构要求所有的元素依次存放在一片连续的存储空间中
链式存储结构无需占用一整块存储空间,但是为了表示节点之间的关系,需要给每一个节点附加指针字段,用于存放后继元素的存储地址。
单链表的数组模拟实现
1 |
|
单链表的局限性
单链表不能在链表的任意位置插入一个数据,因为单链表只能找到某一个数据的后继,而不能找到一个数据的前导。
要解决这一个问题,要使用到双链表。
数据对象的存储结构可以分为两种:0顺序存储结构和链式存储结构
顺序存储结构要求所有的元素依次存放在一片连续的存储空间中
链式存储结构无需占用一整块存储空间,但是为了表示节点之间的关系,需要给每一个节点附加指针字段,用于存放后继元素的存储地址。
1 | #include <iostream> |
单链表不能在链表的任意位置插入一个数据,因为单链表只能找到某一个数据的后继,而不能找到一个数据的前导。
要解决这一个问题,要使用到双链表。
在一个很大的范围内 , 只有 很小的数据容量。
使用离散化,将这些数据 存储在从 1 开始的数组里 , 再进行后续的处理。
1 | vector<int> alls; // 存储所有待离散化的值 |
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 xx 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l ,r]
之间的所有数的和。
1 | #include <iostream> |
lowbit( )
作用: 获取一个二进制数的 最后一位 1 及以后的数
1 | int lowbit() |
给定一个长度为 n的数列,请你求出数列中每个数的二进制表示中 1 的个数。
1 | #include <iostream> |
n >> k & 1
双指针算法,可以优化一些朴素算法,减少遍历的次数 ,降低算法的时间复杂度。
1 | for (int i = 0, j = 0; i < n; i ++ ) |
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
1 | #include <iostream> |
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i] + B[j] = x
A[i]+B[j]= x
的数对 (i,j)(i,j)
。
数据保证有唯一解。
求目标和的时候双指针一个指向首位,一个指向末尾
1 | #include <iostream> |
前缀和数组s[ ]
,即数列的前 n 项和,作用是快速求出数列中连续一段数字的总和,降低算法复杂度
前缀和的求法很简单 , 以下只放一小段代码
1 | for (int i = 1; i <= n; i++) |
求其中一小段 l 到 r 中的所有数的和。
s[r] - s[l - 1]
在一个二维的矩阵中同样可以使用前缀和来简便地计算某一个矩形的和
前缀和数组 s[i][j]
表达的意思是以点(i , j)
为右下角坐标, 以零点为左下角坐标的矩形的和 , 利用这个定义得出以下代码,是每一个坐标的二维前缀和数组
1 | s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j] |
利用这个求出任意矩形的和 ( x1 , y1) 和 ( x2 , y2) 确定的矩形的和
1 | s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]) |
附上一个求子矩阵的模板题及代码
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
1 | #include <iostream> |
差分和前缀和是一组逆运算。
计算差分的前缀和就可以得到原数组 , 计算原数组的前缀和就可以得到前缀和数组
差分就是一个数组中相邻两个数的差值 , 使用差分可以快速便捷地将数组的某一连续段的每一个全部加上一个相同的值,不需要遍历每一个数。
使用一道例题来解释形式和用法
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c l,r,c,表示将序列中 [l,r][l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
1 | #include <iostream> |
改变二维差分的意义: 以此坐标为左上角 确定的矩阵中所有数 都 加 或 减去 c ,
实现方法与二维前缀和相似,加加减减的。似乎有容斥原理的思想。
代码模板:
1 | #include <iostream> |
大整数以数组的方式储存,把每一位都存进数组,把个位存进低位数组, 即倒着存。目的是方便进位,只需在数组后面再添加一个元素即可。
模拟加法运算的竖式,两个数组中的数分别相加 ,定义一个变量来模拟进位
1 | #include <iostream> |
减法同样是遵循竖式运算的方式,相较于加法, 需要处理的点从进位变成了借位
1 | #include <iostream> |
这里的高精度乘法指的是一个大数乘一个小数,并不是一个大数乘 一个大数。
同样的模拟竖式的乘法。和加法是类似进位机制。
1 | #include <iostream> |
同样是一个高精度的数除以一个低精度的数
同样是模拟竖式计算除法的每一步
1 | #include <iostream> |
使用二分的目的是寻找到某一个符合要求的数, 因为数量的 庞大,不可能把整个数组全部都遍历,所以使用二分来减少算法的复杂度。
在一个区间的内部 ,来寻找边答案所在的区间,在这个区间里面继续缩小区间。
当区间的长度是一的时候区间里面的就是所求的答案
二分模板一定是有解的 , (即定义的性质一定是有边界的),但是不一定是题目需求的答案,这时根据题目判断有没有解。
假设目标值在闭区间 中,[l, r]
每次将区间长度缩小一半,当l = r
时,我们就找到了目标值。
下面就是两种模板:
当我们将区间[l, r]
划分成[l, mid]
和[mid + 1, r]
时,其更新操作是r = mid
或者l = mid + 1
;,计算mid
时不需要加1。
1 | int bsearch_1(int l, int r) |
当我们将区间[l, r]
划分成[l, mid - 1]
和[mid, r]
时,其更新操作是r = mid - 1
或者l = mid
;,此时为了防止死循环,计算mid
时需要加1。
1 | int bsearch_2(int l, int r) |
记住这两个模板就可以应对基本所有的二分题目了
浮点数的二分比整数的二分简单,不用考虑边界设置导致的死循环
唯一需要注意的是结束二分的条件的改变。
从l == r
变为判断浮点数的差值 r - l < 1e-6
一般设置判断差值的时候将,设置为比题目要求更往后的两位,来保证答案的准确性。
代码模板:
1 | int bsearch_1(int l, int r) |
归并排序,即通过先递归再排序的方法来实现排序。
1 | #include <iostream> |
逆序对: 一个数组中 , 下标 i , j 两个数 ,满足 :i < j 且 a[ i ] > a[ j ]
用归并排序的过程完成逆序对数量的计算。
进行归并操作时 , 其中一个判断正好符合逆序的定义
1 | while (i <= mid && j <= r) //两组指针指着的数谁大就把谁塞进去。 |
使用两个指针分别指向左右两端 i j
移动左边的 i,直到遇到第一个大于等于X的数停下
移动右边的 j, 直到遇到第一个小于等于X的数停下
交换两个指针所指的数,实现 i 左边 <=X , j 右边 >= X
交换完成后继续进行以上步骤, 直到 i >= j ,实现顺序交换
1 | #include <iostream> |
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。
1 | #include <iostream> |
不知不觉大学的生活已经完完整整的过去了一年,回顾下这一年都学到了什么东西。似乎不能完全的表达出来。就是一种,好像学了但是并没有什么收获的感觉。在自己学过的一些内容中不乏牺牲考试课程而投入了时间。
想来,这是没有记录和复习的缘故,对于本专业的内容来说,记在纸质笔记本上显然是不合适的,markdown的语法记录才是最合适的方法。
想要随时对自己的笔记进行查看和复习,只保存在本地的文件显然是不能满足这个要求的。一个个人博客网站是最佳的解决方案。
查阅了很多教程,最终选择了较为简单的方式,使用GitHub page + Hexo的进行博客的搭建,现在已经有了初步的基本功能,将来会进行美化和完善。
Hexo 是一个部署个人博客的工具,为了一个比较好记的网站,而不是GitHub.io的网址,另购买了本站的snowdawn.top 的域名。原本的设想是.com为结尾的域名,但是比top贵了好多。
希望这个花了心思搭建的网站会是我大学学习过程中最好的见证与记录。它的页面也一定会越来越美好。