堆与栈
堆栈是两种数据结构或者两种内存管理方式
(1)数据结构下的堆栈
①栈
一种运算受限的线性表,只允许在栈顶(Top)进行元素的进栈(Push)和出栈(Pop),使得 栈中元素拥有了”先进后出“的特性(First In Last Out,FILO)。
栈既然是一种线性结构,就可以通过数组或者链表(单向链表、双向链表或循环链表)作为底层数据结构。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈。其区别也同数组和链表的区别一样,顺序栈中的元素地址连续,链式栈中的元素地址不连续。
栈的基本操作包括栈的初始化、判断栈是否为空、入栈、出栈、获取栈顶元素等。
顺序栈
#include
using namespace std;
#define Maxsize 100
//顺序栈的存储结构
typedef struct{
int *base;//栈底指针,始终指向栈底
int *pop;//栈顶指针,初始时指向站地
//入栈top++,出栈top--
//当top==base,空栈
//栈非空时,top始终指向此时的栈顶元素的上一个位置
int stacksize;//栈可使用最大容量
}SqStack;
//1.顺序栈的初始化
/*
步骤:
1.为顺序栈分配一个数组空间
2.将base指向栈底
3.将top初始为base,表示空栈
4.将stacksize设置为Maxsize
*/
void Init(SqStack &s){
s.base = new int[Maxsize];//使用new就不能追加空间,可以改写为:s.base = (int*)malloc(Maxsize*sizeof(int));
if(!s.base) exit(1);//s.base为0,即没有成功分配空间,就异常退出程序
s.top = s.base;
s.stacksize = Maxsize;
}
//2.顺序栈的入栈
/*步骤
1.判断栈是否已满,若满则返回false
2.将新元素压入栈顶,top++
*/
int push(SqStack &s, int e){
if(s.top - s.base == s.stacksize) return false;
*s.top = e;
s.top++;
return 1;
}
//3.顺序栈的出栈
/*
步骤:
1.先判断栈是否是空栈,若为空栈,返回false
2.top--,栈顶元素出栈
*/
int pop(SqStack &e, int e){
if(s.top == s.base) return false;
s.top--;
e = *s.top;
return 1;
}
//4.取顺序栈的栈顶元素
/*
步骤:
当栈非空时,返回栈顶元素的值,栈顶指针保持不变
*/
int getelem(SqStack &s){
if(s.top != s.base) return(*s.top-1);
}
//5.销毁
void Destroy(SqStack &s){
delete s.base;
}
//实验
int main() {
SqStack sqstack;
Init(sqstack);
cout << "请输入一串数字:(以非数字结束)" << endl;
int num;
while (cin >> num)
push(sqstack, num);
while (sqstack.top != sqstack.base) {
pop(sqstack, num);
cout << getelem(sqstack) << " ";
}
Destroy(sqstack);
return 0;
}
链式栈(以单向链表为例)
与链表类似,但插入与删除等操作都在链表的头部,可以理解为链式栈是一个以top为头结点链表
#include
using namespace std;
struct ListStack{
int value;
ListStack *next;
};
//1.初始化
void Init(ListStack *s){
s->next = NULL;
}
//2.将元素压入栈顶
void push(ListStack *s, int e){
ListStack *p;
p = new ListStack;
p->value = e;
p->next = s->next;
s->next = p;
}
//把栈顶元素取出并赋给e,并删除栈顶
int pop(ListStack *s, int &e){
ListStack *delete_r;
if(s->next == NULL) return 0;
else{
delete_r = s->next;
e = s->next->value;
s->next = delete_r->next;
delete delete_r;
}
}
//销毁栈
void Destroy(ListStack *s){
ListStack *delete_q;
while(s->next != NULL){
delete_q = s->next;
s->next = s->next->next;
delete delete_q;
}
}
//判空
int IsEmpty(ListStack *s){
if(s->next == NULL) return 1;
else return 0;
}
//实验
int main(){
ListStack *S_List;
S_List = new ListStack;
Init(S_List);
int num;
cout << "请输入一串数字:(以非数字结束)" << endl;
while(cin >> num) push(S_List, num);
while(!IsEmpty(S_List)){
pop(S_List, num);
cout << num << " ";
}
Destroy(S_List);
return 0;
}
②堆
是一种树形结构,一种特殊的完全二叉树。当且仅当满足,所有结点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的存储一般用数组来存储。第i节点的父节点的下标就为(i-1)/2(小数向下取整),它的左右子节点的下标为2i+1和2i+2。堆分为最大堆和最小堆。
最大堆是指父节点的值比每一个子节点的值都要大。
最小堆是指父节点的值比每一个子节点的值都要小。
堆的应用的步骤:
#include
using namespace std;
int heap_size;//堆大小
int heap_cap_size;//对容量大小
#define INIT_ARRAY_SIZE 50
//1、建堆:将数组变为堆(建堆的思想用的是自底向上的思想)
/*关键的思想:①找到最后一个非父节点;②将这个父节点与左右节点进行比较,找出最大的节点,若不是原父节
点,就交换,若是,就跳过;③从最后一个父节点一直到根节点重复第二部操作;④注意,这一步与第二部同时进
行,这一步实现递归调整。当发生了交换操作时,我们把交换操作中的子节点(左或右节点)作为根节点
c[0],然后找出这个根节点所代表的子树c,从第一个父节点c[0](也就是根节点),第二个父节点c[1],再
依次往下,直到最后一个父节点,对每一个父节点都执行第④步的比较操作。*/
//为了实现上述方案,对堆的左右支分别设计
/*返回以index为根的完全二叉树的左子树的索引,整个二叉树索引以0为开始*/
int left(int index){
return ((index << 1) + 1);//使用移位,提高运算速度
}
/*返回以index为根的完全二叉树的右子树的索引,整个二叉树索引以0为开始*/
int right(int index){
return ((index << 1) + 2);
}
/*两个数的交换*/
void swap(int* a, int* b){
int temp = *a;
*a = *b;
*b = temp;
}
//构建堆
void build_heap(int array[], int length){
heap_size - length;
for(int i = ((heap_size- 1) >> 1); i >= 0; --i){
max_heap_adjust(array, i);
}
}
//堆的调整
void max_heap_adjust(int array[], int index){
int left_index = left(index);
int right_index = right(index);
int largest = index;
//左子树与父节点进行对比
if(left_index <= (heap_size - 1) && array[left_index] > array[largest]){
largest = left_index;
}
//右子树与父节点进行对比
if(right_index <= (heap_size - 1) && array[right_index] > array[largest]){
largest = right_index;
}
if (largest == index) {
return 0;
}
else {
//需要交换
swap(&array[index], &array[largest]);
//递归调用
max_heap_adjust(array, largest);
}
}
//2、插入与删除
/*插入的思想就是在对的最后面新加一个节点,在对新构成的二叉树进行建堆;删除而言,对于堆删除只能删除根节点,但无法直接删除,需要将堆尾元素剪切,覆盖掉原堆首元素,再进行建堆*/
//堆的删除
void heap_selete(int array[], int value){
for(int index = 0; index < heap_size; index++){
if(array[index] == value){
break;
}
}
}
//堆的插入
void heap_insert(int** array, int value){
int index = 0;
int parent_ondex = 0;
if((heap_size + 1) > heap_cap_size){
*array = (int*)realloc(*array, 2 * INIT_ARRAY_SIZE * sizeof(int));//因为插入元素会使堆容量扩大
}
(*array)[heap_size] = value;
index = hwap_size;
heap_size++;
//与父节点做对比,大的向上移动
while(index){
parent_index = ((index - 1) >> 1);
if ((*array)[parent_index] < (*array)[index]) {
swap(&((*array)[parent_index]), &((*array)[index]));
}
index = parent_index;
}
}
//3、堆的排序
/*对于堆,无论是最大堆还是最小堆,其根节点都是这个堆的极值,建堆好后,取出根节点,然后堆容量减一,再对剩下的元素重新建堆。重复上述步骤,直至堆容量为1,此时就得到排序完成的二叉树*/
//堆排完序之后就不再是堆,而是一个有序数组
void heap_sort(int array[], int length) {
int old_heap_size = heap_size;
int i;
for (i = length - 1; i >= 1; --i) {
swap(&array[i], &array[0]);
--heap_size;
max_heap_adjust(array, 0);
}
//恢复堆的大小
heap_size = old_heap_size;
}
(2)内存分配中的堆栈
在内存分配中,堆栈的管理方式、空间大小、能否产生碎片、生长方向、分配方式和效率均不同。
①管理方式的不同
即分配和释放方式不同。栈的内存空间由系统自动分配、释放,堆由程序员自己开拓,比如new、malloc等;
②空间大小的不同
栈的空间较小,一般是1M或者2M,堆的大小受限于计算机系统的虚拟内存,也就基本上没有内存大小的限制;
③能否产生碎片的不同
栈是一块连续的内存的区域,不会产生碎片,堆由于是向高地址扩展的数据结构,是不连续的,从而产生碎片;
④生长方向的不同
栈是由上往下,堆是由下往上;
⑤分配方式的不同
栈有两种分配方式:静态分配和动态分配,堆只有动态分配;
⑥分配效率的不同
堆的效率比栈要低很多。
浙公网安备 33010602011771号