Complete binary tree and heap (including usage of STL heap)

complete binary tree

A complete binary tree is a special type of binary tree. A complete binary tree with a height of h satisfies the following conditions:
(1) All leaf nodes appear at layer h or h-1; (2) All leaf nodes
at layer h-1 All are on the right side of the non-leaf node ; (3) Except that the rightmost non-leaf node of layer h-1 may have a left branch, the rest of the non-leaf nodes have two branches

Features:
(1) Leaf nodes can only appear in the bottom two layers, and the bottom leaf nodes are all concentrated in the left part of the binary tree; (2)
There is at most one node with degree 1 in a complete binary tree, if Yes, the node has only left children;
(3) The first h-1 layer of a complete binary tree with a depth of h must be a full binary tree.

Properties:
The depth of a complete binary tree with n nodes is [\log_{2}n]+1. In the area, operator[x] rounds down x.

heap 

In practical applications, we often encounter the problem of frequently obtaining the maximum or minimum value in a set of dynamic data. This kind of problem can be obtained by sorting the data at each operation, but this method has a relatively large time overhead. The following introduces a data structure that can efficiently solve such problems-heap. There are many forms of heaps, including binary heaps, d-heaps, left-right heaps, oblique heaps, Fibonacci heaps, paired heaps, etc. The following mainly introduces the binary heap. For the sake of brevity, the binary heap is referred to as the heap.

(Dynamic data refers to data that needs to be updated frequently. Update operations include inserting, deleting, and modifying data)

heap definition

Heap (Heap) is a special type of complete binary tree . The key value of each node is larger (smaller) than the value of its child nodes. Therefore, the key value of the root node of the heap is the key value of all nodes The maximum (minimum) value of . There are two types of heaps: if the key value of the root node is the maximum value of all node key values, it is called a maximum heap or a big top heap ; if the key value of the root node is the minimum value of all node key values , it is called the minimum heap or the small top heap .

The nature and operation method of the large top pile are introduced below, and the small top pile only needs to be slightly modified.

#include<iostream>
using namespace std;
typedef int datatype;

//一步操作:在数组heap的前n个元素所构成的完全二叉树中,将以cur为根结点的子树变为大顶堆
/*
	从最后一个非叶结点开始(这样才能进行与孩子结点的比较),从后往前考虑每个结点
	将这每个结点为根结点部分的子树转化为大顶堆(从部分先开始转化为大顶堆)

	需要进行以下的操作:
	1.孩子结点的比较,从大的入手 
	2.父与子结点的比较
	3.若还能向下比较,则注意一开始根结点的数值保存,应在while循环之外

	注意:
	因为每一次转化之前,部分都进行过大顶堆的转换,所以只需要保存每次的最顶上的值,
	依次向下轮换就行,另外注意while循环中cur和i的更新变化
	(i的变化是由于完全二叉树的性质,cur的变化是为了将子节点的较大值赋给父节点,tmp一直保留原先的根结点)
*/
void heap_adjust(datatype heap[], int n, int cur) {
	int i = 0, tmp = heap[cur]; //tmp保留的是根结点数值
	while (i = 2 * cur + 1, i < n) { //i为cur的做孩子
		if (i + 1 < n && heap[i + 1] > heap[i])i++; //孩子结点的比较,heap[i]为较大的孩子结点
		if (heap[i] <= tmp)break;
		heap[cur] = heap[i]; //结点i的值上浮到其父节点
		cur = i; //更新cur
	}
	heap[cur] = tmp;
}
 
//将数组heap的前n个元素所构成的完全二叉树转化为大顶堆
void heap_make(datatype heap[], int n) {
	for (int i = n / 2 - 1; i >= 0; i--)  //由于结点编号从0开始,第一个非叶结点为n/2
		heap_adjust(heap, n, i);
}
 
/*
	插入结点:
	向堆插入一个新的结点时,需要确定其插入的位置,以保证在插入该结点后新的二叉树仍保持堆得特性。
	方法是先将新的结点加入堆的末尾,然后从新的结点出发,自底向上调整二叉树
	思想同转化二叉树
*/
//向堆heap中插入一个值为k的结点,n为堆中元素的个数
void heap_push(datatype heap[],  int k) {
	int cur = 0, pa = 0;
	int n = sizeof(heap) / sizeof(datatype);
	cur = n, n++; //新加入的结点加到heap的末尾,并将其设置为当前结点
	while (cur > 0) {
		pa = (cur - 1) / 2; //结点为cur的父节点
		if (heap[pa] >= k)break;
		heap[cur] = heap[pa]; //当父节点不大于当前结点时,将父节点的值下沉
		cur = pa; //当前结点指向父节点
	}
	heap[cur] = k;
}

/*
	删除结点
	采用对结构的应用通常是为了快速获取一组动态数据中的最大值或最小值,即获取堆的根结点的值,
	因此,删除堆中的结点也只考虑删除对的根结点。

	方法:
	删除大顶堆根结点的方法是将堆的最后一个结点的值取代根结点的值,并删除最后一个结点,
	然后从根节点出发采用heap_adjust算法将完全二叉树转化为大顶堆
*/
//删除大顶堆heap的根结点,n为堆中元素的数量
void heap_pop(int heap[],int &n) { //n为引用类型,删除堆顶后,堆的元素会减1
	
	heap[0] = heap[n - 1];
	n--;
	heap_adjust(heap, n, 0);
}

