堆的插入、删除和建立操作,堆排序

1.       

堆:n个元素序列{k1,k2,...,ki,...,kn},当且仅当满足下列关系时称之为堆:

(ki <= k2i,ki <= k2i+1)

或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4,...,n/2)

若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。

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

2.        堆的插入

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

代码:

/*
 * 堆插入算法。(小顶堆)
 * 先将num插入堆尾,易知从新数据的父结点到根结点是一个有序的序列,
 * 将num插入到该有序序列当中,该过程为直接插入排序。
 * 未插入前数据长度为n。
 */
int HeapInsert(int *heap, int n, int num)
{
	int i, j;

	heap[n] = num;//num插入堆尾
	i = n;
	j = (n - 1) / 2;//j指向i的父结点

	//注意不要漏掉i!=0的条件。因为必须保证i有父结点j。j>=0并不能保证i!=0。
	//如果没有此条件,当i=0时,j=0,若heap[0]>num,程序就会陷入死循环。
	while (j >= 0 && i != 0)
	{
		if (heap[j] <= num)
			break;
		heap[i] = heap[j];
		i = j;
		j = (i - 1) / 2;
	}
	heap[i] = num;

	return 0;
}

3.        堆的删除

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

代码:

/*
 * 堆删除算法。(删除堆顶元素)
 * n表示未删除前堆中数据的总数。
 */
int HeapDelete(int *heap, int n)
{
	//使用堆尾元素直接覆盖堆顶元素。
	heap[0] = heap[n - 1];
	//从堆顶到堆尾(此时堆中只有n-1个元素)进行堆调整。
	HeapAdjust(heap, 0, n - 1);
	return 0;
}

/*
 * 堆调整算法。(小顶堆)
 * 已知heap[top]结点的左右子树均为堆,调整堆中元素,使以heap[top]为根结点的树为堆。
 * n为堆中元素总数。
 */
int HeapAdjust(int *heap, int top, int n)
{
	int j = 2 * top + 1;	//左孩子结点
	int temp = heap[top];

	while (j < n)
	{
		if (j + 1 < n&&heap[j + 1] < heap[j])
			j++;	//使j指向左右孩子中较小的结点。
		if (heap[j] >= temp)
			break;
		heap[top] = heap[j];
		top = j;
		j = 2 * top + 1;
	}
	heap[top] = temp;
	return 0;
}

4.        堆的建立

从无序序列建堆的过程就是一个反复调整的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第(n-2)/2个结点,由此调整过程只需从该结点开始,直到堆顶元素。

代码:

/*
 * 建堆算法。
 * 将无序数组array[]转换为堆。
 */
int CreatHeap(int *array, int n)
{
	int i;
	//最后一个结点的编号为n-1,该结点的父节点(n-2)/2为最后一个非终端结点。
	//从结点(n-2)/2到根结点,依次进行堆调整。
	for (i = (n - 2) / 2; i >= 0; i--)
	{
		HeapAdjust(array, i, n);
	}
	return 0;
}

5.        堆排序

若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重建一个堆,则得到n个元素中的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。

输出堆顶元素之后,以堆中最后一个元素替代之,此时根结点的左右子树均为堆,则仅需进行一次从上到下的调整即可重建一个堆。

代码:

/*
 * 堆排序算法。
 * 形参heap为大顶堆时,实现的是由小到大;
 * 形参heap为小顶堆时,实现的是由大到小;
 */
int HeapSort(int *heap, int n)
{
	int i;
	int temp;

	for (i = n - 1; i > 0; i--)
	{
		//将堆顶元素和未排序的最后一个元素交换。
		temp = heap[0];
		heap[0] = heap[i];
		heap[i] = temp;
		//交换之后进行堆调整
		HeapAdjust(heap, 0, i);
	}
	return 0;
}

6. 测试代码

