C 栈和堆

Posted 别碰我的宏定义

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C 栈和堆相关的知识,希望对你有一定的参考价值。

好久没写博客了,今天找了个时间,准备写一下栈和堆的知识。这些知识我也从各个方面整理和收集,希望对各位有用。废话不多说了,直接开整。
第一个问题面试高频考点:
请你说一下stackoverflow ,并简单举例说明,
栈溢出指的是程序向栈中写入某个变量时对内存进行了越界操作,从而导致栈中与其相邻的变量值被修改。
栈溢出的原因:1.局部数组过大,例如你定义了个int ar[60000],大约会导致栈溢出。
2.递归层数过多,导致栈溢出,例如斐波拉且数列,调用大约快100次的时候,它每次调用都会压栈,只压不释放到时候就会出现溢出。
3.指针或者数组指针越界,例如进行字符串拷贝,或者处理用户输入等等。

在这里插入代码片#include<stdio.h>
void func()
{
	int fun[600000000000];
}
int main()
{
	func();
	return 0;
}

比如这个函数就是因为数组过大,而函数在被调过程中是基于会开辟栈帧,存放传入参数,返回值还有临时变量。而调用过大时会出现报错,编译不过。

#include<stdio.h>
int func(int i)
{
	if (i == 1)
		return 1;
	return func(i - 1) + func(i - 2);
}
int main()
{
	int i = 100;
	func(i);
	printf("100次递归");
	return 0;
}

调用100次递归,程序奔溃。一直压栈,出栈导致栈溢出。导致stackoverflow
2.栈和堆的区别
堆是由低地址向高地址扩展,而栈是从高地址向低地址扩展。栈一般是由操作系统开辟和维护的,而堆是由程序员定义维护的,堆频繁调用malloc和free函数会导致产生内存碎片,降低程序效率,而栈是先进后出的这么一个结构,不会产生内存碎片。
计算机一般会给栈提供一些接口,分配专门的寄存器,而压栈入栈有专门的汇编指令,堆是由C/C++函数库提供的,机制复杂,且处理完之后还要转到汇编,所以栈比堆要快。
小根堆和大根堆:两者都是完全二叉树,两个要素,连续且缺失右节点。
好了,基础完成,开始搞代码,
我们用一个动态数组来模拟栈,它的特点是先进后出,就像是一群旅游去旅游,看见一个山洞,好奇心大发,想进去一探究竟,然后洞口只能每次一个人通过,等到最里面的人发现前面已经没路了,就需要外面的人先出去,他才能出去。只有等到它后面的最后一个人出来了,他才可以出来。
好了,定义一下今天需要实现的函数还有结构吧,先将上层架构搭好。
在这里插入图片描述

#ifndef _STACK_H_
#define _STACK_H_
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<assert.h>
#include<stdbool.h>
typedef char DateType;
typedef struct Stack{
	int capacity;  //数组容量
	int size;  //当前元素个数
	DateType *array;  //指向数组的指针
}Stack;
//栈的初始化
void StackInit(Stack *s,int capacity);
//压栈
void StackPush( Stack *s,DateType data);
//弹栈
void StackPop(Stack *s);
//显示栈顶元素
DateType StackTop(Stack *s);
//判栈满
int StackFill(Stack *s);
//扩容
void StackReserve(Stack *s);
//返回栈的大小
int StackSize(Stack *s);
//销毁栈`在这里插入代码片`
void StackDestroy(Stack *s);
//调试
void test1()
#endif

首先你需要有一个栈,故定义一个栈应该在所有操作之前。故功能还没实现先将需要实现的功能在测试函数理都调一边,需要测试那个,就将其他的屏掉,测试其他的就行了。

void test1()
{
	Stack s;
	StackInit(&s, 3);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	StackPush(&s, 4);
	StackPush(&s, 5);
	StackPush(&s, 6);
	printf("%d\\n",StackSize(&s));
	StackPop(&s);
	StackTop(&s);
	StackPop(&s);
	StackTop(&s);
	StackPop(&s);
	StackTop(&s);
	printf("%d\\n", StackSize(&s));
	StackDestroy(&s);
}

看着需要测试的功能将各个模块开始编写,先编写初始化函数将栈需要的空间开辟好。开辟大小应该为你初次需要的数组大小。将栈容量规定,堆元素个数置为0.

void StackInit(Stack *s,int capacity)
{
	s->array = (DateType*)malloc(sizeof(Stack));
	s->capacity = capacity;
	s->size = 0;
}

初始化好之后,进行压入,相当于往数组里面塞元素。塞完后,元素个数加一。
在这里插入图片描述

void StackPush(Stack *s,DateType data)
{
	if (StackFill(s))
		StackReserve(s);
	s->array[s->size] = data;
	s->size++;
}

待到多次插入之后,如果栈插满,则需要将栈的大小扩大。数组盘满,如果元素个数和容量相同时,则栈满
在这里插入图片描述

int StackFill(Stack *s)
{
	return s->size == s->capacity;
}
void StackReserve(Stack *s)
{
	assert(s);
	s->array = (DateType *)realloc(s->array, sizeof(DateType)*s->capacity * 2);
	if (s->array == NULL)
	{
		assert(0);
	}
	s->capacity *= 2;
}

插入有了,删除的操作也不能少啊。删除就是栈顶指针下移,其中size 可以为栈元素的个数也可以为栈顶指针,可以说是多用了。
在这里插入图片描述

void StackPop(Stack *s)
{
	assert(s);
	if (s->size<=0)
		printf("没有任何元素\\n");
	s->size--;
}

压入,弹出实现了,可以调一个获取栈顶元素的函数来检查

在这里插入图片描述

