搜索与图论

总览:

1.深度优先搜索  DFS

2.宽度优先搜索  BFS

3.树与图的存储

4.树与图的深度优先遍历

5.树与图的宽度优先遍历

6.拓扑排序

1.深度优先搜索:

简称DFS,暴搜

不撞南墙不回头,一条路走到黑,走到头再回溯,往深处搜

特点:

数据结构使用栈 stack     空间:O(h)     不具有最短路性质

重要概念:

回溯, 剪枝

使用情况: 一般算法思路比较奇怪的

过程:

【注】回溯要恢复现场, 比如填满123了,回到上面3是没有被填过的

例题模版:

1.排列数字

给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式
共一行,包含一个整数 n

输出格式
按字典序输出所有排列方案,每个方案占一行。

数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

代码:

//此题就是暴搜,把所有情况遍历一遍
#include<iostream>

using namespace std;

const int N = 10;

int n;
int path[N]; // 存储序列方案
bool st[N]; // 数字是否被用过了

void dfs(int u) // u是第几个位置
{
    if (u > n) // 说明位置填满了,这是一种方案
    {
        for (int i = 1; i <= n; i ++ ) cout << path[i] << " ";  // 输出方案
        cout << endl;
    }
    
    for (int i = 1; i <= n; i ++ ) // 如果u小于n,位置没填满,从数字一开始看哪个数没被用
        if (!st[i])
        {
            path[u] = i; // 没被用的数字放在u这个位置上
            st[i] = true; // i这个数字就被使用过了
            dfs(u + 1); // 遍历,看下一个位置
            
            st[i] = false; // 回溯,i这个数字回到上一层没被使用,一旦从递归里出来,就要回溯啦,就赶快把这个i变成false,未使用
            
        }
}

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n;
    
    dfs(1); // 从第一个位置开始
    
    return 0;
}

2.n-皇后问题(一个非常经典的dfs问题)

n−皇后问题是指将 n个皇后放在 n×n的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式
共一行,包含整数 n。

输出格式
每个解决方案占 n行,每行输出一个长度为 n的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围
1≤n≤9
输入样例:
4
输出样例:
.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

思路1:

每一行必定有一个皇后,对行进行深度优先遍历,对每一行,遍历每一列,如果可以放置就继续递归进行下一行,每一列都不可以回溯到上一行,再继续下一列,这样遍历所有的方案

代码1:

#include<iostream>

using namespace std;

const int N = 20; // 下面的斜方向的判断中会出现2n,所以设成20,比上一题多了一倍

int n;
char g[N][N]; // 存储棋盘
int col[N], dg[N], udg[N]; // col[i]对应第i列有没有皇后,dg是对角线,udg是反对角线是否有皇后

void dfs(int u)
{
    if (u > n) // 超出棋盘,说明放满了,输出这个方案
    {
        for (int i = 1; i <= n; i ++ )
        {
            for (int j = 1; j <= n; j ++ ) cout << g[i][j];
            cout << endl;
        }
        cout << endl;
        return;
    }
    
    for (int i = 1; i <= n; i ++ ) // 若没有放满,在第u行,从第一列开始,遍历这一行看哪一列可以放
    {
        if (!col[i] && !dg[u + i] && !udg[u - i + n]) // 判断是否可以放置皇后,如果这个点的列,正反对角线都没有
        {
            g[u][i] = 'Q'; // 将皇后放在这
            col[i] = dg[u + i] = udg[u - i + n] = true; //这个点的列,正反对角线就有皇后了
            
            dfs(u + 1); // 递归下一行
            
            col[i] = dg[u + i] = udg[u - i + n] = false; // 从递归出来,回溯,恢复现场
            g[u][i] = '.'; // 注意,要变回点
        }
    }
}

int main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            g[i][j] = '.';
            
    dfs(1); // 从第1行开始
    
    return 0;
}

思路2:

原始的做题思路,枚举第一个格子,有两个分支,放皇后是一个分支,不放皇后是第二个分支,再枚举第二个格子,也有两个格子,放皇后是一个,不放皇后是一个,挨个枚举所有格子,当我们枚举完所有格子,就找到了答案

代码2:

