C语言详解如何实现堆及堆的结构与接口

目录
  • 一、堆的结构及实现(重要)
    • 1.1 二叉树的顺序结构
    • 1.2 堆的概念及结构
    • 1.3 堆的实现
      • 1.3.1 堆的向下调整算法
      • 1.3.2 向下调整算法的时间复杂度
      • 1.3.3 堆的创建(向下调整)
      • 1.3.4 堆排序
      • 1.3.5 建堆的时间复杂度
  • 二、堆的相关接口实现(以大堆为例)
    • 2.1 堆的初始化
    • 2.2 堆的销毁
    • 2.3 堆的插入
    • 2.4 堆的删除
    • 2.5 获取堆顶元素
    • 2.6 堆的判空
    • 2.7 找出堆中前k个最大元素
    • 2.8 堆的创建(向上调整)

一、堆的结构及实现(重要)

1.1 二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。在现实中我们通常把堆 (一种完全二叉树) 使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

1.2 堆的概念及结构

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。

堆是非线性数据结构,相当于一维数组,有两个直接后继。

【大根堆和小根堆】:

根结点最大的堆叫做大根堆,树中所有父亲都大于或等于孩子。

根结点最小的堆叫做小根堆,树中所有父亲都小于或等于孩子。

【思考】这个大根堆和小根堆有什么特点呢?

最值总在 0 号位,根据这个特点我们就可以做很多事情,比如TopK问题 (在一堆数据里面找到前 K 个最大 / 最小的数),生活中也有很多实例,比如点餐软件中有上千家店铺,我想选出该地区好评最多的十家川菜店,我们不用对所有数据排序,只需要取出前 K 个最大 / 最小数据。使用堆排序效率也更高。

1.3 堆的实现

1.3.1 堆的向下调整算法

下面给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:该节点的左右子树必须是一个 (大 / 小) 堆,才能调整。

int array[] = { 27,15,19,18,28,34,65,49,25,37 }; // 根节点的左右子树都是小堆

上面的数组,因为根节点的左右子树都是小堆,所以我们从根节点开始调整,将其调成小堆。

向下调整算法思路(调成小堆):

从根节点开始,不断往下调。

选出根节点的左右孩子中「最小的孩子」,与「父亲」进行比较。

  • 如果父亲小于孩子,就不需处理了,整个树已经是小堆了。
  • 如果父亲大于孩子,就跟父亲交换位置,并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。

向下调整算法过程演示(调成小堆,把大的节点往下调整):

向下调整算法代码:

// 向下调整算法,建小堆,把大的节点往下调整
// 前提是:左右子树都是小堆
void AdjustDown(int* a, int size, int parent)
{
	// 指向左孩子,默认左孩子最小
	int child = parent * 2 + 1;
	while (child < size)
	{
		// 1. 选出左右孩子最小的那个,先判断右孩子是否存在
		if (child + 1 < size && a[child] > a[child + 1])
		{
			child++; // 指向右孩子
		}
		// 2. 最小的孩子与父亲比较
		if (a[parent] > a[child]) // 如果父亲大于孩子
		{
			// 父亲与孩子交换位置
			Swap(&a[parent], &a[child]);
			// 更新父子下标,原先最小的孩子作为父亲,继续往下调
			parent = child;
			child = parent * 2 + 1;
		}
		else // 如果父亲小于孩子,说明已经为小堆了,停止调整
		{
			break;
		}
	}
}

1.3.2 向下调整算法的时间复杂度

我们以满二叉树计算,最坏情况下,向下调整算法最多进行满二叉树的高度减1次比较,则说明向下调整算法最多调整满二叉树的高度减1次,n 个节点的满二叉树高度为 log2(n+1),估算后所以时间复杂度为 O(log2n)。

1.3.3 堆的创建(向下调整)

下面给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但不是一个堆,我们需要通过算法把它构建成一个堆。如果根节点左右子树不是一个 (大 / 小) 堆,我们应该怎么调整呢?

我们倒着调整,从下到上,从「倒数第一个非叶子节点的子树」开始,依次遍历完所有非叶子节点,分别对每个子树进行「向下调整」成 (大 / 小) 堆,一直调整到「根节点」,就可以建成一个 (大 / 小) 堆。

为什么要倒着调整呢?因为这样我们可以把「倒数第一个非叶子节点的子树」的左右子树看成是一个 (大 / 小) 堆,此时才能去使用向下调整算法。比如下图中的黄色填充的子树,3 的左子树 6 就可以看成是一个大堆。

【实例】:将下面的数组建成一个大堆

int a[] = { 1,5,3,8,7,6 };

建堆过程演示(以建大堆为例):从下到上,依次遍历完所有非叶子节点,分别对每个子树进行向下调整。

依次对 每一步 中,方框内的树 进行 向下调整 为一个 大堆。

建堆代码:

// 交换函数
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
// 向下调整算法,建大堆,把小的节点往下调
// 前提是:左右子树都是大堆
void AdjustDown(int* a, int size, int parent)
{
	// 指向左孩子,默认左孩子最大
	int child = parent * 2 + 1;
	while (child < size)
	{
		// 1. 选出左右孩子最大的那个,先判断右孩子是否存在
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child++; // 指向右孩子
		}
		// 2. 最大的孩子与父亲比较
		if (a[parent] < a[child]) // 如果父亲小于孩子
		{
			// 父亲与孩子交换位置
			Swap(&a[parent], &a[child]);
			// 更新父子下标,原先最大的孩子作为父亲,继续往下调
			parent = child;
			child = parent * 2 + 1;
		}
		else // 如果父亲大于孩子,说明已经为大堆了,停止调整
		{
			break;
		}
	}
}
void HeapSort(int* a, int size)
{
    /* 建堆(大堆)
    * 倒着调整,从倒数第一个非叶子节点的子树进行向下调整,直到调整到根节点的树
    */
	int parent = ((size - 1) - 1) / 2; // 最后一个叶子节点的父亲的下标
	for (int i = parent; i >= 0; i--)  // 从下到上,依次遍历完所有子树,分别对其进行调整
	{
		AdjustDown(a, size, i);
	}
    /* 堆排序
    * 排升序 --> 建大堆,每次选出一个最大的数放到最后
    * 排降序 --> 建小堆,每次选出一个最小的数放到最后
    */
    // 下面是排升序:
	int end = size - 1; // 记录堆中最后一个元素的下标
	while (end > 0)
	{
		Swap(&a[0], &a[end]);  // 将堆顶元素和堆中最后一个元素交换,把最大的数(堆顶)放到最后
		AdjustDown(a, end, 0); // 不看最后一个数,从根节点开始,对前面的数进行向下调整成大堆
		end--;
	}
}

1.3.4 堆排序

排升序 --> 建大堆:

【思考】排升序,建小堆可以吗?-- 可以是可以,但没啥意思。

首先对 n 个数建小堆,选出最小的数,接着对剩下的 n-1 个数建小堆,选出第2小的数,不断重复上述过程……。建 n 个数的堆时间复杂度是O(N),所以上述操作时间复杂度为O(N2),效率太低,尤其是当数据量大的时候,效率更低,同时堆的价值没有被体现出来,还不如用直接排序。

【最佳方法】排升序,因为数字越来越大,需要找到最大的数字,得建大堆

  • 首先对 n 个数建大堆。
  • 将最大的数(堆顶)和最后一个数交换,把最大的数放到最后。
  • 前面 n-1 个数的堆结构没有被破坏(最后一个数不看做堆里面的),根节点的左右子树依旧是大堆,所以我们进行一次向下调整成大堆即可选出第2大的数,放到倒数第二个位置,然后重复上述步骤……。

【时间复杂度】:建堆时间复杂度为O(N),向下调整时间复杂度为O(log2N),这里我们最多进行N-2次向下调整,所以堆排序时间复杂度为O(N*log2N),效率是很高的。

排降序 --> 建小堆:

【最佳方法】排降序,因为数字越来越小,需要找到最小的数字,得建小堆

  • 首先对 n 个数建小堆。
  • 将最小的数(堆顶)和最后一个数交换,把最小的数放到最后。
  • 前面 n-1 个数的堆结构没有被破坏(最后一个数不看做堆里面的),根节点的左右子树依旧是小堆,所以我们进行一次向下调整成小堆即可选出第2小的数,放到倒数第二个位置,然后重复上述步骤……。
  • 【时间复杂度】:建堆时间复杂度为O(N),向下调整时间复杂度为O(log2N),这里我们最多进行N-2次向下调整,所以堆排序时间复杂度为O(N*log2N),效率是很高的。

1.3.5 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明,计算起来比较好算(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

建堆要从倒数第一个非叶子节点开始调整,也即是从倒数第二层开始调,可得出时间复杂度公式:

T ( n ) = ∑ ( 每 层 节 点 数 ∗ ( 堆 的 高 度 − 当 前 层 数 ) )

所以,建堆的时间复杂度为O(N)。

【上面学了那么多,这里小小总结一下】

  • 堆的向下调整算法就是,在该节点左右子树都是一个小/大堆的前提下,将以该节点为根的树调整成一个小/大堆。
  • 堆的创建就是倒着调整,从下到上,从倒数第一个非叶子节点的子树开始,依次遍历完所有子树,分别对其进行向下调整。
  • 时间复杂度:堆的向下调整算法为O(log2N),堆的创建为O(N)。

二、堆的相关接口实现(以大堆为例)

首先新建一个工程( 博主使用的是 VS2019 )

  • Heap.h(堆的类型定义、接口函数声明、引用的头文件)
  • Heap.c(堆接口函数的实现)
  • Test.c(主函数、测试堆各个接口功能)

Heap.h 头文件代码如下:

#pragma once
#include<stdio.h>   // printf, perror
#include<stdbool.h> // bool
#include<assert.h>  // assert
#include<stdlib.h>  // malloc, free
#include<string.h>  // memcpy
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a; // 指向动态开辟的数组
	int size;      // 数组中有效元素个数
	int capacity;  // d容量
}Heap;
// 交换函数
void Swap(HPDataType* a, HPDataType* b);
// 向下调整函数(调成大堆,把小的往下调)
void AdjustDown(HPDataType* a, int size, int parent);
// 向上调整函数(调成大堆,把大的往上调)
void AdjustUp(HPDataType* a, int child);
// 初始化堆
void HeapInit(Heap* php, HPDataType* arr, int n);
// 销毁堆
void HeapDestroy(Heap* php);
// 插入元素(插入到堆的末尾),插入后并保持它依然是堆
void HeapPush(Heap* php, int x);
// 删除堆顶元素,删除后保持它依然是堆
void HeapPop(Heap* php);
// 获取堆顶元素,也即是最值
HPDataType HeapTop(Heap* php);
// 判断堆是否为空,为空返回true,不为空返回false
bool HeapEmpty(Heap* php);
// 获取堆中有效元素个数
int HeapSize(Heap* php);
// 打印堆
void HeapPrint(Heap* php);

2.1 堆的初始化

堆的初始化,首先需要实现一个向下调整算法:

// 交换函数
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}
// 向下调整算法(调成大堆,把小的往下调)
void AdjustDown(HPDataType* a, int size, int parent)
{
	// 左孩子下标,初始默认左孩子最大
	int child = parent * 2 + 1;
	while (child < size)
	{
		// 选出左右孩子最大的那个,先判断右孩子是否存在
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child++; // 右孩子最大
		}
		// 最大的孩子与父亲比较
		if (a[parent] < a[child]) // 如果父亲小于孩子
		{
			// 父亲与孩子交换位置
			Swap(&a[parent], &a[child]);
			// 更新父子下标,原先最大的孩子作为父亲,继续往下调
			parent = child;
			child = parent * 2 + 1;
		}
		else // 如果父亲大于孩子,说明已经为大堆了,停止调整
		{
			break;
		}
	}
}

堆的初始化代码:

// 初始化堆,用一个给定的数组来初始化
void HeapInit(Heap* php, HPDataType* arr, int n)
{
	assert(php); // 断言
	// 动态开辟n个空间
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	// 把给定数组的各元素值拷贝过去
	memcpy(php->a, arr, sizeof(HPDataType) * n);
	php->size = php->capacity = n;
	// 建堆(建大堆)
	int parent = ((php->size - 1) - 1) / 2; // 倒数第一个非叶子节点下标
	for (int i = parent; i >= 0; i--) // 从下到上,依次遍历完所有子树,分别对其进行调整
	{
		AdjustDown(php->a, php->size, i);
	}
}

