您的位置:首页 > 其它

几大内部排序算法(三)--堆(优先队列)和堆排序

2017-12-12 19:30 363 查看
这儿是我的笔记,希望大家可以友好交流!!谢谢#__#

这是最后一篇关于排序的了,在说堆排序前想先说下优先队列的ADT实现,优先队列最常见的用标准二叉堆实现,标准二叉堆其实就是用完全文二叉树实现的,由于完全二叉树的特点,仅仅用数组就可以实现(其实是将数抽象了,数组化了)。

(一)优先队列–标准二叉堆ADT实现

1:堆的结构性:当用标记法时(0位置用最小或最大的值填充)对数组中任意i上的元素,其左儿子在2*i上,右儿子在2*i+1上,其父亲在i/2;

当不用标记法时,对数组中任意i上的元素,其左儿子在2*i+1上,右儿子在2*i+1+1上,其父亲在(i-1)/2;

:2:堆的有序性:想要快速的找到最小的(大)的元,最小(大)的元应该在根节点上,数组的0或1位置上(看是不是用了标记法),此外,每个带有儿子的节点,其值要比孩子大或者小。

3:基本操作:

Insert:为将某元素x插入,应该在最后面创建一个空穴,若x可以直接放入而不破坏堆的有序性,则直接插入,否则将父亲移到空穴位置,空穴上移动了一层往根方向(上滤)。

【注意,在后面堆排序建堆时,也是从后面的非叶子节点开始检验调整的】

DeleteMax/Min:相当于出列,删除最小或最大的元素,把最小的元素从根处删除,根处多了一个空穴,则需要将最后一个元素相应的位置。若此时x放到空穴上,比根节点两个儿子中较大的元素大,则x放到空穴后完成;若x不能放,则选择2个儿子较大的放到空穴,空穴下移动(下滤),直到x能当道合适的位置。

4:其他操作:

DecreseKey(P,deta,H)

给在某个位置p处的元素降低deta,再通过调整达到平衡。

IncreseKey(P,deta,H)

给在某个位置p处的元素增加deta,再通过调整达到平衡。

以下是没有用标记法的大根堆ADT。

头文件

#ifndef _BIAOZHUNERCHADUI_H_
#define _BIAOZHUNERCHADUI_H_
typedef (*OPERATOR)();
typedef enum
{
false = 0,
true,
}bool;
struct Heap;
typedef struct Heap HEAP;
typedef struct Heap* HEAP_T;

HEAP_T Heap_Init(HEAP_T HP,int N);
//为了build——heap
bool Heap_Insert(HEAP_T HP,OPERATOR cmp,void* data);
void*  Heap_DeleteMax(HEAP_T HP,OPERATOR cmp,OPERATOR myfree,void* sxw);
void  Heap_DeleteAll(HEAP_T HP,OPERATOR op,void* sxw);
int Heap_GetSize(HEAP_T HP);
int Heap_GetLen(HEAP_T HP);
void Heap_Printf(HEAP_T HP);
#endif


实现:

