2024年网络安全最全【算法】常见数据结构基本算法整理

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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值