栈和队列循环队列(C/C++)

        本篇将用数组实现栈、链表实现队列以及使用数组实现循环队列,然后实现了用栈实现队列和用队列实现栈以及一些其他的栈和队列的习题,加深队栈和队列的理解。

        若只看对应所需,可直接看文章旁的目录。

1.栈

1.1栈的概念及结构

        栈:一种特殊的线性表,其中只允许在固定的一端插入和删除元素,进行数据插入和删除操作的一端被称为栈顶,另一端被称为栈底栈中的数据元素遵守先进后出LIFO(Last In First Out)的原则。

        压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

        出栈:栈的删除操作叫做出栈,出数据也在栈顶。

        上图就是出栈和压栈的示意图。 

1.2栈的实现

        栈的实现一般可以使用数据或者链表实现,但相对而言使用数组实现更优一些,因为数组在尾上插入数据的代价比较小。

        所以本篇将使用数组实现栈。

        注:对于数组实现栈,我们可以将栈顶指针指向栈顶元素,也可以指向栈顶元素的上一个位置,这两种实现方式都是可以的。本篇的栈顶指针指向的是栈的上一个元素。两种的实现方式都有略微的不同。

        对于栈的实现,对于较难理解的将给出注释,如下:

1.2.1 Stack.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>//定义元素数据类型
typedef int STDataType;//栈的数据结构
typedef struct Stack {STDataType* arr;int top;int capacity;
}Stack;//栈的初始化以及销毁
void STInit(Stack* ps);
void STDestroy(Stack* ps);//压栈与出栈
void STPush(Stack* ps, STDataType x);
void STPop(Stack* ps);//返回栈顶元素/栈的尺寸/栈是否为NULL
STDataType STTop(Stack* ps);
int STSize(Stack* ps);
bool STEmpty(Stack* ps);

1.2.2 Stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"void STInit(Stack* ps) {assert(ps);ps->arr = NULL;ps->capacity = 0;ps->top = 0;
}void STDestroy(Stack* ps) {assert(ps);free(ps->arr);ps->arr = NULL;ps->capacity = 0;ps->top = 0;ps = NULL;
}void STPush(Stack* ps, STDataType x) {assert(ps);//判断栈是否需要扩容if (STSize(ps) == ps->capacity) {//三目操作符进行扩容,第一次的容量为4,以后每次扩容一倍int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));if (tmp == NULL) {perror("realloc:");exit(1);}ps->arr = tmp;ps->capacity = newcapacity;}ps->arr[ps->top] = x;ps->top++;
}void STPop(Stack* ps) {assert(ps);assert(!STEmpty(ps));//出栈只需要将栈的尺寸减小,下次压栈的元素直接进行覆盖ps->top--;
}STDataType STTop(Stack* ps) {assert(ps);assert(!STEmpty(ps));STDataType ret = ps->arr[ps->top - 1];return ret;
}int STSize(Stack* ps) {assert(ps);//栈顶的编号其实就是栈的尺寸return ps->top;
}bool STEmpty(Stack* ps) {assert(ps);return ps->top == 0;
}

1.2.3 All of Code

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>//定义元素数据类型
typedef int STDataType;//栈的数据结构
typedef struct Stack {STDataType* arr;int top;int capacity;
}Stack;//栈的初始化以及销毁
void STInit(Stack* ps);
void STDestroy(Stack* ps);//压栈与出栈
void STPush(Stack* ps, STDataType x);
void STPop(Stack* ps);//返回栈顶元素/栈的尺寸/栈是否为NULL
STDataType STTop(Stack* ps);
int STSize(Stack* ps);
bool STEmpty(Stack* ps);void STInit(Stack* ps) {assert(ps);ps->arr = NULL;ps->capacity = 0;ps->top = 0;
}void STDestroy(Stack* ps) {assert(ps);free(ps->arr);ps->arr = NULL;ps->capacity = 0;ps->top = 0;ps = NULL;
}void STPush(Stack* ps, STDataType x) {assert(ps);//判断栈是否需要扩容if (STSize(ps) == ps->capacity) {//三目操作符进行扩容,第一次的容量为4,以后每次扩容一倍int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));if (tmp == NULL) {perror("realloc:");exit(1);}ps->arr = tmp;ps->capacity = newcapacity;}ps->arr[ps->top] = x;ps->top++;
}void STPop(Stack* ps) {assert(ps);assert(!STEmpty(ps));//出栈只需要将栈的尺寸减小,下次压栈的元素直接进行覆盖ps->top--;
}STDataType STTop(Stack* ps) {assert(ps);assert(!STEmpty(ps));STDataType ret = ps->arr[ps->top - 1];return ret;
}int STSize(Stack* ps) {assert(ps);//栈顶的编号其实就是栈的尺寸return ps->top;
}bool STEmpty(Stack* ps) {assert(ps);return ps->top == 0;
}int main() {Stack st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);while (!STEmpty(&st)) {STDataType top = STTop(&st);STPop(&st);printf("%d ", top);}STPush(&st, 4);STPush(&st, 5);while (!STEmpty(&st)) {STDataType top = STTop(&st);STPop(&st);printf("%d ", top);}STDestroy(&st);return 0;
}

        测试结果: 

2.队列

2.1 队列的概念及结构

        队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的性质。

        入队列:进行插入操作的一端称为队尾。

        出队列:进行删除操作的一端称为队头。 

2.2队列的实现 

        对于队列的实现,都可以使用数组以及链表结构,但是使用链表结构实现出队以及入队将更方便,同时还会节省更多的空间。

        如上图所示,当使用数组实现队列时,由于开辟的空间是固定的,所以出队时将会浪费一些空间。 

2.2.1 Queue.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int QDataType;//队列节点的数据结构
typedef struct Node {QDataType val;struct Node* next;
}QNode;//队列的队首指针、队尾指针、尺寸
typedef struct Queue {int size;QNode* phead;QNode* ptail;
}Queue;//队列的初始化/销毁
void QueueInit(Queue* pQ);
void QueueDestory(Queue* pQ);//队列的入队/出队
void QueuePush(Queue* pQ, QDataType x);
void QueuePop(Queue* pQ);//返回队首元素/队尾元素
QDataType QueueFront(Queue* pQ);
QDataType QueueBack(Queue* pQ);//判断队列是否为NULL/返回队列的大小
bool QueueEmpty(Queue* pQ);
int QueueSize(Queue* pQ);

2.2.2 Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"void QueueInit(Queue* pQ) {assert(pQ);pQ->phead = pQ->ptail = NULL;pQ->size = 0;
}void QueueDestory(Queue* pQ) {assert(pQ);QNode* cur = pQ->phead;while (cur) {QNode* next = cur->next;free(cur);cur = next;}pQ->phead = pQ->ptail = NULL;pQ->size = 0;
}void QueuePush(Queue* pQ, QDataType x) {assert(pQ);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {perror("malloc:");exit(1);}newnode->val = x;newnode->next = NULL;if (pQ->phead == NULL) {pQ->phead = pQ->ptail = newnode;}else {pQ->ptail->next = newnode;pQ->ptail = newnode;}pQ->size++;
}void QueuePop(Queue* pQ) {assert(pQ);assert(pQ->phead);QNode* cur = pQ->phead;pQ->phead = pQ->phead->next;free(cur);cur = NULL;pQ->size--;
}QDataType QueueFront(Queue* pQ) {assert(pQ);assert(pQ->phead);return pQ->phead->val;
}QDataType QueueBack(Queue* pQ) {assert(pQ);assert(pQ->phead);return pQ->ptail->val;
}bool QueueEmpty(Queue* pQ) {assert(pQ);return pQ->phead == NULL;
}int QueueSize(Queue* pQ) {assert(pQ);return pQ->size;
}

2.2.3 All of Code

 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int QDataType;//队列节点的数据结构
typedef struct Node {QDataType val;struct Node* next;
}QNode;//队列的队首指针、队尾指针、尺寸
typedef struct Queue {int size;QNode* phead;QNode* ptail;
}Queue;//队列的初始化/销毁
void QueueInit(Queue* pQ);
void QueueDestory(Queue* pQ);//队列的入队/出队
void QueuePush(Queue* pQ, QDataType x);
void QueuePop(Queue* pQ);//返回队首元素/队尾元素
QDataType QueueFront(Queue* pQ);
QDataType QueueBack(Queue* pQ);//判断队列是否为NULL/返回队列的大小
bool QueueEmpty(Queue* pQ);
int QueueSize(Queue* pQ);void QueueInit(Queue* pQ) {assert(pQ);pQ->phead = pQ->ptail = NULL;pQ->size = 0;
}void QueueDestory(Queue* pQ) {assert(pQ);QNode* cur = pQ->phead;while (cur) {QNode* next = cur->next;free(cur);cur = next;}pQ->phead = pQ->ptail = NULL;pQ->size = 0;
}void QueuePush(Queue* pQ, QDataType x) {assert(pQ);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {perror("malloc:");exit(1);}newnode->val = x;newnode->next = NULL;if (pQ->phead == NULL) {pQ->phead = pQ->ptail = newnode;}else {pQ->ptail->next = newnode;pQ->ptail = newnode;}pQ->size++;
}void QueuePop(Queue* pQ) {assert(pQ);assert(pQ->phead);QNode* cur = pQ->phead;pQ->phead = pQ->phead->next;free(cur);cur = NULL;pQ->size--;
}QDataType QueueFront(Queue* pQ) {assert(pQ);assert(pQ->phead);return pQ->phead->val;
}QDataType QueueBack(Queue* pQ) {assert(pQ);assert(pQ->phead);return pQ->ptail->val;
}bool QueueEmpty(Queue* pQ) {assert(pQ);return pQ->phead == NULL;
}int QueueSize(Queue* pQ) {assert(pQ);return pQ->size;
}int main() {Queue queue;QueueInit(&queue);QueuePush(&queue, 1);QueuePush(&queue, 2);printf("%d ", QueueFront(&queue));QueuePop(&queue);QueuePush(&queue, 3);QueuePush(&queue, 4);printf("%d ", QueueFront(&queue));QueuePop(&queue);QueuePush(&queue, 5);while (!QueueEmpty(&queue)) {printf("%d ", QueueFront(&queue));QueuePop(&queue);}QueueDestory(&queue);return 0;
}

        测试结果:

2.3循环队列的实现 

        在上文中我们提到,当我们使用数组实现队列的时候,会出现一些空间浪费的情况,这是因为出队时,出队下标将向后移动,前面的空间则浪费,当我们使用循环链表就可以很好的解决该问题。

        循环链表:将数组的首尾相连,组成的一个特殊结构。我们用两个指针来表示队列的队首和队尾,front 表示队首,back表示队尾的下一个元素。

        为了能使Q.front=Q.back来区别队列是否为空还是满,我们常常认为出现作图时的情况即为队满的情况,此时:Q.front=Q.back+1;

        对于循环队列的代码实现如下,对于代码的解释在注释给出。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct {int* arr;int front;int back;int k;
} MyCircularQueue;//循环队列的初始化
MyCircularQueue* myCircularQueueCreate(int k) {//堆上分配空间,才能在出函数时仍然存在MyCircularQueue* Queue=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));if(Queue==NULL){perror("malloc:");exit(1);}//分配k+1个空间,便于我们判断是否为满队列还是空队列Queue->arr=(int*)malloc(sizeof(int)*(k+1));Queue->front=Queue->back=0;Queue->k=k;return Queue;
}//判断队列是否为NULL队列
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front==obj->back;
}//判断队列是否为满队列
bool myCircularQueueIsFull(MyCircularQueue* obj) {return obj->front==(obj->back+1)%(obj->k+1);
}//在队列中加入元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if(myCircularQueueIsFull(obj)){return false;}else{obj->arr[obj->back]=value;obj->back++;obj->back%=(obj->k+1);return true;}
}//队列中删除元素
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return false;}else{obj->front++;obj->front%=(obj->k+1);return true;}
}//返回队首元素
int myCircularQueueFront(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}return obj->arr[obj->front];
}//返回队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}//对于队尾元素的返回,需要注意是否为队列中第k+1个元素,有以下两种写法//return obj->arr[(obj->back+obj->k)%(obj->k+1)];if(obj->back==0){return obj->arr[obj->k];}else{return obj->arr[obj->back-1];}
}//释放空间
void myCircularQueueFree(MyCircularQueue* obj) {free(obj->arr);free(obj);
}

        对于以上循环队列的实现,其实是一个习题,链接如下:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/design-circular-queue/description/         测试结果:

3.栈与队列相关习题

        注:在习题中使用的函数都是以上的代码函数。

3.1队列实现栈

        使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。 

        习题链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/implement-stack-using-queues        若要使用两个队列实现栈的操作,最难的两个操作及就是出栈和压栈。

        对于压栈,我们只需将要压栈的元素压入一个非空队列,另一个队列保存空。

        对于出栈,我们只需要除最后一个元素的队列进入另一个空队列,然后在删除最后一个元素及就实现了出栈。代码如下:

typedef struct {Queue p1;Queue p2;
} MyStack;MyStack* myStackCreate() {MyStack* stack=(MyStack*)malloc(sizeof(MyStack));if(stack==NULL){perror("stack's malloc:");exit(1);}QueueInit(&stack->p2);QueueInit(&stack->p1);return stack;
}void myStackPush(MyStack* obj, int x) {if(!QueueEmpty(&obj->p1))QueuePush(&obj->p1,x);elseQueuePush(&obj->p2,x);
}int myStackPop(MyStack* obj) {Queue* empty=&obj->p1;Queue* nonempty=&obj->p2;if(!QueueEmpty(empty)){empty=&obj->p2;nonempty=&obj->p1;} while(QueueSize(nonempty)>1){QueuePush(empty,QueueFront(nonempty));QueuePop(nonempty);}int top=QueueFront(nonempty);QueuePop(nonempty);return top;
}int myStackTop(MyStack* obj) {if(!QueueEmpty(&obj->p1)){return QueueBack(&obj->p1);}else{return QueueBack(&obj->p2);}
}bool myStackEmpty(MyStack* obj) {return QueueEmpty(&obj->p1)&&QueueEmpty(&obj->p2);
}void myStackFree(MyStack* obj) {QueueDestory(&obj->p1);QueueDestory(&obj->p2);free(obj);obj=NULL;
}

        测试结果:

3.2栈实现队列 

        使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

        习题链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/implement-queue-using-stacks/         使用两个栈实现队列,主要思想为:一组数据进栈两次即可实现先进先出。

        一个栈作为入队栈,只用来将数据压入栈;

        另一个栈作为出队栈,只用来将数据弹出栈;

        当我们需要将数据出队时,我们将入队栈的数据全部压入出队栈,此时出栈的顺序正确,注意:这里只有当出队栈为空时才能将入队栈的数据压入出队栈,要不然会导致出队的顺序错乱。

typedef struct {Stack pushst;Stack popst;
} MyQueue;MyQueue* myQueueCreate() {MyQueue* Queue=(MyQueue*)malloc(sizeof(MyQueue));if(Queue==NULL){perror("queue malloc:");exit(1);}STInit(&Queue->pushst);STInit(&Queue->popst);return Queue;
}void myQueuePush(MyQueue* obj, int x) {//将元素压入push栈STPush(&obj->pushst,x);
}int myQueuePeek(MyQueue* obj) {if(STEmpty(&obj->popst)){while(!STEmpty(&obj->pushst)){STPush(&obj->popst,STTop(&obj->pushst));STPop(&obj->pushst);}}int front=STTop(&obj->popst);return front;
}int myQueuePop(MyQueue* obj) {int front=myQueuePeek(obj);STPop(&obj->popst);return front;
}bool myQueueEmpty(MyQueue* obj) {return STEmpty(&obj->popst)&&STEmpty(&obj->pushst);
}void myQueueFree(MyQueue* obj) {STDestroy(&obj->popst);STDestroy(&obj->pushst);free(obj);obj=NULL;
}

        测试结果: 

3.3有效的括号匹配

        给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

        习题链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/valid-parentheses/description/         对于该题的实现,主要思想是将所有的左括号压入栈,只要遇到右括号,将栈的中左括号弹出,判断是否配对,若不配对则返回ERROR,一直配对和判断,若最后栈为NULL,说明完全配对,则我们可以判断括号匹配。代码如下:

