树的应用 (堆排序,二叉排序树)

堆排序

堆排序是对选择排序的一种改进方法,在此首先引进堆的概念。堆的定义是满足下列性质的数列:这些数据排列成一棵完全二叉树,并且树中所有结点的值都要大于(或者小于)其左右孩子的值,此树的根结点就是堆顶,必定最大或者最小,对应的就是大顶堆和小顶堆

堆排序的算法思想是:将待排序数列整理成一个大顶堆,然后将堆顶数据与大顶堆的最后一个数据进行交换。接着再将除最后一个数据外的大数据调整为一个大顶堆,再将这个大顶堆的堆顶和最后一个数据进行交换。这样整个数列就从最右端开始逐渐向左有序化。 

该算法的核心包括两个:对初始序列建立为大顶堆交换堆顶数据后调整剩余部分为大顶堆

1.交换堆顶数据剩余部分为大顶堆:   交换大顶堆堆顶数据之后,发现此时根结点的左右子树分别是两个大顶堆,则将根结点的值取出待定,然后比较根结点的左右孩子,选择其中较大的那一个将它和根结点进行比较,如果根结点比它小,则将这个孩子结点的值赋给根结点。如果没有根结点的值大,那么就跳出循环。

2,令 i 从 i = H.length / 2;开始,当 i>0 时,循环调用HeapAdjust(H, i, H.length);函数就能建立一个大顶堆

#include<stdio.h>

//定义记录
typedef struct {
	int key;   //关键字
	char data;     //数据项
}RcdType;

//定义堆,本质上是一个顺序表
const int MAXSIZE = 10;     //顺序表的最大长度
typedef struct {
	RcdType *r;   //r[0]闲置或作为哨兵单元
	int length;       //顺序表的当前长度 顺序表排序的记录空间为r[1...length]
}HeapType;   

void InitList(HeapType &H)
{                                     //定义过数据类型后引用时要先给它分配内存空间
	H.r = new RcdType[MAXSIZE + 1];
	H.length = 0;                     //初始化表长为0
}

//已知 H.r[s..m] 中记录的关键字除 H.r[s].key 之外均满足堆的定义,本函数依据关键字的大小对
// H.r[s]进行调整,使 H.r[s..m] 成为一个大顶堆
void HeapAdjust(HeapType &H, int s, int m)
{
	int j;
	RcdType rc;    
	rc = H.r[s];      //暂存根结点记录
	for (j = 2 * s; j <= m; j *= 2)
	{
		//沿着key较大的孩子结点向下筛选
		if (j < m&&H.r[j].key < H.r[j + 1].key)   //对两个孩子结点进行比较
			j++;    //j 为key较大的孩子记录的下标
		if (rc.key >= H.r[j].key)
			break;   //不需要调整
		H.r[s] = H.r[j];
		s = j;    //把大关键字记录往上调
	} 
	H.r[s] = rc;      //回移筛选下来的记录 
}

//对顺序表H进行堆排序
void HeapSort(HeapType &H)
{
	int i;
	RcdType w;   //用于交换记录的辅助变量
	for (i = H.length / 2; i > 0; i--)   //把 H.r[1..length]建成大顶堆
		HeapAdjust(H, i, H.length);
	w = H.r[1];
	H.r[1] = H.r[H.length];
	H.r[H.length] = w;    //交换堆顶和堆底的记录
	for (i = H.length - 1; i > 1; i--)
	{
		HeapAdjust(H, 1, i);     //从根开始调整,将 H.r[1..i] 重新调整为大顶堆
		w = H.r[1];
		H.r[1] = H.r[i];
		H.r[i] = w;
		  //将堆顶记录和当前的堆底记录相互交换,使已有序的记录堆积到底部
	}
}


void main()
{
	int i;
	HeapType H;
	InitList(H);
	char a[MAXSIZE] = { 'Y','L','V','X','O','L','!','I','U','E' };
	int key[MAXSIZE] = { 3,5,7,2,6,1,10,4,9,8 };
	for (i = 1; i <= MAXSIZE; i++)
	{
		H.r[i].data = a[i - 1];
		H.r[i].key = key[i - 1];
		H.length++;                 //千万不要忘记每输入一个记录,表长要加一
	}
	printf("排序前顺序表中数据为:");
	for (i = 1; i <= MAXSIZE; i++)
	{
		printf("%c ", H.r[i].data);
	}
	printf("\n");
	HeapSort(H);
	printf("排序后顺序表中数据为:");
	for (i = 1; i <= MAXSIZE; i++)
	printf("%c ", H.r[i].data);
}

二叉排序树