/*
 * 堆的建立、插入、删除和堆排序算法
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define TOTAL 20

int HeapInsert(int *heap, int n, int num);
int HeapDelete(int *heap, int n);
int HeapAdjust(int *heap, int top, int n);
int HeapSort(int *heap, int n);
int CreatHeap(int *array, int n);

int main()
{
	int heap[TOTAL];
	int num;
	int i;

	//先输入一半的数据,对输入的数组建堆。
	printf("输入Total/2个数据:\n");
	for (i = 0; i < TOTAL / 2; i++)
		scanf("%d", &heap[i]);

	CreatHeap(heap, TOTAL / 2);

	//检验是否建堆成功。
	printf("建堆后:\n");
	for (i = 0; i < TOTAL / 2; i++)
		printf("%-3d", heap[i]);
	putchar(‘\n‘);

	//向已建好的堆中插入数据,并重组为堆。
	printf("继续输入Total/4个数据:\n");
	for (i = TOTAL / 2; i < TOTAL / 2 + TOTAL / 4; i++)
	{
		scanf("%d", &num);
		HeapInsert(heap, i, num);
	}

	//检验是否插入成功。
	printf("重组为堆之后:\n");
	for (i = 0; i < TOTAL / 2 + TOTAL / 4; i++)
		printf("%-3d", heap[i]);
	putchar(‘\n‘);

	//删除堆顶元素Total/4次。
	printf("删除Total/4个数据:\n");
	for (i = 0; i < TOTAL / 4; i++)
		HeapDelete(heap, TOTAL / 2 + TOTAL / 4 - i);

	//检验是否删除成功。
	for (i = 0; i < TOTAL / 2; i++)
		printf("%-3d", heap[i]);
	putchar(‘\n‘);

	//向堆中插满数据,进行堆排序。
	printf("继续输入Total/2个数据:\n");
	for (i = TOTAL / 2; i < TOTAL; i++)
	{
		scanf("%d", &num);
		HeapInsert(heap, i, num);
	}

	HeapSort(heap, TOTAL);
	printf("排序后:\n");
	for (i = 0; i < TOTAL; i++)
		printf("%-3d ", heap[i]);
	putchar(‘\n‘);
	return 0;
}

/*
 * 堆插入算法。(小顶堆)
 * 先将num插入堆尾,易知从新数据的父结点到根结点是一个有序的序列,
 * 将num插入到该有序序列当中,该过程为直接插入排序。
 * 未插入前数据长度为n。
 */
int HeapInsert(int *heap, int n, int num)
{
	int i, j;

	heap[n] = num;//num插入堆尾
	i = n;
	j = (n - 1) / 2;//j指向i的父结点

	//注意不要漏掉i!=0的条件。因为必须保证i有父结点j。j>=0并不能保证i!=0。
	//如果没有此条件,当i=0时,j=0,若heap[0]>num,程序就会陷入死循环。
	while (j >= 0 && i != 0)
	{
		if (heap[j] <= num)
			break;
		heap[i] = heap[j];
		i = j;
		j = (i - 1) / 2;
	}
	heap[i] = num;

	return 0;
}

/*
 * 堆删除算法。(删除堆顶元素)
 * n表示未删除前堆中数据的总数。
 */
int HeapDelete(int *heap, int n)
{
	//使用堆尾元素直接覆盖堆顶元素。
	heap[0] = heap[n - 1];
	//从堆顶到堆尾(此时堆中只有n-1个元素)进行堆调整。
	HeapAdjust(heap, 0, n - 1);
	return 0;
}

/*
 * 堆调整算法。(小顶堆)
 * 已知heap[top]结点的左右子树均为堆,调整堆中元素,使以heap[top]为根结点的树为堆。
 * n为堆中元素总数。
 */
int HeapAdjust(int *heap, int top, int n)
{
	int j = 2 * top + 1;	//左孩子结点
	int temp = heap[top];

	while (j < n)
	{
		if (j + 1 < n&&heap[j + 1] < heap[j])
			j++;	//使j指向左右孩子中较小的结点。
		if (heap[j] >= temp)
			break;
		heap[top] = heap[j];
		top = j;
		j = 2 * top + 1;
	}
	heap[top] = temp;
	return 0;
}

/*
 * 堆排序算法。
 * 形参heap为大顶堆时,实现的是由小到大;
 * 形参heap为小顶堆时,实现的是由大到小;
 */
int HeapSort(int *heap, int n)
{
	int i;
	int temp;

	for (i = n - 1; i > 0; i--)
	{
		//将堆顶元素和未排序的最后一个元素交换。
		temp = heap[0];
		heap[0] = heap[i];
		heap[i] = temp;
		//交换之后进行堆调整
		HeapAdjust(heap, 0, i);
	}
	return 0;
}

/*
 * 建堆算法。
 * 将无序数组array[]转换为堆。
 */
int CreatHeap(int *array, int n)
{
	int i;
	//最后一个结点的编号为n-1,该结点的父节点(n-2)/2为最后一个非终端结点。
	//从结点(n-2)/2到根结点,依次进行堆调整。
	for (i = (n - 2) / 2; i >= 0; i--)
	{
		HeapAdjust(array, i, n);
	}
	return 0;
}

7. 测试结果

参考:白话经典算法系列之七 堆与堆排序

堆的插入、删除和建立操作,堆排序

时间: 2024-10-19 14:17:45

