C 言語は、プライオリティ キュー、バイナリ ヒープ、およびヒープ ソートを実装します。

アルゴリズムカタログの並べ替えと検索

【マージしてソート。バケットソート】マージソート、カウンティングソート、バケットソート、基数ソートをC言語で実装

【交換ソートを実現するC言語】交換ソート(バブルソート、クイックソート)

【挿入ソートを実現するC言語】挿入ソート(ダイレクト、ハーフ、ツーウェイ、ヒル)

【3つの古典的な検索】3つの古典的な検索(ジャンプ、補間、フィボナッチ)

【3つの基本検索】3つの基本検索(シーケンシャル、バイナリ、ブロック)


目次

1. プライオリティ キューとバイナリ ヒープ

1.バイナリヒープの概念

2.ヒープの性質

3.優先キュー

第二に、優先キューの実装

1.挿入する

 2. 最小値を削除します

3.ヒープソート


 序文

検索と並べ替えは、データ構造とアルゴリズムの不可欠な部分です. これらは、アルゴリズムの道の先人たちが残した重要で便利なスキルです. これらの古典的な検索と並べ替えを学ぶことで、私たちはより良く、より速く問題を解決することができます. このコラムでは、6 つの主要な検索と上位 10 のソートのアルゴリズムとアイデアを学び、この記事ではヒープ ソートについて詳しく説明します。

注: この記事のすべての並べ替えは昇順で並べ替えられており、降順は論理を逆にするだけで済みます。


1. プライオリティ キューとバイナリ ヒープ

1.バイナリヒープの概念

ヒープの並べ替えを学習する前に、まずバイナリ ヒープを学習する必要があります。

データ構造のスタックがハードウェアのスタックを模倣するように構築されている場合、ヒープはまったく異なります。バイナリ ヒープは、完全なバイナリ ツリー(以降、ヒープと呼びます) に基づくデータ構造です。

二分探索木と同様に、ヒープには次の 2 つのプロパティがあります。

  • 構造的で積み重ね可能。

AVL ツリーと同様に、ヒープに対する操作はこれら 2 つのプロパティのいずれかを破壊する可能性が高いため、ヒープのプロパティが満たされるまでヒープの操作を停止してはなりません。

重要な点は、完全な二分木は規則性があるため、ポインタのない配列で表すことができるということです。

  •  数学的関係を介して取得できます。添字 0 から始まる配列で、親ノードの添字が x の場合、その左の子の添字は 2 * x + 1 であり、右の子の添字は2 * x + 2. 同様に、親ノードの添字は (x - 1) / 2 と推定できます

 つまり、ヒープ データ構造は、配列、最大値を表す整数、および現在のヒープ サイズで構成されます。 

2.ヒープの性質

ヒープは、大きなトップ ヒープと小さなトップ ヒープに分割されます。 

  • ビッグ トップ ヒープのルート ノードの値は、常に子の値より大きくなります。
  • 小さなトップ ヒープ ルート ノードの値は常に子ノードよりも小さくなります。

これはまさにヒープ順序の具現化です. このプロパティによれば, 最小値または最大値は常にルートにあるため, 最小値または最大値をすばやく見つけることができます.

3.優先キュー

実際のプロジェクトでは通常、複数のタスクがあり、その中には特に重要なタスクが最初に実行され、次にこのタスクが優先される必要があります; この特別なアプリケーション シナリオのキューを、優先キュー ( PriorityQueue) と呼びます。

プライオリティ キューでは、キュー内の各要素に特定の優先度が必要であることを理解するのは難しくありません; 優先度の高い要素または優先度の低い要素 (人為的な定義) を最初にデキューする必要があり、同じ優先度を持つ要素はキューからデキューされます。 order. 先入れ先出し。

バイナリ ヒープは、プライオリティ キューのニーズを満たすのに十分です。

第二に、優先キューの実装

上記のヒープの性質によると、バイナリ ヒープと優先キューを簡単に構築できます。

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 に達し、パーコレート アップ戦略を使用する

 2. 最小値を削除します

最小値の削除は、挿入と同様に実装されます。ルートノードである最小値を見つけるのは簡単ですが、難しいのはそれを削除することです。

挿入ステップを参照してください, 今回はそれが逆です.ルートノードが最後の要素と交換されている限り, 最後の要素は葉でなければならず、それは最後の葉であるため, の構造を破壊しません.ヒープが常に完全な二分木であることを保証し、ルートノードから開始して継続的にヒープダウンするだけで済みます。

同様に、上のフィルターの操作と同様の交換を回避することもできます: ルートに穴を作成します. ヒープ内の要素が 1 つ少なくなったので、最後の要素 x を移動し、x を配置する必要もあります.一番小さい息子を含むように、ルートからエッジに入る パス上の正しい位置。この戦略は、一般にパーコレートダウンと呼ばれます。

 

上位フィルタリングと下位フィルタリングによる挿入と削除のアルゴリズムの時間計算量は 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