数据结构之堆的实现以及实现堆排序和建堆解决Top K问题

news2025/1/10 23:47:40

文章目录

  • 前言
  • 1.堆的相关介绍
    • 1.什么是堆
    • 2.堆的结构
  • 2.堆的相关接口具体实现
    • 1.堆的声明和堆的初始化
    • 2.堆插入数据和删除数据
    • 3.堆的其他函数接口
  • 3.堆的实际运用
    • 1.建堆算法
    • 2.堆的应用之堆排序
    • 3.堆解决Top k问题
  • 4.总结

前言

之前对树的相关知识概念进行了简单介绍,本文将实现一种树相关的数据结构——堆。堆的本质是二叉树,下面将会对堆进行讲解。

1.堆的相关介绍

1.什么是堆

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

在这里插入图片描述
简单来说,堆是一颗特别完全二叉树,堆有大小堆之分,大堆的根节点一定是比孩子节点要大的,兄弟节点之间无所谓大小顺序,以上图为例 大堆也可以写成 3 1 2,小堆反之。也就是说大堆的根节点一定是这个堆中最大的数据,小堆的根节点一定是堆中最小的数据。


2.堆的结构

堆是一颗完全二叉树,完全二叉树可以由数组和链表实现,堆的实现方式通常是使用数组,普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费,而完全二叉树更适合使用顺序结构存储。其实二叉树这种结构最大的作用是用来搜索查找,之前的线性结构主要是实现增删查改。数组实现堆的物理结构比较简单,直接在数组空间填数即可,前提是要维持住堆的结构特性,由此数组实现堆最大的难度在于维持堆的逻辑结构。由于数组可以随机访问的特性,所以在维持堆的逻辑特性,也比较好处理。

用数组实现堆最重要的一点在于通过数组下标确双亲节点和孩子节点。我们画图分析一下。
在这里插入图片描述


2.堆的相关接口具体实现

这里堆采用数组实现,堆的的相关接口大致就是的创建和初始化,插入数据 ,删除数据,堆的销毁。

1.堆的声明和堆的初始化

这个堆的实现刚才提到是用数组来实现的。所以定义结构的时候和顺序表基本上是一样的。

代码示例

typedef int HPDataType;
typedef struct heap
{
	HPDataType* data;
	int sz;
	int capacity;
}Heap;

这里采用申请动态内存方式来实现动态数组存储数据,结构体中的sz记录数组元素个数,capacity记录数组空间容量,如果数组空间不够了,需要及时申请空间。


堆的初始化
代码示例

void HeapInit(Heap* hp)//堆初始化
{
    assert(hp);
    hp->data = (HPDataType*)malloc(sizeof(HPDataType) * 5);
    hp->sz = 0;
    hp->capacity = 5;
    return;
}

堆的初始化就是对动态数组的初始化,先申请5个整型大小的空间,没有存储数据sz初始化为0,容量大小就是5。


2.堆插入数据和删除数据

堆是一个特别的二叉树,以大堆为例,要保证每层的根节点都比孩子节点要大。当插入一个数据到堆中时,为了保证插入数据后堆还是堆,这势必会对堆进行调整,这个调整算法叫做向上调整。向上的调整的意思是插入的数据会因为大小关系,从而导致这个数据不是孩子节点而是成为其他节点的双亲节点。从底层开始往上爬,直到找到适合位置。

向上调整代码示例

