1. 内存中的堆区和栈区
1.1 栈区简介
- 栈由操作系统自动分配和释放,用于存放函数的参数值、局部变量等,通常在函数执行完后就释放,其操作方式类似于数据结构中的栈。
- 函数中定义的局部变量按照先后定义的顺序依次入栈,即相邻变量的地址之间不会存在其他变量。
- 栈的内存地址生长方向由高到低。
- 栈中存储的数据的生命周期随着函数的执行完成而释放。
栈的内存分配
栈内存分配运算内置于CPU的指令集,效率很高,但分配的内存量有限,比如Linux中栈区的大小通常为8M。
只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
1.2 堆区简介
栈上的数据在函数返回的时候就会被释放掉,所以无法将数据传递至函数外部,而全局变量无法动态的产生,只能在编译的时候定义,在这种情况下,堆的使用十分必要。
- 堆由开发人员分配和释放,分配方式类似于链表。
- 堆的内存地址生长方向由低到高,但后申请的内存不一定在先申请的内存的后面,原因在于先申请的内存空间一旦被释放,后申请的内存空间则会利用先前被释放的内存。
- 堆中存储的数据若未释放,则其生命周期等同于程序的生命周期。
堆的内存分配
操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该堆结点从空闲结点链表中删除,并将该结点的空间分配给程序。
对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,便于delete
语句正确释放本内存空间。此外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多与的那部分重新放入空闲链表中。
1.3 堆区和栈区的区别
栈和堆实际上是操作系统对进程占用的内存空间的两种管理方式,主要有以下区别:
- 管理方式:操作系统自动分配和释放vs程序员手动分配和释放
- 空间大小:每个进程拥有的栈大小 ≪ \ll ≪堆大小
- 内存生长方向:由高到低vs由低到高
- 分配方式:栈支持静态分配和动态分配,堆都是动态分配的
- 分配效率:栈
≫
\gg
≫堆
栈由OS自动分配,会在硬件层级提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。
堆由C/C++提供的库函数或运算符完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。 - 存储内容:
栈:在函数调用时,第一个进栈的是主函数中函数调用语句的下一条可执行语句的地址,然后是函数的各个参数,在大多数的C编译器中,参数是从右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
堆:一般是在堆的头部用一个字节存放堆的大小,堆的具体内容由程序员安排。
虽然栈效率更高,但相比堆不够灵活。无论是堆还是栈,在内存使用时都要防止非法越界。
2. 数据结构中的堆和栈
2.1 栈
栈是一种运算受限(只允许在表的一端进行插入和删除操作)的线性表,先进后出;栈中只有栈顶的元素才可以被外界使用,因此栈不允许有遍历行为;栈可以使用数组或链表作为底层数据结构,使用数组实现的称为顺序栈,使用链表实现的为链式栈。
栈的应用:括号匹配
//给定一个字符串,里边可能包含“()”和其他字符,请编写程序检查该字符串中的括号是否成对出现。
#include <iostream>
#include <stack>
using namespace std;
bool isValid(string str)
{
stack<char> s;
char c;
int len = str.length();
for (int i = 0; i < len; i++)
{
if (str[i] == '(' || str[i] == '[' || str[i] == '{')
{
s.push(str[i]);
}
else if (str[i] == ')')
{
if (s.empty()){
return false;
}
else if ((c = s.top())&&(c == '('))
s.pop();
else{
return false;
}
}
else if (str[i] == ']')
{
if (s.empty()){
return false;
}
else if ((c = s.top())&&(c == '['))
s.pop();
else{
return false;
}
}
else if (str[i] == '}')
{
if (s.empty()){
return false;
}
else if ((c = s.top())&&(c == '{'))
s.pop();
else{
return false;
}
}
}
if (s.empty())
return true;
else
return false;
}
int main()
{
string str = "y={x+[x(x+2)]}";
bool check=true;
check = isValid(str);
if (check == true)
cout << "Valid."<<endl;
else
cout << "Not valid."<<endl;
return 0;
}
2.2 堆
堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有结点的值总是不大于(大顶堆)或不小于(小顶堆)其父节点的值的完全二叉树被称之为堆。
堆一般使用数组进行存储,从左至右从上至下,
i
i
i节点的父节点下标为
i
−
1
2
\frac{i-1}{2}
2i−1,它的左、右子节点下标分别为
2
i
+
1
2i+1
2i+1和
2
i
+
2
2i+2
2i+2。
堆的应用:堆排序HeapSort
时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 。
基本思想:
- 构造初始堆(一般升序采用大顶堆,降序采用小顶堆);
- 将堆顶元素和最后一个元素交换,然后将剩下的结点重新构造成一个小顶堆;
- 重复步骤2。
代码实现:(以小顶堆为例)
#include <iostream>
using namespace std;
void makeMinHeap(int arr[], int start, int end)
{
int dad = start;
int son = 2 * dad + 1;
while (son <= end)
{
if (son + 1 <= end && arr[son] > arr[son + 1])
{
son += 1;
}
if (arr[dad] < arr[son])
{
return;
}
else
{
swap(arr[dad], arr[son]);
dad = son;
son = 2 * dad + 1;
}
}
}
void HeapSort(int arr[], int len)
{
for (int i = len / 2 - 1; i >= 0; i--)
{
makeMinHeap(arr, i, len - 1);
}
for (int i = len - 1; i > 0; i--)
{
swap(arr[0], arr[i]);
makeMinHeap(arr, 0, i - 1);
}
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
int arr[] = {4, 5, 2, 7, 6, 8};
int len = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr, len);
return 0;
}