C 언어는 우선 순위 대기열, 이진 힙 및 힙 정렬을 구현합니다.

알고리즘 카탈로그 정렬 및 찾기

[병합 및 정렬. 버킷 정렬] C 언어는 병합 정렬, 카운팅 정렬, 버킷 정렬 및 기수 정렬을 구현합니다.

[교환 정렬을 달성하기 위한 C 언어] 교환 정렬(버블 정렬, 빠른 정렬)

[삽입정렬을 이루는 C언어] 삽입정렬(direct, half, two-way, Hill)

[세 가지 기본 검색] 세 가지 기본 검색(점프, 보간, 피보나치)

[3가지 기본 검색] 3가지 기본 검색(순차, 이진, 블록)


목차

1. 우선순위 큐와 바이너리 힙

1. 바이너리 힙의 개념

2. 힙의 특성

3. 우선순위 대기열

둘째, 우선순위 큐의 구현

1. 삽입

 2. 최소값 제거

3. 힙 정렬


 머리말

검색 및 정렬은 데이터 구조 및 알고리즘의 필수 불가결한 부분입니다.그들은 알고리즘의 길에서 선배들이 남긴 중요하고 편리한 기술입니다.이러한 고전적인 검색 및 정렬을 배우면 우리도 문제를 더 잘 해결할 수 있습니다. 이 칼럼에서는 6개의 주요 검색과 상위 10개 정렬의 알고리즘과 아이디어를 배우고 이 기사에서는 힙 정렬에 대해 자세히 설명합니다.

참고: 이 문서의 모든 정렬은 오름차순으로 정렬되며 내림차순으로만 논리를 뒤집으면 됩니다!


1. 우선순위 큐와 바이너리 힙

1. 바이너리 힙의 개념

힙 정렬을 배우기 전에 먼저 이진 힙을 배워야 합니다.

데이터 구조의 스택이 하드웨어의 스택을 모방하도록 구축된 경우 힙은 완전히 다릅니다! 이진 힙은 완전한 이진 트리 (이하 힙이라고 함)를 기반으로 하는 데이터 구조입니다 .

이진 검색 트리와 마찬가지로 힙에는 두 가지 속성이 있습니다.

  • 구조적이며 쌓을 수 있습니다.

AVL 트리와 마찬가지로 힙에 대한 작업은 이 두 속성 중 하나를 파괴할 가능성이 있으므로 힙의 작업은 힙의 속성이 충족될 때까지 중지해서는 안 됩니다.

중요한 점은 완전한 이진 트리의 규칙성 때문에 포인터가 없는 배열로 나타낼 수 있다는 것입니다.

  •  수학적 관계를 통해 얻을 수 있습니다. 첨자 0부터 시작하는 배열에서 부모 노드의 첨자가 x이면 왼쪽 자식의 첨자는 2 * x + 1이고 오른쪽 자식의 첨자는 입니다. 2 * x + 2. 마찬가지로 부모 노드의 첨자는 (x - 1) / 2로 추론할 수 있습니다 .

 즉, 힙 데이터 구조는 배열, 최대값을 나타내는 정수 및 현재 힙 크기로 구성됩니다

2. 힙의 특성

힙은 큰 상단 힙과 작은 상단 힙으로 나뉩니다. 

  • 큰 상위 힙의 루트 노드 값은 항상 하위 노드의 값보다 큽니다.
  • 작은 상단 힙 루트 노드의 값은 항상 하위 노드보다 작습니다.

이것은 정확히 힙 순서의 구현입니다.이 속성에 따르면 최소값 또는 최대값이 항상 루트에 있기 때문에 최소값 또는 최대값을 빠르게 찾을 수 있습니다 .

3. 우선순위 대기열

실제 프로젝트에는 일반적으로 여러 작업이 있으며 그 중 먼저 수행해야 하는 특히 중요한 작업이 있으며 이 작업이 우선 순위를 가져야 합니다.

우선순위 큐는 큐의 각 요소가 특정 우선순위를 가져야 한다는 것을 쉽게 알 수 있습니다. order.first in first out;

바이너리 힙은 우선순위 큐의 요구를 충족시키기에 충분합니다.

둘째, 우선순위 큐의 구현

위에서 설명한 힙의 특성에 따라 바이너리 힙과 우선 순위 큐를 쉽게 만들 수 있습니다.

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

작은 상위 힙을 만들 때 최소값은 항상 루트에서 찾을 수 있으며 힙 순서가 유지되는 한 논리적으로나 실제적으로 이 작업을 구현하는 것은 매우 쉽습니다.

1. 삽입

전통적으로 삽입 작업을 수행할 때 배열의 끝에 삽입한 다음 다시 돌아가서 삽입된 요소와 해당 부모 노드 사이의 크기 관계를 비교하고(형제 간에는 중요하지 않음) 조정합니다—— (소형 상위 힙의 경우) 올바른 순서가 달성될 때까지 교환보다 적은 이 프로세스를 힙 처리라고 합니다 .

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);
        }
    }
}

그러나 교환을 수행하려면 3개의 대입문이 필요하며, 요소가 d층으로 돌아가면 교환 횟수는 3*d가 되고, percolate up 전략을 사용 할당하면 됩니다.

 2. 최소값 제거

최소값 제거는 삽입과 유사하게 구현됩니다. 루트 노드인 최소값을 찾는 것은 쉽습니다. 어려운 부분은 이를 제거하는 것입니다.

삽입 단계를 참조하되 이번에는 반대로 루트 노드가 마지막 요소와 교환되는 한 마지막 요소는 리프여야 하며 마지막 리프이기 때문에 해당 노드의 구조를 파괴하지 않습니다. 힙이 항상 완전한 이진 트리임을 보장하고 계속해서 힙을 쌓기 위해 루트 노드에서 시작하기만 하면 됩니다.

같은 방식으로 상위 필터의 작업과 유사한 교환도 피할 수 있습니다: 루트에 구멍을 만듭니다. 이제 힙에 요소가 하나 줄어들었으므로 마지막 요소 x도 이동하고 x를 배치해야 합니다. 가장 작은 아들을 포함하도록 루트에서 가장자리로 경로의 올바른 위치. 이 전략은 일반적으로 퍼콜레이트 다운 (percolate down)이라고 합니다.

 

상위 필터링과 하위 필터링을 통한 삽입과 삭제의 알고리즘 시간복잡도는 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. 힙 정렬

사실 우선순위 큐의 기능을 구현할 때 이미 힙 정렬을 구현한 바 있습니다. 최소값을 삭제할 때 계속해서 최소값을 큐에서 빼내고 다음 최소값을 찾는데, 이는 작은 것에서 큰 것(내림차순)으로 정렬한 결과입니다. 정렬만 큰 상위 힙을 빌드해야 함);

그러나 알고리즘 문제에서 위의 단계를 따라 우선 순위 대기열을 구축하는 것은 너무 번거롭기 때문에 이 아이디어를 사용하고 위 프로세스를 단순화하면 간단한 힙 구성 및 힙 정렬을 실현할 수 있습니다.

#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;
}

 연산 결과

 

추천

출처blog.csdn.net/ZER00000001/article/details/125653585