Data structure – binary tree (2)

Table of Contents

1. Relevant properties of binary trees:

2. Storage structure of binary tree:

(1), sequential storage (array)

(2) Derived data structure – heap:

1. The concept of heap

2. Classification of heaps:

(3) Implementation of heap (sequential storage)

1. Definition of heap:

2. Heap initialization:

3. Heap destruction

4. Heap printing:

5. Insert data:

6. Delete data:

7. Get the top element of the heap (get the root node)

8.Short call

(4) Heap sorting

Misconceptions that are easy to occur:

Correct heap sorting:


Continuing from the previous article http://t.csdnimg.cn/nsKsW, this time we will explain the relevant knowledge about Binary Tree.

1. Relevant properties of binary trees:

1.
If the number of levels of the root node is specified as
1
, then a non-empty binary tree
No.
i
At most on layer
2^(i-1)
node.

2.
If the number of levels of the root node is specified as
1
,but
Depth is
h
The maximum number of nodes of a binary tree is
2^(h-1)

3.
For any binary tree
,
If the degree is
0
The number of leaf nodes is n0
,
Degree is
2
The number of branch nodes is n1
,
Then n0=n1 + 1

4.
If the number of levels of the root node is specified as
1
, with
n
Depth of a full binary tree of nodes, h=

5.
For those who have
n
A complete binary tree of nodes, if all nodes are sorted in array order from top to bottom, left to right
0
Starting number, then the serial number is i
The nodes are:

①.
If
i>0
,
i
The parent number of the location node is:
(i-1)/2
;
i=0;
like
i
Number the root node, no parent node

②.
If
2i + 1
, left child serial number:
2i + 1;
2i + 1>=n
Otherwise there will be no left child

③.
If
2i + 2
, right child serial number:
2i + 2;
2i + 2>=n
Otherwise there is no right child

6. Find parents through children: Assume the number of the child is i, then the number of its parents is A=(i-1)/2; the root node has no parents;

2. Storage structure of binary tree:

(1), sequential storage (array)

1. Sequential structure storage is to use
Array to store
, generally use arrays
Only suitable for representing complete binary trees
, because it is not a complete binary tree and there will be a waste of space. However, in actual use, only
Heap
Only then can we use arrays for storage. We will explain specifically about the heap in the following chapters. Binary Tree Order
Ordinal storage is physically an array and logically a binary tree.

According to the above, there are several properties:

2.
For those who have
n
A complete binary tree of nodes, if all nodes are sorted in array order from top to bottom, left to right
0
Starting number, then the serial number is i
The nodes are:

①.
If
i>0
,
i
The parent number of the location node is:
(i-1)/2
;
i=0;
like
i
Number the root node, no parent node

②.
If
2i + 1
, left child serial number:
2i + 1;
2i + 1>=n
Otherwise there will be no left child

③.
If
2i + 2
, right child serial number:
2i + 2;
2i + 2>=n
Otherwise there is no right child

3. Find parents through children: Assume the number of the child is i, then the number of its parents is A=(i-1)/2; the root node has no parents;

4. Full binary trees or complete binary trees are suitable for sequential storage, while incomplete binary trees are suitable for chain storage;

(2), Derived data structure – Heap:

Ordinary binary trees are not suitable for storage in arrays because there may be a lot of wasted space. A complete binary tree is more suitable for sequential structure storage. In reality, we usually use heaps
(
A binary tree
)
Use an array of sequential structure for storage. You need to pay attention to the heap and operating system here
The heap in the virtual process address space is two different things. One is a data structure, and the other is an area segmentation that manages memory in the operating system.

1. The concept of heap

The heap is a non-linear structure and a special complete binary tree, so it is suitable for array storage;

2. Classification of heap:

Small heap (small root heap): The value of any parent in the tree is less than or equal to its children;

Big pile (big root pile): The value of any father in the tree is greater than or equal to its children;

As shown below:

(3), Implementation of Heap (Sequential Storage)

Generally, we use the sequential storage method to implement the heap, that is, use a one-dimensional array, so the definition is similar to the sequential table, but the implementation logic is different, so the basic definition and destruction operations will be roughly explained.

