68、最大的leftmax与rightmax之差的绝对值
69、求最大子数组和
70、字符串是否可循环右移得到某字符串
71、字符串右移K位后的字符串,原地调整
72、生成窗口最大值数组
73、拼接字符串,使其字典顺序最小
74、占用会议室问题
75、Morris遍历
76、搜索二叉树
77、平衡树AVL
78、SB树、红黑树
79、跳表
一、第一次课(2017.11.11)
1、时间复杂度计算
描述一个流程中常数操作数的指标。只看高阶项,忽略常数系数。
2、冒泡排序、利用位运算交换元素
2.1 利用位运算交换
已知:a^a=0 a^0=a
void swap(int &a ,int &b)
{
a = a^b; //
b = a^b; //b=a^b^b=a
a = a^b: //a=a^b^a=b
}
注意,这种操作要保证两个数是不同地址的数,否则会发生自己跟自己异或结果为0的情况。
2.2 冒泡排序
每次都首项开始依次向后比较,将大的数向后传送,找到最大的那个,放到末尾。第二次时找到倒数第二大的数。
时间:O(N^2)
空间:O(1)
可做到稳定性
void bubble_sort(vector<int>&a)
{
for(int i=a.size()-1;i>0;i--)
{
for(int j=0;j<i;j++)
{
if(a[j+1]>a[j])
swap(a[j+1],a[j]);
}
}
}
3、插入排序
类似摸牌,从数组第二个位置开始,依次向前面已经排好序的数组中插入。
时间:O(N^2)
空间:O(1)
可做到稳定性
void insert_sort(vector<int>&a)
{
for(int i=1;i<a.size();i++)
{
for(int j=i;j>0;j--)
{
if(a[j]<a[j-1])
swap(a[j],a[j-1]);
}
}
}
4、选择排序
从第一个位置开始,从后开始找到最小的那个,放在第一个位置;
继续从第二个位置往后找第二小,放第二个位置;
…….
时间:O(N^2)
空间:O(1)
不可做到稳定性
void select_sort(vector<int>&a)
{
for(int i=0;i<a.size()-1;i++)
{
int min = i;
for(int j=i+1;j<a.size();j++)
{
if(a[j]<a[min])
min=j;
}
swap(a[i],a[min]);
}
}
5、随机快排
时间:O(NlogN)
空间:O(logN)
常规实现做不到稳定性,有论文实现,不必考虑
利用递归,每次随机选取一个值,将其作为基准值,小于它的放左边,等于的放中间,大于的放右边,然后递归调用左右两边。
vector<int> partition(vector<int>a, int l, int r)
{
int less = l-1;
int more = r;
while(l<more)
{
if(a[l]<a[r])
swap(a[++less], a[l++]);
else if(a[l]>a[r])
swap(a[--more], a[l]);
else
l++;
}
swap(a[r],a[more]);
vector<int>p;
p.push_back(less+1);
p.push_back(more);
return p;
}
void quick(vector<int>a, int l, int r)
{
if(l>=r)
return;
int pindex = rand()%(r-l+1);
swap(a[l + pindex],a[r]);
vector<int>p = partition(a, l, r);
quick(a, l, a[p[0]-1]);
quick(a, a[p[1]+1], r);
}
void quick_sort(vector<int>a)
{
if (a.size()<2)
return ;
quick(a, 0, a.size()-1);
}
6、二分查找
在已经排好序的数组中查找某个数是否存在。一般是取中间值比较,等于则存在,大于则递归调用右边,小于则递归调用左边。
bool binary(vector<int>a, int x)
{
int l = 0;
int r = a.size()-1;
while(l<r)
{
int mid = l + (r-l)>>1;
if (a[mid]==x)
return true;
if (a[mid] > x)
l = mid + 1;
if (a[mid] < x)
r = mid - 1;
}
return false;
}
7、Master公式
对于递归行为的复杂度,可以用公式来解决。如果一个N规模的程序可以分为N/b个规模,一共做a次(a次递归,每次N/b次操作),或者还带有其他非递归的复杂度,如下:
T(N)=aT(Nb)+O(Nd)
T(N)=aT(\frac{N}{b}) + O(N^d)
如果
logba>d
log_ba>d,则
T(N)=O(Nlogba)
T(N)=O(N^{log_ba})
如果
logba<d
log_ba<d,则
T(N)=O(Nd)
T(N)=O(N^{d})
如果
logba=d
log_ba=d,则
T(N)=O(NdlogN)
T(N)=O(N^dlogN)
二、第二次课(2017.11.12)
8、归并排序
递归划分两组,直到不能再分,组内有序,两两合并排序,需要额外数组。
时间:O(NlogN)
空间:O(N)
void merge(vector<int>&a, int l, int mid, int r)
{
vector<int>help(r-l+1);
int p1 = l;
int p2 = mid + 1;
int i = 0;
while(p1<=mid && p2<=r)
help[i++] = a[p1]<a[p2]?a[p1++]:a[p2++];
while(p1<=mid)
help[i++] = a[p1++];
while(p2<=r)
help[i++] = a[p2++];
for(i = 0;i<help.size();i++)
a[l+i] = help[i];
}
void merge_sort(vector<int>&a, int l, int r)
{
if(l == r)
return;
int mid = l + (r-l)>>1;
merge_sort(a, l, mid);
merge_sort(a, mid+1, r);
merge(a, l, mid, r);
}
merge_sort(a, 0, a.size()-1);
9、利用归并排序求小和
小和就是某个数之前所有比它小的数的和。求小和就是求整个数组的小和之和。
如:3 5 1 4 6
,其小和就是3+(3+1)+(3+5+1+4)=20
利用归并排序,每次两两合并时,如果左边组一个数比右边组一个数小,那他肯定比右边组那个数及其后面的数都小。
用O(NlogN)解决。
int merge(vector<int>&a, int l, int mid, int r)
{
vector<int>help(r-l+1);
int p1 = l;
int p2 = mid + 1;
int i = 0;
int sum = 0;
while(p1<=mid && p2<=r)
sum += a[p1]<a[p2]?(a[p1]*(l-p2+1)):0;
help[i++] = a[p1]<a[p2]?a[p1++]:a[p2++];
while(p1<=mid)
help[i++] = a[p1++];
while(p2<=r)
help[i++] = a[p2++];
for(i = 0;i<help.size();i++)
a[l+i] = help[i];
return sum;
}
int merge_sort(vector<int>&a, int l, int r)
{
if(l == r)
return;
int mid = l + (r-l)>>1;
return merge_sort(a, l, mid)+merge_sort(a, mid+1, r)+merge(a, l, mid, r);
}
merge_sort(a, 0, a.size()-1);
10、利用归并排序求逆序对
逆序对是一个数,前面有比他大的数,这两个数就构成了逆序对。求数组中有多少个逆序对。用O(NlogN)解决。
利用归并排序,当两个组在合并时,如果左边组的一个数比右边组的一个数大,那左边组这个数及其后面的数都会比右边组的这个数大,这些数就构成了逆序对。
int merge(vector<int>&a, int l, int mid, int r)
{
vector<int>help(r-l+1);
int p1 = l;
int p2 = mid + 1;
int i = 0;
int sum = 0;
while(p1<=mid && p2<=r)
sum += a[p1]>a[p2]?(mid-p1+1):0;
help[i++] = a[p1]>a[p2]?a[p2++]:a[p1++];
while(p1<=mid)
help[i++] = a[p1++];
while(p2<=r)
help[i++] = a[p2++];
for(i = 0;i<help.size();i++)
a[l+i] = help[i];
return sum;
}
int merge_sort(vector<int>&a, int l, int r)
{
if(l == r)
return;
int mid = l + (r-l)>>1;
return merge_sort(a, l, mid)+merge_sort(a, mid+1, r)+merge(a, l, mid, r);
}
merge_sort(a, 0, a.size()-1);
11、堆排序、建堆、调整 堆
算法上的堆是一个完全二叉树,已知节点i,其父节点为(i-1)/2,左孩子为2i+1,右孩子为2i+2。
使用大根堆,任何一个节点,都是其子树中值最大的点。
堆排序,就是把整个数组变成一个大根堆,然后将根节点与尾节点交换,再向下调整大根堆。
如何建立大根堆:将节点依次插入堆中,如果碰到比自己的父节点大,则与其交换,递归向上调整。
根节点与尾节点交换后如何调整大根堆:向下调整,与左右孩子比较,若大于他们,不需要调整,如果小于,则跟其中大的孩子交换。然后递归向下调整。
void heapinsert(vector<int>&a, int index)
{
while(a[index]>a[(index-1)/2])
{
swap(a[index],a[(index-1)/2]);
index = (index-1)/2;
}
}
void heapify(vector<int>&a, int index, int size)
{
int left = 2*index+1;
while(left<size)
{
int largest = ((left+1<size) && (a[left+1]>a[left]))?left+1:left;
largest = a[largest]>a[index]?largest:index;
if (largest==index)
break;
swap(a[largest], a[index]);
index = largest;
left = 2*index+1;
}
}
void heap_sort(vector<int>&a)
{
if(a.size()<2)
return;
for(int i=1;i<a.size();i++)
heapinsert(a, i);
int size = a.size();
swap(a[0],a[--size]);
while(size>0)
{
heapify(a, 0, size);
swap(a[0],a[--size]);
}
}
12、桶排序
分为计数排序和基数排序。分开说。
这种不基于比较的排序,可实现时间复杂度为O(N),但浪费空间,对数据的位数与范围有限制。
13、比较器的使用
使用系统的默认排序函数sort时,是从小到大的,你可以改变比较器实现不同方式的排序。
bool mycompare1(int a, int b)
{//降序
return a - b > 0;
}
// 注意不能加等于号!
bool mycompare2(int a, int b)
{//升序
return b - a > 0;
}
sort(A.begin(), B.end(), mycomapre1);
14、计数排序
根据数组中的最大值max准备max+1个桶,每个桶记录数组中的这个数出现的次数。然后根据桶中的数字一次倒出每个数即可。
void bucket_sort(vector<int>&a)
{
if(a.size()<2)
return;
int max = INT_MIN;
for (int i=0;i<a.size();i++)
max = a[i]>max?a[i];max;
vector<int>bucket(max+1,0);
for(int i=0;i<a.size();i++)
bucket[a[i]]++;
int j=0;
for(int i=0;i<bucket.size();i++)
{
while(bucket[i]-->0)
a[j++] = i;
}
}
15、排序后最大相邻数差值问题
即n个数,则准备n+1个桶,最后一个桶放最大值max,其他min~(max-1)的数在前n个桶桑平分。比如说数i,它放在哪个桶中就取决于:((i-min)/(max-min))*n。
如果说有一个桶为空,则其左边第一个不为空的桶的最大值a,与其右边第一个不为空的桶的最小值b,这两个数在排序后一定是相邻的,且他们的差值比每个桶内的差值都大。所以只需要记录这些每个桶内的max和min,比较相邻的前后非空桶的min-max与桶内的max-min的差值大小即可。
所以准备三个n+1大小的数组,一个数组记录这个桶是否为空,另外两个数组分别记录这个桶内的max和min。
int maxgap(vector<int>a)
{
if(a.size()<2)
return 0;
int imax = INT_MIN;
int imin = INT_MAX;
for(int i=0;i<a.size();i++)
{
imax = a[i]>imax?a[i]:imax;
imin = a[i]<imin?a[i]:imin;
}
if(imin==imax)
return 0;
vector<bool>isempty(a.size()+1);
vector<int>amax(a.size()+1);
vector<int>amin(a.size()+1);
int index=0;
for(int i=0;i<a.size();i++)
{
index = (a[i]-imin)* a.size()/(imax-imin);
amax[index] = amax[index]?max(amax[index], a[i]):a[i];
amin[index] = amin[index]?min(amin[index],a[i]):a[i];
isempty[index] = true;
}
int gap = isempty[0]?amax[0]-amin[0]:0;
int lastmax = amax[0];
for (int i=1;i<isemppty.size();i++)
{
if(isempty[i])
{
gap = max(gap, amin[i]-lastmax);
lastmax = amax[i];
}
}
return gap;
}
16、KMP算法
想看详细介绍,请参见博客:
http://blog.csdn.net/liuxiao214/article/details/78026473
字符串匹配问题,字符串p是否是字符串s的子串,是的话,返回p在s中的起始位置,否的话返回-1。
暴力的方法是p与s的第一个字符比较,相等就继续比较,不相等就换s的第二个字符继续比较,这样做的话,遍历s的指针i会发生回溯,导致时间复杂度为O(N^2)。
那最好是i指针不用发生回溯。这里就用到了字符串的最大公共前缀后缀。
首先是找字符串p的最大公共前缀后缀,比如ABCAB,其最大公共前缀后缀就是AB,长度是2。这样当我们发生不匹配时,直接按照其最大公共前缀后缀长度移动字符串p就可以。
当然我们不直接使用最大相同公共前缀后缀,我们使用的是next数组,这个数组中的数是不包含当前字符的最大前缀后缀的长度,如图:
然后这样字符串匹配时,如果发生不匹配,遍历字符串s的指针i不必回溯,只需移动字符串p即可,即更改遍历p的指针j,j变为其next数组中的值。
求next数组利用递归。直接上代码:
vector<int> getnext(string s)
{
vector<int>next(s.length());
next[0] = -1;
if (s.length()<2)
return next;
next[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length())
{
if(s[i-1]==s[cn])
next[i++] = ++cn;
else if(cn>0)
cn = next[cn];
else
next[i++] = 0;
}
}
int kmp(string s, string p)
{
if(s.length()<p.length() || s.length==0 || p.length==0)
return -1;
int i = 0;
int j = 0;
vector<int>next=getnext(p);
while(i<s.length() && j<p.length())
{
if(s[i]==p[j])
{
i++;
j++;
}
else if(next[j]==-1)
i++;
else
j = next[j];
}
return j==p.length()?i-j:-1;
}
三、第三次课(2017.11.18)
17、KMP算法应用:最大重合的新串
给定一个字符串str1,只能往str1的后面添加字符变成str2。
要求:str2中必须包含两个str1,即两个str1可以有重合,但不能是同一位置开头,即完全重合;且str2尽量短。
利用next数组,找到str1的最大公共前缀后缀,然后向str1尾部添加从前缀后开始到str1原尾部的字符。
如:str1=abracadabra,则str2=abracadabra+cadabra
int getnext(string s)
{
vector<int>next(s.length()+1);
next[0]=-1;
if (s.length()<2)
retrun -1;
next[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length()+1)
{
if(s[i-1]==s[cn])
next[i++] = ++cn;
else if(cn>0)
cn = next[cn];
else
next[i++] = -1;
}
return next[s.length()];
}
string gettwo(string s)
{
if (s.length()==0)
return "";
if (s.length()==1)
return s+s;
if (s.length()==2)
return s[0]==s[1]?s+s.substr(1):s+s;
return s + s.substr(getnext(s));
}
18、KMP算法应用:两个二叉树是否子树匹配
给定两个二叉树T1,T2,返回T1的某个子树结构是否与T2相等。
常规解法是遍历头节点,看其子树结构是否与T2相等。
这里将二叉树T1与T2都转换为字符串,是否匹配就看T2是否是T1的子串。
如何转换呢?每一个节点的值后面都加一个特殊符号,比如“_”,空节点用另一个特殊符号表示,如“#”。序列化要按照同样的遍历方式,比如前序遍历。
3
1 2
2 3
上图中序列化后为“3 _ 1 _ 2 _ # _ #_ 3 _ # _ # _ 2 _ # _ # _”
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x),left(NULL),right(NULL){}
};
string tree2string(TreeNode *root)
{
if(root==NULL)
return "#\_";
string str = to_string(root->val) + "\_";
return str + tree2string(root->left) + tree2string(root->right);
}
vector<int> getnext(string s)
{
vector<int>next(s.length());
next[0] = -1;
if (s.length()<2)
return s;
next[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length())
{
if(s[i-1]==[cn])
next[i++] = ++cn;
else if (cn>0)
cn = next[cn];
else
next[i++] = -1;
}
return next;
}
int kmp(string s, string p)
{
if(s.length() < p.length() || s.length()==0 || p.length()==0)
return -1;
vector<int>next = getnext(p);
int i = 0;
int j = 0;
while(i<s.length() && j<p.length())
{
if(s[i] == p[j])
{
i++;
j++;
}
else if (next[j]>0)
j = next[j];
else
i++:
}
return j == p.length() ? i-j : -1;
}
bool issubtree(TreeNode *root1, TreeNode *root2)
{
string s = tree2string(root1);
string p = tree2string(root2);
return kmp(s, p) != -1;
}
19、Manacher算法及其扩展(添加新串使回文且最短)
19.1 Manacher:找出字符串str最大的回文子串
首先解决奇回文和偶回文的问题。
我们在判断字符串是否回文时,是根据一个字符(奇回文)或空(偶回文)来判断的,这样需要分情况讨论,比较麻烦,所以最好统一一下。
添加辅助字符,(随便哪个字符都可以,不会影响原有字符匹配就可以),如“#”,在字符与字符之间、开头和结尾各添加“#”,这个“#”就相当于一个虚轴,虚轴只会和虚轴匹配。这样就可以统一奇回文和偶回文了。
传统方法是从str的每个字符开始,向两边扩,找到最大回文子串,复杂度为O(N^2)。
首先是将字符串转换为添加特殊字符后的新字符串。然后进行查找最大回文子串。
首先规定几个概念:
- 回文最右边界R
- 回文最右边界R的回文中心C
- 回文半径数组radius,记录每个字符以其为中心的回文半径。
- 最大回文半径与最大回文半径中心。
一共分为四种情况讨论。
1、当遍历到i时,如果字符i在最右边界R的右边,只能采用暴力匹配方式,向两边扩。
2、如果i在最右边界R的左边,则找到i关于R的对称点i’,如果i’的回文左边界在R的回文边界中,则i的回文边界也在R的回文边界中,不会再向外扩。
3、如果i’的回文左边界不在R的回文边界中,则i的回文右边界刚好与R重合,也不会扩。
4、如果i’的回文左边界刚好在R的回文左边界上,则i的回文半径至少到了R,至于R之后会不会扩,只能暴力匹配了。
那么在上述四种情况下,2、3的回文边界就不用暴力匹配了,直接去radiux[2C-i](即i’的回文半径)与R-i的小值就可以了。
string get_newstring(stirng s)
{
string ss = "#";
for (int i=0;i<s.length();i++)
ss = ss + s[i] + "#";
return ss;
}
string manacher(string s)
{
if(s.length()<2)
return "";
string news = get_newstring(s);
vector<int>radius(news.length(), 0);
int C = -1;
int R = -1;
int rmax = INT_MIN;
int rc = 0;
for(int i=0; i<news.length(); i++)
{
radius[i] = 1;
radius[i] = R>i ? min(R-i, radius[2*C-i]) : radius[i];
while( i - radius[i] >= 0 && i + radius[i] < news.length())
{
if(news[i - radius[i]] == news[i + radius[i]])
radius[i]++;
else
break;
}
if(i+raidus[i]>R)
{
R = i + radius[i];
C = i;
}
if(radius[i]>rmax)
{
rmax = radius[i];
rc = i;
}
}
rmax--;
return s.substr((ic-imax)/2, imax);
}
19.2 给定str1,在其后添加字符使得新字符串是回文字符串且最短
找到包含最后一个字符的最大回文子串,然后将这个回文子串之前的字符反向贴在str1的后面即可。
如“abc12321”,即将“abc”反向贴在后面即可,“abc12321cba”
string get_newstring(string s)
{
string news = "#";
for(int i=0; i<s.length(); i++)
news = news + s[i] + "#";
return news;
}
int manacher(string s)
{
string news = get_newstring(s);
vector<int>radius(news.length());
int C = -1;
int R = -1;
for(int i=0; i<news.length(); i++)
{
radius[i] = R>i ? min(R-i, radius[2*C-i]) : 1;
while(i - radius[i] >=0 && i + radius[i] < news.length())
{
if(news[i-radius[i]] == news[i+radius[i]])
radius[i]++;
}
if(i+radius[i]>R)
{
R = i+ radius[i];
C = i;
if (R == news.length())
return radius[i]-1;//包含最后一个字符的最大回文子串的长度
}
}
}
string getmaxstring(string s)
{
int r = manacher(s);
string ss = s;
for(int i= s.length()-r-1; i>=0; i--)
ss = ss + s[i];
return ss;
}
20、BFPRT算法:求第K小/大的数
利用快排中的partition过程。如果求第K小,就是求排序后数组中下标为k-1的值。如果k-1属于partition后的等于区,则可直接返回等于区的数,如果,在大于区,则递归调用大于区,在小于区,则递归调用小于区。
到那时BFPRT又做了优化,即在选取partition的划分值时不再是随机选取,而是有策略的选取。
如何选呢?首先,将数组5个一组进行划分,不足5个自动一组。
然后分别组内排序(可用插入排序),求出每个组内的中位数,将这些中位数组成新的数组mediumarray。
然后递归调用这个BFPRT的函数,得到这个中位数数组的上中位数。这个值就是进行partition的划分值。
vector<int> partition(vector<int>&a, int l, int r, int value)
{
int less = l-1;
int more = r+1;
while(l<more)
{
if(a[l]<value)
a[++less]=a[l++};
else if (a[l]>value)
a[--more]=a[l];
else
l++;
}
vector<int>p;
p.push_back(less+1);
p.push_back(more-1);
}
void insert_sort(vector<int>&a, int start, int end)
{
if(start==end)
return ;
for(int i=start+1;i<=end;i++)
{
for(int j=i-1;j>=start;j--)
{
if(a[j+1]<a[j])
swap(a[j+1],a[j]);
}
}
}
int getmedium(vector<int>&a, int start, int end)
{
insert_sort(a, start, end);
return a[strat + (end-start)/2];
}
int get_medium_of_medium(vector<int>&a, int start, int end)
{
int num = end-start+1;
int flag = num%5 == 0 ? 0 : 1;
vector<int>mediums(num/5+flag);
for(int i=0; i< mediums.size(0; i++)
{
int istart = start + i*5;
int iend = istart + 4;
mediums[i] = getmedium(a, istart, min(iend, end));
}
return findk(mediums, 0, mediums.size()-1, (mediums.size()-1)/2);
}
int findk(vector<int>&a, int start, int end, int k)
{
if(start==end)
return a[strat];
int pvalue = get_medium_of_medium(a, start, end);
vector<int>p = partition(a, start, end, pvalue);
if(k>=p[0] && k<=p[1])
return pvalue;
else if(k<p[0])
return findk(a, start, p[0]-1, k);
else
returun findk(a, p[1]+1, end, k);
}
int mink(vector<int>a, int k)
{
if(k<1 || k>a.size())
return NULL;
return findk(a, 0, a.size()-1, k-1);
}
21、基数排序
找到最大数的位数,并准备10个桶(0-9)。从个位数到高位数依次排序,按照其位上的数的大小依次入桶,顺序要保持一致,保持先进先出。
int maxbit(vector<int>a)
{
int imax = INT_MIN;;
for(int i=0; i<a.size() && imax<a[i]; i++)
imax = a[i];
int digit = 0;
while(imax>0)
{
imax /= 10;
digit++;
}
return digit;
}
int getnum(int x, int digit)
{
return (x/(pow(10, digit-1))) % 10;
}
void radix(vector<int>&a, int start, int end, int digit)
{
vector<int>help(end-start+1);
int num;
for(int d=1; d<=digit; d++)
{
vector<int>count(10,0);
for(int i=0; i<a.size(); i++)
{
num = getnum(a[i], d);
count[num]++;
}
fot(int i=1; i<10; i++)
count[i] += count[i-1];
for(int i=end; i>=start; i--)
{
num = getnum(a[i], d);
help[count[num]-1] = a[i];
count[num]--;
}
for(int i=0; i<help.size(); i++)
a[start + i]=help[i];
}
}
void radix_sort(vector<int>a)
{
if(a.size()<2)
return ;
radix(a, 0 ,a.size()-1, maxbits(a));
}
22、希尔排序
设置步长,每次选一个步长来进行排序,做很多遍,最终落在步长为1的排序上。大步长调整完基本有序,则小步长不必调整过多。
插入排序就是步长为1的希尔排序。
12 3 1 9 4 8 2 6 (未排序)
选择步长为4
4 3 1 6 12 8 2 9
选择步长为2
1 3 2 6 4 8 12 9
选择步长为1
1 2 3 4 6 8 9 12
23、实现特殊栈,O(1)实现getmin()
进栈、出栈、返回栈顶这些都与原来的栈的操作保持一致,主要是加了怎样在O(1)时间下得到栈内最小值。
两种方法,不过都要准备两个栈,一个数据栈data存放数据,一个help栈,存放最小值。
方法一:help栈存放最小值,每次data栈压栈时,help也压栈,不过help要保持最小的栈顶,即如果data新进的数如果比help栈顶小,则这个数可以压栈入help,否则将help栈顶的那个数再一次压入help栈中。
压栈的时候同步压,出栈同步出。
方法二:当压入data栈的数小于等于help栈顶时,可以向help压入,否则不压。当要弹出data栈的数时,如果这个数与help的栈顶值相等,则help也弹出,否则help不弹出。
则help的栈顶一直是当前栈内最小的数。
24、用数组结构实现大小固定的队列和栈
24.1 实现栈
维护一个size(初始为0),代表栈的大小,当push时,数直接放在a[size]上,size++,当pop时,直接取a[size-1]]的值,size–。
struct array_stack
{
vector<int>astack;
int size;
array_stack(int initsize)
{
if(initsize<0)
cout<<"请输入一个大于0的数,以保证栈不为空"<<endl;
astack=vector<int>(initsize);
size = 0;
}
int top()
{
if(size==0)
{
cout<<"the stack is empty"<<endl;
return NULL;
}
return a[size-1];
}
void push(int x)
{
if(size==astack.size())
{
cout<<"the stack is full"<<endl;
return ;
}
astack[size++] = x;
}
int pop()
{
if(size==0)
{
cout<<"the stack is empty"<<endl;
return NULL;
}
return astack[--size];
}
};
24.2 实现队列
维护start、end、size三个指针,size表示队列的大小(约束start与end的关系)。当push时,end++,pop时,start++,用start去追end,谁先触底,则回到开始位置。
struct array_queue
{
vector<int>aqueue;
int size;
int start;
int end;
aqueue(int initsize)
{
if(initsize<0)
cout<<"请输入一个大于0的数,以保证队列不为空"<<endl;
aqueue = vector<int>(initsize);
size = 0;
start = 0;
end = 0;
}
int top()
{
if(size == 0)
return null;
return aqueue[start];
}
void push(int x)
{
if(size == aqueue.size())
{
cout<<"the queue is full"<<endl;
return ;
}
aqueue[end] = x;
size ++;
end = end==aqueu.size()-1?0:end+1;
}
int pop()
{
if(size == 0)
{
cout<<"the queue is empty"<<endl;
return NULL;
}
size--;
int temp = start;
start = start == aqueue.size()-1?0:start+1;
return aqueue[temp];
}
}
25、用栈实现队列、用队列实现栈
25.1 用栈实现队列
准备两个栈,一个数据栈data,一个辅助栈help,数据栈负责入队列,如果需要出队列,就把数据栈中的当前所有数据倒入辅助栈help中。然后从help中出队列。
注意两点:如果help栈不为空,data栈不要向help栈倒数据;data栈倒数据时,一次要全倒完。
struct stack2queue
{
stack<int>data;
stack<int>help;
stack2queue()
{
data = new stack<int>();
help = new stack<int>();
}
int top()
{
if(data.empty() && help.empty())
{
cout<<"the queue is empty"<<endl;
return NULL;
}
else if(help.empty())
{
while(!data.empty())
help.push(data.pop());
}
return help.top();
}
void push(int x)
{
data.push(x);
}
int pop()
{
if(data.empty() && help.empty())
{
cout<<"the queue is empty"<<endl;
return NULL;
}
else if(help.empty())
{
while(!data.empty())
help.push(data.pop());
}
return help.pop();
}
};
25.2 用队列实现栈
准备两个队列A和B,两个队列互相配合,称为数据队列和缓存队列。当进栈时,向数据队列中入,当出栈时,将目前的那个数据栈除最后一个元素外全部出队列,并进入到缓存队列,此时数据队列只剩那个要出栈的元素,可出。
此数据队列变为空,为缓存队列,缓存队列为数据队列。
struct queue2stack
{
queue<int>data;
queue<int>help;
queue2stack()
{
data = new queue<int>();
help = new queue<int>();
}
int top()
{
if(data.empty())
{
cout<<"the stack is empty"<<endl;
return NULL;
}
while(data.size()!=1)
help.push(data.pop());
int temp = data.pop();
help.push(temp);
swap();
return temp;
}
void push(int x)
{
data.push(x);
}
int pop()
{
if(data.empty())
{
cout<<"the stack is empty"<<endl;
return NULL;
}
while(data.size()!=1)
help.push(data.pop());
int temp = data.pop();
swap();
return temp;
}
void swap()
{
queue<int>temp = data;
data = help;
help = data;
### 一、网安学习成长路线图
网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
![在这里插入图片描述](https://img-blog.csdnimg.cn/aa7be04dc8684d7ea43acc0151aebbf1.png)
### 二、网安视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f0aeee2eec7a48f4ad7d083932cb095d.png)
### 三、精品网安学习书籍
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
![在这里插入图片描述](https://img-blog.csdnimg.cn/078ea1d4cda342f496f9276a4cda5fcf.png)
### 四、网络安全源码合集+工具包
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
![在这里插入图片描述](https://img-blog.csdnimg.cn/e54c0bac8f3049928b488dc1e5080fc5.png)
### 五、网络安全面试题
最后就是大家最关心的网络安全面试题板块
![在这里插入图片描述](https://img-blog.csdnimg.cn/15c1192cad414044b4dd41f3df44433d.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/b07abbfab1fd4edc800d7db3eabb956e.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**