//方法2  
// 不同搜索方法 时间复杂度不一样, 所以搜索顺序很重要
#include<iostream>

using namespace std;

const int N = 20; // 下面的斜方向的判断中会出现2n,所以设成20,比上一题多了一倍

int n;
char g[N][N]; // 存储棋盘
int row[N], col[N], dg[N], udg[N]; // row[i]对应第i行有没有皇后,col[i]对应第i列有没有皇后,dg是对角线,udg是反对角线是否有皇后

void dfs(int x, int y, int s) // 第x行,第y列,目前放置了s个皇后
{
    
    if (y == n) y = 0, x ++ ; // 这一行到了边界了,将点变成下一行的开头
    
    if (x == n) // 最后一行
    {
        if (s == n) // 说明每行在合法的情况下都放了皇后,这就是一种方案
        {           // 输出
            for (int i = 0; i < n; i ++ )
            {
                for (int j = 0; j < n; j ++ ) cout << g[i][j];
                 cout << endl;
            }
            cout << endl;
        }
        return; // 结束
    }
    
    //分支1 放皇后
    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) // 确保这个点的行列对角线和反对角线都没有皇后
    {
        g[x][y] = 'Q'; // 放置皇后
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true; // 这个点的行列对角线和反对角线就有皇后了
        dfs(x, y + 1, s + 1); // 递归下一个点
        
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false; // 回溯,恢复现场
        g[x][y] = '.';
    }
    
    //分支2 不放皇后
    dfs(x, y + 1, s);
    
}

int main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n;
    
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            g[i][j] = '.';
            
    dfs(0, 0, 0); 
    
    return 0;
}

剪枝:

第一种:可行性剪枝:提前判断不合法,下面的子序列就没必要进行了,直接回溯

第二种:最优性剪枝:判断当前的路径肯定不如最优解,就可以剪枝了

正反对角线:

2.宽度优先搜索

简称BFS

眼观六路耳听八方

一层一层的搜,第一层搜到的是所有距离为1的点,第二层把所有距离为2的点全部搜到,第三层把所有距离为3的点全部搜到......(图里面的边权重是1)

特点:

数据结构使用队列 queue     空间:O(2^h)     有最短路的性质:第一次搜索到的一定是离的最近的

使用情况:

一般要求最小步数,最短距离  ,最少次数

优势就是可以搜到最短路

基础框架:

例题模版:

例题:

844. 走迷宫
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0
表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。

数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。

输入格式
第一行包含两个整数 n 和 m

接下来 n 行,每行包含 m个整数(0 或 1),表示完整的二维数组迷宫。

输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围
1 ≤ n, m ≤ 100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8

思路:

代码:

// 宽度优先搜索  
#include<iostream>
#include<queue>
#include<cstring> // memset头文件

using namespace std;

const int N = 110;

typedef pair<int, int> PII;

int n, m;
int g[N][N]; // 地图
int d[N][N]; // 每个点到起点的距离


int bfs()
{
    queue<PII> q; // 创建一个坐标队列
    
    memset(d, -1, sizeof d); // 初始化各个点到起点的距离都是-1
    
    d[0][0] = 0; // 起点到起点的距离是0
    q.push({0, 0}); // 把起点放进队列里面
    
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; // 往四个方向的数组,比如往左x不变,y减1(x是行,y是列,我的是按上,右,下,左)
    
    while (!q.empty()) // 队列不是空的
    {
        auto t = q.front(); // 把队头取出来
        q.pop(); // 把队头删了,弹出队头
        
        for (int i = 0; i < 4; i ++ ) // 遍历四个方向,看看哪个方向可以走哇
        {
            int x = t.first + dx[i], y = t.second + dy[i]; // 下个点(叫它p点吧)的xy坐标是这个点的x加上x方向的向量,y加上y方向的向量
            
            // 假如,p点没出界(x不小于0,没大于n,用不小于0,没大于m),并且p点可以走(地图上是0,因为1是墙嘛),同时p点之前没走过(之前每个点到起点距离为-1,所以没走过的都是-1,不走回头路!)
            if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
            {
                d[x][y] = d[t.first][t.second] + 1; // 如果p点合法可以走,那p点到起点的坐标就是p点上一个点到起点的距离加1
                q.push({x, y}); // 把p点放坐标队列里
            }
        }
        
    } // 只要队列里有点就一直重复上面的,最后我们就得到了所有点到起点的距离了,存储在d数组里面了
    
    return d[n - 1][m - 1]; // 输出出口那个点就行
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            cin >> g[i][j];
            
    cout << bfs() << endl;
    
    
    return 0;
}

