服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++数据结构之堆详解

C++数据结构之堆详解

2022-11-04 13:36CairBin C/C++

本文详细讲解了C++数据结构之堆,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

堆的概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象,即是一种顺序储存结构的完全二叉树。

提示:完全二叉树

完全二叉树:对一棵深度为k、有n个结点二叉树编号后,各节点的编号与深度为k的满二叉树相同位置的结点的编号相同,这颗二叉树就被称为完全二叉树。[2]

堆的性质

  • 堆中某个结点的值总是不大于或不小于其父结点的值
  • 堆总是一棵完全二叉树
  • 除了根结点和最后一个左子结点可以没有兄弟结点,其他结点必须有兄弟结点

最大堆最小堆

  • 最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。

  • 最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。

代码

定义

有限数组形式

?
1
int Heap[1024];    //顺序结构的二叉树

若某结点编号为i,且存在左儿子和右儿子,则他们分别对应

?
1
2
Heap[i*2+1];      //左儿子
Heap[i*2+2];      //右儿子

其父节点

?
1
Heap[i/2];      //i的父节点

动态数组形式

在项目开发中,经常以动态数组形式出现,在本文主要对这种形式进行介绍

?
1
2
3
4
5
6
7
8
9
//默认容量
#define DEFAULT_CAPCITY 128
 
typedef struct _Heap
{
    int *arr;       //储存元素的动态数组
    int size;       //元素个数
    int capacity;   //当前存储的容量  
}Heap;

操作

只使用InitHeap()函数进行初始化即可,AdjustDown()与BuildHeap()仅为堆建立时的内部调用

向下调整结点

  • 以创建最大堆为例
  • 将“判断最大子结点是否大于当前父结点”处的>=改为<=即可创建最小堆
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//向下调整,将当前结点与其子结点调整为最大堆
//用static修饰禁止外部调用
static void AdjustDown(Heap& heap, int index)
{
    int cur = heap.arr[index];  //当前待调整结点
    int parent, child;
 
    /*
        判断是否存在子结点大于当前结点。
        若不存在,堆是平衡的,则不调整;
        若存在,则与最大子结点与之交换,交换后该子节点若还有子结点,则以此方法继续调整。
    */
    for (parent = index; (parent * 2 + 1) < heap.size; parent = child)
    {
        child = parent * 2 + 1; //左子结点
 
        //取两个子结点中最大节点,(child+1)<heap.size防止越界
        if (((child + 1) < heap.size && (heap.arr[child] < heap.arr[child + 1])))
            child++;
 
        //判断最大子结点是否大于当前父结点
        if (cur >= heap.arr[child])  //将此处的>=改成<=可构建最小堆
        {
            //不大于,不需要调整
            break;
        }
        else
        {
            //大于,则交换
            heap.arr[parent] = heap.arr[child];
            heap.arr[child] = cur;
        }
 
    }
}

建立堆

?
1
2
3
4
5
6
7
8
9
10
//建立堆,用static修饰禁止外部调用
static void BuildHeap(Heap& heap)
{
    int i;
    //从倒数第二层开始调整(若只有一层则从该层开始)
    for (i = heap.size / 2 - 1; i >= 0; i--)
    {
        AdjustDown(heap, i);
    }
}

初始化

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//初始化堆
//参数:被初始化的堆,存放初始数据的数组, 数组大小
bool InitHeap(Heap &heap, int *orginal, int size)
{
    //若容量大于size,则使用默认量,否则为size
    int capacity = DEFAULT_CAPCITY>size?DEFAULT_CAPCITY:size;
    
    heap.arr = new int[capacity];   //分配内存,类型根据实际需要可修改
    if(!heap.arr) return false;     //内存分配失败则返回False
    
    heap.capacity = capacity;       //容量
    heap.size = 0;                  //元素个数置为0
    
    //若存在原始数组则构建堆
    if(size>0)
    {
        /*
        内存拷贝,将orginal的元素拷贝到堆数组arr中
        size*sizeof(int)为元素所占内存空间大小
        */
        memcpy(heap.arr,orginal, size*sizeof(int));
        heap.size = size;   //调整大小
        BuildHeap(heap);    //建堆
    }
    
    return true;
}

打印堆

  • 实际上是一个层序遍历[4]
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//以树状的形式打印堆
void PrintHeapAsTreeStyle(Heap hp)
{
    queue<int> que;
    int r = 0;
    que.push(r);
    queue<int> temp;
 
    while (!que.empty())
    {
        r = que.front();
        que.pop();
 
        if (r * 2 + 1 < hp.size) temp.push(r * 2 + 1);
        if (r * 2 + 2 < hp.size) temp.push(r * 2 + 2);
 
        if (que.empty())
        {
            cout << hp.arr[r] << endl;
            que = temp;
            temp = queue<int>();
        }
        else
            cout << hp.arr[r] << " ";
 
    }
}

测试

main函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
    Heap hp;
    int vals[] = { 1,2,3,87,93,82,92,86,95 };
 
    if (!InitHeap(hp, vals, sizeof(vals) / sizeof(vals[0])))
    {
        fprintf(stderr, "初始化堆失败!\n");
        exit(-1);
    }
 
    PrintHeapAsTreeStyle(hp);
 
    return 0;
}

结果

?
1
2
3
4
95
93 92
87 1 82 3
86 2

完整代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <iostream>
#include <queue>
 
using namespace std;
 
//默认容量
#define DEFAULT_CAPCITY 128
typedef struct _Heap {
    int* arr;
    int size;
    int capacity;
}Heap;
 