//获取栈顶元素
DateType StackTop(Stack *s)
{
	return s->array[s->size-1];
}
//返回栈的元素个数
int StackSize(Stack *s)
{
	return s->size;
}

最后将动态申请的那个空间给他释放掉

void StackDestroy(Stack *s)
{
	assert(s);
	free(s->array);
	s->array = NULL;
	s->capacity = 0;
	s->size = 0;
}

在这里插入图片描述
接下来实现本次博客的最后一个模块,包括堆排序和大根堆的建立
和前文的栈的手法一样,先搞上层架构,再来具体实现。
堆得大概模型
在这里插入图片描述

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HPDateType;
typedef struct Heap
{
	int *array;
	int _size;
	int _capacity;
}Heap;
//堆排序是在建立好大根堆或者小根堆的基础上进行的,大根堆拍完后是从小到达,小根堆拍完后是从大到小。具体是因为每次都把整个堆的最大(最小元素)放在最末尾,对其余元素接着排序实现的,
HeapSwap(Heap *hp,int *array, int k)//最大堆的具体实现函数向下调整
void MaxHeapHelper(int *array, int size, int parent)//最大堆的实现函数
void MaxHeapCreate(Heap *hp,int *array, int size)// 堆的构建
void HeapCreate(Heap* hp, HPDateType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDateType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDateType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
//增大堆的容量
void EnCreatHeap(Heap *hp);
void Print(Heap *hp);

接下来实现功能函数,堆的扩容,删除和获取堆顶元素,还有堆得元素和上面的栈配方一样。主要来一下那个堆排序,还有那个插入。
核心思想1:向下调整每次将左右根三个节点中的最大值放在根上,一次递归向下调整。
建立大堆的方法。
在这里插入图片描述
堆排序,由于大堆建立完成,堆顶元素为整个树的最大元素值,故每次将堆顶元素和队尾元素位置交换,将堆元素的个数减一,采用向下调整,直至排序成功。
在这里插入图片描述

#include"heap.h"
void MaxHeapHelper(int *array, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && array[child + 1] > array[child])
			child += 1;
		if (array[child]>array[parent])
		{
			int temp = array[child];
			array[child] = array[parent];
			array[parent] = temp;
			//进行循环向下调整,重点理解
			MaxHeapHelper(array, size, child);
			return;
		}
		else
		{
			return;
		}
	}
}
void MaxHeapCreate(Heap *hp,int *array, int size)
{
	for (int root = (size - 1) / 2; root >= 0; root--)
	{
		MaxHeapHelper(array, size, root);
	}
	Print(hp);
}
void HeapSwap(Heap *hp,int *array, int k)
{
	int temp = k;
	while (temp)
	{
		int t = array[temp - 1];
		array[temp - 1] = array[0];
		array[0] = t;
		temp--;
		MaxHeapHelper(array, temp, 0);
	}
	Print(hp);
}
void Print(Heap *hp)
{
	for (int i = 0; i < hp->_size; i++)
	{
		printf("%d ", hp->array[i]);
	}
	printf("\\n");
}

// 堆的构建
void HeapCreate(Heap *hp,HPDateType* a, int n)
{
	hp->array = (HPDateType *)malloc(sizeof(HPDateType)*n);
	hp->_capacity = n;
	hp->_size = n;
	for (int i = 0; i < n; i++)
	{
		hp->array[i] = a[i];
	}
	printf("建立大堆\\n");
	MaxHeapCreate(hp,hp->array, hp->_capacity);
	printf("堆排序,从小到大\\n");
	HeapSwap(hp,hp->array,hp->_capacity);
	printf("删除堆元素\\n");
	HeapPop(hp);
	Print(hp);
    
	//HeapSwap(hp, hp->array, hp->_size);
	printf("压入堆元素\\n");
	HeapPush(hp, 1);
	Print(hp);
	MaxHeapCreate(hp, hp->array, hp->_capacity);
	HeapSwap(hp, hp->array, hp->_size);
	printf("堆顶元素 :%d\\n", HeapTop(hp));
	printf("堆元素个数:%d\\n", HeapSize(hp));
}

删除元素的时候,先插到最后一个元素,然后给他向下调整,变为大根堆,删除则和排序操作类似。删除的话,将堆顶元素和最后一个元素交换,然后将最后一个元素删除,将堆调为大根堆。

//堆的扩容
void EnCreatHeap(Heap *hp)
{
	assert(hp);
	hp->array = (HPDateType *)realloc(hp->array, sizeof(HPDateType)*hp->_capacity * 2);
	hp->_capacity *= 2;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	free(hp->array);
	hp->_capacity = hp->_size = 0;
}
// 堆的插入
void HeapPush(Heap* hp, HPDateType x)
{
	assert(hp);
	if (hp->_size == hp->_capacity)
		EnCreatHeap(hp);
	hp->array[hp->_size++] = x;
}
// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	if (!HeapEmpty(hp))
	{
		HPDateType temp = hp->array[hp->_size-1];
		hp->array[hp->_size - 1] = hp->array[0];
		hp->array[0] = temp;
		hp->_size--;
	}
}
// 取堆顶的数据
HPDateType HeapTop(Heap* hp)
{
	return hp->array[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}
#include"heap.h"
int main()
{
	Heap hp;
	HPDateType array[] = { 3, 2, 5, 8, 4, 7, 6, 9, 10 };
	HeapCreate(&hp, array,9);
}

在这里插入图片描述
本次就到此为止吧,欢迎各位补充评论指点。

以上是关于C 栈和堆的主要内容,如果未能解决你的问题,请参考以下文章

C 栈和堆

栈和堆

栈和堆

栈和堆区的区别等

栈和堆的区别

栈和堆