2.2 堆的销毁

// 销毁堆
void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->a); // 释放动态开辟的空间
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.3 堆的插入

先插入一个新元素到数组的尾上,从插入的新元素开始,进行向上调整算法,直到满足(大/小)堆。

堆的插入过程演示:

堆的插入,首先需要实现一个向上调整算法:

// 向上调整算法(调成大堆,把大的往上调)
void AdjustUp(HPDataType* a, int child)
{
	// 父节点的下标
	int parent = (child - 1) / 2;
	//while (parent >= 0) parent不会小于0
	while (child > 0)
	{
		// 孩子与父亲进行比较
		if (a[child] > a[parent]) // 如果孩子大于父亲
		{
			// 孩子与父亲交换
			Swap(&a[child], &a[parent]);

			// 更新父子下标,原先父亲作为孩子,继续往上调
			child = parent;
			parent = (child - 1) / 2;
		}
		else // 如果孩子小于父亲,说明已经为大堆了,停止调整
		{
			break;
		}
	}
}

堆的插入代码:

// 插入元素(插入到堆的末尾),插入后并保持它依然是堆
void HeapPush(Heap* php, int x)
{
	assert(php);
	// 先检查空间是否已满
	if (php->capacity == php->size)
	{
		// 增容两倍
		php->capacity *= 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity);
		if (tmp != NULL)
		{
			php->a = tmp;
			tmp = NULL;
		}
	}
	// 插入元素
	php->a[php->size] = x;
	php->size++;
	// 从插入的元素开始,进行向上调整,保持它依然是堆
	AdjustUp(php->a, php->size - 1);
}

2.4 堆的删除

  • 将堆顶元素和最后一个元素交换(这样就变成尾删了,很方便)
  • 删除堆中最后一个元素
  • 从根节点开始,对剩下元素进行向下调整,调成(大/小)堆

堆的删除过程演示:

堆的插入,首先需要实现一个向下调整算法:前面已经实现过了,这里就不展示了。

堆的删除代码:

// 删除堆顶元素,删除后保持它依然是堆
void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php)); // 堆不能为空
	// 将堆顶元素和最后一个元素交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	// 删除堆中最后一个元素
	php->size--;
	// 从根节点开始,对剩下元素进行向下调整成大堆,保持它依然是堆
	AdjustDown(php->a, php->size, 0);
}

2.5 获取堆顶元素

// 获取堆顶元素,也即是最值
HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php)); // 堆不能为空
	return php->a[0];
}

2.6 堆的判空

// 判断堆是否为空,为空返回true,不为空返回false
bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

2.7 找出堆中前k个最大元素

堆的相关接口实现好了,因为是大堆,所以我们可以很方便的来找出堆中前 k 个最大元素。

这里要和前面的堆排序区分开哦,这里我们并不是在堆中对所有元素排好序。

void TestHeap()
{
	int a[] = { 1,5,3,8,7,6 };
	Heap hp;
	HeapInit(&hp, a, sizeof(a) / sizeof(a[0])); // 初始化堆
	int k = 0;
	scanf("%d", &k);
	printf("找出堆中前%d个最大元素:\n", k);
	while (!HeapEmpty(&hp) && k--)
	{
		printf("%d ", HeapTop(&hp)); // 获取堆顶元素
		HeapPop(&hp); // 删除堆顶元素
	}
	printf("\n");
}

运行结果:

2.8 堆的创建(向上调整)

下面给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但不是一个堆,我们需要通过「向上调整算法」把它构建成一个堆。如果根节点左右子树不是一个 (大 / 小) 堆,我们应该怎么调整呢?

我们从上到下,从「第一个节点(也就是根节点)的左孩子」开始,依次遍历完所有节点,分别对每个节点进行「向上调整」,一直到「最后一个节点」,就可以建成一个 (大 / 小) 堆。

我们把数组中的「第一个元素」看作是一个「堆」,剩余的元素依次插入到这个「堆」中。前面我们也实现了堆的插入接口,原理就是向上调整。

