1.
1. 分配方式
- 栈(Stack):由编译器自动管理。函数调用时,函数的局部变量、参数等数据会在栈上分配。当函数返回时,这些数据会自动销毁。因此,栈内存是临时的,随着函数的调用和返回而动态变化。
- 堆(Heap):由程序员手动管理。使用诸如
new
、malloc
等操作分配内存,分配的内存块在程序显式释放(如delete
、free
)之前一直存在。堆内存的生命周期由程序员控制。
2. 内存管理
- 栈:栈空间是由系统自动分配和释放的,具有严格的LIFO(后进先出)管理机制。当函数调用结束时,栈帧会自动弹出,释放内存。栈的分配和释放速度非常快。
- 堆:堆内存由程序员管理,使用灵活,但需要显式释放。堆内存的分配和释放速度相对较慢,因为它涉及到复杂的内存管理操作,如碎片整理。
3. 内存大小
- 栈:通常较小,有限的大小(例如几MB到几十MB),因为它主要用于短期存储。过大的栈空间使用会导致栈溢出(Stack Overflow)。
- 堆:通常较大,内存大小仅受限于系统的物理内存和操作系统的限制。可以存储大量数据,但过多的未释放堆内存会导致内存泄漏。
4. 使用场景
- 栈:适合存储局部变量、小型对象和函数调用相关的数据,由于其高效的管理方式,适用于生命周期较短的数据。
- 堆:适合存储需要跨函数调用、生命周期较长或动态大小的数据,如大型对象、动态数组或其他复杂数据结构。
5. 安全性
- 栈:由于栈空间是自动管理的,出现内存泄漏的可能性较低,但需要注意栈溢出。
- 堆:更灵活但也更容易出错,例如出现内存泄漏(分配内存但未释放)或悬挂指针(指向已经释放的内存)。
2 .栈(Stack)和队列(Queue)被称为容器适配器,是因为它们的实现依赖于底层的基本数据结构,如数组、链表、或者更抽象的标准容器(如std::vector
、std::deque
或std::list
),而不是独立的数据结构。以下是一些原因解释为什么栈和队列是容器适配器:
-
抽象接口与功能特定性:
- 栈和队列的核心在于它们操作的特定性:栈是后进先出(LIFO),队列是先进先出(FIFO)。这些行为方式本质上只是对元素访问顺序的约束,而不是对底层数据存储方式的约束。所以可以使用已经优化好的数据结构,而不是将栈和队列从新设计为一种底层的容器。
- 因此,栈和队列的实现可以利用现有的更通用的数据结构,通过对这些数据结构的特定操作进行适配来实现特定的行为。例如,栈的
push
和pop
操作可以通过使用动态数组的push_back
和pop_back
实现,而队列的enqueue
和dequeue
可以使用双端队列的push_back
和pop_front
实现。
-
代码复用与灵活性:
- 通过将栈和队列设计为容器适配器,可以复用已有的标准容器(如
std::vector
或std::deque
)的代码。这不仅简化了实现,还提高了代码的可维护性和灵活性。 - 用户可以根据具体需求选择合适的底层容器来适配栈或队列。例如,可以用
std::deque
作为队列的底层容器,因为std::deque
支持高效的前后两端插入和删除操作。
- 通过将栈和队列设计为容器适配器,可以复用已有的标准容器(如
-
性能优化与空间利用:
- 使用容器适配器的方式,允许在实现栈或队列时对底层数据结构进行性能优化。例如,如果频繁需要在队列两端插入和删除元素,那么使用
std::deque
比std::vector
更为高效。 - 同时,基于不同数据结构的实现可以有效地利用空间,例如链表可以避免动态数组扩展所带来的额外空间开销。
- 使用容器适配器的方式,允许在实现栈或队列时对底层数据结构进行性能优化。例如,如果频繁需要在队列两端插入和删除元素,那么使用
总之,栈和队列作为容器适配器的设计思想是为了充分利用已有的数据结构的优势,提供一种抽象的、功能特定的接口,而无需重新设计或实现底层存储方式。这样既能确保操作的效率,又能保持实现的简洁性和灵活性。
3. new
/delete
和 malloc
/free
都用于动态内存分配,但它们之间有显著的区别,尤其是在 C++ 编程中。这些区别涵盖了功能、使用场景、管理对象、以及语法特性。
1. 功能和用途
-
malloc
/free
(C 和 C++ 都可用):malloc
(Memory Allocation):从堆中分配指定字节大小的内存,并返回指向该内存的指针。内存未初始化,即其中的内容是未定义的。free
:释放由malloc
分配的内存,使其可被再次分配。
-
new
/delete
(仅 C++):new
:不仅从堆中分配内存,还调用对象的构造函数来初始化分配的内存。new
返回的是指向对象的类型安全指针。delete
:不仅释放内存,还调用对象的析构函数,执行任何必要的清理操作。
-
总结
malloc
/free
是 C 语言中使用的内存管理函数,提供了基本的内存分配和释放功能,不支持对象的构造和析构。new
/delete
是 C++ 中专用的内存管理操作符,支持对象的动态分配、构造、析构和释放,更加符合 C++ 的面向对象特性和类型安全性。
栈和队列理论
栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。
从下图中可以看出,栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。
个人理解:这张图非常形象,内部私有数据通过数组队列等数据结构就能够实现栈的功能,对外的接口就是所实现的栈的功能,所以对外看起来就是一个栈的数据结构,但是底层试利用其他数据结构实现的,所以它往往不被归为容器,而是容器适配器
std::stack<int, std::vector<int> > third; // 使用vector为底层容器的栈
队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构。
也可以指定list 为起底层实现,初始化queue的语句如下:
std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列
2.利用栈实现队列,四个功能push(),pop(),peek(),empth()
push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。
class MyQueue {
public:
stack<int> stIn;
stack<int> stOut;
MyQueue() {
}
void push(int x) {
stIn.push(x);
}
int pop() {
if(stOut.empty()){
while(!stIn.empty()){
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
int peek() {
int res = this->pop();
stOut.push(res);
return res;
}
bool empty() {
return stIn.empty() && stOut.empty();
}
};
Python 中的 pop()
在Python中,pop()
方法通常用于list
、deque
、set
等数据结构:
list.pop([index])
:
- 默认行为:如果不指定索引,
pop()
会移除并返回list
中最后一个元素。这与栈的后入先出(LIFO)原则一致。 - 指定索引:如果提供了索引
index
,则pop(index)
会移除并返回该索引处的元素
deque.pop()
:
deque
(双端队列)在默认情况下,pop()
方法移除并返回双端队列中的最后一个元素。
set.pop()
:
- 在
set
中,pop()
会随机移除并返回一个元素,因为集合是无序的
在C++中,pop()
方法通常用于标准模板库(STL)中的容器,如stack
、queue
、priority_queue
等。这些容器的pop()
方法仅移除元素而不返回被移除的元素。
stack
是后入先出(LIFO)的数据结构。pop()
方法移除栈顶元素,但不返回该元素。
queue
是先进先出(FIFO)的数据结构。pop()
方法移除队首元素,但不返回该元素。
class MyQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
self.stack_in.append(x)
def pop(self) -> int:
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
result = self.pop()
self.stack_out.append(result)
return result
def empty(self) -> bool:
return not(self.stack_in or self.stack_out)
3.队列实现栈:一个队列即可,就依靠不断将队首的元素push到队尾主要依靠que.push(que.front())实现;
class MyStack {
public:
queue<int> que;
MyStack() {
}
void push(int x) {
que.push(x);
}
int pop() {
int size = que.size();
size--;
while(size--){
que.push(que.front());
que.pop();
}
int result = que.front();
que.pop();
return result;
}
int top() {
int size = que.size();
size--;
while(size--){
que.push(que.front());
que.pop();
}
int result = que.front();
que.push(que.front());
que.pop();
return result;
}
bool empty() {
return que.empty();
}
};
靠list实现
class MyStack:
def __init__(self):
self.que = []
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
return self.que.pop()
def top(self) -> int:
result = self.que.pop()
self.que.append(result)
return result
def empty(self) -> bool:
return not self.que
靠deque实现 实现是一样的主要是deque由leftpop和pop
class MyStack:
def __init__(self):
self.que = deque()
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
return self.que.pop()
def top(self) -> int:
result = self.que.pop()
self.que.append(result)
return result
def empty(self) -> bool:
return not self.que
4.有效括号 对于这种对称字符串匹配,栈数据结构非常有用;
class Solution {
public:
bool isValid(string s) {
if(s.size()%2 != 0) return false;
stack<int> st;
for(int i=0;i<s.size();i++){
if(s[i]=='(') st.push(')');
else if (s[i]=='{') st.push('}');
else if (s[i]=='[') st.push(']');
else if (st.empty() || st.top()!=s[i]) return false;
else st.pop();
}
return st.empty();
}
};