1. Definition of heap:
typedef int HPDatatype;
typedef struct Heap
{
HPDatatype* a;//One-dimensional array
int size;//Number of existing elements
int capacity;//Maximum space of current structure
}HP;
2. Heap initialization:
//Initialization
void HPinit(HP* php)
{
assert(php);
php->size = 0;
php->capacity = 0;
php->a = NULL;
}
3. Heap destruction
//Destroy
void HPDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
4. Heap printing:
//Print
void HPprint(HP* php)
{
assert(php);
for (int i = 0; i < php->size; i + + )
{
printf("%d ", php->a[i]);
}
printf("\
");
}
5.Insert data:

Because the heap is a special complete binary tree, the insertion algorithm is completely different from that of the sequential table;

Let’s take implementing a small heap as an example:

①: First of all, we should consider whether the stack is full. According to our definition, the stack is full when size==capacity. At this time, we need to expand the capacity, because expansion is only possible here, so there is no need to separate it into one The functions and expansion methods are similar in structure to the previous sequence tables, etc., so I won’t explain them too much;

②: According to the sequential storage structure of a complete binary tree, we know that the tail element of the array is the tail element of the complete binary tree, so when we insert data, we only need to insert it at the end of the array, and because the heap is a special complete binary tree, a small heap That is, the value of the parent node is smaller than the value of all its children, so when the data is inserted, the data must be compared with its parents. If the conditions are not met, we need to exchange the data, and we need to perform this operation in a loop. Until the root node is compared, and because we are constantly looking for parents, we call this method “Upward adjustment“. The premise of upward adjustment is that the previous structure is already a heap structure.

③: Since we are looking for parents, we need to keep in mind the previous positional relationship between the parent node and the child node, which is the properties of the above-mentioned complete binary trees. The specific algorithm for upward adjustment is as follows:

④: The time complexity is O(log n with base 2), because the time complexity of inserting elements is O(1), and the worst case of upward adjustment is to adjust to the root node. That is, the height of a complete binary tree is log n with base 2;

⑤, source code

//Exchange function
void Swap(HPDatatype* p1, HPDatatype* p2)
{
HPDatatype tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}

//Adjust upward
void AdjustUp(HPDatatype* a, int child)
{
int parent = (child - 1) / 2;

while (child > 0)
{
//Insert data using a small heap as an example
if (a[parent] > a[child])
{
//Swap positions
Swap( & amp;a[parent], & amp;a[child]);
//Reposition and adjust upward after comparing a group
child = parent;
parent = (parent - 1) / 2;
}
else
{
//End of insertion
break;
}
}
}

//insert element
void HPPush(HP* php, HPDatatype x)
{
assert(php);
//expansion
if (php->size == php->capacity)
{
php->capacity = (php->capacity == 0 ? 4 : php->capacity * 2);
HPDatatype* tmp = (HPDatatype*)realloc(php->a, sizeof(HPDatatype) * php->capacity);
//Check expansion
if (tmp == NULL)
{
perror("realloc");
return;
}
php->a = tmp;
}
\t

//insert element
php->a[php->size] = x;
//Check if upward adjustment is needed
AdjustUp(php->a, php->size);
php->size + + ;
}
6.Delete data:

First, let’s consider a question, which element makes sense to delete?

Obviously, deleting the root node makes the most sense, because in a large heap, the root node is the maximum value; in a small heap, the root node is the minimum value; so deleting the root node makes more sense;

Many friends may think, “Deleting the root node is nothing more than moving the array elements and overwriting them directly.” The answer is no, because we need to be clear that the heap structure only has a relationship between the child and the parents, but between the children There is no relationship with brothers, so moving data to overwrite elements may cause the children or brothers to be misplaced, so the heap structure may not be the same after overwriting;

Here is an idea: above we inserted data using “up adjustment”, now we delete data and use “down adjustment”;

Adjust ideas downward (taking the small heap structure as an example):

①: First exchange the values of the root node and the tail node;

②: Delete the tail node (decrease the total element size of the array by 1)

③: Find the smaller of the two children of the root node, and then exchange the values of the parents and the smaller child;

④: Then reposition the parents and children, and adjust downwards in sequence;

Note: Special attention should be paid to many of the details. For example, there may be cases where there is no right child, etc. See the comments for specific ideas;