#include"biaozhunerchadui.h"
#include<stdlib.h>
/**因为有0*/
//#define leftchild(i) (2*i+1)
//#define father(child) (child-1)/2
struct Heap
{
void** data;
int size;
int len;
};
HEAP_T Heap_Init(HEAP_T HP,int N)
{
if(N<0)
{
printf("堆的大小不合适而exit(0)\n");
exit(0);
}
HP = (HEAP_T)malloc(sizeof(struct Heap));
if(HP==NULL)
return NULL;
HP->size = N;
HP->len = -1;
HP->data = (void**)malloc(HP->size * sizeof(void*));
if(HP->data == NULL)
{
free(HP);
HP = NULL;
return NULL;
}
for(int i = 0; i < HP->size; i++)
{
HP->data[i] = NULL;
}
return HP;
}
bool Heap_Insert(HEAP_T HP,OPERATOR cmp,void* data)//为了build——heap
{
if(data == NULL || HP == NULL)
{
printf("插入失败,数据为空或堆为空\n");
return false;
}
//前面HP->len初始化为-1,所以为了能用上0,先加上去
int index = HP->len+1;
if(HP->data[index] == NULL)
{
HP->data[index] = (void*)malloc(sizeof(void*));
if(HP->data[index]  == NULL)
{
printf("插入时分配空间失败\n");
return false;
}
}
while(index!=0)
{
if(cmp!=NULL)
{
if(cmp(data,HP->data[index/2]))
{
HP->data[index] = HP->data[index/2];
HP->data[index/2] = data;
index = index/2;
}
else
{
HP->data[index] = data;
HP->len++;
return true;
}
}
else
{
if(*(int*)data > *(int*)HP->data[index/2])
{
HP->data[index] = HP->data[index/2];
HP->data[index/2] = data;
index = i
dc6c
ndex/2;
}
else
{
HP->data[index] = data;
HP->len++;
return true;
}
}
}

HP->data[index] = data;
printf("元素%d入堆了\n",*(int*)HP->data[index]);
HP->len++;
return true;

}
void*  Heap_DeleteMax(HEAP_T HP,OPERATOR cmp,OPERATOR myfree,void* sxw)
{
void* res = HP->data[0];
int child = 0, i = 0;
if(HP == NULL)
{
printf("堆是空的,初始化\n");
return NULL;
}
if(HP->len < 0)
{
printf("队列元素已经空了\n");
return NULL;
}
child = 1;
//删除完要从根部开始调整,while内说明至少有儿子
while(child <= HP->len)
{
if(cmp!=NULL && myfree!=NULL)
{
if(cmp(HP->data[child+1],HP->data[child]))
child++;
if(cmp(HP->data[HP->len],HP->data[child]))
{
HP->data[child/2] = HP->data[HP->len];
/**不释放那个元素的单位,留到最后deleteall一起做处理,但是如果不释放,再入列的话,就会去比较了
所以还是要进行释放然后置NULL的
*/
if(myfree!=NULL)
{
myfree(HP->data[HP->len]);
}
HP->data[HP->len] = NULL;
break;
}
else
{
HP->data[child/2] = HP->data[child];
child = child *2;
}
}
else
{
/**两个儿子时找最大的*/
if(HP->data[child+1]&&HP->data[child])
{
if(*(int*)HP->data[child+1] > *(int*)HP->data[child])
child++;
if(*((int*)(HP->data[HP->len])) > *((int*)(HP->data[child])))
{
HP->data[(child-1)/2] = HP->data[HP->len];
HP->data[HP->len] = NULL;
HP->len--;
return res;
}
else
{
HP->data[(child-1)/2] = HP->data[child];
child = child*2+1;
}
}
/**只有一个孩子说明到底了*/
else if(HP->data[child+1] == NULL || HP->data[child] == NULL)
{
HP->data[(child-1)/2] = HP->data[HP->len];
HP->data[HP->len] = NULL;
HP->len--;
return res;
}

}

}
//没有孩子后决定最后一个元素是否要填补
if((child-1) / 2 < HP->len )
{
HP->data[(child)/2] = HP->data[HP->len];
if(myfree!=NULL)
myfree(HP->data[HP->len]);

}
HP->data[HP->len] = NULL;
HP->len--;
return res;
}
void  Heap_DeleteAll(HEAP_T HP,OPERATOR op,void* sxw)
{
if(HP==NULL)
{
printf("堆是无效的\n");
exit(0);
}
int index = Heap_GetLen(HP);
if(op!=NULL)
{
for(;index>=0;index--)
{
op(HP->data[index],sxw);
HP->data[index] = NULL;
}
}
free(HP);
HP = NULL;

}
int Heap_GetSize(HEAP_T HP)
{
if(HP == NULL) exit(0);
else
return HP->size;
}
int Heap_GetLen(HEAP_T HP)
{
if(HP == NULL) exit(0);
else
return HP->len;
}
//用于检验的
void Heap_Printf(HEAP_T HP)
{
int i = 0;
for(i = 0;i <= Heap_GetLen(HP);i++)
{
printf("%d\t",*(int*)HP->data[i]);
}
printf("\n");
}


关于小堆的也很简单,只要理解了deletamax后,其他的increaseKe就很简单。

(二)堆排序

堆排序,其实就是借助于每次从根处得到最大的(小)的值,连续N次就可以排好了,为了不用临时数组,所以不借助ADT的实现,直接从无序的数组(无序堆序列)开始先建一个有序堆,然后开始排序。

第一步:把无序堆建成有序堆

从后面的非叶子节点开始(N/2,可以画出而得到)调整,使得有子节点的根大(或者小)

for(i= N/2;i>=0;i–)调整这么多非叶子结点

第二步:多次排序调整

交换第1各元素和最后一个元素,并减小的对的大小,此时根处的有序性被破坏,所以从根处调整。

实现:

一个很关键的函数,从堆A的某个位置index调整

void PercDown(int *A ,int index,int N)
{

int tmp = A[index];
int child = 2*index+1;
while(child < N)//至少有一个子节点
{
if(child< N && child+1 < N)//2个节点时
{
if(A[child+1] > A[child])
child++;
if(tmp < A[child])
{
A[(child-1)/2] = A[child];
A[child] = tmp;
child = 2*child + 1;
}
else
break;
}
else if(child < N && child+1 >=N)//一个节点
{
if(tmp < A[child])
{
A[(child-1)/2] = A[child];
A[child] = tmp;
break;
}
else
break;

}
else//无节点
break;

}
//为了和之前的堆方法搭配所以就多一步可以直接A[(child-1)/2]=tmp;
if((child-1)/2 < N)
{
A[(child-1)/2] = tmp;
}
}


建堆

void Heap_Bulid(int* A,int N)
{
int index = N/2;
for(;index >= 0 ; index--)
/**从N/2处开始更新每个非叶子节点*/
{
PercDown(A,index,N);
}
}


排序

void Heap_Sort(int *A,int N)
{
Heap_Bulid( A, N);
/**实际排序操作*/
for(int i=N;i>0;i--)
{
Swap(A,0,i-1);
//从根处调整
PercDown(A,0,i-1);
}
}


测试代码可以用“排序算法(一)中的测试代码”。

用数组实现的优先队列不能支持合并操作,所以还有后面会和树,散列表那些一起补记回来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: