C language implements priority queue, binary heap and heap sort

Sorting and Finding Algorithms Catalog

[Merge and sort. Bucket sorting] C language implements merge sorting, counting sorting, bucket sorting, and radix sorting

[C language to achieve exchange sorting] exchange sorting (bubble sorting, quick sorting)

[C language to achieve insertion sort] Insertion sort (direct, half, two-way, Hill)

[Three classic searches] Three classic searches (jump, interpolation, Fibonacci)

[Three basic searches] Three basic searches (sequential, binary, and block)


Table of contents

1. Priority queue and binary heap

1. The concept of binary heap

2. The nature of the heap

3. Priority queue

Second, the implementation of the priority queue

1. insert

 2. Remove the minimum value

3. Heap sort


 foreword

Searching and sorting are an indispensable part of data structures and algorithms. They are important and convenient skills left by the predecessors on the road of algorithms. Learning these classic searching and sorting can also make us better and faster Solve the problem. In this column, we will learn the algorithms and ideas of the six major searches and the top ten sorts, and this article will explain the heap sort in detail;

Note: All sorts in this article are sorted in ascending order, and descending order only needs to reverse the logic!


1. Priority queue and binary heap

1. The concept of binary heap

Before learning heap sorting, we must first learn binary heap;

If the stack in the data structure is built to imitate the stack in the hardware, then the heap is completely different! A binary heap is a data structure based on a complete binary tree (hereinafter referred to as a heap);

Like a binary search tree, a heap has two properties:

  • Structural and stackable;

Just like the AVL tree, the operation on the heap is likely to destroy one of these two properties, so the operation of the heap must not stop until the properties of the heap are satisfied;

An important observation is that, because of the regularity of a complete binary tree, it can be represented by an array without pointers;

  •  We can get it through the mathematical relationship: In an array starting from subscript 0, if the subscript of a parent node is x, then the subscript of its left child is 2 * x + 1, and the subscript of its right child It is 2 * x + 2. Similarly, the subscript of the parent node can be deduced to be (x - 1) / 2 ;

 That is, a heap data structure will consist of an array, an integer representing the maximum value, and the current heap size

2. The nature of the heap

The heap is divided into a large top heap and a small top heap: 

  • The value of the root node of the big top heap is always greater than that of the child;
  • Small top heap The value of the root node is always smaller than the child;

This is exactly the embodiment of the heap order. According to this property, we can quickly find the minimum or maximum value, because the minimum or maximum value is always on the root ;

3. Priority queue

In actual projects, there are usually multiple tasks, and among them there is a particularly important task to be done first, then this task should have priority; the queue for this special application scenario, we call it: priority queue ( PriorityQueue);

It is not difficult to see that the priority queue requires each element in the queue to have a certain priority; those with high priority or low priority (man-made definition) must be dequeued first, and those with the same priority will be dequeued in order. first in first out;

The binary heap is just enough to meet the needs of the priority queue;

Second, the implementation of the priority queue

According to the nature of the heap described above, we can easily build a binary heap and a priority queue:

typedef int ElementType;
typedef struct HeapStruct
{
	int capacity;//容量
	int size;//当前的大小
	ElementType* elements;//元素指针
}Heap, *PriorityQueue;

When we build a small top heap, the minimum value can always be found at the root. It is very easy to realize this operation, both logically and practically, as long as the heap order is maintained;

1. insert

