果堆合并(急求PASCAL 哈夫曼树解法)!

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了果堆合并(急求PASCAL 哈夫曼树解法)!相关的知识,希望对你有一定的参考价值。

1. 果堆合并(merge.pas)
【问题描述】
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
【输入】(merge.in)
输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
【输出】(merge.out)
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2^31。

最好是O(n)的,不过O(N^2)也行

同楼上
附上插入排序算法
var a:array[0..20000]of longint;
j,i,n,start,m,sum,k,t:longint;
begin
readln(n);
for i:=1 to n do read(a[i]);
for i:=2 to n do begin
a[0]:=a[i];j:=i-1;
while (a[0]<a[j])and(j>0) do begin
a[j+1]:=a[j];
dec(j);
end;
a[j+1]:=a[0];
end;
start:=1;
m:=n;
for i:=1 to n-1 do begin
sum:=a[start]+a[start+1]+sum;
t:=a[start]+a[start+1];
inc(m);
a[m]:=t;
inc(start);
inc(start);
for j:=start to m do begin
a[0]:=a[j];k:=j-1;
while (a[0]<a[k])and(k>0) do begin
a[k+1]:=a[k];
dec(k);
end;
a[k+1]:=a[0];
end;
end;
writeln(sum);
end.
楼主也可以试试堆排
参考技术A 两种算法 单调双队列 堆实现
双队列: 先快排 一个队列(a)是空的 另个队列(b)就是快排后的果子 先取前2个果子(也就是最小的)和放到另个队列(a) 第二次 比较两个队列的队头 看谁小就把谁取出 然后取出两个在相加 又放在(a)队列 可证明 (a)队列也是递增的
堆:var
i,n,a1,a2,t,sum:longint;
a:array[0..10001] of longint;
procedure init;
var
i:longint;
begin
assign(input,'fruit.in');
reset(input);
readln(n);
for i:=1 to n do
read(a[i]);
close(input);
end;
procedure down(i,m:longint);
begin
while i*2<=m do
begin
i:=i*2;
if (a[i+1]<a[i]) and (a[i+1]<>0) then inc(i);
if a[i div 2]> a[i] then
begin
a[0]:=a[i];
a[i]:=a[i div 2];
a[i div 2]:=a[0];
end;
end;
end;
begin
init;
for i:=n div 2 downto 1 do
down(i,n);
t:=n;
while t<>1 do
begin
a1:=a[1];
a[1]:=a[t];
a[t]:=0;
dec(t);
down(1,t);
a2:=a[1];
a[1]:=a[t];
a[t]:=0;
dec(t);
sum:=sum+a1+a2;
inc(t);
a[t]:=a[1];
a[1]:=a1+a2;
down(1,t)
end;
assign(output,'fruit.out');
rewrite(output);
writeln(sum);
close(output);
end.
参考技术B var a:array[1..10000] of longint;
i,j,n,m,x,t:longint;
procedure swap(var a,b:longint);
var x:longint;
begin
x:=a; a:=b; b:=x;
end;
procedure down(i:longint);
var t:longint;
begin
if i *2 > n then exit;
t:=i;
if a[i*2] <a[i] then t:=i*2;
if i*2+1<= n then
if a[i*2+1] < a[t] then t:=i*2+1;
swap(a[i],a[t]);
if i <> t then down(t);
end;

procedure up(i:longint);
begin
if i=1 then exit;
if a[i div 2]>a[i] then begin swap(a[i div 2],a[i]); up(i div 2); end;
end;

begin
assign(input,'stone.in');reset(input);
assign(output,'stone.out');rewrite(output);

read(n);

for i := 1 to n do
read(a[i]);
for i:=n div 2 downto 1 do
down(i);
m:=n;
t:=0;
for i:=1 to m-1 do
begin
x:=0;
for j:=1 to 2 do
begin
inc(x,a[1]);
a[1]:=a[n];
dec(n);
down(1);
end;
inc(t,x);
inc(n);
a[n]:=x;
up(n);
end;
writeln(t);
close(input);
close(output);
end.
参考技术C 这...这贪心啊...先找最小两堆合并,放回去,之后重复上述操作.... 参考技术D

数据结构-并查集和堆哈夫曼树

一、并查集的定义

  1. 并查集是一种维护集合的数据结构,它的名字中“并”、“查”、“集”。分别取自Union(合并)、Find(查找)、Set(集合)。
  • 合并:就是合并两个集合
  • 查找:判断两个元素是否在一个集合
  • 那么并查集是用什么实现的,就是一个数组,
  • 对于同一个集合来说只存在一个根结点,且将其作为所属集合的标识。

二、并查集的基本操作

  1. 初始化,每个元素都是一个独立的集合,因此需要令所有的father[i] = i;
for(int i = 1; i <= N; i++){
    father[i] = i;
}
  1. 查找,就是查找反复寻找父亲结点。