如果知道路径是什么?,我们只需要记住这个点是从哪个点来的就行
开一个额外的数组

#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

const int N = 110;

int n, m;
int g[N][N], d[N][N];

typedef pair<int, int> PII;

PII Prev[N][N]; // 记录哪个点来的

int bfs()
{
    queue<PII> q;
    
    memset(d, -1, sizeof d);
    
    d[0][0] = 0;
    q.push({0, 0});
    
    int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
    
    while (!q.empty())
    {
        auto t = q.front();
        q.pop();
        
        for (int i = 0; i < 4; i ++ )
        {
            int x = t.first + dx[i], y = t.second + dy[i];
            
            if (x >= 0 && x < n && y >= 0 && y < m && d[x][y] == -1 && g[x][y] == 0)
            {
                d[x][y] = d[t.first][t.second] + 1;
                Prev[x][y] = t;
                q.push({x, y});
            }
        }
    }
    
    int x = n - 1, y = m - 1;
    while (x || y)
    {
        cout << x << ' ' << y << endl;
        auto t = Prev[x][y];
        x = t.first, y = t.second;
    } // 输出路径,这个是倒着的
    
    return d[n - 1][m - 1];
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            cin >> g[i][j];
            
    cout << bfs() << endl;
    
    return 0;
}

3.树与图的深度优先遍历

树是无环连通图++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

图:

分为有向图和无向图

无向图:

边没有方向,假如有边ab,可以从a走到b也可以从b走到a,因此在算法题里面,我们建造两个边就行了,建一条a到b的建一条b到a的,    所以无向图就是一种特殊的有向图

有向图:

边有方向,给定ab这条边,a只能到b或者b只能走向a

因此只要考虑有向图怎么存储的就行了


有向图的存储:
1.邻接矩阵(使用较少)

开一个二维数组,g[a][b] 就存储a到b的信息,有权重的话g[a][b]就是这个边的权重,没有权重g[a][b]就是个bool值,true就是有边,false就是没有边
不能存储重边,  浪费空间,比较适合存储稠密图

2.邻接表

每个节点都开了一个单链表,存储这个点可以到达哪些点,类似与哈希表的拉链法

插入跟单链表的插入一样的

有向图的遍历:

深度搜索,宽度搜索

例题代码

例题:

846. 树的重心
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式
第一行包含整数 n,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a和点 b 之间存在一条边。

输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围
1 ≤ n ≤ 10^5
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4

代码:

// 邻接矩阵
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010, M = N * 2; // 数据范围是10^5, 以有向图的方式存储无向图,要多开二倍

int n; // n个节点
int h[N], e[M], ne[M], idx; 
/* h数组是队列的头结点,因为有n个节点,所以要n个头结点, e数组存储元素的值, 
ne数组就是指针指向下一个节点, idx是用到第几个节点了*/
bool st[N]; // 记录节点是否遍历过了true是遍历过了,false是没遍历过
int ans = N; // 表示重心的所有子树中,最大子树的节点数,最小的最大值

// 插入一个数,跟单链表的插入一样
void add(int a, int b)// a是根,b是插入的
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