void AdjustUp(HPDataType* arr,int child)
{
    int parents = (child - 1) / 2;
    while (child > 0)
    {
        if (arr[child] > arr[parents])
        {
            Swap(&arr[child], &arr[parents]);
            child = parents;
            parents = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
    return;
}

在这里插入图片描述
我们看到这个33插入到堆后和它的双亲节点进行比较,如果这个节点比它的双亲节点大,就交换位置,然后继续往上走比较交换,直到找到合适的位置为止。这个过程就相当于是孩子取代祖先。

分析代码这个向上调整是先将数据插入到堆中在进行调整,这个过程可以抽象的理解为孩子要当祖先,是一个以下犯上谋朝篡位的过程.在这个过程需要交换,可以写一个交换函数。
我们之前分析了孩子节点和双亲节点之间的下标关系,所以通过孩子节点找到父节节点就很容易,当chilid比所有的双亲节点大时,那么这个child就成为了根节点。由此while循环条件是child,当child为0时就不用比较交换了。

交换函数

void Swap(HPDataType* a,HPDataType* b)
{ 
    HPDataType tem = *a;
    *a = *b;
    *b = tem;
    return;
}

这个交换函数很简单,要注意是这个参数是址传递,这样才能起到交换的作用

插入数据代码示例

void HeapPush(Heap* hp,HPDataType x)// 堆的插入
{
    assert(hp);
    if (hp->sz == hp->capacity)
    {   hp->data = 
    (HPDataType*)realloc(hp->data, (sizeof(HPDataType))*hp->capacity * 2);
        if (hp->data == NULL)
        {
            perror("malloc fail");
            exit(-1);
        }
        hp->capacity = hp->capacity * 2;
    }
    hp->data[hp->sz] = x;
    hp->sz++;
    AdjustUp(hp->data, hp->sz-1);
    return;
}

这个插入数据的增容还是和以前实现顺序表类似没啥好说的。


当实现了插入数据之后紧接着就是堆中数据的删除,堆中数据删除规定必须删除堆顶元素,也就是删除根节点。那么有又一个问题了,原堆顶数据删除后那么剩下的元素谁当新的根节点呢,又怎么继续维持剩余节点之前的关系呢?那么这就有需要调整了,这个调整被为向下调整。向下调整简单来说就是将先将堆的根节点和堆中最后一个元素交换,让最后一个节点暂时充当根节点,在让这个节点依次向下比较,交换位置,直到找到适合位置为止。

向下调整代码示例

void AdjustDown(HPDataType* arr, int n,int parents)//向下调整
{  
    //n的作用只是用来约束孩子和双亲防止越界
    int child = parents * 2 + 1;
    while (child < n)
    {    //保证child是指向大孩子的
        if (child + 1 < n && arr[child + 1] > arr[child])
        {
            child++;
        }
        if (arr[parents] < arr[child])
        {
            Swap(&arr[parents], &arr[child]);
            parents = child;
            child = parents * 2 + 1;
        }
        else
        {
            break;
        }
    }
    return;
}

堆的数据删除

void HeapPop(Heap* hp)//删除堆顶元素
{
    assert(hp);
    assert(hp->sz >0);
    Swap(&hp->data[0], &hp->data[hp->sz - 1]);
    hp->sz--;
    //删除后堆要重新进行调整保证是一个堆
    AdjustDown(hp->data, hp->sz, 0);
    //向上调整 双亲变孩子
    return;
}

这里我们将重点放在向下调整上,我们来好好分析一下这个向下调整
在这里插入图片描述

如果说向上调整是孩子变祖先的过程,那么向下调整就是祖先变孩子的过程。因为一个双亲节点有两个孩子节点,我们要选取最大的孩子节点进行比较交换,这样才能保证每个双亲节点大于孩子节点。这个过程需要不断循环,直到中途直到适合的位置,或者将这个堆的所有较大的孩子节点比较完毕。这个调整过程是祖先变孩子是从上至下的,因此为了防止数组越界访问这样的话就必须要多一个参数n进行限定。比较的过程中child和parents是要不断更新的,这样才能将这个过程循环起来。

我们看到这个不管是向上调整还是向下调整while中的约束条件都是child,child的范围就是0到n-1,当只有一个节点时孩子节点和双亲节点都是它本身,这样的约束条件具有普适性。


3.堆的其他函数接口

堆比较重要的核心点就是向上向下调整算法,其他的接口实现都是很简单的,和之前的顺序表类似。这里简单介绍一下堆的其他接口函数

堆顶元素获取

HPDataType HeapTop(Heap* hp)//获取堆顶数据
{
    assert(hp);
    assert(hp->sz > 0);
    return hp->data[0];
}

堆判空

int HeapEmpty(Heap* hp)//堆判空
{
    return hp->sz == 0;
}

堆中元素个数

int HeapSize(Heap* hp)//获取堆元素个数
{
    assert(hp);
    return hp->sz;
}

堆数据打印

void HeapPrint(Heap* hp)//打印显示
{
    assert(hp);
    assert(!HeapEmpty);
    for (int i = 0; i < hp->sz; i++)
    {
        printf("%d ", hp->data[i]);
    }
    printf("\n");
    return;
}

堆的销毁

void HeapDestory(Heap* hp)// 堆的销毁
{
    assert(hp);
    free(hp->data);
    hp->data = NULL;
    hp->sz = 0;
    hp->capacity = 0;
    return;
}

这些接口都很简单没必要做过多的解释说明


3.堆的实际运用

堆一般可以用来选数,选比如从某堆数据中选取前5大的数,堆还可以用来解决Tok问题,所谓TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等.

关于堆的运用下面将会详细介绍,我们现在要做的就是实现了解建堆算法,只有先有将堆建立起来才能进行后续操作。所以首先介绍的就是建堆算法。


1.建堆算法

建堆算法有两种,分别是向上建堆和向下建堆。这两种算法本质都是之前提到的向上调整和向下调整。

向上建堆代码示例

void HeapCreate(Heap* hp, HPDataType* a, int n)//堆的创建
{
      assert(hp);
    //用HeapPush建堆 实际就是向上建堆
       HeapInit(hp);
      for (int i; i < n; i++)
       {
        HeapPush(hp, a[i]);
       }
      return ;
  }

调用插入数据接口HeapPush本质就是通过AdjustUp对数组中数据进行调整使得数组成为一个堆,数组只有一个数据时就认为它是一个堆,往后插入的数据再挨个进行调整,当所有数据插入完后,这些数据就按照堆的特性在数组中排列好了。
在这里插入图片描述
这个向上调整也就是相当于堆已经存,在这个存在的堆中进行相应的调整。哪怕只有一个数据也默认它就是堆。


向下调整建堆

void HeapCreate(Heap* hp, HPDataType* a, int n)//堆的创建
{
    assert(hp);
    //向下建堆
    hp->data = (HPDataType*)malloc(sizeof(HPDataType) * n);
    if (hp->data == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    memcpy(hp->data, a, sizeof(HPDataType) * n);
    hp->sz = hp->capacity = n;
    for (int i = (n - 1 - 1) / 2; i--; i >=0)
    {
        AdjustDown(hp->data, n, i);
    }
    return;
}

这个向下调整建堆,我们可以先回忆之前pop删除堆中的数据的时候,当时进行向下调整的时候是除了根节点,其余节点都是维持着堆的特性的,在之前的博客关于树的知识铺垫中提到了树的子树的概念的,所以对于堆来说,pop时左右子树还是堆,在这个前提条件下才进行的向下调整。现在向下调整建对实际上是将整个二叉树分成一个个独立的子树,将这些子树建成一个堆,在最后整体调整建成一个堆。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
其实,向下调整是对堆每层节点进行调整,最后一层的叶子节点都可以单独看作只有一个节点的堆,但是只有一个节点的堆调整后也是原样,所以调不调都无所谓。这也就是代码种i是从(n-1-1)/2开始调整的原因。
这种建堆算法就是从局部最后到整体,向上调整是数据进来一次就进行整体调整,向下调整是将数据从底往上开始整体,相当于先解决第k-1层到k层,在解决第k-2层到第k层,最后是解决第1层到第k层。在实际种通常采用向下调整进行建堆,效率更好。

我们来分析一下向上调整建堆和向下调整建堆的时间复杂度

向上调整时间复杂度
在这里插入图片描述
向下调整时间复杂度
在这里插入图片描述

由此得到结论 向上建堆的时间复杂度是O(N*logN),向下建堆的时间复杂度是O(N).所以采用向下调整进行建堆。


2.堆的应用之堆排序

堆的应用第一个就是堆排序,利用堆对数据进行排序。思路大概是:以升序排布为例,我们利用建堆算法先将原数组建成一个大堆,大堆建好后,将堆顶元素和堆中末尾元素进行交换,然后从堆顶开始到堆中倒数第二个节点为止,在对原来的堆进行调整,在将堆顶元素和堆的倒数第二个元素交换。再次,重复上述操作,直到将每个节点都交换调整后,数组便是升序排列了。

画图分析

在这里插入图片描述
代码示例

void HeapSort(int* a, int n)
{  
    for (int i = (n - 1 - 1) / 2; i--; i >= 0)
    {
        AdjustDown(a, n, i);
    }
    int end = n - 1;
    while (end > 0)
    {
        Swap(&a[0], &a[end]);
        AdjustDown(a, end, 0);
        end--;
    }
    return;
}

分析一下就是通过大堆可以找到某组数据中的最大值,因为根节点一定是堆中最大的元素,,我们将堆的根节点从前往后放,再对剩余元素进行堆调整,循环这个过程即可。我们分析一下时间复杂度,这个建堆的算法时间复杂是n,一次向下调的时间复杂度是logN,循环n次,就是n*logn,由此总的来说堆排序的时间复杂度就是N*logN

关于这个一次向上或者向下调整的时间复杂度没有仔细分析,但是可以看出,一次调整是和二叉树的高度相关的,挪动交换数据的次数,就是logN。这个可以对照代码画画图很容易看出。


3.堆解决Top k问题

在文章前面就提到过Top k问题,就是在海量的数据中找到前几大或小的数据,我们也是采用堆来结解决。如果是想要找到前k大的数据需要建立小堆,反之就需要建大堆。用找前k大的数据为例,我们先用数据源中的前k个数据建立个小堆,在遍历剩余的源数据如果源数据中某个数据比堆顶数据大,就替换这个堆顶数据,在进行向下调整,最后要找的前k大的数据都在这个堆中。

代码示例

#include<stdio.h>
#include<stdlib.h>
void Swap(int* e1, int* e2)
{
	int a = *e1;
	*e1 = *e2;
	*e2 = a;
}
void  AdjustDown(int* arr, int n, int parents)
{
	int child = parents * 2 + 1;
	while (child < n)
	{
		if (arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parents])
		{
			Swap(&arr[child], &arr[parents]);
			parents = child;
			child = parents * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void TestHeap()
{
	// 造数据
	int k;
	printf("请输入k:");
	scanf("%d ", &k);
	srand(time(0));
	int a1[20];
	for (int i = 0; i < 20; i++)
	{
		scanf("%d", &a1[i]);
	}

	int* minHeap = (int*)malloc(sizeof(int) * k);
	if (minHeap == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0; i < k; ++i)
	{
		minHeap[i]=a1[i];
	}

	// 建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(minHeap, k, i);
	}
	int val;
	for (int j = k; j < 20; j++)
	{
		val = a1[j];
		if (val > minHeap[0])
		{
			minHeap[0] = val;
			AdjustDown(minHeap, k, 0);
		}
	}
	for (int i = 0; i < k; ++i)
	{
		printf("%d ", minHeap[i]);
	}
	printf("\n");
	free(minHeap);
}
int main()
{
	TestHeap();
}

代码示例中,我用了20个数据当作数据源,数组a1就是数据源,假定这个20个数据代表海量数据,在这个20个数据中筛选出前k大的数据. 建立小堆根节就是最小的,每次建堆都能保证堆顶数据是这堆数据中最小的,一旦出现了比堆顶元素还大的数据,就对堆顶元素替换再进行调整,通过这样的处理,就会很快筛选出Top k数据。我们知道一次建堆的时间复杂度是O(N),每次进行调整时间复杂度是O(logk),最差情况的调整(N-K)次,总的时间复杂度就是(N-K)logk,所以总的时间复杂度就是N*(logK)空间复杂度是k,这样的时间复杂度处理海量数据简直太友好了,效率很高。

总的来说,建堆处理Top k问题很有效率。大致就是先建立一个堆,再比较替换堆顶元素重新调整堆。这些操作步骤也比较简单,但是确实很有用。


4.总结

  • 1.本文中的堆举例主要是以大堆为主,阅读文章时要稍微注意一下。不管是大堆还是小堆,掌握知识后都还是比较简单的。
  • 2.堆的本质是完二叉树但是物理存储方式往往采用数组来实现,堆的比较重要的地方就是向上向下调整和建堆算法。虽然这两者的代码实现差不多,但是时间复杂度可是不相同。向上向下调整一次时间复杂度是logN,建堆算法常常采用向下建堆,时间复杂度是N。
  • 3.以上内容,如有问题,欢迎指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118403.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

odps-jdbc提交MaxCompute数据源SQL报错HTTP/1.0 is not allowed

概述 最近负责的一款数据产品。有个功能&#xff1a;选择某个数据源&#xff0c;比如阿里云的MaxCompute数据源&#xff0c;然后手写SQL&#xff0c;点击自动生成字段&#xff08;即获取前置SQL里的查询字段。前置SQL可以有drop then create table动作子句&#xff0c;但是最后…

【PS-选区速成】快速选择工具、魔棒工具、对象选择工具

目录 快速选择工具 1、位置 2、3种模式&#xff1a;新选区、添加到选区、从选区减去 3、画笔的设置参数 画笔大小&#xff1a;识别的范围 硬度&#xff1a;边缘的识别能力 间距&#xff1a;识别的连贯程度 跟【选区工具】配套使用的快捷键 1、按【ALT】减区 2、放大…

数据结构之排序【快速排序和归并排序的非递归代码实现及分析】

引言&#xff1a; 今天因为要写论文&#xff0c;所以现在有点迟了&#xff0c;并且此时是北京时间&#xff1a;2022/12/28/1:41 ,我发现晚睡我真的是专业的&#xff0c;当然睡觉我也是专业的&#xff0c;懂的都懂&#xff0c;现在有点迟加上天大寒&#xff0c;手指不可屈伸&am…

android之View的滑动

其实不管是哪种滑动方式&#xff0c;基本思想都是类似的&#xff1a;当点击事件传递到View时&#xff0c;系统记下触摸点的坐标&#xff0c;手指移动的时候&#xff0c;系统记下移动后的坐标&#xff0c;并计算出偏移量&#xff0c;并通过偏移量来修改View的坐标。 下面我们来…

黑客比程序员高在哪里?

黑客其实和一般的程序员一样&#xff0c;但是他们的关注点不一样。黑客关注的是如何破坏&#xff0c;通过这些有创造性的破坏来获取利益&#xff0c;展现自己的能力。而程序员关注的是如何创造&#xff0c;通过创造来获取利益&#xff0c;展现自己的能力。 就如同一个硬币的两…

CCF BDCI|算能赛题决赛选手说明论文-01

基于TPU平台实现人群密度估计 加速器队伍 黄显钧 个人名义参赛 中国-广东广州peterhuang0323qq.com 团队简介 加速器队伍队长&#xff1a;黄显钧&#xff0c;现任某科技公司的高级工程师&#xff0c;技术栈涉足嵌入式全栈开发&#xff0c;AI 开发等领域&#xff0c;对技术充满…

云桌面 Vscode 远程debug python

云桌面 Vscode 远程debug python1、进入云桌面2、下载VScode配套软件3、挂载本地磁盘4、安装软件4.1 安装VScode4.2 安装插件vsix文件4.3 在服务端安装vscode server5、VScode 配置6、远程调试6.1 python解释器选择6.2 设置debug7. mtu 配置Author: 沧海一阳1、进入云桌面 根据…

傻白入门芯片设计,盘点计算机体系结构顶会

目录 一、集成电路/半导体领域的三大顶会&#xff1a; &#xff08;1&#xff09;ISSCC &#xff08;2&#xff09;IEDM &#xff08;3&#xff09;VLSI 二、计算机体系结构四大顶会 &#xff08;1&#xff09;ISCA &#xff08;2&#xff09;HPCA &#xff08;3&#x…

42. 网络中的网络(NiN)

LeNet、AlexNet和VGG都有一个共同的设计模式&#xff1a;通过一系列的卷积层与汇聚层来提取空间结构特征&#xff1b;然后通过全连接层对特征的表征进行处理。 AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。 或者&#xff0c;可以想象在这个过程的早期使用全连…

利用GithubPage和Hexo搭建个人博客

title: 利用Github搭建个人博客 date: 2022-11-28 20:55:30 tags: [blogs] categories: Hexo 建立Git远程仓库 固定格式为&#xff1a;name.github.io ![]](https://img-blog.csdnimg.cn/fa9d7320d1cc422a8a79f2b41dd8458e.png) 开启Github Pages 设置github的token登陆 连接…

免费在线绘制高颜值,带填充的连贯堆叠柱状图

堆叠柱状图是我们日常工作中经常使用的一类图形。然而当分类较多时&#xff0c;堆叠柱状图看起来不是那么清晰&#xff0c;通过添加额外的连线&#xff0c;可以增加堆叠柱状图的颜值&#xff0c;给人一种连贯的感觉&#xff0c;并且能够更好地观察数据比例的变化。 图1. 堆叠柱…

1.8 异常 模块和包

文章目录了解异常异常的捕获方法为什么需要捕获异常捕获常规的异常捕获指定的异常捕获多个异常捕获所有的异常异常else异常的finally异常的传递Python模块模块的导入自定义模块测试模块\_all\_模块Python包了解异常 当我们的解释器运行时发生了一些没办法的操作&#xff0c;或…

初学Java web(十)

Filter和Listener 一.Filter 概念&#xff1a;Filter表示过滤器&#xff0c;是JavaWeb三大组件(Servlet、Filter、Listener)之一。 过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些特殊的功能。 过滤器一般完成一些通用的操作&#xff0c;比如&#xff1a;权限控…

新键盘到了,我的工作效率提升了数十倍

前言&#xff1a;快过年了&#xff0c;找到了一份满意的实习&#xff0c;正好旧的键盘坏掉了&#xff0c;最近入手了一款不错的机械键盘奖励自己。到货使用一段时间了&#xff0c;来一篇键盘开箱的博客做一个反馈。 新键盘使用感言&#xff1a;优良的键盘如图云上漫步&#xf…

白质中的BOLD信号激活检测问题

白质中BOLD信号的生理意义存在争议的两个主要原因&#xff1a; BOLD信号依赖于脑血流量CBF和脑血容量CBV&#xff0c;但是白质中的血流量和血容量比灰质中的少得多&#xff08;利用MRI估计的微血管数量&#xff1a;白质为10-192条/mm^2&#xff0c;灰质为99-761条/mm^2&#x…

【GUI界面】基于Python的WSG84三点定位系统(经纬度坐标与平面坐标转换法求解)

【GUI界面】基于Python的WSG84三点定位系统&#xff08;经纬度坐标与平面坐标转换法求解&#xff09; 方法汇总&#xff1a; blog.csdn.net/weixin_53403301/article/details/128441789 【精准三点定位求解汇总】利用Python或JavaScript高德地图开放平台实现精准三点定位&…

[开源工具]使用Fiddler简单计算QPS[新手开箱可用]

使用Fiddler简单计算QPS1.什么是QPS?2.怎么计算QPS?3.如何使用Fiddler得到一个API接口的QPS?3.1配置&#xff1a;打开Fiddler文件夹&#xff0c;点击Fiddler.exe运行fiddler进行配置4.如何得到本机的核心数?5.根据公式计算QPS?6.扩展计算单机可支撑PV(理论值)?1.什么是QP…

springboot中controller层接收参数,servers层调用mapper层,一条sql搞定排序

前言 很多小伙伴们在公司不管是测试C端产品还是B端产品&#xff0c;都会测到排序的业务需求&#xff1b;那么我们就会好奇排序是如何实现的呢&#xff1f;下面我们开始介绍代码的实现 数据库建表 我们需要创建一个书籍book表结构&#xff0c;如下图所示 CREATE TABLE book ( …

嵌入式C语言面向对象编程 --- 总结

什么是 C 语言面向对象? 在开始嵌入式 C 语言设计模式系列文章之前,先通过三篇文章讲述了如何使用 C 语言实现面向对象的三大特性,封装,继承,多态。 图片来源公众号:码农翻身 对于“面向对象”这个词语,相信很多软件工程师都不会感觉到陌生,并且很多软件工程师在刚开…

ABAP: 定义关键字的区别

问题&#xff1a;TYPE、LIKE、LIKE TABLE OF、LIKE LINE OF、TYPE TABLE OF 的区别&#xff1f; 1、TYPE 用于变量的类型定义&#xff0c;可以是表中预定义好的字段&#xff0c;也可以是C(字符)&#xff0c;F(浮点型)&#xff0c;I(整型)等。 例如&#xff1a; DATA: NAME TY…