从大数据文件中挑选K个最小的记录

大根堆:

根结点(称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆(大顶堆)。大根堆要求根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。

Heap是一种数据结构具有以下的特点:

1)  完全二叉树;
2)heap中存储的值是偏序;

小根堆(最大堆): 父节点的值小于或等于子节点的值;

大根堆(最小堆): 父节点的值大于或等于子节点的值;

堆的存储

一般都用数组来表示堆,i结点的父结点下标就为(i–1)/2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

堆排序

堆建好之后堆中第0个数据是堆中最大的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最大的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

堆的操作:insert

插入一个元素:新元素被加入到heap的末尾,然后更新树以恢复堆的次序。

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中。

堆的操作:Removemax

按定义,堆中每次都删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最大的,如果父结点比这个最小的子结点还大说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。

 

/**
*    实验题目:
*        从大数据文件中挑选K个最小的记录
*    实验目的:
*        掌握外排序的过程及堆的应用算法设计
*    实验内容:
*        编写程序,从大数据文件中挑选K个最小的记录。假设内存工作区的大小为K,
*    模拟这个过程,并输出每趟的结果。假设整数序列为(15, 4, 97, 64, 17, 32, 108,
*    44, 76, 9, 39, 82, 56, 31, 80, 73, 255, 68),从中挑选5个最小的整数。
*    备注:
*       为了简单,假设每个记录仅仅包含整型关键字,用全局变量fi模拟存放所有的记录,
*    R[1..K]存放大根堆。
*/

#include <stdio.h>
#include <stdbool.h>

#define MAX_SIZE        (100)
#define MAX_KEY         (32767)                     //  最大关键字值∞
#define K   5                                       //  内存工作区可容纳的记录个数

typedef int key_type;                               //  关键字类型
typedef struct
{
    key_type recs[MAX_SIZE];                        //  存放文件中的数据项
    int length;                                     //  存放文件中实际记录个数
    int cur_rec;                                    //  存放当前位置
}file_type;                                         //  文件类型

file_type fi;                                       //  定义输入文件,为全局变量
key_type R[K + 1];                                  //  存放大根堆


/*------------------输入文件初始化-------------------*/
static void initial(void)
{
    int i;
    int n = 18;

    key_type a[] = {15, 4, 97, 64, 17, 32, 108, 44, 76, 9, 39, 82, 56, 31, 80, 73, 255, 68};
    for(i = 0; i < n; i++)                          //  将n个记录存放到输入文件中
        fi.recs[i] = a[i];
    fi.length = n;                                  //  输入文件中有n个记录
    fi.cur_rec = -1;                                //  输入文件中当前位置为-1
}

/*-------------------从输入文件中取一个记录r--------------------*/
static bool get_one_rec(key_type &r)
{
    fi.cur_rec++;
    if(fi.cur_rec == fi.length)
        return false;
    else
    {
        r = fi.recs[fi.cur_rec];
        return true;
    }
}

/*-------------------显示堆中所有记录--------------------*/
static void disp_heap(void)
{
    int i;

    for(i = 1; i <= K; i++)
        printf("%d ", R[i]);

    printf("\n");
}

/*---------------筛选为大根堆算法------------------*/
static void sift(int low, int high)
{
    int i = low;
    int j = 2 * i;                                      //  R[j]是R[i]的左孩子
    key_type tmp = R[i];

    while(j <= high)
    {
        if(j < high && R[j] < R[j + 1])                 //  若右孩子较大,把j指向右孩子
            j++;                                        //  变为2i+1

        if(tmp < R[j])
        {
            R[i] = R[j];                                //  将R[j]调整到双亲结点位置上
            i = j;                                      //  修改i和j的值,以便继续向下筛选
            j = 2 * i;
        }
        else
            break;                                      //  筛选结束
    }
    R[i] = tmp;                                         //  被筛选结点的值放入最终位置
}

/**
*   功能:
*       从输入文件fi中挑选K个最小的记录。
*   算法思路:
*       首先,从fi中取出开头的K个记录存放在R中,将其调整为一个大根堆R,
*   然后依次取出fi的其余记录r,若r小于大根堆R的根结点R[1],用r替代R[1],
*   再筛选为大根堆。当fi所有记录取出完毕,R中即为K个最小的记录。
*/
static void select_k(void)
{
    int i;
    key_type r;

    for(i = 0; i < K; i++)                  //  从输入文件fi中取出开头的K个记录存放在R[1...K]中
    {
        get_one_rec(r);
        R[i + 1] = r;                       //  R[1...5]依次为15, 4, 97, 64, 17
    }

    for(i = K / 2; i >= 1; i--)             //  建立初始堆 i = 2, 1
        sift(i, K);

    printf("开头%d个记录创建的大根堆:", K);
    disp_heap();
    while(get_one_rec(r))                   //  从输入文件fi中取出其余的记录
    {
        printf("  处理%d:", r);
        if(r < R[1])                        //  若r小于堆的根结点
        {
            R[1] = r;                       //  用r替代堆的根结点
            sift(1, K);                     //  继续筛选
            printf("\t需要筛选,结果:");
            disp_heap();
        }
        else
            printf("\t不需要筛选\n");
    }
}

int main(void)
{
    initial();
    select_k();
    printf("最终结果: ");
    disp_heap();

    return 0;
}
测试结果:

开头5个记录创建的大根堆:97 64 15 4 17
  处理32:       需要筛选,结果:64 32 15 4 17
  处理108:      不需要筛选
  处理44:       需要筛选,结果:44 32 15 4 17
  处理76:       不需要筛选
  处理9:        需要筛选,结果:32 17 15 4 9
  处理39:       不需要筛选
  处理82:       不需要筛选
  处理56:       不需要筛选
  处理31:       需要筛选,结果:31 17 15 4 9
  处理80:       不需要筛选
  处理73:       不需要筛选
  处理255:      不需要筛选
  处理68:       不需要筛选
最终结果: 31 17 15 4 9

 

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/87623576