基础算法 排序 1. 快排 确定一组数据 ,即q数组
左端点为l,右端点为r
(1)确定分界点
q[l] 或 q[ (l + r) / 2] 或 q[r] 或任意一个数 作为分界点,分界点数为x
(2)调整区间 (重点)
使 <= x的数放在左半边,使 >= x 的数放在右半边,两边的数可以乱序,暂时不用排序
(3)利用递归处理左右两端 ,进行排序
暴力法
(1)开两个额外数组a,b
(2)扫描 l 到 r 的所有数,将 <= x 的值放到a中,将 >= x的数放到b中
(3)将a,b排序后放到q数组中 a[ ] -> q[ ], b[ ]->q[ ];
(4)弊端:额外开辟空间,占用内存
简便方法(指针)
用两个指针i,j指向左端与右端的数
(1)判断指针i所指向的数是否小于x,符合条件时往右移一位,当大于等于x时指针i停下
(2)判断指针j所指向的数是否大于x,符合条件时往左移一位,当小于等于x时指针停下
(3)将i与j指针所指向的数交换,并再次重复1,2,3,直到i,j走到中间位置
(4)两边排序
模板 #include <iostream> #include <cstdio> #include <algorithm> using namespace std;const int N = 1000001 ;int n;int q[N];void quick_sort (int q[ ], int l, int r) { if (l >= r) return ; int x = q[l], i = l - 1 , j = r + 1 ,; while (i < j) { do { i++; } while (q[i] < x); do { j--; } while (q[j] > x); if (i < j) swap (q[i],q[j]); } if (l < j )quick_sort (q, l, j); if (i < r)quick_sort (q, j + 1 , r); } int main () { scanf ("%d" , &n); for (int i = 0 ;i < n;++i) scanf ("%d" , &q[i]); quick_sort (q, 0 , n-1 ); for (int i = 0 ;i < n;++i) printf ("%d " , q[i]); return 0 ; }
2. 归并排序 (方法和快排类似,但顺序不同)
有一组数据q[ ]
(1) 以数组中间作为分界点
(2)将两边利用递归排序
(3)归并,将两个数组合二为一(重点)
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <cstdio> using namespace std;const int N = 100010 ;int n;int q[N];int tmp[N];void merge_sort (int q[], int l, int r) { if (l >= r) return ; int mid = (l + r) >> 1 ; merge_sort (q, l, mid); merge_sort (q, mid+1 , r); int k = 0 , i = l, j = mid + 1 ; while (i <= mid && j <= r) { if (q[i] <= q[j] ) tmp[k++] = q[i++]; else tmp[k++] = q[j++]; } while (i <= mid) tmp[k++] = q[i++]; while (j <= r) tmp[k++] = q[j++]; for (int i = l, j = 0 ;i <= r;i++, j++) q[i] = tmp[j]; } int main () { scanf ("%d" , &n); for (int i = 0 ;i < n;++i) scanf ("%d" , &q[i]); merge_sort (q, 0 , n - 1 ); for (int i = 0 ;i < n;++i) printf ("%d " , q[i]); return 0 ; }
时间复杂度 快排的平均时间复杂度为 n * n,归并的时间复杂度为 n * log n 且log 以2为底,因为快排存在递归,所耗时间多,归并每个数据只移动一次,时间是线性的
二分 二分的本质 如果有单调性一定可以二分,但是可以二分的题目不一定非得有单调性(没有单调性,也有可能可以二分),二分的本质并不是单调性,单调性和二分没有直接的关系
单调性:如果想找一个中间值的话,如果这个点小的话,答案一定在右半边,这个点如果大的话,答案一定在左半边
二分的本质是边界:
1. 整数二分 (1) 找到中间值
模板1(分左半边区域):mid = ( l + r + 1 )/2,并判断该中间值是否满足性质,check该值是否属于左半边区域,如果满足,则一定在左半边区域,如果不满足,则在右半边区域,又继续判断新的mid值是否满足性质,以此类推
int bsearch_1 (int l,int r) { while (l < r) { int mid = l + r + 1 >> 1 ; if ( check (mid) ) l = mid; else r = mid - 1 ; } return l; }
目的:在升序,无重复元素的数组中寻找特定值的下标
模板2(分右半边区域):mid = (l + r)/2,并判断该数是否满足规律,如果不满足,则让 ,r= mid,如果不满足,则让l = mid + 1
int bsearch_2 (int l ,int r) { while (l < r) { int mid = l + r >> 1 ; if (chech (mid)) r= mid; else l = mid + 1 ; } return l; }
选择哪个模板,重点在于看下一次区间更新是l = mid,还是r = mid
如果是前者,mid则需要补1,如果是后者,则不需要补1
补1的原因:如果l = mid ,由于整数除法是下取整,有可能l 会一直等于mid,r一直不变,l,r不再更新,形成死循环
2. 浮点数二分 基本和整数二分类似,但无需考虑边界问题,是否会形成死循环
高精度 位数最大为1e6(10 的6 次方)
加减乘除四种存储方式是相同的
1.高精度加法 思想:分别用两个数组逆存储两个整数的值,数组下标为0的值存储个位,为1存在十位,以此类推;
再通过模拟加法进位来将得到的值的每一位存储的新数组当中,得到相加数
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> #include <string> using namespace std;const int N = 1000010 ;vector<int > add (vector<int > &A, vector<int > &B) { vector<int > C; int t = 0 ; for (int i = 0 ;i < A.size () || i < B.size ();++i) { if (i < A.size ()) t += A[i]; if (i < B.size ()) t += B[i]; C.push_back (t % 10 ); t /= 10 ; } if (t) C.push_back (1 ); return C; } int main () { string a, b; vector <int > A, B; cin >> a >> b; for (int i = a.size ( )-1 ;i >= 0 ;--i) A.push_back (a[i] - '0' ); for (int i = b.size ( )-1 ;i >= 0 ;--i) B.push_back (b[i] - '0' ); auto C = add (A, B); for (int i = C.size () - 1 ;i >= 0 ;--i) { cout << C[i]; } return 0 ; }
2.高精度减法 思想:分别用两个数组逆存储两个整数的值,数组下标为0的值存储个位,为1存在十位,以此类推;
提前判断A,B的谁大谁小,使得最终能够大数减小数,如果是B-A,还要提前打印一个负号
再通过模拟减法运算,将得到的值的每一位存储的新数组当中,得到相减数,最后注意前导0的存在
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> using namespace std;bool cmp (vector<int >& A, vector<int >& B) { if (A.size () != B.size ()) return A.size () > B.size (); for (int i = A.size () - 1 ;i >= 0 ;--i) { if (A[i] != B[i]) return A[i]>B[i]; } return true ; } vector<int > Sub (vector<int >& A, vector<int >& B) { vector<int > C; int t = 0 ; for (int i = 0 ;i < A.size ();++i) { t = A[i] - t; if (i < B.size ()) t -= B[i]; C.push_back ((t + 10 ) % 10 ); if (t < 0 ) t = 1 ; else t = 0 ; } while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; } int main () { string a, b; vector<int > A, B; cin >> a >> b; for (int i = a.size () - 1 ;i >= 0 ;--i) A.push_back (a[i] - '0' ); for (int i = b.size () - 1 ;i >= 0 ;--i) B.push_back (b[i] - '0' ); if (cmp (A, B)) { auto C = Sub (A, B); for (int i = C.size () - 1 ;i >= 0 ;--i) cout << C[i]; } else { auto C = Sub (B, A); cout << '-' ; for (int i = C.size () - 1 ;i >= 0 ;--i) cout << C[i]; } return 0 ; }
3.高精度相乘 (大整数x小整数) 思想:用一个数组逆存储一个整数的值,数组下标为0的值存储个位,为1存在十位,以此类推;用int 存储一个较小数
通过将大数每一位的值与B相乘,并且每次运算只把算得的个位传给C,下一次将上次除去个位的数加上这一次相乘的值,最后将留下来的未能放进C的数再放进C中,得到最终的相乘数
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <string> #include <vector> using namespace std;vector<int > Mul (vector<int > &A, int &b) { int t = 0 ; vector<int > C; for (int i = 0 ;i < A.size ();++i) { t += A[i] * b; C.push_back (t % 10 ); t /= 10 ; } while (t) { C.push_back (t % 10 ); t /= 10 ; } return C; } int main () { string a; vector<int > A; int b; cin >> a>>b; for (int i = a.size ()-1 ;i >= 0 ;--i) A.push_back (a[i] - '0' ); auto C = Mul (A, b); for (int i = C.size ()-1 ;i >= 0 ;--i) cout << C[i]; return 0 ; }
4.高精度相除(大整数/小整数) 思想:用一个数组逆存储一个整数的值,数组下标为0的值存储个位,为1存在十位,以此类推;用int 存储一个较小数
和之前一样,但这次是从高位到低位,目的是模拟除法。并通过每次的余数来推演下一次的运算,比如算1234/11,第一位得到的余数r是1,
然后通过将余数r*10+下一位的数,得到12,12/11得到余数1,以此类推,最后得到的就是两数之商,同时也可得到最终的余数
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> #include <string> using namespace std;vector<int > Div (vector<int > &A, int &b, int &r) { vector<int > C; r = 0 ; for (int i = A.size () - 1 ;i >= 0 ;--i) { r = r * 10 + A[i]; C.push_back (r / b); r %= b; } reverse (C.begin (), C.end ()); while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; } int main () { string a; int b; vector<int > A; cin >> a >> b; for (int i = a.size () - 1 ;i >= 0 ;--i) A.push_back (a[i] - '0' ); int r; auto C = Div (A, b,r); for (int i = C.size () - 1 ;i >= 0 ;--i) cout << C[i]; cout << endl; cout << r; return 0 ; }
前缀和 一维前缀和 思想:假设有原数组a1,a2,a3….an;
求前缀和si
则si = a1 + a2 + … + ai;
若要求区间[l,r]之间的前缀和,则只需将r的前缀和减去l - 1的前缀和即可
时间复杂度为O(1);
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <cstdio> using namespace std;const int N = 100010 ;int q[N];int s[N];int main () { int n,m; scanf ("%d %d" , &n,&m); for (int i = 1 ;i <= n;++i) scanf ("%d" , &q[i]); for (int i = 1 ;i <= n;++i) s[i] = s[i - 1 ] + q[i]; while (m--) { int l, r; scanf ("%d %d" , &l, &r); printf ("%d\n" , s[r] - s[l-1 ]); } return 0 ; }
二维前缀和 思想:有一二维数组q,将其看作一个长方形,其二维前缀和s[ i ] [ j ]就是从a[0] [0] 至 a [i] [j] 所有元素的和,
如果想要快速得到每一个s[ i ] [ j ],可以用公式推得
s[i] [j] = s[i - 1] [j] + s[i] [j - 1] - s[i - 1] [j - 1] + a[i] [j];
如果想要求出特定长方形x1,y1至x2,y2的长方形前缀和
则可以利用公式
sum = s[x2] [y2] - s[x1 - 1] [y2] - s[x2] [y1 - 1] + s[x1 - 1] [y1 - 1]
模板
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std ; const int N = 10010 ;int q[N][N];int s[N][N];int main () { int n, m,k; scanf ("%d%d%d" , &m, &n, &k); for (int i = 1 ;i <= m;++i) { for (int j = 1 ;j <= n;++j) scanf ("%d" , &q[i][j]); } for (int i = 1 ;i <= m;++i) for (int j = 1 ;j <= n;++j) s[i][j] = s[i - 1 ][j] + s[i][j - 1 ] - s[i-1 ][j-1 ]+q[i][j]; while (k--) { int x1, y1, x2, y2; scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2); printf ("%d\n" , s[x2][y2] - s[x1 - 1 ][y2] - s[x2][y1 - 1 ] + s[x1 - 1 ][y1 - 1 ]); } return 0 ; }
差分 一维差分 本质 前缀和的逆运算
原理 有一数组a,b,使得 ai = b1 + b2 + b3 + … + bi
例如 b1 = a1
b2 = a2 - a1;
b3 = a3-a2
bn = an-a(n-1);
b就称为a的差分,a就称为b的前缀和
使某一区域的前缀和被同步改变,
本质上是改变边界的值,使得中间连续值得以被间接改变
用处:且比原先(n)的时间复杂度优化为O(1)
例题模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <cstdio> using namespace std ; const int N = 10001 ;int n, m;int a[N];int b[N];void insert (int l, int r, int c) { b[l] += c; b[r + 1 ] -= c; } int main () { scanf ("%d%d" , &n, &m); for (int i = 1 ;i <= n;++i) scanf ("%d" , &a[i]); for (int i = 1 ;i <= n;++i) insert(i, i, a[i]); while (m--) { int l, r, c; scanf ("%d%d%d" , &l, & r, &c); insert(l, r, c); } for (int i = 1 ;i <= n;++i) b[i] += b[i - 1 ]; for (int i = 1 ;i <= n;++i) printf ("%d " , b[i]); return 0 ; }
二维差分 本质 与一维差分相同,也是前缀和的逆运算
原理 假设要改变存储前缀和的二维数组中特定矩阵发生改变
则使存储前缀和的二维数组某一坐标的右下角全部改变,并使多余部分减掉,由于需改变矩阵的右下角的矩阵被减掉两次,还需要使其重新加回来,成为原本的前缀和,从而得到一个存储前缀和的矩阵中特定的小矩阵发生改变
用处 降低原先笨方法的时间复杂度
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <cstdio> const int N = 1001 ;int n, m, q;int a[N][N];int b[N][N];using namespace std ; void insert (int x1, int y1, int x2, int y2, int c) { b[x1][y1] += c; b[x2 + 1 ][y1] -= c; b[x2][y1 + 1 ] -= c; b[x2 + 1 ][y2 + 1 ] += c; } int main () { scanf ("%d%d" , &n, &m, &q); for (int i = 1 ;i <= n;++i) for (int j = 1 ;j <= m;++j) scanf ("%d" , &a[i][j]); for (int i = 1 ;i <= n;++i) { for (int j = 1 ;j <= m;++j) insert(i, j, i, j, a[i][j]); } while (q--) { int x1, y1, x2, y2,c; scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2,&c); insert(x1, y1, x2, y2, c); } for (int i = 1 ;i <= n;++i) { for (int j = 1 ;j <= m;++j) b[i][j] += b[i - 1 ][j] + b[i][j - 1 ] - b[i - 1 ][j - 1 ]; } for (int i = 1 ;i <= n;++i) { for (int j = 1 ;j <= m;++j) printf ("%d " , b[i][j]); printf ("\n" ); } return 0 ; }
双指针算法 第一类 两个指针分别指向两个序列,例如归并排序
第二类 两个指针都指向一个序列,一个在区间的开头,另一个在区间的结尾,例如快排
双指针算法并非一定要用指针来表示
核心思想 先写出一种暴力做法,然后观察i与j之间是否存在关联,比如单调性,从而优化代码
for (int i = 0 ;i < n;++i) for (int j = 0 ;j < n;++j) if (check (i,j)) ...
将上方原先O(n^2)的朴素算法优化为O(n)
通用模板 for (i = 0 ,j = 0 ;i < n; ++i){ while (j < i && check (i,j)) j++; }
举例 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <string> using namespace std;int main () { string str; getline (cin,str); int len = str.size (); for (int i = 0 ;i < len;++i) { int j = i; while (j < len && str[j] != ' ' ) j++; for (int k = i;k < j;++k) cout << str[k]; cout << endl; i = j; } }
例题 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 10010 ;int n;int a[N], s[N];int main () { cin >> n; for (int i = 0 ;i < n;++i) cin >> a[i]; int res = 0 ; for (int i = 0 ,j = 0 ;i < n;++i) { s[a[i]]++; while (s[a[i]] > 1 ) { s[a[j]]--; j++; } res = max (res, i - j + 1 ); } cout << res << endl; return 0 ; }
例题题解说明:
a和s分别作为存储序列和存储每个元素重复次数的数组,
在每次for循环记录每个重复元素次数
如果大于1则利用双指针使得 j 能够追上i,即双指针算法的原理
i作为结尾,j作为开头,让j不断自增直到i与j相等
**此时 j 就可以作为新子序列的首元素坐标 **
并利用res记录每次的子序列长度
整个题解可以理解为,用res记录子序列的长度,如果不出现重复元素则让res由i-j+1得出,如果出现重复元素,先记录新子序列的长度,再与上一个子序列进行比较,每次遍历都运算一次,时间复杂度就为O(n),最后快速得出最长连续子序列的长度
主要作用 优化运算量,时间复杂度
位运算 最常用的两种操作
1.n的二进制表示中第k位是几 比如
n = 15 的 二进制 (1111 ) 从个位起算第0 位,十位为第二位,以此类推
操作符>> 把n的第k位移到最后一位
比如
n = 16 , k = 3 ;n == (0001 0000 )n >> k == (0010 )n = 15 ,k = 3 n == (0000 1111 )n >> k == (0000 0001 )
2.n的二进制个位是多少 比如
操作符& 按位与&,两个操作数按位与时,二进制某一位相同则为1,不同则为0
比如
n == 2 ,k = 1 n == (0010 )k== (0001 ) 四位都不相同 n &1 ==(0000 )
3.进阶运算 计算某数二进制某一位是多少
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;int main () { int n,k; cin >> n>>k; cout << ((n >> k) & 1 ) << endl; return 0 ; }
lowbit函数 返回n的二进制的最低位1,即返回最后一个是1的数,最后的1之后有多少0就再返回多少个0,再把它转化为十进制数
函数实现 int lowbit (int n) { return n&(-n); }
或者
int lowbit (int n) { return n&(~n+1 ) }
关于按位取反~的解释:按位取反 c语言基础难关:按位取反_涅槃豆的博客-CSDN博客](https://blog.csdn.net/niepandou/article/details/128422923?spm=1001.2014.3001.5501 ))
原理 n & (-n ) 实际上按位与的是他们的补码n 如果为1 n 的 补码为0000 0000 -n 的补码为 1111 1110 按位与得到的就是最低位1 与其之后的0 转化出的十进制数
模板 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;int my_lowbit (int n) { return n & (-n); } int main () { int n; cin >> n; cout << my_lowbit (n) << endl; return 0 ; }
运用 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;int lowbit (int n) { return n & (-n); } int main () { int n; cin >> n; int res = 0 ; while (n) { n -= lowbit (n); res++; } cout << res << endl; return 0 ; }
整数离散化 离散化的概念 对一组序列进行升序排列,然后用0映射,代表序列的最小数,1映射第二小的数,以此类推,与利用数组存储相似
如果序列中有重复元素,应当先去重删除重复元素再排序映射
对序列1 2 2 3 5离散化 排序得到1 2 2 3 5 去重并删除得到1 2 3 5 离散化映射 序列1 2 3 5 映射0 1 2 3
模板 int main () { vector<int > alls; sort (alls.begin (), alls.end ()); alls.erase (unique (alls.begin (), alls.end ()),alls.end ());
例题 1 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> #include <algorithm> using namespace std;int n,x;vector<int > alls; int find (int x) { int l = 0 , r = alls.size () - 1 ; while (l < r) { int mid = (l + r) >> 1 ; if (alls[mid] >= x) r = mid; else l = mid + 1 ; } return r; } int main () { cin >> n >> x; int number; for (int i = 0 ;i < n;++i) { cin >> number; alls.push_back (number); } sort (alls.begin (), alls.end ()); alls.erase (unique (alls.begin (), alls.end ()),alls.end ()); cout << find (x) << endl; return 0 ; }
2 假定有一个无限长的数轴,数轴上每个坐标上的数都是 0 。 现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c 。 接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l, r] 之间的所有数的和。 输入格式 第一行包含两个整数 n 和 m。 接下来 n 行,每行包含两个整数 x 和 c 。 再接下来 m 行,每行包含两个整数 l 和 r。 输出格式 共 m 行,每行输出一个询问中所求的区间内数字和。 数据范围 −109 ≤x ≤109 , 1 ≤n, m≤105 , −109 ≤l≤r≤109 , −10000 ≤c ≤10000
输入样例:
输出样例:
题解
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> #include <algorithm> using namespace std;typedef pair<int , int > PII;const int N = 300010 ;int n, m;int a[N], s[N];vector <int > alls; vector<PII> add, query; int find (int x) { int l = 0 , r = alls.size () - 1 ; while (l < r) { int mid = (l + r) >> 1 ; if (alls[mid] >= x) r = mid; else l = mid + 1 ; } return r + 1 ; } int main () { cin >> n >> m; for (int i = 0 ;i < n;++i) { int x, c; cin >> x >> c; add.push_back ({ x,c }); alls.push_back (x); } for (int i = 0 ;i < m;++i) { int l, r; cin >> l >> r; query.push_back ({l,r}); alls.push_back (l); alls.push_back (r); } sort (alls.begin (), alls.end ()); alls.erase (unique (alls.begin (),alls.end ()),alls.end ()); for (auto item : add) { int x = find (item.first); a[x] += item.second; } for (int i = 1 ;i <= alls.size ();++i) s[i] = s[i - 1 ] + a[i]; for (auto item : query) { int l = find (item.first), r = find (item.second); cout << s[r] - s[l - 1 ] << endl; } return 0 ; }
区间合并 概念:在有序升序序列中,如果某一区间合另一区间存在交集,则两个区间可以合并为一个区间
思考情况:
1.绿色区间在蓝色区间内部 2.橙色区间与蓝色区间有交集 3.粉色区间与蓝色区间无关联
最终得到新的合并区间[ st , ed ]
st全称start,ed全称end
例题 题目描述 给定 n 个区间 [li , ri] ,要求合并所有有交集的区间。 注意如果在端点处相交,也算有交集。 输出合并完成后的区间个数。 例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6] 。 输入格式 第一行包含整数 n。 接下来 n 行,每行包含两个整数 l 和 r。 输出格式 共一行,包含一个整数,表示合并区间完成后的区间个数。 数据范围 1 ≤ n ≤ 100000 ,−1 e9 ≤ li ≤ ri ≤ 1 e9
输入样例
输出样例
题解 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> #include <algorithm> using namespace std;typedef pair<int , int > PII;const int N = 100010 ;int n;vector<PII> segs; void merge (vector<PII>& segs) { vector<PII> res; sort (segs.begin (), segs.end ()); int st = -2e9 , ed = -2e9 ; for (auto seg : segs) { if (ed < seg.first) { if (ed != -2e9 ) res.push_back ({ st,ed }); st = seg.first, ed = seg.second; } else { ed = max (ed, seg.second); } } if (st != -2e9 ) res.push_back ({ st,ed }); segs = res; } int main () { cin >> n; for (int i = 0 ;i < n;++i) { int l, r; cin >> l >> r; segs.push_back ({ l,r }); } merge (segs); cout << segs.size () << endl; return 0 ; }
数据结构 链表与邻链表 0.传统的链表创建方式在运算速度上非常慢
struct ListNode { int val; struct ListNode * next; }; new struct ListNode ();
1.数组模拟单链表 数组模拟链表,也成为静态链表
链表的实现方式:
一开始用head指针存储链表头节点,head之后就是空指针
之后让head之后的指针指向新节点,以此类推,直到最后一个节点,再让下一个节点指向空指针
定义e[n]为该节点的值,ne[n]为该节点的next指针
e[0] = 3,ne[0] = 1(指节点1)
e[1] = 5,ne[1] = 2(指节点2)
…
e[3] = 9,ne[3] = -1(空节点)
如何在头节点插入一个节点(算法题常用) 链表需要数形结合
1.将所想要插入的值的next指针指向0节点
2.将head的next指针指向该值
3.存储该值
代码实现
void add_to_head (int &x) { ne[idx] = head; head = idx; e[idx] = x; idx++; }
如何在任意位置插入一个值 1.首先需要直到该位置的上一节点k
2.与头节点插入类似,先让该值的next变为k的next指针
3.让k的next指针指向该节点
4.存储该值
实现代码
void add (int & k, int & x) { ne[idx] = ne[k]; ne[k] = idx; e[idx] = x; idx++; }
如何删除一个节点 链表节点的删除本质上是跳过该节点
实现代码
void remove (int & k) { ne[k] = ne[ne[k]]; }
总实现代码 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 100010 ;int head, e[N], ne[N];int idx;void Init () { head = -1 ; idx = 0 ; } void add_to_head (int &x) { ne[idx] = head; head = idx; e[idx] = x; idx++; } void add (int & k, int & x) { ne[idx] = ne[k]; ne[k] = idx; e[idx] = x; idx++; } void remove (int & k) { ne[k] = ne[ne[k]]; } int main () { Init (); }
例题 题目描述
实现一个单链表,链表初始为空,支持三种操作: 向链表头插入一个数; 删除第 k个插入的数后面的数; 在第 k个插入的数后插入一个数。 现在要对该链表进行 M次操作,进行完所有操作后,从头到尾输出整个链表。 注意: 题目中第 k个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。 接下来 M行,每行包含一个操作命令,操作命令可能为以下几种: H x ,表示向链表头插入一个数 x 。 D k,表示删除第 k个插入的数后面的数(当 k 为 0 时,表示删除头结点)。 I k x ,表示在第 k个插入的数后面插入一个数 x (此操作中 k 均大于 0 )。
输出格式
共一行,将整个链表从头到尾输出。 数据范围 1≤M≤10000
题解
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 10010 ;int idx, e[N], ne[N];int head;void Init () { head = -1 ; idx = 0 ; } void add_to_head (int x) { e[idx] = x; ne[idx] = head; head = idx; idx++; } void add (int k, int x) { e[idx] = x; ne[idx] = ne[k]; ne[k] = idx; idx++; } void remove (int k) { ne[k] = ne[ne[k]]; } int main () { int m; cin >> m; Init (); while (m--) { char ch; int k, x; cin >> ch; if (ch == 'H' ) { cin >> x; add_to_head (x); } else if (ch == 'D' ) { cin >> k; if (!k) head = ne[head]; else remove (k-1 ); } else { cin >> k >> x; add (k-1 , x); } } for (int i = head;i != -1 ;i = ne[i]) { cout << e[i] << ' ' ; } return 0 ; }
邻接表:
将每个单链表连接起来,就是邻接表
邻接表的应用:
存储树和图
2.数组模拟双链表 双链表有两个方向,一个指向前,另一个指向后,而单链表只能指向后
因此也可以看出单链表插入时只能插入在往后的方向
需要的数组有e[n],l[n],r[n]和下标idx
e[n]表示该点的值
l[n]表示该点左边节点的指针
r[n]表示右边节点的指针,与单链表next同理
idx表示第几次操作数
head默认为0,tail默认为1
对于插入节点和删除节点的操作与单链表相类似
插入节点 1.存储要插入节点的值(可以放在任意位置)
2.改变k的右节点和插入值的左节点,让k和idx连接
3.改变k原先右节点的左节点和插入值的右节点,让k原先的右节点和插入值连接
4.最后操作数+1
实现代码
void addr (int k,int &x) { e[idx] = x; r[idx] = r[k]; l[idx] = k; l[r[k]] = idx; r[k] = idx; idx++; }
如果想要在k的左边插入一个节点,可以理解为在k的左节点的后面插入一个点,也就是add( l [ k ] , x )
删除节点 让该节点的左节点所指的next直接指向该节点的右节点
让该节点的右节点所指的last直接指向该节点的左节点
实现代码
void remove (int k) { r[l[k]] = r[k]; l[r[k]] = l[k]; }
总实现代码 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 10010 ;int e[N], l[N], r[N], idx;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; idx++; } void remove (int k) { r[l[k]] = r[k]; l[r[k]] = l[k]; } int main () { Init (); }
栈 栈的特点 先进后出,和一个半封闭容器类似,放了一堆肉饼,先放的肉饼后倒出来
stk[n]数组代表栈,tt代表栈点
关于栈的插入 void add (int x) { stk[++t] = x; }
关于栈的删除
关于判断栈是否为空 bool stkempty () { if (tt > 0 ) return false ; else return true ; }
总实现代码 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 10010 ;int stk[N], tt;void add (int x) { stk[++t] = x; } void del ( ) { tt--; } bool stkempty () { if (tt > 0 ) return false ; else return true ; } int main () { return 0 ; }
例题 问题描述
有一序列,输出每个数左边第一个比他小的数,没有则输出-1
诀窍:在while循环判断条件时,只要想该数不符合条件则跳过即可
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <cstdio> using namespace std;const int N = 10010 ;int stk[N], tt;int main () { int n; scanf ("%d " , &n); for (int i = 0 ;i < n;++i) { int x; scanf ("%d " , &x); while (tt && stk[tt] >= x) tt--; if (tt) printf ("%d\n" , stk[tt]); else printf ("-1\n" ); stk[++tt] = x; } }
队列 队列的特点 先进先出,和枪上膛相似,先插进去的子弹先打出来
q[N]表示队列,hh表示对头,tt表示队尾
关于队列的插入 void add (int x) { q[++tt] = x; }
关于队列的删除(弹出)
关于队列的判断是否为空 bool qempty () { if (hh <= tt) return false ; else return true ; }
总实现代码 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 10010 ;int q[N], hh, tt = -1 ;void add (int x) { q[++tt] = x; } void del ( ) { hh++; } bool qempty () { if (hh <= tt) return false ; else return true ; } int main () { return 0 ; }
例题 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;const int N = 1000010 ;int a[N],q[N],hh,tt;int main () { int n,k; scanf ("%d %d" , &n,&k); hh = 0 , tt = -1 ; for (int i = 0 ;i < n;++i) scanf ("%d" , &a[i]); for (int i = 0 ;i < n;++i) { if (hh <= tt && i - k + 1 > q[hh]) hh++; while (hh <= tt && a[q[tt]] >= a[i]) tt--; q[++tt] = i; if (i >= k - 1 ) printf ("%d " , a[q[hh]]); } cout << endl; hh = 0 , tt = -1 ; for (int i = 0 ;i < n;++i) { if (hh <= tt && i - k + 1 > q[hh]) hh++; while (hh <= tt && a[q[tt]] <= a[i]) tt--; q[++tt] = i; if (i >= k - 1 ) printf ("%d " , a[q[hh]]); } cout << endl; return 0 ; }
大 佬 您 都 看 完 了 , 不 妨 点个 关 注 或 者 点 赞 收 藏 一 下 吧