队列和栈学习记录(自用)

知识点来自队列和栈

实践为答题代码

队列和栈

一、队列:先入先出的数据结构

1.先入先出的数据结构(FIFO)

在先入先出的数据结构中,将首先处理添加到队列中的第一个元素。

队列是典型的 FIFO 数据结构。

  • 插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾

  • 删除(delete)操作也被称为出队(dequeue)。 只能移除第一个元素

2.队列-实现

为了实现队列,可以使用动态数组和指向队列头部的索引。

队列应支持两种操作:入队和出队。入队会向队列追加一个新元素,而出队会删除第一个元素。 所以我们需要一个索引来指出起点。

这是一个供你参考的实现:

#include <iostream>

class MyQueue {
    private:
        // store elements
        vector<int> data;       
        // a pointer to indicate the start position
        int p_start;            
    public:
        MyQueue() {p_start = 0;}
        /** Insert an element into the queue. Return true if the operation is successful. */
        bool enQueue(int x) {
            data.push_back(x);
            return true;
        }
        /** Delete an element from the queue. Return true if the operation is successful. */
        bool deQueue() {
            if (isEmpty()) {
                return false;
            }
            p_start++;
            return true;
        };
        /** Get the front item from the queue. */
        int Front() {
            return data[p_start];
        };
        /** Checks whether the queue is empty or not. */
        bool isEmpty()  {
            return p_start >= data.size();
        }
};

int main() {
    MyQueue q;
    q.enQueue(5);
    q.enQueue(3);
    if (!q.isEmpty()) {
        cout << q.Front() << endl;
    }
    q.deQueue();
    if (!q.isEmpty()) {
        cout << q.Front() << endl;
    }
    q.deQueue();
    if (!q.isEmpty()) {
        cout << q.Front() << endl;
    }
}

缺点:

上面的实现很简单,但在某些情况下效率很低。 随着起始指针的移动,浪费了越来越多的空间。

让我们考虑一种情况,即我们只能分配一个最大长度为 5 的数组。当我们只添加少于 5 个元素时,我们的解决方案很有效。 例如,调用入队函数四次后还想要将元素 10 入队,那么可以成功。

但是不能接受更多的入队请求,因为现在队列已经满了。但是如果将一个元素出队,在这种情况下,应该能够再接受一个元素。

3.循环队列

上面提供了一种简单但低效的队列实现。

更有效的方法是使用循环队列。 具体来说,可以使用固定大小的数组和两个指针来指示起始位置和结束位置。 目的是重用我们之前提到的被浪费的存储。

实践:设计循环队列
typedef int Datatype;
typedef struct {
	int head,tail;
	int size,Maxnum; 
    Datatype *data;
} MyCircularQueue;

bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
	MyCircularQueue *p=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	p->data=(Datatype*)malloc(sizeof(Datatype)*k);
	p->size=0;
	p->Maxnum=k;
	p->head=-1;
	p->tail=-1;
	return p;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
	if(myCircularQueueIsFull(obj)){
		return false;
	}
	if(obj->head==-1&&obj->tail==-1){
		obj->head=0;
		obj->tail=0;
		obj->data[obj->tail]=value;
		obj->size++;
	}else if(obj->tail==obj->Maxnum-1){
		obj->tail=0;
		obj->data[obj->tail]=value;
		obj->size++;
	}else{
		obj->tail++;
		obj->data[obj->tail]=value;
		obj->size++;
	}
	return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
	if(myCircularQueueIsEmpty(obj)){
		return false;
	}
	if(obj->head==obj->Maxnum-1){
		obj->head=0;
		obj->size--;
	}else{
		obj->head++;
		obj->size--;
	}
	return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
	if(myCircularQueueIsEmpty(obj)){
		return -1;
	}
	return obj->data[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
	if(myCircularQueueIsEmpty(obj)){
		return -1;
	}
	return obj->data[obj->tail];
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
	if(obj->size==0){
		return true;
	}else{
		return false;
	}
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
	if(obj->size==obj->Maxnum){
		return true;
	}else{
		return false;
	}
}

void myCircularQueueFree(MyCircularQueue* obj) {
	free(obj->data);
	free(obj);
}

4.循环队列-实现

class MyCircularQueue {
private:
    vector<int> data;
    int head;
    int tail;
    int size;
public:
    /** Initialize your data structure here. Set the size of the queue to be k. */
    MyCircularQueue(int k) {
        data.resize(k);
        head = -1;
        tail = -1;
        size = k;
    }
    
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    bool enQueue(int value) {
        if (isFull()) {
            return false;
        }
        if (isEmpty()) {
            head = 0;
        }
        tail = (tail + 1) % size;
        data[tail] = value;
        return true;
    }
    
    /** Delete an element from the circular queue. Return true if the operation is successful. */
    bool deQueue() {
        if (isEmpty()) {
            return false;
        }
        if (head == tail) {
            head = -1;
            tail = -1;
            return true;
        }
        head = (head + 1) % size;
        return true;
    }
    
    /** Get the front item from the queue. */
    int Front() {
        if (isEmpty()) {
            return -1;
        }
        return data[head];
    }
    
    /** Get the last item from the queue. */
    int Rear() {
        if (isEmpty()) {
            return -1;
        }
        return data[tail];
    }
    
    /** Checks whether the circular queue is empty or not. */
    bool isEmpty() {
        return head == -1;
    }
    
    /** Checks whether the circular queue is full or not. */
    bool isFull() {
        return ((tail + 1) % size) == head;
    }
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * bool param_1 = obj.enQueue(value);
 * bool param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * bool param_5 = obj.isEmpty();
 * bool param_6 = obj.isFull();
 */

5.队列-用法

队列有两个重要的操作,入队 enqueue 和出队 dequeue。 此外,我们应该能够获得队列中的第一个元素,因为应该首先处理它。

下面是使用内置队列库及其常见操作的一些示例:

#include <iostream>

int main() {
    // 1. Initialize a queue.
    queue<int> q;
    // 2. Push new element.
    q.push(5);
    q.push(13);
    q.push(8);
    q.push(6);
    // 3. Check if queue is empty.
    if (q.empty()) {
        cout << "Queue is empty!" << endl;
        return 0;
    }
    // 4. Pop an element.
    q.pop();
    // 5. Get the first element.
    cout << "The first element is: " << q.front() << endl;
    // 6. Get the last element.
    cout << "The last element is: " << q.back() << endl;
    // 7. Get the size of the queue.
    cout << "The size is: " << q.size() << endl;
}

二、*队列和广度优先搜索

先决条件:树的层序遍历树的遍历

广度优先搜索(BFS)是一种遍历或搜索数据结构(如树或图)的算法。

如前所述,我们可以使用 BFS 在树中执行层序遍历。

我们也可以使用 BFS 遍历图。例如,我们可以使用 BFS 找到从起始结点到目标结点的路径,特别是最短路径。

我们可以在更抽象的情景中使用 BFS 遍历所有可能的状态。在这种情况下,我们可以把状态看作是图中的结点,而以合法的过渡路径作为图中的边。

1.队列和BFS

广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径。

  1. 结点的处理顺序是什么?

在第一轮中,我们处理根结点。在第二轮中,我们处理根结点旁边的结点;在第三轮中,我们处理距根结点两步的结点;等等等等。

与树的层序遍历类似,越是接近根结点的结点将越早地遍历

如果在第 k 轮中将结点 X 添加到队列中,则根结点与 X 之间的最短路径的长度恰好是 k。也就是说,第一次找到目标结点时,你已经处于最短路径中