// 向上调整算法建堆
void CreateHeap(int* a, int size)
{
	// 把第一个元素看作是堆,剩余的元素依次插入堆中
	for (int i = 1; i < size; i++)
	{
		AdjustUp(a, i);
	}
}

到此这篇关于C语言详解如何实现堆的结构与接口的文章就介绍到这了,更多相关C语言堆的结构与接口内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言 深入解读数据结构之堆的实现

    堆的概念与结构 概念:如果有一个关键码的集合K={ k0,k1 ,k2 ,-,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足K i<=K 2*i+1且Ki<=K 2*i+2(K i>=K 2*i+1且Ki>=K 2*i+2) i = 0,1,2...,则称为小堆(或大堆).将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆. 性质: 堆中某个节点的值总是不大于或不小于其父节点的值: 堆总是一棵完全二叉树. 结构: 1.大堆 2

  • C语言数据结构之堆、堆排序的分析及实现

    目录 1.堆的概念结构及分类 1.2堆的分类 1.2.1 大堆 1.2.2 小堆 2. 堆的主要接口 3.堆的实现 3.1 堆的初始化 HeapInit 3.2 堆的销毁 HeapDestory 3.3 堆的打印 HeapPrint 3.4 堆的插入元素 HeapPush   * 3.5 堆的删除元素 HeapPop  * 4.堆的应用:堆排序   *** 4.1 堆排序实现过程分析 4.3 堆排序结果演示 5.堆(小堆)的完整代码 总结  1.堆的概念结构及分类 以上这段概念描述看起来十分复杂

  • C语言数据结构二叉树之堆的实现和堆排序详解

    目录 一.本章重点 二.堆 2.1堆的介绍 2.2堆的接口实现 三.堆排序 一.本章重点 堆的介绍 堆的接口实现 堆排序 二.堆 2.1堆的介绍 一般来说,堆在物理结构上是连续的数组结构,在逻辑结构上是一颗完全二叉树. 但要满足 每个父亲节点的值都得大于孩子节点的值,这样的堆称为大堆. 每个父亲节点的值都得小于孩子节点的值,这样的堆称为小堆. 那么以下就是一个小堆. 百度百科: 堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆. 若将和此次序列对应的一维数

  • C语言详解实现链式二叉树的遍历与相关接口

    目录 前言 一.二叉树的链式结构 二.二叉树的遍历方式 1.1 遍历方式的规则 1.2 前序遍历 1.3 中序遍历 1.4 后序遍历 1.5 层序遍历 三.二叉树的相关接口实现 3.1 二叉树节点个数 3.2 二叉树叶子节点个数 3.3 二叉树第 k 层节点个数 3.4 二叉树的深度(高度) 3.5 二叉树查找值为 x 的节点 3.6 总结 & 注意 四.二叉树的创建和销毁 4.1 通过前序遍历的字符串来构建二叉树 4.2 二叉树销毁 4.3 判断二叉树是否是完全二叉树 前言 二叉树的顺序结构就

  • C语言详解如何实现堆及堆的结构与接口

    目录 一.堆的结构及实现(重要) 1.1 二叉树的顺序结构 1.2 堆的概念及结构 1.3 堆的实现 1.3.1 堆的向下调整算法 1.3.2 向下调整算法的时间复杂度 1.3.3 堆的创建(向下调整) 1.3.4 堆排序 1.3.5 建堆的时间复杂度 二.堆的相关接口实现(以大堆为例) 2.1 堆的初始化 2.2 堆的销毁 2.3 堆的插入 2.4 堆的删除 2.5 获取堆顶元素 2.6 堆的判空 2.7 找出堆中前k个最大元素 2.8 堆的创建(向上调整) 一.堆的结构及实现(重要) 1.1

  • c语言详解动态内存分配及常见错误的解决

    目录 为什么会有动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的错误 对NULL指针的解引用操作 越界访问 对非动态内存进行free 使用free释放动态开辟内存的一部分 对同一块动态内存多次释放 对动态内存内存忘记释放(内存泄漏) 为什么会有动态内存分配 内存使用方式有两种 1.创建一个变量 2.创建一个数组 int a = 1; int arr[10]; 但是这两种方式都有一些特点 1.空间是固定大小的,不会变 2.必须提前知道要开辟多少空间,必

  • C语言详解如何应用模拟字符串和内存函数

    目录 1.strlen 求字符串长度 使用案例: 1.计数法 2.不创建临时变量计数器-递归 3.指针-指针的方式 2.长度不受限制的字符串函数 1.strcpy 使用案例: 模拟实现: 2.strcat 使用案例: 模拟实现: 3.strcmp-比较字符串首字母的大小 使用案例: 模拟实现: 3.长度受限制的字符串函数  1.strncpy 使用案例: 2.strncat  使用案例: 3.strncmp 使用案例: 4.strstr-找子串  使用案例: 模拟实现: 5.strtok 用法:

  • C语言 详解如何删除有序数组中的重复项

    目录 删除有序数组中的重复项Ⅰ a.思路 b.图解 c.代码 d.思考 删除有序数组中的重复项Ⅱ a.思路 b.图解 c.代码 d.思考 删除有序数组中的重复项Ⅰ a.思路 定义变量 int dest=0,cur=1,nums[cur]与nums[dest]逐一比较. nums[cur]!=nums[dest],将nums[cur]放入dest下一个位置,更新dest. nums[cur]!=nums[dest],cur移动. cur==numsSize,结束.返回dest+1. b.图解 c.

  • C语言详解数据结构与算法中枚举和模拟及排序

    目录 枚举 连号区间数 递增三元组 二分 双指针 前缀和 模拟 特别数的和 错误票据 排序 快速排序 归并排序 枚举 连号区间数 来源:第四届蓝桥杯省赛C++B组,第四届蓝桥杯省赛JAVAB组 小明这些天一直在思考这样一个奇怪而有趣的问题: 在 1∼N 的某个排列中有多少个连号区间呢? 这里所说的连号区间的定义是: 如果区间 [L,R] 里的所有元素(即此排列的第 L 个到第 R 个元素)递增排序后能得到一个长度为 R−L+1 的“连续”数列,则称这个区间连号区间. 当 N 很小的时候,小明可以

  • C语言详解float类型在内存中的存储方式

    目录 1.例子 2.浮点数存储规则 1.例子 int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); re

  • C语言详解如何实现带头双向循环链表

    目录 创建链表存储结构 创建结点 链表的初始化 双向链表的打印 双向链表尾插 双向链表尾删 双向链表头插 双向链表头删 双向链表查找 双向链表pos前插入结点 双向链表删除pos位置的结点 双向链表的销毁 顺序表和链表的区别 2022042311415360.{C}{C}png" /> 创建链表存储结构 我们需要创建一个结构体来存储一个链表结点的相关信息. typedef int ListDataType;//将ListDataType先定义为int类型,根据需要可以改为不同的类型 //创

  • C语言详解冒泡排序实现

    目录 前言 一.冒泡排序是什么 二.具体步骤 1.代码解释 2.读入数据 总结 前言 在排序中,有各种各样的排序方式,今天我们将要来介绍<冒泡排序>.今天会从冒泡排序的具体意义和他的操作来展开. 一.冒泡排序是什么 从左到右,相邻元素进行比较.每次比较一轮,就会找到序列中最大的一个或最小的一个.这个数就会从序列的最右边冒出来. 以从小到大排序为例,第一轮比较后,所有数中最大的那个数就会浮到最右边:第二轮比较后,所有数中第二大的那个数就会浮到倒数第二个位置……就这样一轮一轮地比较,最后实现从小到

  • C语言详解结构体的内存对齐与大小计算

    目录 结构体的内存对齐 1.计算结构体的大小 2.结构体的对齐规则 3.为什么存在内存对齐? 4.总结 结构体的内存对齐 1.计算结构体的大小 struct S1 { char c1; // 1 byte,默认对齐数为8,所以c1的对齐数是1,第一个成员变量放在与结构体变量偏移量为0的地址处 int i; // 4 byte,默认对齐数为8,所以i的对齐数是4,所以i要放到偏移量为 4的整数倍 的地址处 char c2; // 1 byte,默认对齐数为8,所以c2的对齐数是1,所以c2要放到偏

随机推荐