c语言删除最小的j个元素,最小-最大堆的插入和删除

一、定义

最小-最大堆:各层交替为最小层和最大层的堆。

最大层:该层上的节点大于等于以其为根节点的子树上的所有节点。

最小层:该层上的节点小于等于以其为根节点的子树上的所有节点。

本文中,我们令堆的层数从1开始,节点编号也从1开始,即根节点为第1层,编号为1。

最小最大对具有如下性质:

对于任一节点,从该节点到叶节点的任意一条路径,

(1)

处于最小层的节点关键字逐渐增大;

(2)

处于最大层的节点关键字逐渐减小;

(3)

所有处于最大层的节点关键字大于所有处于最小层的节点的关键字。

证明:

假设取这样一条路径,上面的节点依次为x1、x2、x3、…、xn。假设x1处于最小层,则,所有处于最小层的元素为x3、x5等,所有处于最大层的元素为x2、x4等。

(1) 由于x3处于以x1为根的树上,且x1处于最小层,因此x1小于x3,同理,x5处于以x3为根的树上,且x3处于最小层,因此x3小于x5等,依次类推,所有处于最小层的元素逐渐增大;

(2) 因为x4处于以x2为根的树上,且x2处于最大层,因此x2大于x4,依次类推,所有处于最大层的元素依次减小;

(3) 假设最小层最后一个节点是xs,最大层最后一个节点为xt,因为不可能连续两层或两层以上是最大层或最小层,因此xs与xt必然处于相邻的两层,且xs小于xt(假设xs是xt的父节点,因为xs是最小层,而xt处于以xs为根的子树上,因此,xs小于xt;假设xs是xt的子节点,因为xt是最大层,而xs处于以xt为根的子树上,因此,xs小于xt,因此,不管xs和xt谁是父节点,都有xs小于xt)。

由前面可知,xs是x1、x3、…、xs等节点的最大值,xt是x2、x4、…、xt等节点的最小值,前一序列的最大值小于后一序列的最小值,因此前一序列的所有值小于后一序列的所有值。

证明结束。

推论:假设最小-最大堆中有n个节点,其中最小层节点数为ms,最大层节点数为mt,则最小层节点必是n个节点中最小的ms个节点,最大层节点必是n个节点中最大的mt个节点。

二、最小-最大堆的插入

具体实现见下面,我们使用min_max_insert将item插入到堆heap中,插入前节点个数为*n。边界条件不进行详细解释。

首先将item存入heap[*n+1]处,检查该元素的父节点是最大层还是最小层。

1)父节点是最小层,且该元素小于父节点

该节点是最后一个节点且处于最大层,本次插入只增加最大层节点数,最小层节点数不变;

父节点是最小层的最后一个元素,是最小层节点中最大的节点;

假设插入前最小层节点数为ms,最大层节点数为mt,则插入后最小层节点数不变,最小层节点数为mt+1。由推论可知,最小层节点是所有节点中最小的前ms个节点,父节点属于这ms个节点中最大的节点,现在新插入的节点小于父节点,就意味着,新节点将代替父节点成为最小的前ms个节点之一,因此父节点属于最大层节点。

又由于父节点原先小于最小层,因此小于所有原先最大层的节点,现在转入最大层后,成为最大层节点中最小的节点,根据最小最大堆的性质可知,父节点必属于其所属路径上的最后一个节点。因此我们直接把父节点放入heap[*n+1]处即可。

对于新加入节点,我们不需要改动其它路径,只需沿着heap[*n+1]到根节点的这条路径,找出所有最小层节点,将其放在某位置,使得该路径上节点顺序满足最小最大堆性质(1)即可。

2)父节点是最小层,且该元素大于父节点

同理,新加入节点只是改变了最大层节点数,不会改变最小层节点数。

父节点是前ms个最小节点中最大的,新节点大于父节点,所以新节点不可能是前ms个最小节点,所以新节点属于最大层。

因此,我们只需找出heap[*n+1]到根节点的这条路径上所有最大层节点,将新节点放入某个位置,其它节点依次后移,使得这些节点满足最小最大堆的性质(2).

3)父节点是最大层,且该元素大于父节点

此时,新加入的节点处于最小层,所以新节点的加入改变的是最小层节点数,不会改变最大层节点数。

最大层节点是所有节点中最大的mt个节点,且父节点是则mt个节点中最小的节点,新节点大于父节点,就意味着父节点不可能再成为最大的前mt个节点,所以新节点代替父节点成为最大层节点。

由于父节点大于所有最小层节点,所以父节点应该是出于该路径上的最后一个位置,因此直接将父节点放入heap[*n+1]即可。