int main() {
	datatype a[] = { 2,3,5,1,4,8,3,9,6 };

	//转化为堆
	heap_make(a, 9);
	/*for (int i = 0; i <= 8; i++)
		cout << a[i] << endl;*/
	
	//插入值
	/*heap_push(a, 7);
	for (int i = 0; i <= 8; i++)
		cout << a[i] << endl ;
	cout << endl;*/

	//删除值
	int n = sizeof(a) / sizeof(datatype);
	heap_pop(a,n);
	for (int i = 0; i <= 8; i++)
		cout << a[i] << endl;
}

Example display: (with the above code)

The array is converted into a large top heap: 

 Large top heap insert value:

 Large top heap delete value

  Heap in STL - priority_queue

        Ordinary queue is a first-in first-out data structure, elements are appended at the end of the queue and deleted from the head of the queue. A priority queue means that elements are given priority. When accessing an element, only the element with the highest priority can be accessed, and when an element is deleted, only the element with the highest priority can be deleted. Since the element with the highest priority is obtained every time, the priority queue generally adopts a heap storage structure at the bottom layer, and the sorting rules of the elements are sorted according to the priority from high to low.
        The priority queue adapter container prority-queue is integrated in STL, which supports quickly finding and deleting elements with the maximum or minimum value from a collection. priority_queue can be implemented based on the container deque or vector. The default container is deque, and the container can also be set to vector during use. It can be divided into two types: the minimum priority queue, suitable for finding and deleting the smallest element, similar to the small top heap; the maximum priority queue, suitable for finding and deleting the largest element, similar to the large item heap. When creating priority_queue, the default large top heap.

The header file must be added when using priority_queue

#include<queue>

Object definition method of priority_queue type

        In general, priority_queue has 3 class template parameters: the first template parameter is the data type , the second template parameter is the base container , and the default is deque<type> type; the third template parameter is the method of element comparison , there are Two ways: greater<type> and less<type>, the default is less<type>.


        If the element comparison method uses less<type>, the user-defined type must define the "less than" operator overload
; if using greater<type>, the user-defined type must define the "greater than" operator overload.

The operation of priority_queue

The basic operation is the same as the queue:

  • top Access the head element of the queue
  • empty Whether the queue is empty
  • size returns the number of elements in the queue
  • push inserts elements to the end of the queue (and sorts)
  • emplace constructs an element in-place and inserts it into the queue
  • pop pops up the queue head element
  • swap exchange content

Before using the top and pop functions, it is generally necessary to use the empty function to determine whether the current priority queue is empty.

priority_queue instance

1. Basic type example

#include<iostream>
#include <queue>
using namespace std;
int main()
{
    //对于基础类型 默认是大顶堆
    priority_queue<int> a;
    //等同于 priority_queue<int, vector<int>, less<int> > a;

    priority_queue<int, vector<int>, greater<int> > c;  //这样就是小顶堆,并指定基础类型为vector

    priority_queue<string> b;

    for (int i = 0; i < 5; i++)
    {
        a.push(i);
        c.push(i);
    }

    while (!a.empty())
    {
        cout << a.top() << ' ';
        a.pop();
    }
    cout << endl;

    while (!c.empty())
    {
        cout << c.top() << ' ';
        c.pop();
    }
    cout << endl;

    b.push("abc");
    b.push("abcd");
    b.push("cbd");
    while (!b.empty())
    {
        cout << b.top() << ' ';
        b.pop();
    }
    cout << endl;
    return 0;
}

 2. The comparison of pari first compares the first element, and the first equal compares the second

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() 
{
    priority_queue<pair<int, int> > a;
    pair<int, int> b(1, 2);
    pair<int, int> c(1, 3);
    pair<int, int> d(2, 5);
    a.push(d);
    a.push(c);
    a.push(b);
    while (!a.empty()) 
    {
        cout << a.top().first << ' ' << a.top().second << '\n';
        a.pop();
    }
}

3. For custom types

#include <iostream>
#include <queue>
using namespace std;

//方法1
struct tmp1 //运算符重载<
{
    int x;
    tmp1(int a) {x = a;}
    bool operator<(const tmp1& a) const
    {
        return x < a.x; //大顶堆
    }
};

//方法2
struct tmp2 //重写仿函数
{
    bool operator() (tmp1 a, tmp1 b) 
    {
        return a.x < b.x; //大顶堆
    }
};

int main() 
{
    tmp1 a(1);
    tmp1 b(2);
    tmp1 c(3);
    priority_queue<tmp1> d;
    d.push(b);
    d.push(c);
    d.push(a);
    while (!d.empty()) 
    {
        cout << d.top().x << '\n';
        d.pop();
    }
    cout << endl;

    priority_queue<tmp1, vector<tmp1>, tmp2> f;
    f.push(c);
    f.push(b);
    f.push(a);
    while (!f.empty()) 
    {
        cout << f.top().x << '\n';
        f.pop();
    }
}

Summarize: 

        Priority queue is a very useful data structure. Huffman algorithm, Dijkstra shortest path algorithm, Prim minimum spanning tree algorithm, etc. are all implemented by priority queue. In addition, A* search algorithm and thread scheduling of operating system also use priority queue.

reference:

1. Detailed usage of c++ priority queue (priority_queue)_Lu Bai_'s Blog-CSDN Blog_priority_queue 

2. Super detailed summary of STL usage in C++_list_HUST_Miao-DevPress Official Community (csdn.net) 

Guess you like

Origin blog.csdn.net/qq_62687015/article/details/128672863