// 返回以u为根的子树中节点的数量
int dfs(int u)
{
    int res = 0; // res是删除某个节点后,剩下的连通块中最大的连通块中节点的数量
    st[u] = true; // 标记一下,已经被搜过了
    int sum = 1; // sum是以u为根节点的树的节点数,因为包括u这个点,所以从一开始
    
    // 遍历以u为根节点的子树,跟单链表一样
    for (int i = h[u]; i != -1; i = ne[i]) 
    {
        int j = e[i];
        
        // 因为每个节点的值都不一样,所以把节点的值当作下标
        if (!st[j])
        {
            int s = dfs(j); // 以u为根的子树中其中一个子树的节点数量
            res = max(res, s); // 要子树的节点数量的最大值
            sum += s; // sum是以u为根节点的树的节点数,包括这个子树,要加上
        }
    }
    
    res = max(res, n - sum); // 假如以u为重心,下面的连通块和上面的比较,得到以此节点为重心时的各个连通块的节点数最大的值
    ans = min(res, ans); //  
    return sum;
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a); // 无向图,a可以到b,b可以到a

    }
    
    dfs(1);
    
    cout << ans << endl;
    
    return 0;
}

4.树与图的宽度优先遍历

例题代码:

例题:

847.图中点的层次:

给定一个 n个点 m条边的有向图,图中可能存在重边和自环。

所有边的长度都是 1,点的编号为 1∼n

请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n号点,输出 −1

输入格式
第一行包含两个整数 n 和 m

接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1 的边。

输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。

数据范围
1 ≤ n, m ≤ 10^5
输入样例:
4 5
1 2
2 3
3 4
1 3
1 4
输出样例:
1

代码:

stl队列:

// 宽搜
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

const int N = 1e5 + 10;

int n, m;

int e[N], ne[N], h[N], idx;
int d[N];


void add(int a, int b) // 插入
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int bfs()
{
    memset(d, -1, sizeof d); // 距离到起点初始化为-1
    
    queue<int> q;
    d[1] = 0; // 1到起点的距离是0,起点是1嘛
    q.push(1); // 把1放队列里
    
    while (!q.empty()) // 框架
    {
        int t = q.front(); // 取队头
        q.pop(); // 删队头
        
        for (int i = h[t]; i != -1; i = ne[i]) // 沿着边(链表)遍历
        {
            int j = e[i]; // 这个点e数组存的是值也是编号
            if (d[j] == -1) // 说明没遍历过
            {
                d[j] = d[t] + 1; // j这个点到起点的距离是t到起点的距离加上1,j是从t来的嘛
                q.push(j); // 把j放队列里,后面还要从j开始看往哪可以走,这样把所有点到起点的距离都算出来了
            }
        }
    }
    
    return d[n];
}

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> m;
    
    memset(h, -1, sizeof h); // 队头为-1
    
    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);  // 有向图
    }
    
    cout << bfs() << endl;
    
    return 0;
}

数组模拟队列:
(看不懂hh和tt加加什么意思的可以转到最底下有演示)


// 宽搜(数组模拟队列)
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

const int N = 1e5 + 10;

int n, m;

int e[N], ne[N], h[N], idx;
int d[N], q[N];

/*框架    
*/

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int bfs()
{
    int hh = 0, tt = 0;
    q[0] = 1;
    
    memset(d, -1, sizeof d);
    
    d[1] = 0;
    while (hh <= tt)
    {
        int t = q[hh ++];
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (d[j] == -1)
            {
                d[j] = d[t] + 1;
                q[++ tt] = j;
            }
        }
    }
    
    return d[n];
}

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    cout << bfs() << endl;
    
    return 0;
}

拓扑序列:

(图的宽搜的应用)


概念:


拓扑序列:

针对有向图来说的,有向图才有拓扑序列无向图没有拓扑序列,

定义:对于每条边xy,对于每个序列x在y的左边

对于这个的一个有向图,123就是一个拓扑序列,看12这条边1在2前面,23这条边,2在3前面,13这条边,1在3前面
当我们把一个图用拓扑序排好序后所有的边都是从前指向后的

并不是所有图都有拓扑序列 比如一个环 一定有一条边从后指向前

可以证明有向无环图一定有一个拓扑序列,因此有向无环图又叫拓扑图

度数:

有向图的每个点有两个度   入度   和  出度

一个点有几条边指向自己就是它的入度
一个点有几条边出去就是它的出度

因此所有入度为0的点都可以当作起点,排在最前面的位置

 

思路:

首先把所有入度为0的点入队,后面进行宽搜