堆的插入、删除和建立操作,堆排序的相关文章

(透彻理解)最精锐代码::堆的三种基本操作新建-插入-删除

1.删除堆顶的最大元素 (以某个元素为根结点向下调整为堆) 比如:删除这个堆的20 第一步:是20 和 3 交换 第二步:以3为根节点开始调整为堆(3和17交换) 再以3为根节点调整为堆(16和3交换)这样又形成了一个堆 2.往一个完整的堆中插入元素(唯一一个自底向上调整的例子) 目标:往堆的尾部插入元素21,这样就破坏了堆,然后要调整为堆 算法1:21和8调整   这样20的左右是一个堆了 算法2:将21和20进行调整,这样堆就形成了 3.新建一个堆(以某个元素为根结点向下调整为堆) 算法1:

JAVA带表头的双向链表插入,删除,查找操作

1.建立双向链表的结点Node2.建立类Linkedlist,成员head是指向表头的引用,在linkedlist中定义链表的操作方法.ps:插入删除操作要判断pos,如果在表尾,要特判防止出现空引用. public class Node {//建立双向链表的结点 public Node left; public Node right; public int data; public Node(){ this.left=null; this.right=null; this.data=0; }

JavaScript之jQuery-3 jQuery操作DOM(查询、样式操作、遍历节点、创建插入删除、替换、复制)

一.jQuery操作DOM - 查询 html操作 - html(): 读取或修改节点的HTML内容,类似于JavaScript中的innerHTML属性 文本操作 - text(): 读取或修改节点的文本内容,类似于JavaScript中的textContent属性 值操作 - val(): 读取或修改节点的value属性值,类似于 JavaScript 中的value值 属性操作 - attr(): 读取或者修改节点的属性 - removeAttr(): 删除节点的属性 二.jQuery操作

红黑树、插入删除操作

二叉排序树 一棵自平衡的二叉排序树(二叉搜索树) 生成二叉排序树的过程是非常容易失衡的,最坏的情况就是一边倒(只有右/左子树),这样会导致二叉树的检索效率大大降低(O(n)). 为了维持二叉树的平衡,有各种的算法,如:AVL,SBT,伸展树,TREAP ,红黑树等等. 红黑树 红黑树需要满足5条性质: - 节点非红即黑 - 根节点是黑色 - 所有NULL结点称为叶子节点,且认为颜色为黑 - 所有红节点的子节点都为黑色,一条路径上不能出现相邻的两个红色结点 - 从任一节点到其叶子节点的所有路径上都

顺序表的查找、插入、删除、合并操作及其优缺点

顺序表的查找.插入.删除.合并操作,用c++实现相关代码: #include<iostream> using namespace std; //定义线性表顺序存储结构 #define MAXSIZE 100  //线性表最大长度 typedef struct { //线性表占用数组空间 int elem[MAXSIZE]; //记录线性表中最后一个元素在数组elem[]中的位置(下标值),空表置为-1 int last; }SeqList; //顺序表的按内容查找运算 //在顺序表L中查找与e

jQuery---jq操作标签文本(html(),text()),jq操作文档标签(插入,删除,修改),克隆,,jq操作属性,jq操作class属性,jq操作表单value,jq操作css,jq操作盒子(重要),jq操作滚动条

jQuery---jq操作标签文本(html(),text()),jq操作文档标签(插入,删除,修改),克隆,,jq操作属性,jq操作class属性,jq操作表单value,jq操作css,jq操作盒子(重要),jq操作滚动条 一丶jQ操作标签内文本 html() 标签元素中的内容 /** 替换的内容可以使一个js对象,jq对象,文本 **/ /* 获取值:获取选中标签元素的所有内容 ,包括标签*/ $('ul').html() " <li>1</li> <li&g

单链表 初始化 创建 头插法 尾插法 插入 删除 查找 合并 长度

#include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR -1 #define TRUE 1 #define FALSE -1 #define NULL 0 #define OVERFLOW -2 #define ElemType int #define Status int typedef int ElemType typedef int Status #define LEN sizeof(LNode) #

[LeetCode] Insert Delete GetRandom O(1) 常数时间内插入删除和获得随机数

Design a data structure that supports all following operations in average O(1) time. insert(val): Inserts an item val to the set if not already present. remove(val): Removes an item val from the set if present. getRandom: Returns a random element fro

父兄子节点的获取及子节点的添加、删除、克隆操作

第一.父兄子节点的获取 1.父节点的获取 某节点的parentNode属性值即为该节点的父节点.示例: <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <body> <div id="father"> <div id="son1"></div> </