二叉堆面试题目

tan625747 2010-03-10 09:16:12
现在有这么一个需求,需要求一组数据的里最低的值.可以用二叉堆来快速的实现这个功能.
二叉堆
在有序列表中,每个元素都按照由低到高或由高到低的顺序保存在恰当的位置。这很有用,但是还 不够。事实上,我们并不关心数字 127 是否比 128 在更低的位置上。我们只是想让 值最低的元素能放在列表顶端以便容易访问。列表的其他部分即使是混乱的也不必在意。列表的其他部分只有在我们需要另一个 值最低的元素的时候,才有必要保持有序。
基本上,我们真正需要的是一个 “ 堆 ” ,确切的说,是个二叉堆。二叉堆是一组元素,其中最大或者最小(取决于需要)的元素在堆顶端。既然我们要寻找 值最小的元素,我们就把它放在堆顶端。这个元素有两个子节点,每个的 值等于,或者略高于这个元素。每个子节点又有两个子节点,他们又有和他们相等或略高的子节点。。。依次类推。这里是一个堆可能的样子:


注意, 值最低的元素 (10) 在最顶端,第二低的元素 (20) 是它的一个子节点。可是,其后就没有任何疑问了。在这个特定的二叉堆里,第三低的元素是 24 ,它离堆顶有两步的距离,它比 30 小,但是 30 却在左侧离堆顶一步之遥的地方。简单的堆放,其他的元素在堆的哪个位置并不重要,每个单独的元素只需要和它的父节点相等或者更高,而和它的两个子节点相 比,更低或者相等,这就可以了。这些条件在这里都完全符合,所以这是个有效的二叉堆。
很好,你可能会想,这的确有趣,但是如何把它付诸实施呢?嗯,关于二叉堆的一个有趣的事实是,你可以简单的把它存储在一个一维数组中。
在这个数组中,堆顶端的元素应该是数组的第一个元素 ( 是下标 1 而不是 0) 。两个子节点会在 2 和 3 的位置。这两个节点的 4 个子节点应该在 4 - 7 的位置。

总的来说,任何元素的两个子节点可以通过把当前元素的位置乘以 2 (得到第一个子节点)和乘 2 加 1 (得到第二个子节点)来得到。就这样,例如堆中第三个元素(数值是 20 )的两个子节点,可以在位置 2*3 = 6 和 2*3 +1 = 7 这两个位置找到。那两个位置上的数字非别是 30 和 24 ,当你查看堆的时候就能理解。
你其实不必要知道这些,除了表明堆中没有断层之外知道这些没有任何价值。 7 个元素,就完整的填满了一个三层堆的每一层。然而这并不是必要的。为了让我们的堆有效,我们只需要填充最底层之上的每一行。最底层自身可以是任意数值的元 素,同时,新的元素按照从左到右的顺序添加。这篇文章描述的方法就是这样做的,所以你不必多虑。
往堆中添加新元素
大致的,为了往堆里添加元素,我们把它放在数组的末尾。然后和它在 当前位置 /2 处的父节点比较,分数部分被圆整。如果新元素的 值更低,我们就交换这两个元素。然后我们比较这个元素和它的新父节点,在 (当前位置) /2 ,小数部分圆整,的地方。如果它的 值更低,我们再次交换。我们重复这个过程直到这个元素不再比它的父节点低,或者这个元素已经到达顶端,处于数组的位置 1 。
我们来看如何把一个 值为 17 的元素添加到已经存在的堆中。我们的堆里现在有 7 个元素,新元素将被添加到第 8 个位置。这就是堆看起来的样子,新元素被加了下划线。
10 30 20 34 38 30 24 17
接下来我们比较它和它的父节点,在 8/2 也就是 4 的位置上。位置 4 当前元素的 值是 34 。既然 17 比 34 低,我们交换两元素的位置。现在我们的堆看起来是这样的 :
10 30 20 17 38 30 24 34
然后我们把它和新的父节点比较。因为我们在位置 4 ,我们就把它和 4/2 = 2 这个位置上的元素比较。那个元素的 值是 30 。因为 17 比 30 低,我们再次交换,现在堆看起来是这样的:
10 17 20 30 38 30 24 34
接着我们比较它和新的父节点。现在我们在第二个位置,我们把它和 2/2 = 1 ,也就是堆顶端的比较。这次, 17 不比 10 更低,我们停止,堆保持成现在的样子。
从堆中删除元素
从堆中删除元素是个类似的过程,但是差不多是反过来的。首先,我们删除位置 1 的元素,现在它空了。然后,我们取堆的最后一个元素,移动到位置 1 。在堆中,这是结束的条件。以前的末元素被加了下划线。
34 17 20 30 38 30 24
然后我们比较它和两个子节点,它们分别在位置 ( 当前位置 *2) 和 ( 当前位置 * 2 + 1) 。如果它比两个子节点的 值都低,就保持原位。反之,就把它和较低的子节点交换。那么,在这里,该元素的两个子节点的位置在 1 * 2 = 2 和 1*2 + 1 = 3 。显然, 34 不比任何一个子节点低,所以我们把它和较低的子节点,也就是 17 ,交换。结果看起来是这样:
17 34 20 30 38 30 24
接着我们把它和新的子节点比较,它们在 2*2 = 4 ,和 2*2 + 1 = 5 的位置上。它不比任何一个子节点低,所以我们把它和较低的一个子节点交换(位置 4 上的 30 )。现在是这样:
17 30 20 34 38 30 24
最后一次,我们比较它和新的子节点。照例,子节点在位置 4*2 = 8 和 4*2+1 = 9 的位置上。但是那些位置上并没有元素,因为列表没那么长。我们已经到达了堆的底端,所以我们停下来。
二叉堆为什么这么快?
现在你知道了堆基本的插入和删除方法,你应该明白为什么它比其他方法,比如说插入排序更快。 假设你有个有 1000 个数据,如果你使用插入排序,从起点开始,到找到新元素恰当的位置,在把新元素插入之前,平均需要做 500 次比较。