Traditionally, when performing an insertion operation, we can insert at the end of the array, and then go back up to compare the size relationship between the inserted element and its parent node (it doesn't matter between brothers) and make adjustments——(For small top heaps In terms of) less than the exchange until the correct order is achieved, this process we call heap processing ;

void swap(int a[], int i, int j)
{
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

//这里实现大顶堆的堆调整
void heapify(int a[], int len, int k)
{
    //k是要调整的元素下标
    if (k < len)
    {
        int root = k;//根节点
        int left_child = 2 * k + 1;//左孩子
        int right_child = 2 * k + 2;//右孩子
        //查找左右孩子中大的孩子节点
        if (left_child < len && a[root] < a[left_child])
        {
            root = left_child;
        }
        if (right_child < len && a[root] < a[right_child])
        {
            root = right_child;
        }
        //交换
        if (root != k)
        {
            swap(a, k, root);
            //递归下去
            heapify(a, len, root);
        }
    }
}

However, performing an exchange requires 3 assignment statements. If an element goes back to d layers, then the number of exchanges reaches 3*d, and if we use percolate up strategy, we only need to assign d + 1 time;

 2. Remove the minimum value

Removal of minimum values ​​is implemented similarly to insertion. Finding the minimum is easy, which is the root node, the hard part is removing it;

Refer to the insertion step, but this time it is reversed. As long as the root node is exchanged with the last element , because the last element must be a leaf, and it is the last leaf, we will not destroy the structure of the heap when we operate on it. Guarantee that the heap is always a complete binary tree, and then only need to start from the root node to heap down continuously;

In the same way, we can also avoid exchange similar to the operation of the upper filter: create a hole at the root. Since there is one less element in the heap now, we also need to move the last element x, and place x into the edge from the root to include the smallest son. A correct position on a path. This strategy is generally called percolate down;

 

The algorithm time complexity of inserting and deleting through upper filtering and lower filtering is O(log N); 

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
//二叉堆实现优先队列
typedef int ElementType;
typedef struct HeapStruct
{
	int capacity;//容量
	int size;//当前的大小
	ElementType* elements;//元素指针
}Heap, *PriorityQueue;

//初始化 这里是小顶堆构建的优先队列
PriorityQueue init(int MaxSize)
{
	PriorityQueue H;
	H = (PriorityQueue)malloc(sizeof(Heap));
	if (H == NULL)
	{
		printf("MALLOC FAIL\n");
	}
	H->capacity = MaxSize;
	H->size = 0;
	H->elements = (ElementType*)malloc(sizeof(ElementType) * (MaxSize + 1));//分配数组加上一个额外的哨兵位
	if (H->elements == NULL)
	{
		printf("MALLOC FAIL\n");
	}
    //如果新插入的指是新的最小值,那么它将一直被推向堆顶,在某一时刻i=1,我们就需要跳出循环,所以将0处标记为哨兵位使循环终止
    //保证这个值要小于任何一个元素才能使循环终止
	H->elements[0] = INT_MIN;

	return H;
}

int isFull(PriorityQueue H)
{
	return H->capacity == H->size;
}

int isEmpty(PriorityQueue H)
{
	return H->size == 0;
}

//插入 
void insert(PriorityQueue H, ElementType x)
{
	int i;
	//判断是否已经满容了
	if (isFull(H))
	{
		printf("PriorityQueue is full\n");
	}
	//在最后一位插入 如果该点的父节点大于插入的值 就要交换
	for (i = ++H->size; H->elements[i / 2] > x; i /= 2)
	{
		H->elements[i] = H->elements[i / 2];
	}
	H->elements[i] = x;
}

ElementType deleteMin(PriorityQueue H)
{
	//判断是否为空
	if (isEmpty(H))
	{
		printf("PriorityQueue is empty\n");
		return H->elements[0];
	}
	ElementType child, parent;
	ElementType min = H->elements[1];
	//记录最后一个元素的值
	ElementType last = H->elements[H->size--];

	for (parent = 1; parent * 2 <= H->size; parent = child)
	{
		//找出左右子树中最小的
		child = parent * 2;
		//如果右子树存在 并且右子树小于左子树
		if (child != H->size && H->elements[child] > H->elements[child + 1])
		{
			child++;
		}
		//如果还没有找到最后一个元素正确的位置
		if (last > H->elements[child])
		{
			H->elements[parent] = H->elements[child];
		}
		else
			break;
	}
	H->elements[parent] = last;
	return min;
}

void destroy(PriorityQueue H)
{
	free(H->elements);
	free(H);
}

void makeEmpty(PriorityQueue H)
{
	H->size = 0;
}

int main()
{
	PriorityQueue H = init(100);
	int arr[] = { 50, 45, 15, 25, 10 };
	int length = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < length; i++)
	{
		insert(H, arr[i]);
	}
	for (int i = 0; i < length; i++)
	{
		printf("%d ", deleteMin(H));
	}
	system("pause");
	return 0;
}

3. Heap sort

In fact, we have already implemented heap sorting when implementing the function of the priority queue - when deleting the minimum value, we keep dequeuing the minimum value and looking for the next minimum value, which is a result of sorting from small to large (descending sort only You need to build a large top heap);

But in the algorithm problem, it is too troublesome for us to follow the above steps to build a priority queue. We only need to use this idea and simplify the above process to realize a simple heap construction and heap sorting;

#include<stdio.h>
#include<stdlib.h>

void swap(int a[], int i, int j)
{
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

//最大堆调整
void heapify(int a[], int len, int k)
{
    if (k < len)
    {
        int root = k;//根节点
        int left_child = 2 * k + 1;//左孩子
        int right_child = 2 * k + 2;//右孩子
        //查找左右孩子中大的孩子节点
        if (left_child < len && a[root] < a[left_child])
        {
            root = left_child;
        }
        if (right_child < len && a[root] < a[right_child])
        {
            root = right_child;
        }
        //交换
        if (root != k)
        {
            swap(a, k, root);
            //递归下去
            heapify(a, len, root);
        }
    }
}

//构建大顶堆
void creatHeap(int a[], int n)
{
    int last = n - 1;//最后一个节点的下标
    int parent = (last - 1) / 2;//最后一个节点的父节点的下标
    //从最后一个节点的父节点到根节点进行最大堆调整
    for (int i = parent; i >= 0; i--)
        heapify(a, n, i);
}

//堆排序
void heapSort(int a[], int n)
{
    creatHeap(a, n);
    for (int i = n - 1; i >= 0; i--)
    {
        //将根节点(最大值)和最后一个节点交换,也就是最大值到最后一个下标位置上 
        swap(a, i, 0);
        //因为此时根节点不有序,整体从根节点开始最大堆调整
        //而此时根结点小于所有父结点,因而在调整时只需考虑最大孩子的分支即可
        heapify(a, i, 0);
    }
}

int main()
{
    int arr[] = { 50, 45, 15, 25, 10 };
    int length = sizeof(arr) / sizeof(arr[0]);
    heapSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    system("pause");
    return 0;
}

 operation result

 

Guess you like

Origin blog.csdn.net/ZER00000001/article/details/125653585