二叉排序树或者是一棵空树,或者是一棵具有下列特性的树:1,若左子树不空,则左子树上所有结点的值均小于根结点的值。2,若右子树不空,则右子树上所有结点的值均大于或者等于根结点的值。3,它的左右子树也分别是二叉排序树。对二叉排序树进行中序遍历可以得到一个有序序列。

对于一个无序序列,将其建成二叉排序树的过程就是从一个空树开始不断插入新结点的过程。以序列{49,38,65,76,49,13,27,52}为例,首先插入49,是个空树所以新建结点为树的根结点,然后将38插入,因为38 < 49,所以将38插入到49的左边,然后是65,因为65>49,所以将65插入到49的右边,之后是76,因为 76>49 ,且76>65,所以将76插入到65的左边...以此类推。总结一句话,就是先建立根结点,然后依次从无序序列中取数,从根结点开始向下比较,直到插入到合适的位置

本程序值得注意的点是:1,二叉排序树中,每个结点中的data值为一个记录。2,利用二叉排序树的目的是对一个无序序列进行排序,因此还是需要建立顺序表,将多个记录放入顺序表中。3,中序遍历时,对树结点进行的操作函数 visit(), 该函数的形参是 RcdType e; 即将一个记录传进 visit() 函数,然后读取时,输出 e.data;

通常取二叉链表作为二叉排序树的存储结构。利用二叉排序树进行排序的代码如下:

#include<stdio.h>

//定义记录
typedef struct {
	int key;   //关键字
	char data;     //数据项
}RcdType;

//定义顺序表
const int MAXSIZE = 10;     //顺序表的最大长度
typedef struct {
	RcdType *r;   //r[0]闲置或作为哨兵单元
	int length;       //顺序表的当前长度 顺序表排序的记录空间为r[1...length]
}SqTable;

//定义树的结构类型
typedef struct BiTNode {
	RcdType data;
	struct BiTNode *lchild, *rchild;    //左右孩子指针  
}BiTNode, *BiTree;

//建立顺序表
void InitList(SqTable &L)
{                                     //定义过数据类型后引用时要先给它分配内存空间
	L.r = new RcdType[MAXSIZE + 1];
	L.length = 0;                     //初始化表长为0
}

//在以 T 为根指针的二叉排序树中插入记录 e
void Insert_BST(BiTree &T, RcdType e)
{
	BiTree s = new BiTNode;   //生成新结点
	BiTNode *p, *f;    //定义树结点指针,用于后续插入
	f = new BiTNode;
	s->data = e;
	s->lchild = NULL;
	s->rchild = NULL;      //新插入的结点必为叶子结点
	if (!T)
		T = s;     //插入的结点为根结点
	else
	{
		p = T;
		while (p)     //查找插入位置
		{
			if (e.key < p->data.key)
			{
				f = p;
				p = p->lchild;      //应插入在左子树
			}
			else
			{
				f = p;
				p = p->rchild;     //应插入在右子树
			}
		}
		if (e.key < f->data.key)
			f->lchild = s;     //插入为f所指结点的左子树根
		else
			f->rchild = s;    //插入为f所指结点的右子树根
	}
}

void visit(RcdType c)
{
	printf("%c ", c.data);
}
//中序遍历二叉树
void InOrder(BiTree T)
{
	if (T)     //通过判断结点是否为空来决定是否继续进行操作
	{
		InOrder(T->lchild);
		visit(T->data);
		InOrder(T->rchild);
	}
}

//利用二叉排序树对顺序表L进行排序
void BSTSort(SqTable &L)
{
	BiTree T = NULL;
	int i;
	for (i = 1; i <= L.length; i++)
		Insert_BST(T, L.r[i]);    //按待排序的顺序表L构造二叉排序树
	InOrder(T);
}

void main()
{
	int i;
	SqTable L;
	InitList(L);
	char a[MAXSIZE] = { 'Y','L','V','X','O','L','!','I','U','E' };
	int key[MAXSIZE] = { 3,5,7,2,6,1,10,4,9,8 };
	for (i = 1; i <= MAXSIZE; i++)
	{
		L.r[i].data = a[i - 1];
		L.r[i].key = key[i - 1];
		L.length++;                 //千万不要忘记每输入一个记录,表长要加一
	}
	printf("排序前顺序表中数据为:");
	for (i = 1; i <= MAXSIZE; i++)
	{
		printf("%c ", L.r[i].data);
	}
	printf("\n");
	printf("排序后顺序表中数据为:");
	BSTSort(L);
}

本笔记所依据的教材为严薇敏版的《数据结构及应用算法教程》

所有代码在Visual Studio 2017上均可正常运行

如有错误欢迎指出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值