对于新节点,只需找出heap[*n+1]到根节点路径上的所有最大层节点,将新节点放入某位置,其它节点依次后移,使得该路径上所有最小层节点满足性质(1)即可。

4)父节点是最大层,且该元素小于父节点

最大层节点不变,仍是原先的mt个节点。

新节点属于最小层节点,将新节点加入heap[*n+1],找出heap[*n+1]到根节点路径上所有最小层节点,重新排序,满足性质(1)。

三、最小-最大堆的删除(最小元素)

基本思想:最小元素就是最小-最大堆根节点对应的元素,所以每次都是直接删除该节点。如果还有剩余节点,接着要做的就是将剩余节点重新组织成一个新的最小-最大堆。

最直接的方式就是对所有元素重新建堆,即把剩余元素逐个输入堆里,每输入一个元素调用一次函数min_max_insert,将已输入元素建成最小-最大堆,直到所有元素输入为止。但这么做显然效率是比较低的,原先的堆里所包含的信息被全部丢弃。直觉告诉我们,可以利用原先堆的信息,找到更简便的方法。

我们现在将剩余节点全部保留在原位置,缺了一个根节点,于是,我们很自然的想到要找一个节点放入根节点位置,我们找到剩余节点中最小的节点放入根节点。

因为最小最大堆的根节点是整个堆中最小的元素,所有首先在剩余节点中找到最小的节点,并将该节点放入根节点处;假设最小节点位置为k。

现在k位置空了下来。另外,现在堆节点数少了一个,最后一个节点需要删除,也就意味着该节点存放的元素将没有位置存放,用item表示,我们很自然地想到,将item放在k位置。那么这么做究竟是否可行呢?通过分析,直接放在有些情况下会违反最小-最大堆,下面我们具体分析:

(1)只剩一个节点,不需进行任何操作(该节点已被放入根节点);

(2)剩余两个或两个以上节点且k是最后一个节点,不需进行任何操作(该节点已被放入根节点);

(3)剩余两个或两个以上节点且k不是最后一个节点其它情况,则需要根据k可能出现的位置来进一步分析:

首先,k只可能出现在第二层或第三层(否则不可能是最小节点)。

如果出现在第二层,则有两种可能:左儿子、右儿子。

如果是左儿子,说明左儿子没有子节点,右儿子也没有子节点(完全二叉树),我们只需要将item直接放入k节点处即可;

如果是右儿子,则右儿子没有子节点,左儿子可能有子节点。不管左儿子有没有子节点,都只需将item放入k节点即可;

如果出现在第三层,不管最后一个节点是不是k节点的后代,甚至不管k是否有后代,将item放入k节点后,都只会影响从根节点出发到到叶节点结束,且经过k节点的路径,其它路径仍满足最小最大堆的性质。

我们由最小最大堆的性质和定义可知,检查一个堆是否为最小最大堆,只需检查其各条路径是否满足最小-最大堆性质即可。

违反最小-最大堆有下面几种情况:

item大于k的父节点:我们只需要item和父节点调换,此时其它路径仍不会违反,父节点仍是那些路径上的最大节点。接下来,只需把item(此时已变为原先k的父节点元素)放入k节点,但同样不能直接放入,插入方式与最开始将最后一个元素插入根节点一样。

因为此时k是第三层,其父节点是第二层,为最大层,所以父节点应大于k节点;父节点处于第二层,大于以其为根的堆中所有元素。item既然大于父节点,且即将成为这个堆中的元素之一,因此item是新堆中最大的元素,应将其放在父节点的位置,且不管后面如何排,都不会改变,也就是说,我们接下来只需考虑以k节点及k节点的兄弟为根的子树。另外,以k节点的兄弟为根的子树在父节点和item交换后并不违反最小-最大堆性质,因此,我们只需考虑以k为根的子树。问题就变成了将新的item插入以k为根的子树的根节点(即k节点)。这和最先将最后一个元素插入根节点完全相同。

item大于k的子节点:把item插入k节点,插入方式与最开始将最后一个元素插入根节点一样。

通过上面的分析,item插入以k的父节点为根的堆中后,堆的最大元素仍然是k的父节点,所以父节点不变,因此,k的兄弟节点所在的路径或子堆也未违反最小-最大堆性质,不需改变,所以我们就只考虑将item插入以k为根的堆中的根节点(即k节点)即可。

总结起来就是三种情况:

a)

k处于第二层:

b)

k处于第三层且item大于k的父节点;

c)

k处于第三层且item大于k的子节点;

结合边界条件,我们很容易进行实现。

四、C语言实现

#include

#include

#define MAX_SIZE 100

#define FALSE 0

#define TRUE

1