bool isValid(char* s) {Stack st;STInit(&st);while(*s){if(*s=='['||*s=='('||*s=='{'){STPush(&st,*s);}else{if(STEmpty(&st)){STDestroy(&st);return false;}char ch=STTop(&st);STPop(&st);if((ch=='('&&*s!=')')||(ch=='{'&&*s!='}')||(ch=='['&&*s!=']')){return false;}}s++;}bool ret=STEmpty(&st);STDestroy(&st);return ret;
}

        测试结果:

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/469029.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ros自定义msg记录

文章目录 自定义msg1. 定义msg文件2. 修改 package.xml3. 修改 CMakeLists.txt4. message_publisher.py5. message_subscriber.py6. 运行 catkin build 测试 自定义msg ros 版本&#xff1a;kinetic 自定义test包的文件结构如下 |-- test | |-- CMakeLists.txt | |-- msg…

拓扑排序入门

文章目录 写在前面一些概念算法步骤字典序最大/最小的拓扑序列&#xff1f;模板例题3704. 排队家谱树奖金P1983 [NOIP2013 普及组] 车站分级1639. 拓扑顺序 写在前面 昨晚cf div3的F就是一道基本上可以说板子的拓扑排序的题目&#xff0c;没有做出来感觉图论很早之前就看了&am…

九、OpenCV自带colormap

项目功能实现&#xff1a;每隔1500ms轮流自动播放不同风格图像显示&#xff0c;按下Esc键退出 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 colormap.h #pragma once #include<opencv2/opencv.hpp> using namespace cv;class ColorMap { public:vo…

Java+SpringBoot构建智能捐赠管理平台

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

在Meteor Lake平台上使用NPU进行AI推理加速

在Meteor Lake平台上&#xff0c;英特尔通过神经处理单元 (NPU) 将人工智能直接融入芯片中&#xff0c;实现桌面电脑平台的AI推理功能。神经处理单元 (NPU) 是一种专用人工智能引擎&#xff0c;专为运行持续的人工智能推理工作负载而设计。与即将推出的支持深度人工智能集成的 …

快速学习Spring

Spring 简介 Spring 是一个开源的轻量级、非侵入式的 JavaEE 框架&#xff0c;它为企业级 Java 应用提供了全面的基础设施支持。Spring 的设计目标是简化企业应用的开发&#xff0c;并解决 Java 开发中常见的复杂性和低效率问题。 Spring常用依赖 <dependencies><!-…

Acwing---842.排列数字

排列数字 1.题目2.基本思想3.代码实现 1.题目 给定一个整数 n&#xff0c;将数字 1∼n排成一排&#xff0c;将会有很多种排列方法。 现在&#xff0c;请你按照字典序将所有的排列方法输出。 输入格式 共一行&#xff0c;包含一个整数 n。 输出格式 按字典序输出所有排列方案…

FastAI 之书(面向程序员的 FastAI)(二)

原文&#xff1a;www.bookstack.cn/read/th-fastai-book 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第三章&#xff1a;数据伦理 原文&#xff1a;www.bookstack.cn/read/th-fastai-book/9bc6d15b4440b85d.md 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4…

机器学习12-基本感知器

感知器(Perceptron)是一种最简单的人工神经网络结构,由美国心理学家Frank Rosenblatt在1957年提出。它是一种单层的前馈神经网络,通常用于二分类问题。 基本感知器由多个输入节点、一个输出节点和一组权重参数组成。每个输入节点都与输出节点连接,并且具有一个对应的权重参…

[计算机提升] 备份系统:设置备份

6.5 备份系统&#xff1a;设置备份 1、进入到控制面板系统和安全\备份和还原&#xff0c;点击右侧的设置备份&#xff1a; 2、在弹出的设置备份对话框中&#xff0c;选择要保存的位置&#xff0c;点击下一步开始备份。 3、选择要备份的内容。根据需要选择即可。这种备份的…

【MySQL进阶之路】十亿量级评论表SQL调优实战

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

红日靶场(初学)

按照以前的来说一般是有两层网络的内网和外网 这个也是这样的 所以需要两张网卡&#xff0c;一个用来向外网提供web服务&#xff0c;一个是通向内网 以下就是配置 以下就是一些相关信息 外网网段是写成了192.168.111.1/24 WEB PC DC kali 开始扫描 nmap -sS -sV -Pn -T4 19…