//向下调整,将当前结点与其子结点调整为最大堆
static void AdjustDown(Heap& heap, int index)
{
    int cur = heap.arr[index];  //当前待调整结点
    int parent, child;
 
    /*
        判断是否存在子结点大于当前结点。
        若不存在,堆是平衡的,则不调整;
        若存在,则与最大子结点与之交换,交换后该子节点若还有子结点,则以此方法继续调整。
    */
    for (parent = index; (parent * 2 + 1) < heap.size; parent = child)
    {
        child = parent * 2 + 1; //左子结点
 
        //取两个子结点中最大节点,(child+1)<heap.size防止越界
        if (((child + 1) < heap.size && (heap.arr[child] < heap.arr[child + 1])))
            child++;
 
        //判断最大子结点是否大于当前父结点
        if (cur >= heap.arr[child])  //将此处的>=改成<=可构建最小堆
        {
            //不大于,不需要调整
            break;
        }
        else
        {
            //大于,则交换
            heap.arr[parent] = heap.arr[child];
            heap.arr[child] = cur;
        }
 
    }
 
 
}
 
//建立堆,用static修饰禁止外部调用
static void BuildHeap(Heap& heap)
{
    int i;
    //从倒数第二层开始调整(若只有一层则从该层开始)
    for (i = heap.size / 2 - 1; i >= 0; i--)
    {
        AdjustDown(heap, i);
    }
}
 
//初始化堆
//参数:被初始化的堆,存放初始数据的数组, 数组大小
bool InitHeap(Heap& heap, int* orginal, int size)
{
    //若容量大于size,则使用默认量,否则为size
    int capacity = DEFAULT_CAPCITY > size ? DEFAULT_CAPCITY : size;
 
    heap.arr = new int[capacity];   //分配内存,类型根据实际需要可修改
    if (!heap.arr) return false;        //内存分配失败则返回False
 
    heap.capacity = capacity;       //容量
    heap.size = 0;                  //元素个数置为0
 
    //若存在原始数组则构建堆
    if (size > 0)
    {
        /*
        内存拷贝,将orginal的元素拷贝到堆数组arr中
        size*sizeof(int)为元素所占内存空间大小
        */
        memcpy(heap.arr, orginal, size * sizeof(int));
        heap.size = size;   //调整大小
        BuildHeap(heap);    //建堆
    }
 
    return true;
}
 
//以树状的形式打印堆
void PrintHeapAsTreeStyle(Heap hp)
{
    queue<int> que;
    int r = 0;
    que.push(r);
    queue<int> temp;
 
    while (!que.empty())
    {
        r = que.front();
        que.pop();
 
        if (r * 2 + 1 < hp.size) temp.push(r * 2 + 1);
        if (r * 2 + 2 < hp.size) temp.push(r * 2 + 2);
 
        if (que.empty())
        {
            cout << hp.arr[r] << endl;
            que = temp;
            temp = queue<int>();
        }
        else
            cout << hp.arr[r] << " ";
 
    }
 
}
 
int main()
{
    Heap hp;
    int vals[] = { 1,2,3,87,93,82,92,86,95 };
 
    if (!InitHeap(hp, vals, sizeof(vals) / sizeof(vals[0])))
    {
        fprintf(stderr, "初始化堆失败!\n");
        exit(-1);
    }
 
    PrintHeapAsTreeStyle(hp);
 
    return 0;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://www.cnblogs.com/cairbin/p/16122303.html

延伸 · 阅读

精彩推荐
  • C/C++Ubuntu 20.04 下安装配置 VScode 的 C/C++ 开发环境(图文教程)

    Ubuntu 20.04 下安装配置 VScode 的 C/C++ 开发环境(图文教程)

    这篇文章主要介绍了Ubuntu 20.04 下安装配置 VScode 的 C/C++ 开发环境,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借...

    _NO GAME NO LIFE11682021-09-07
  • C/C++C语言实现带头双向循环链表的接口

    C语言实现带头双向循环链表的接口

    这篇文章主要为大家详细介绍了C语言实现带头双向循环链表的接口,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    __ericZhao6562022-01-25
  • C/C++详解c/c++赋值函数(重载=号运算符)

    详解c/c++赋值函数(重载=号运算符)

    大家都知道c++里的各种运算符都是用函数实现的,比如=就等号函数,所以当用=给一个对象赋值的时候,实际调用的是=号所对应的=号函数。下面通过本文给...

    小石王11782021-07-01
  • C/C++C++实现简单计算器功能

    C++实现简单计算器功能

    这篇文章主要为大家详细介绍了C++实现简单计算器功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    我来试试11102021-09-06
  • C/C++C++命名空间实例解析

    C++命名空间实例解析

    这篇文章主要介绍了C++命名空间实例解析,对C++程序员来说是非常重要的知识点,需要的朋友可以参考下...

    C++教程网3692021-01-28
  • C/C++C++连接mysql的方法(直接调用C-API)

    C++连接mysql的方法(直接调用C-API)

    首先安装mysql,点完全安装,才能在在安装目录include找到相应的头文件,注意,是完全安装,需要的朋友可以参考下...

    C++教程网11012021-05-19
  • C/C++浅析C++中memset,memcpy,strcpy的区别

    浅析C++中memset,memcpy,strcpy的区别

    本篇文章是对C++中memset,memcpy,strcpy的区别进行了详细的分析介绍,需要的朋友参考下...

    C++教程网5302020-12-17
  • C/C++C++的函数与指针

    C++的函数与指针

    今天小编就为大家分享一篇关于C++函数与指针的文章,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    uncle_ll6382022-02-16