int findFather(int x){
    while(x != father){
        x = father[x];
    }
    return x;
}

int findFather(int x){
    if(x == father[x]) return x;
    else return findFather(father[x]);
}
  1. 合并,就是把两个集合合并成一个集合。
void Union(int a, int b){
    int faA = findFather(a);
    int faB = findFather(b);
    if(faA != faB){
        father[faA] = faB;
    }
}

三、路径压缩

  • 把当前查询结点的路径上的所有结点的父亲都指向根结点。
int findFather(int x){
    //由于x在下面的while中会变成根结点,因此先把原先的x保存一下
    int a = x;
    while(x != father[x]){
        x = father[x];
    }
    //到这里,x存放的是根结点,下面把路径上所有的结点的father都改成根结点
    while(a != father[a]){
        int z = a;
        a = father[a];//a回溯父亲结点
        father[z] = a;//把原来的结点a的父亲改为根结点
    }
    return x;//返回根结点
}

四、堆的定义

  1. 堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子结点的值。
  2. 如果父亲结点的值大于或等于孩子结点的值,那么称这样的堆为大顶堆,反之为小顶堆。
  3. 堆一般用于优先队列的实现,而优先队列默认情况下使用大顶堆。

五、堆的基本操作

1. 建堆向下调整
//对于heap数组在[low, high]的范围进行向下调整
//其中low为欲调整结点的数组下标,high一般为堆的最后一个元素的数组下标
void downAdjust(int low, int high){
    int i = low, j = i * 2;
    while(j <= high){
        if(j+1 <= high && heap[j+1] > heap[j]){
            j = j + 1;
        }
        if((heap[j] > heap[i]){
            swap(heap[j], heap[i]);
            i = j;
            j = i * 2;
        }else{
            break;
        }
    }
}
2. 建堆
  • 假设序列中元素的个数为n,根据完全二叉树的特性,知道叶子结点个数为n/2个,因此数组下标在[1, n/2]的范围内是非叶子结点。于是可以从n/2号位开始倒着枚举结点,对每个遍历到结点i进行[i, n]范围的调整。
void createHeap(){
    for(int i = n / 2; i >= 1; i--){
        downAdjust(i, n);
    }
}
3. 删除堆中最大元素
  • 只需要最后一个元素覆盖堆顶元素,然后对根结点进行调整。
void deleteTop(){
    heap[1] = heap[n--];
    downAdjust(1, n);
}
4. 添加元素
  • 也就是把添加的元素放在数组的最后一个位置,然后进行上调操作。
//对于heap数组在[low, high]的范围进行向上调整
//其中low为欲调整结点的数组下标设置为1,high一般为堆的最后一个元素的数组下标
void upAdjust(int low, int high){
    int i = high, j = i / 2;//i为欲调整结点,j为其父亲
    while(j >= low){
        if(heap[j] < heap[i]){
            swap(heap[j], heap[i]);
            i = j;
            j = i / 2;
        }else{
            break;
        }
    }
}

//添加元素
void insert(int x){
    heap[++n] = x;//让元素个数加1,然后将数组末尾赋值为x
    upAdjust(1, n);//向上调整新加入的结点n
}
5. 堆排序
  • 是使用堆结构对一个序列进行排序。
void heapSort(){
    createHeap();
    for(int i = n; i > 1; i--){
        swap(heap[i], heap[1]);
        downAdjust(1, i - 1);
    }
}

六、哈夫曼树的相关定义

  1. 其中叶子结点的路径长度是指从根结点出发到达结点的边数。
  2. 把叶子结点的权值乘以其路径长度的结果称为这个叶子结点的带权路径长度。
  3. 树的带权路径长度(WPL)等于它所有叶子结点带权路径的长度之和。
  4. 带权路径长度最小的就是称为哈夫曼树(最优二叉树),显然对于同一组叶子结点来说,哈夫曼树可以是不唯一的,但是带权路径长度一定是唯一的。

七、哈夫曼编码

  1. 对于任何一个叶子结点,其编码一定不会成为其任何一个结点编码前缀。
  2. 其中任何一个字节编码都不是另一个字符编码的前缀,同时满足这种方式的编码方式称为前缀编码,意义不产生混淆。
  3. 哈夫曼编码,就是使得字符串编码成01串后长度最短的前缀编码。
  4. 哈法曼编码是针对于特定的字符串来讲的。

以上是关于果堆合并(急求PASCAL 哈夫曼树解法)!的主要内容,如果未能解决你的问题,请参考以下文章

哈夫曼树

小妹急求数据结构习题的一些答案 请高手指教!!

两个队列+k叉哈夫曼树 HDU 5884

[Huffman树] aw149. 荷马史诗(哈夫曼模型+贪心)

hdu5884 Sort(二分+k叉哈夫曼树)

HDU 5884 Sort (二分+k叉哈夫曼树)