删掉t到j的边,到后面判断d[j]为0,这一段意思就是判断j的入度为1,也就是只有起点的t指向它,保证了拓扑排序的进行,就是没有其他点指向j并且出现在j后面的情况了,把唯一的入度删掉,j就可以当做剩下点里的起点了,以此把所有点都排序,一个点一个点蚕食
逐个击破的顺序就是这个拓扑排序了


环上的点一定不会入队,一个有向无环图一定存在一个入度为0的点

一个有向无环图的拓扑序不一定唯一

这个图123和132都是拓扑序

例题代码:

例题;

有向图的拓扑序列:

给定一个 n个点 m条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1

若一个由图中所有点构成的序列 A满足:对于图中的每条边 (x,y),x在 A中都出现在 y之前,则称 A是该图的一个拓扑序列。

输入格式
第一行包含两个整数 n 和 m。
接下来 m行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y的有向边 (x,y)

输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1


数据范围
1 ≤ n, m ≤ 10^5
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3

代码:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N =100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N]; // q数组模拟队列,存储入度为0的点,   d数组存储各个点的入度

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool topsort()
{
    int hh = 0, tt = -1; // hh头, tt尾
    
    for (int i = 1; i <= n; i ++ ) // 遍历一遍,把入度为0的点装队列里
        if (!d[i])
            q[++ tt] = i;
            
    while (hh <= tt) // 循环处理队列中的点,条件的意思是队列不空
    {
        int t = q[hh ++]; // hh加加就是弹出队头元素,相当于t = q.front(); q.pop();这两步
        
        for (int i = h[t]; i != -1; i = ne[i]) // 沿着链表走,以入度为0的点(假设a)走到下一个点(假设是b),把这条边删除
        {
            int j = e[i]; 
            d[j] --; // 删除边后,b的入度减1
            if (d[j] == 0) q[++ tt] = j; // 如果b的入度减去1后是0,在剩下的节点中,b可以当作新起点了,入队列,可以输出
        }
    }
    return tt == n - 1; // 当全部都进过队了说明是无环的,出队的顺序就是拓扑序,如果不相等说明有环
}

int main()
{
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        cin >> a >> b;
        add(a, b); // 添加一条a到b的边
        d[b] ++; // 节点b的入度加1
    }
    
    if (topsort())
    {
        for (int i = 0; i < n; i ++ ) cout << q[i] << " ";
        
        
    }
    else cout << -1 << endl;
    
    return 0;
}

数组模拟队列的演示:

假设我们有一个初始状态的队列如下,使用数组 `q` 来模拟:

```
q: [ , , , , , , , , , , ...]
    0  1  2  3  4  5  6  7  8  9 ...
hh: 0
tt: -1
```

初始状态下,`hh` 指向队列的第一个位置(0),`tt` 为 -1 表示队列为空。

###入队操作
假设我们依次入队元素 5, 10, 15:

1. 入队 5:
   ```
   q: [5, , , , , , , , , , ...]
       0  1  2  3  4  5  6  7  8  9 ...
   hh: 0
   tt: 0
   ```

2. 入队 10:
   ```
   q: [5, 10, , , , , , , , , ...]
       0  1  2  3  4  5  6  7  8  9 ...
   hh: 0
   tt: 1
   ```

3. 入队 15:
   ```
   q: [5, 10, 15, , , , , , , , ...]
       0  1  2  3  4  5  6  7  8  9 ...
   hh: 0
   tt: 2
   ```

### 出队操作
现在我们依次出队元素:

1. 出队 5:
   ```
   q: [5, 10, 15, , , , , , , , ...]
       0  1  2  3  4  5  6  7  8  9 ...
   hh: 1
   tt: 2
   ```
   队列中的元素为 [10, 15]。

2. 出队 10:
   ```
   q: [5, 10, 15, , , , , , , , ...]
       0  1  2  3  4  5  6  7  8  9 ...
   hh: 2
   tt: 2
   ```
   队列中的元素为 [15]。

3. 出队 15:
   ```
   q: [5, 10, 15, , , , , , , , ...]
       0  1  2  3  4  5  6  7  8  9 ...
   hh: 3
   tt: 2
   ```
   队列为空。

通过这种方式,`hh` 和 `tt` 分别维护队列的头和尾指针,实现入队和出队操作。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值