根据以上资料实现一个二叉堆的类,包含两个函数
void Add( int data );//往堆添加数值
int GetMin();//获得最小值并从堆里删除这个数值


自己做的,下午16:00要交不知道有什么错误,请指教。

#include <iostream>
#include <stdio.h>
//#include "BinaryHeap.h"

using namespace std;


#pragma once

class BinaryHeap
{
public:
BinaryHeap(int *m_arr, int asize, int anum);
~BinaryHeap(void);
void Add( int data );//往堆添加数值
int GetMin();//获得最小值并从堆里删除这个数值
void ShowArray();

private:
int *arr;
int num;
int size;


};


BinaryHeap::BinaryHeap(int *m_arr,int asize, int anum)
{
size = asize;
num = anum;

arr = new int[size];
for(int i=0; i<size; i++) arr[i] = m_arr[i];
}

BinaryHeap::~BinaryHeap(void)
{
delete arr;
}

void BinaryHeap::Add(int data)
{
num++ ;
arr[num-1] = data;
int i = num/2 -1;
int j = num - 1;
while(arr[i] > arr[j])
{
arr[i] += arr[j];
arr[j] = arr[i] - arr[j];
arr[i] = arr[i] - arr[j];
j = i;
i = (j+1)/2 - 1;
}
}

int BinaryHeap::GetMin ()
{
int min = arr[0];
arr[0] = arr [num -1];
arr [ num -1 ] =0;
num--;

int local = 1;
int child = local*2;
while(child < num)
{
int minarr;
minarr = arr[child-1]<arr[child]?arr[child-1]:arr[child];

if(arr[local-1] > minarr)
{
if(arr[child-1] > arr[child])
{
arr[local-1] += arr[child];
arr[child] = arr[local-1] - arr[child];
arr[local-1] = arr[local-1] - arr[child];

local = child +1;
}
else
{
arr[local-1] += arr[child-1];
arr[child-1] = arr[local-1] - arr[child-1];
arr[local-1] = arr[local-1] - arr[child-1];
local = child ;
}
}
child = local*2;
}

return min;
}

void BinaryHeap::ShowArray()
{

for(int i=0;i<num;i++) cout << arr[i] << " ";
cout << endl;
}

int main()
{

int aarr[16] ={10,30,20,34,38,30,24};
int intadd;
BinaryHeap bh(aarr,(sizeof aarr)/4, 7 );
bh.ShowArray();


bh.Add(15);
bh.ShowArray();
cout << "数组最小的数: "<<bh.GetMin() << endl<<"删除最小的数后:";
bh.ShowArray();

return 0;
}

...全文
175 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
_伯德_ 2010-03-18
  • 打赏
  • 举报
回复
为什么我们的面试题也是这道,纠结呀,看了你的,自己就不想写了,哎
lzy18lzy 2010-03-10
  • 打赏
  • 举报
回复


//当初始num = 1时候,程序会有问题
void BinaryHeap::Add(int data)
{
num++ ; //++后num = 2
arr[num-1] = data;
int i = num/2 -1; // i = 0
int j = num - 1; // j = 1
while(arr[i] > arr[j]) //假设arr[i] > arr[j]进入while
{
arr[i] += arr[j];
arr[j] = arr[i] - arr[j];
arr[i] = arr[i] - arr[j];
j = i; //j = 0
i = (j+1)/2 - 1; //问题来了,这时i = -1,再到while 中的 arr[i],i是-1
}
}



你可以检查修改下程序.

我习惯把数组的第一个元素不用,下标从1开始
hellodota121 2010-03-10
  • 打赏
  • 举报
回复
引用 1 楼 lzy18lzy 的回复:
C/C++ code//当初始num = 1时候,程序会有问题void BinaryHeap::Add(int data)
{
num++ ;//++后num = 2 arr[num-1]= data;int i= num/2-1;// i = 0int j= num-1;// j = 1while(arr[i]> arr[j])//假设arr[i] > arr[j]进入while {
arr[i]+= arr[j];
arr[j]= arr[i]- arr[j];
arr[i]= arr[i]- arr[j];
j= i;//j = 0 i= (j+1)/2-1;//问题来了,这时i = -1,再到while 中的 arr[i],i是-1 }
}

你可以检查修改下程序.

我习惯把数组的第一个元素不用,下标从1开始

顶一下。

33,008

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