#define SWAP(x,y,t)

((t)=(x),(x)=(y),(y)=(t))

typedef struct{

int key;

}element;

element heap[MAX_SIZE];

void min_max_insert(element heap[], int *n,

element item);

int level(int n);

void verify_min(element heap[], int n);

void verify_max(element heap[], int n);

element delete_min(element heap[], int

*n);

int find_min(element heap[] ,int n1,int

n2);

void show(element heap[],int n);

int main(void)

{

int n=0;

int i,j;

element

rr[23]={{7},{3},{70},{20},{40},{80},{30},{5},{9},{17},{10},{100},

{15},{3},{45},{0},{50},{2},{30},{88},{20},{99},{12}};

for(i=0;i<23;i++)

min_max_insert(heap,&n, arr[i]);

show(heap,n);

element temp;

for(i=0;i<23;i++)

{

temp=delete_min(heap,&n);

printf("the deleted element is %d\n",temp.key);

}

system("PAUSE");

return 0;

}

element delete_min(element heap[], int

*n)

{

heap[0]=heap[1];//heap[0]保存要删除的元素

if((*n)==1)

{

(*n)--;

return heap[0];

}

int parent,last,i,k;

element temp,st;

temp=heap[(*n)--];

for(i=1,last=(*n)/2;i<=last;)

{

k=find_min(heap,i,*n);

if(temp.key<=heap[k].key)

break;

heap[i]=heap[k];

if(k<=2*i+1)

{

i=k;

break;

}

parent=k/2;

if(heap[parent].key

SWAP(heap[parent],temp,st);

i=k;

}

heap[i]=temp;

return heap[0];

}

void show(element heap[],int n)

{

int j;

printf("The heap is:\n");

for(j=1;j<=n;j++)

printf("%d

",heap[j].key);

printf("\n");

}

int find_min(element heap[] ,int n1,int

n2)

//在堆中寻找n1节点的后代中最小的节点,注意,并不是heap中所有位于n1后面的节点都是n1节点的后代

{

if(n1>=n2)

{

fprintf(stderr,"n1 must be smaller than n2");

exit(1);

}

int i,j,k;

k=2*n1;

for(i=1,j=pow(2,i)*n1;j<=n2;)

{

if(heap[j].key

k=j;

if(j==pow(2,i)*(n1+1)-1)

{

i++;

j=pow(2,i)*n1;

}

else

j++;

}

return k;

}

void min_max_insert(element heap[], int *n,

element item)

{

(*n)++;

if (*n==MAX_SIZE)

{

fprintf(stderr,"The heap is full.\n");

exit(1);

}

int parent=(*n)/2; //父节点下标

element temp;

if (parent==0) //说明

*n=1,即当前插入的item是heap中的第一个元素

heap[1]=item;

else

{

heap[*n]=item; //先把item放在最后一个位置

switch(level(parent))

{

case FALSE:

if(heap[*n].key

{

SWAP(heap[*n],heap[parent],temp);

verify_min(heap,parent);

break;

}

else

{

verify_max(heap,*n);

break;

}

case TRUE:

if(heap[*n].key>heap[parent].key)

{

SWAP(heap[*n],heap[parent],temp);

verify_max(heap,parent);

}

else

verify_min(heap,*n);

default:break;

}

}

}

int level(int n)

{

int i=log2(n);

if(i%2)

//max

return

1;

return

0;

}

void verify_min(element heap[], int n)

{

if (n>1)

{

int parent=(n/2);

parent=parent/2;

if(parent)

if(heap[n].key

{

element temp;

SWAP(heap[n],heap[parent],temp);

verify_min(heap,parent);

}

}

}

void verify_max(element heap[], int n)

{

if (n>1)

{

int parent=(n/2);

parent=parent/2;//父节点的父节点

if(parent)

if(heap[n].key>heap[parent].key)

{

element temp;

SWAP(heap[n],heap[parent],temp);

verify_min(heap,parent);

}

}

}

运行结果:

0

99 100

2

10

15

3

40

30

50 88

80

70

30

45

5

7

3 9

20

20

17

12

the

deleted element is 0

the

deleted element is 2

the

deleted element is 3

the

deleted element is 3

the

deleted element is 5

the

deleted element is 7

the

deleted element is 9

the

deleted element is 10

the

deleted element is 12

the

deleted element is 15

the

deleted element is 17

the

deleted element is 20

the

deleted element is 20

the

deleted element is 30

the

deleted element is 30

the

deleted element is 40

the

deleted element is 45

the

deleted element is 50

the

deleted element is 70

the

deleted element is 80

the

deleted element is 88

the

deleted element is 99

the

deleted element is 100

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值