  1. 队列的入队和出队顺序是什么?

如上面的动画所示,我们首先将根结点排入队列。然后在每一轮中,我们逐个处理已经在队列中的结点,并将所有邻居添加到队列中。值得注意的是,新添加的节点不会立即遍历,而是在下一轮中处理。

结点的处理顺序与它们添加到队列的顺序是完全相同的顺序,即先进先出(FIFO)。这就是我们在 BFS 中使用队列的原因。

2.广度优先搜索-模板

之前,我们已经介绍了使用 BFS 的两个主要方案:遍历找出最短路径。通常,这发生在树或图中。正如我们在章节描述中提到的,BFS 也可以用于更抽象的场景中。

在特定问题中执行 BFS 之前确定结点和边缘非常重要。通常,结点将是实际结点或是状态,而边缘将是实际边缘或可能的转换。

模板1(伪代码)
/**
 * Return the length of the shortest path between root and target node.
 */
int BFS(Node root, Node target) {
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    // BFS
    while (queue is not empty) {
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
                add next to queue;
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}
  • 如代码所示,在每一轮中,队列中的结点是等待处理的结点。
  • 在每个更外一层的 while 循环之后,我们距离根结点更远一步。变量 step 指示从根结点到我们正在访问的当前结点的距离。
模板2(伪代码)

有时,确保我们永远不会访问一个结点两次很重要。否则,我们可能陷入无限循环。如果是这样,我们可以在上面的代码中添加一个哈希集来解决这个问题。这是修改后的伪代码:

/**
 * Return the length of the shortest path between root and target node.
 */
int BFS(Node root, Node target) {
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    Set<Node> used;     // store all the used nodes
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    add root to used;
    // BFS
    while (queue is not empty) {
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
                if (next is not in used) {
                    add next to queue;
                    add next to used;
                }
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}

有两种情况你不需要使用哈希集:

  1. 你完全确定没有循环,例如,在树遍历中;
  2. 你确实希望多次将结点添加到队列中。

三、栈:后入先出的数据结构

1.后入先出的数据结构(LIFO)

在 LIFO 数据结构中,将首先处理添加到队列中的最新元素

与队列不同,是一个 LIFO 数据结构。通常,插入操作在栈中被称作入栈 push 。与队列类似,总是在堆栈的末尾添加一个新元素。但是,删除操作,退栈 pop ,将始终删除队列中相对于它的最后一个元素

2.栈-用法

内置的栈库:

#include <iostream>

int main() {
    // 1. Initialize a stack.
    stack<int> s;
    // 2. Push new element.
    s.push(5);
    s.push(13);
    s.push(8);
    s.push(6);
    // 3. Check if stack is empty.
    if (s.empty()) {
        cout << "Stack is empty!" << endl;
        return 0;
    }
    // 4. Pop an element.
    s.pop();
    // 5. Get the top element.
    cout << "The top element is: " << s.top() << endl;
    // 6. Get the size of the stack.
    cout << "The size is: " << s.size() << endl;
}
实践:最小栈
#define MAXNUM 30000
typedef int Datetype;
typedef struct {
    int top;
	int min; 
    Datetype data[MAXNUM];
} MinStack;


MinStack* minStackCreate() {
	MinStack *p=(MinStack*)malloc(sizeof(MinStack));
	p->min=0;
	p->top=0;
    return p;
}

void minStackPush(MinStack* obj, int val) {
	if(obj){
		if(obj->top==0){
			obj->data[obj->top]=val;
			obj->min=val;
			obj->top++;
		}else{
			if(val<obj->min){
				obj->data[obj->top]=val;
				obj->min=val;
				obj->top++;
			}else{
				obj->data[obj->top]=val;
				obj->top++;
			}
		}
	}
}

void minStackPop(MinStack* obj) {
	if(obj&&obj->top!=0){
		if(obj->data[obj->top-1]>obj->min){
			obj->top--;
		}else{
			obj->top--;
			if(obj->top!=0){
				obj->min=obj->data[obj->top-1];
				int i;
				for(i=0;i<obj->top;i++){
					if(obj->data[i]<obj->min){
						obj->min=obj->data[i];
					}
				}
			}
		}
	}
}

int minStackTop(MinStack* obj) {
	return obj->data[obj->top-1];
    
}

int minStackGetMin(MinStack* obj) {
		return obj->min;
}

void minStackFree(MinStack* obj) {
	obj->top=0;
}
实践:有效的括号
class Solution {
public:
    bool isValid(string s) {
        stack<char> bracket;
        int i;
        for(i=0;i<s.length();i++){
            switch(s[i]){
                case '(':
                    bracket.push(')');
                    break;
                case '[':
                	bracket.push(']');
                	break; 
                case '{':
                	bracket.push('}');
                	break;
                case ')':
                	if(!bracket.empty()&&bracket.top()==')'){
                		bracket.pop();
					}else{
						return false;
					}
                	break;
                case ']':
                	if(!bracket.empty()&&bracket.top()==']'){
                		bracket.pop();
					}else{
						return false;
					}
                	break;
                case '}':
                	if(!bracket.empty()&&bracket.top()=='}'){
                		bracket.pop();
					}else{
						return false;
					}
                	break;
            }
        }
		if(bracket.empty()){
			return true;
		}else{
			return false;
		}
    }
};
实践:每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        vector<int> ret(temperatures.size());
        int i;
        stack<int> b;
        for(i=0;i<temperatures.size();i++){
        	if(b.empty()){
        		b.push(i);
			}else{
				if(temperatures[i]>temperatures[b.top()]){
					while(!b.empty()&&temperatures[i]>temperatures[b.top()]){
						ret[b.top()]=i-b.top();
						b.pop();
					}
					b.push(i);
				}else{
					b.push(i);
				}
			}
		}
		while(!b.empty()){
			ret[b.top()]=0;
			b.pop();
		}
        return ret;
    }
};
实践:逆波兰表达式求值
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
		stack<long long int> num;
		 int i;
		 for(i=0;i<tokens.size();i++){
		 	if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){
		 		long long int a=num.top();
		 		num.pop();
		 		long long int b=num.top();
		 		num.pop();
		 		int c;
		 		if(tokens[i]=="+"){
		 			c=a+b;
				 }else if(tokens[i]=="-"){
				 	c=b-a;
				 }else if(tokens[i]=="*"){
				 	c=a*b;
				 }else{
				 	c=b/a;
				 }
				 num.push(c);
			 }else{
			 	int n=stoi(tokens[i]);
			 	num.push(n);
			 }
		 }
		 return num.top();
    }
};

四、*栈和深度优先搜索

先决条件:树的遍历树的遍历

与 BFS 类似,深度优先搜索(DFS)是用于 在树/图中遍历/搜索 的另一种重要算法。也可以在更抽象的场景中使用。

正如树的遍历中所提到的,我们可以用 DFS 进行 前序遍历中序遍历后序遍历。在这三个遍历顺序中有一个共同的特性:除非我们到达最深的结点,否则我们永远不会回溯

这也是 DFS 和 BFS 之间最大的区别,BFS永远不会深入探索,除非它已经在当前层级访问了所有结点

通常,我们使用递归实现 DFS。栈在递归中起着重要的作用。在本章中,我们将解释在执行递归时栈的作用。我们还将向你展示递归的缺点,并提供另一个 **没有递归 **的 DFS 实现。

1.栈和DFS

与 BFS 类似,深度优先搜索(DFS)也可用于查找从根结点到目标结点的路径。

在 DFS 中找到的第一条路径并不总是最短的路径。

2.DFS-模板1

3.DFS-模板2

(模板都是java的…)

作者:力扣 (LeetCode)
链接:https://leetcode.cn/leetbook/read/queue-stack/k6zxm/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值