//Adjust downwards
void AdjustDown(HPDatatype* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child<n)
{
//Find the child, and pay attention to whether there is a right child to prevent child + 1 from crossing the boundary.
if (a[child] > a[child + 1] & amp; & amp;child + 1<n)
{
child + + ;
}
\t\t//exchange
if (a[child] < a[parent])
{
Swap( & amp;a[child], & amp;a[parent]);
//Continue to adjust downwards
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}

//Delete element
void HPPop(HP* php)
{
assert(php);
//Judge that the heap is empty
assert(php->size > 0);
//Exchange the first and last nodes
Swap( & amp;php->a[0], & amp;php->a[php->size - 1]);
//Delete the tail node. Because it is an array, just size-1 the existing element without accessing it.
--php->size;
//Adjust downward
AdjustDown(php->a, php->size, 0);
}
7. Get the top element of the heap (get the root node)
//Get the top of the heap (get the root node)
HPDatatype HPTop(HP* php)
{
assert(php);
//Determine whether the heap is empty
assert(php->size > 0);
return php->a[0];
}
8. Short judgment
//empty judgment
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}

(4), heap sort

Misconceptions that are easily generated:

First of all, we know that by inserting a set of data into the heap, and then taking the top elements of the heap in sequence, we can get a set of ordered data, so some friends are wondering, “Is heap sorting just inserting the contents of the array into the heap, and then Then take the top of the heap and store it in the array, as follows:

int arr[7] = { 10,40,60,20,30,80,90 };
HP hp;
//Initialize the heap
HPinit( & amp;hp);
//Insert array elements into the heap
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i + + )
{
HPPush( & amp;hp, arr[i]);
}
int i = 0;
while (!HPEmpty( & amp;hp))
{
//Take the smallest element (the top element of the heap) and store it in the array
arr[i] = HPTop( & amp;hp);
//Get the next smallest element after getting out of the heap
HPPop( & amp;hp);
}
HPDestroy( & amp;hp);

This seems reasonable, but there are two flaws in writing this way:

Correct heap sort:

①: You can think of it as the storage of heap is the sequential storage of binary tree. Isn’t the sequential storage using one-dimensional array? So we can regard the array to be sorted as an ordinary binary tree, not a heap structure, so then we have to find a way to turn this array into a heap structure. The idea is as follows:

A “foreshadowing” has been laid in our previous code, that is, the first parameter of the “Upward Adjustment Algorithm” function AdjustUp that inserts data is a first-level pointer to the contents of the array, not a structure pointer; the second parameter The parameter is the position to be adjusted upward, so we can directly use the AdjustUp function to build the heap:

 //Heap sort
void HeapSort(int* a, int n)
{
//Build a heap
for (int i = 0; i < n; i + + )
{
AdjustUp(a, i);
}

//......
}

In this way, the elements of the sorted array a can be adjusted upward in sequence, and there is no need to consider expansion. Finally, the elements of the array a are stored sequentially in a heap;

②: Since it is sorting, we have to consider whether it is ascending or descending order. So let’s think about it. Should we use a large heap or a small heap for ascending order and descending order respectively?

Many friends will think about the above erroneous idea, “Build a small heap in ascending order, and then take the top elements of the heap in ascending order; build a small heap in descending order”;

Answer: But this is not the case, because we only form the array to be sorted into a heap structure, but we do not have a series of real heap operations, and we cannot cycle through the complete heap idea like above. Top, Pop, Top, Pop…, after Top once, the remaining array needs to be re-judged whether it is a heap structure. We do exactly the opposite operation:

If ascending order is required, we build a large pile;

If descending order is required, we build a small heap;

Take the example of using a small heap in descending order to explain the idea:

After the small heap is established, we exchange the top element and the tail element of the heap, so that the smallest element goes to the back of the array, and then we adjust the new top element downward like Pop ( The cost is log (n with base 2)), so that it re-presents a new small heap structure, and then exchanges the new top and bottom elements of the heap, so that the next smallest element goes to the penultimate positions, and loop operations in sequence, and finally we can get an array in descending order;

The total time complexity of this algorithm is: N*log (N with base 2);

//Heap sort
void HeapSort(int* a, int n)
{
//Build a heap (take building a small heap in descending order as an example)
for (int i = 0; i < n; i + + )
{
AdjustUp(a, i);
}

//Record the subscript of the end of the heap
int end = n - 1;
//Start sorting
while (end > 0)
{
//Swap the top and bottom of the heap
Swap( & amp;a[0], & amp;a[end]);
//Adjust downward
AdjustDown(a, end, 0);
end--;

}

//print array
for (int i = 0; i < n; i + + )
{
printf("%d ", a[i]);
}
printf("\
");
}

This ends this knowledge, I hope it is helpful to you.