c学习笔记 第17章 高级数据表示 20210414

链表和数组

数组:
优点:

  1. c直接支持
  2. 提供随机访问、
    缺点:
  3. 在编译时确定大小
  4. 插入和删除元素很费时
    链表:
    优点:
  5. 运行时确定大小
  6. 快速插入和删除元素
    缺点:
  7. 不能随机访问
  8. 用户必须提供编程支持
    数组可以直接使用数组下标直接访问数组中的任意元素, 这叫做随机访问(random access), 对链表而言, 必须从链表首节点开始逐个访问, 这称之为顺序访问(sequential access).
    若要查找链表中的特定项, 一种算法是顺序查找(ssequential search)(可以使用并发编程, 同时查找列表中的不同部分)。
    对于列表, 可以先进行排序, 来改进顺序查找。
    对于一个排序的列表, 用二分查找(binary search)更好。
    项数越多, 二分查找的优势越大。但链表不能使用二分查找。

二叉查找树

二叉树每个节点包含两个子节点——左节点和右节点。
左节点在父节点之前; 右节点在父节点之后。
该树的顶部称之为根(root).
二叉查找树的·每个节点是其后代节点的根, 该节点与其后代节点构成了一个子树(subtree)

二叉树ADT

类型名: 二叉查找树
类型属性: 二叉树要么是空节点的集合(空树), 要么是有一个根节点的节点集合
每个节点都有两个子树, 叫做左子树和右子树
每个子树本身也是一个二叉树, 也有可能是空树
二叉查找树是一个有序的二叉树, 每个节点包含一个项
左子树的所有项都在根节点项的前面, 右子树的所有项都在根节点的后面

类型操作:
初始化树为空
确认树是否已满
确定树中的项数
在书中添加一个项
在树中删除一个项
在树中查找一个项
在树中访问一个项
清空树

二叉查找树接口

现在要开发维护一个Merfville宠物俱乐部花名册

/*tree.h -- 二叉查找树*/
/*树中不能有重复的项*/
#pragma once
#include <stdbool.h>

//根据具体情况定义Item
#define SLEN 20
typedef struct item
{
	char petname[SLEN];
	char petkind[SLEN];
}Item;
#define MAXITEMS 10
typedef struct trnode
{
	Item item;
	struct trnode* left;
	struct trnode* right;
}Trnode;

typedef struct tree
{
	Trnode* root;		//指向根节点的指针
	int size;			//树的项数
}Tree;

// 函数原型
// 操作:	把树初始为空
//前提条件; ptree指向一个树
//后置条件: 树被初始化为空
void InitializeTree(Tree* ptree);

/* 操作: 确定树是否为空*/
/* 前提条件: ptree指向一个树*/
// 后置条件:如果树为空, 返回true, 否则返回false
bool TreeIsEmpty(const Tree* ptree);

// 操作: 确定树是否已满
// 前提条件: ptree指向一个树
//后置条件: 如果树已满, 返回true, 否则返回false
bool TreeIsFull(const Tree* ptree);

// 操作: 确定树的项数
// 前提条件: ptree指向一个树
// 后置条件: 返回树的项数
int TreeItemCount(const Tree* ptree);

//操作: 在树中添加一个项
//前提条件: pi是待添加项的地址, ptree指向一个树
// 后置条件: 如果可以添加, 该函数在树中添加一个项, 并返回true, 否则返回false
bool AddItem(const Item* pi, Tree* ptree);

//操作: 在树中查找一个项
//前提条件: pi指向一个项, ptree指向一个树
// 后置条件: 如果在树中找到指定项, 返回true, 否则返回false
bool Intree(const Item* pi, const Tree* ptree);

// 操作: 从树中删除一个项
//前提条件: pi是删除项的地址, ptree指向一个树
// 后置条件: 从树中成功删除一个项, 返回true, 否则返回false
bool DeleteItem(const Item* pi, Tree* ptree);

//操作: 把函数应用于树中的每一项
//前提条件: ptree指向一个树, pfun指向一个函数, 该函数接受一个Item类型的参数, 并无返回值
// 后置条件: pfun指向的函数为树中的每一项执行一次
void Traverse(const Tree* ptree, void (*pfun)(Item item));


//操作:  删除树中的所有内容
//前提条件: ptree指向一个已初始化的树
//后置条件: 数为空
void deleteAll(Tree* ptree);

二叉树的实现

1.添加项

bool AddItem(const Item* pi, Tree* ptree)
{
	Trnode* new_node;
	if (TreeIsFull(ptree))
	{
		fprintf(stderr, "Tree is full \n");
		return false;
	}
	if (SeekItem(pi, ptree).child != NULL)
	{
		fprintf(stderr, "Attempted to add duplicate item\n");
		return false;
	}
	new_node = MakeNode(pi);
	if (new_node == NULL)
	{
		fprintf(stderr, "Could not create node\n");
		return false;
	}
	ptree->size++;
	if (ptree->root == NULL)   // 情况: 树为空
		ptree->root = new_node;
	else
		AddNode(new_node, ptree->root);
	return true;
}

MakeNode 负责动态内存分配和初始化结点, 返回值是新节点的指针

static Trnode* MakeNode(const Item* pi)
{
	Trnode* new_node;
	new_node = (Trnode*)malloc(sizeof(Trnode));
	if (new_node != NULL)
	{
		new_node->item = *pi;
		new_node->left = NULL;
		new_node->right = NULL;
	}
	return new_node; 
}

AddNode()是二叉查找树中最麻烦的第2个函数, 其需确定新结点的位置, 随后添加新节点

static void AddNode(Trnode* new_node, Trnode* root)
{
	if (ToLeft(&new_node->item, &root->item))
	{
		if (root->left == NULL)
			root->left = new_node;
		else
			Addnode(new_node, root->left);
	}
	else if (ToRight(&new_node, &root->item))
	{
		if (root->right == NULL)
			root->right = new_node;
		else
			AddNode(new_node, root->right);
	}
	else					//不应含有重复项
	{
		fprintf(stderr, "location error in AddNode()\n");
		exit(1);
	}
}

该程序来向树添加项

static bool ToLeft(const Item* i1, const Item* i2)
{
	int comp1;
	if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
		return true;
	else if (comp1 == 0 && strcmp(i1->petkind, i2->petkind) < 0)
		return true;
	else
		return false;
}

ToRight() 函数于ToLeft思想相同
2. 查找项

使用SeekItem()函数进行查找, 因为DeleteItem()函数需要知道待删除项的父节点, 因此设计SeekItem()函数时返回的结构应该包含两个指针, 一个指向包含项的节点(未找到返回NULL), 一个指针指向父节点(如果该节点为根节点, 即没有父节点, 即为NULL), 定义如下;

typedef struct pair{
	Trnode * parent;
	Trnode * child;
}Pair;
static Pair SeekItem(const Item* pi, Tree* ptree)
{
	Pair look;
	look.parent = NULL;
	look.child = ptree->root;
	if (look.child == NULL)
		return look;
	while (look.child != NULL)
	{
		if (ToLeft(pi, &(look.child->item)))
		{
			look.parent = look.child;
			look.child = look.child->left;
		}
		else if (ToRight(pi, &(look.child->item)))
		{
			look.parent = look.child;
			look.child = look.child->right;
		}
		else					// 前两种情况不满足则必相等
			break;
	}
	return look;
}

因为其返回一个结构, 那么就可以和成员运算符一起使用

if (SeekItem(pi, ptree).child != NULL)

编写InTree()公共接口就变得相当简单

bool InTree(const Item * pi, const Tree * ptree)
{
	return (SeekItem(pi, ptree).child == NULL ? false : true);
}
  1. 考虑删除项

删除项是最复杂的任务
没有子节点的节点称为叶节点(leaf).

删除一个节点

须注意两点:

  1. 该程序必须标识删除节点的父节点
  2. 为了修改指针, 代码必须把该指针的地址传递给执行删除任务的函数

先对第二点进行分析, 假设有合适的地址可用, 函数如下:

// ptr是指向目标节点的指针成员的地址
static void DeleteNode(Trnode** ptr)
{
	Trnode* temp;
	if ((*ptr)->left == NULL)
	{
		temp = *ptr;
		*ptr = (*ptr)->right;
		free(temp);
	}
	else if ((*ptr)->right == NULL)
	{
		temp = *ptr;
		*ptr = (*ptr)->left;
		free(temp);
	}
	else					//被删除节点有两个子节点
	{
		//找到重新连接右子树的位置
		for (temp = (*ptr)->left; temp->right != NULL; temp = temp->right)
			continue;
		temp->right = (*ptr)->right;
		temp = *ptr;
		*ptr = (*ptr)->left;
		free(temp);
	}
}

删除一个项

接下来要做的是把一个节点于特定项连接起来

bool DeleteItem(const Item* pi, Tree* ptree)
{
	Pair book;
	look = SeekItem(pi, ptree);
	if (look.child == NULL)
		return false;
	if (look.parent == NULL)
		DeleteNode(&ptree->root);
	else if (look.parent->left == look.child)
		DeleteNode(&look.parent->left);
	else
		DeleteNode(&look.parent->right);
	ptree->size--;
	return true;
}
  1. 遍历树

树的分支特性非常适合使用分而制之的递归, 需要完成如下任务:
处理节点中的项
处理左子树 (递归调用)
处理右节点(递归调用)
可以将遍历分成两个函数来完成: Traverse()和InOrder

void Traverse(const Tree* ptree, void(*pfun)(Item item))
{
	if (ptree != NULL)
		InOrder(ptree->root, pfun);
}

static void InOrder(const Trnode* root, void (*pfun)(Item item))
{
	if (root != NULL)
	{
		InOrder(root->left, pfun);
		(*pfun)(root->item);
		InOrder(root->right, pfun);
	}
}
  1. 清空树

清空树基本上和遍历树相同, 除了需要free()释放内存, 还需要重置Tree类型结构的成员。
DeleteAll()负责处理Tree类型结构, DeleteAllNode() 负责释放内存

void DeleteAll(Tree* ptree)
{
	if (ptree != NULL)
		DeleteAllNode(ptree->root);
	ptree->root = NULL;
	ptree->size = 0;
}
static void DeleteAllNodes(Trnode* root)
{
	Trnode* pright;
	if (root != NULL)
	{
		pright = root->right;
		DeleteAllNodes(root->left);
		free(root);
		DeleteAllNodes(pright);
	}
}
  1. 完整的包
/* tree.c ---树的支持函数 */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "tree.h"

//局部数据类型
typedef struct pair {
	Trnode* parent;
	Trnode* child;
}Pair;

//局部函数原型
static Trnode* MakeNode(const Item* pi);
static bool ToLeft(const Item* i1, const Item* i2);
static bool ToRight(const Item* i1, const Item* i2);
static void AddNode(Trnode* new_node, Trnode* root);
static void InOrder(const Trnode* root, void (*pfun)(Item item));
static Pair SeekItem(const Item* pi, Tree* ptree);
static void DeleteNode(Trnode** ptr);
static void DeleteAllNodes(Trnode* root);

//函数定义
void IntializeTree(Tree* ptree)
{
	ptree->root = NULL;
	ptree->size = 0;
}

bool TreeIsEmpty(const Tree* ptree)
{
	if (ptree->root == NULL)
		return true;
	else
		return false;
}

bool TreeIsFull(const Tree* ptree)
{
	if (ptree->size == MAXITEMS)
		return true;
	else
		return false;
}

int TreeItemCount(const Tree* ptree)
{
	return ptree->size;
}

bool AddItem(const Item* pi, Tree* ptree)
{
	Trnode* new_node;
	if (TreeIsFull(ptree))
	{
		fprintf(stderr, "Tree is full \n");
		return false;
	}
	if (SeekItem(pi, ptree).child != NULL)
	{
		fprintf(stderr, "Attempted to add duplicate item\n");
		return false;
	}
	new_node = MakeNode(pi);
	if (new_node == NULL)
	{
		fprintf(stderr, "Could not create node\n");
		return false;
	}
	ptree->size++;
	if (ptree->root == NULL)   // 情况: 树为空
		ptree->root = new_node;
	else
		AddNode(new_node, ptree->root);
	return true;
}

bool InTree(const Item* pi, const Tree* ptree)
{
	return (SeekItem(pi, ptree).child == NULL ? false : true);
}

bool DeleteItem(const Item* pi, Tree* ptree)
{
	Pair look;
	look = SeekItem(pi, ptree);
	if (look.child == NULL)
		return false;
	if (look.parent == NULL)
		DeleteNode(&ptree->root);
	else if (look.parent->left == look.child)
		DeleteNode(&look.parent->left);
	else
		DeleteNode(&look.parent->right);
	ptree->size--;
	return true;
}

void Traverse(const Tree* ptree, void(*pfun)(Item item))
{
	if (ptree != NULL)
		InOrder(ptree->root, pfun);
}


void DeleteAll(Tree* ptree)
{
	if (ptree != NULL)
		DeleteAllNode(ptree->root);
	ptree->root = NULL;
	ptree->size = 0;
}


//局部函数
static Trnode* MakeNode(const Item* pi)
{
	Trnode* new_node;
	new_node = (Trnode*)malloc(sizeof(Trnode));
	if (new_node != NULL)
	{
		new_node->item = *pi;
		new_node->left = NULL;
		new_node->right = NULL;
	}
	return new_node;
}

static bool ToLeft(const Item* i1, const Item* i2)
{
	int comp1;
	if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
		return true;
	else if (comp1 == 0 && strcmp(i1->petkind, i2->petkind) < 0)
		return true;
	else
		return false;
}

static bool ToPight(const Item * i1, const Item * i2)
{
	int compl;
	if(compl = strcmp(i1->petname, i2->petname) > 0)
		return true;
	else if(compl == 0 && strcmp(i1->petkind, i2->petkind) > 0)
		return true;
	else
		return false;
}

static void AddNode(Trnode* new_node, Trnode* root)
{
	if (ToLeft(&new_node->item, &root->item))
	{
		if (root->left == NULL)
			root->left = new_node;
		else
			Addnode(new_node, root->left);
	}
	else if (ToRight(&new_node, &root->item))
	{
		if (root->right == NULL)
			root->right = new_node;
		else
			AddNode(new_node, root->right);
	}
	else					//不应含有重复项
	{
		fprintf(stderr, "location error in AddNode()\n");
		exit(1);
	}
}

static void InOrder(const Trnode* root, void (*pfun)(Item item))
{
	if (root != NULL)
	{
		InOrder(root->left, pfun);
		(*pfun)(root->item);
		InOrder(root->right, pfun);
	}
}


static Pair SeekItem(const Item* pi, Tree* ptree)
{
	Pair look;
	look.parent = NULL;
	look.child = ptree->root;
	if (look.child == NULL)
		return look;
	while (look.child != NULL)
	{
		if (ToLeft(pi, &(look.child->item)))
		{
			look.parent = look.child;
			look.child = look.child->left;
		}
		else if (ToRight(pi, &(look.child->item)))
		{
			look.parent = look.child;
			look.child = look.child->right;
		}
		else					// 前两种情况不满足则必相等
			break;
	}
	return look;
}

// ptr是指向目标节点的指针成员的地址
static void DeleteNode(Trnode** ptr)
{
	Trnode* temp;
	if ((*ptr)->left == NULL)
	{
		temp = *ptr;
		*ptr = (*ptr)->right;
		free(temp);
	}
	else if ((*ptr)->right == NULL)
	{
		temp = *ptr;
		*ptr = (*ptr)->left;
		free(temp);
	}
	else					//被删除节点有两个子节点
	{
		//找到重新连接右子树的位置
		for (temp = (*ptr)->left; temp->right != NULL; temp = temp->right)
			continue;
		temp->right = (*ptr)->right;
		temp = *ptr;
		*ptr = (*ptr)->left;
		free(temp);
	}
}

static void DeleteAllNodes(Trnode* root)
{
	Trnode* pright;
	if (root != NULL)
	{
		pright = root->right;
		DeleteAllNodes(root->left);
		free(root);
		DeleteAllNodes(pright);
	}
}

使用二叉树

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "tree.h"

char menu(void);
void addpet(Tree* pt);
void droppet(Tree* pt);
void showpets(const Tree* pt);
void findpet(const Tree* pt);
void printitem(Item item);
void uppercase(char* str);

int main(void)
{
	Tree pets;
	char choice;

	InitializeTree(&pets);
	while ((choice = menu()) != 'q')
	{
		switch (choice)
		{
		case 'a': addpet(&pets);
			break;
		case 'l': showpets(&pets);
			break;
		case 'f': findpet(&pets);
			break;
		case 'n': printf("%d pets in club\n",
			TreeItemCount(&pets));
			break;
		case 'd': droppet(&pets);
			break;
		default: puts("Switching error");
		}
	}
	DeleteAll(&pets);
	puts("Bye.");
	return 0;
}

char menu(void)
{
	int ch;
	puts("Nerfville Pet Club Membership Program\n\
Enter the letter corresponding to your choice:\n\
a) add a pet			l) show list of pets\n\
n) number of pets		f) find pets\n\
d) delete a pet		q) quit");
	while ((ch = getchar()) != EOF)
	{
		while ((ch = getchar()) != '\n')
			continue;
		ch = tolower(ch);
		if (strchr("alrfndq", ch) == NULL)
			puts("Please enter an a, l, f, n, d, or q:");
		else
			break;
	}
	if (ch == EOF)
		ch = 'q';
	return ch;
}

void addpet(Tree* pt)
{
	Item temp;

	if (TreeIsFull(pt))
		puts("No room in the club!");
	else
	{
		puts("Please enter name of pet:");
		s_gets(temp.petname, SLEN);
		puts("Please enter pet kind:");
		s_gets(temp.petkind, SLEN);
		uppercase(temp.petname);
		uppercase(temp.petkind);
		AddItem(&temp, pt);
	}
}

void showpets(const Tree* pt)
{
	if (TreeIsEmpty(pt))
		puts("No entries!");
	else
		Traverse(pt, printitem);
}

void printitem(Item item)
{
	printf("Pet; %-19s	Kind: %-19s\n", item.petname, item.petkind);
}

void findpet(const Tree* pt)
{
	Item temp;

	if (TreeIsEmpty(pt))
	{
		puts("No entries!");
		return;
	}
	puts("Please enter name of pet you wish to find:");
	s_gets(temp.petname, SLEN);
	puts("Please enter pet kind:");
	s_gets(temp.petkind, SLEN);
	uppercase(temp.petname);
	uppercase(temp.petkind);
	printf("%s the %s ", temp.petname, temp.petkind);
	if (InTree(&temp, pt))
		printf("is a member.\n");
	else
		printf("is not a memeber.\n");
}

void droppet(Tree* pt)
{
	Item temp;

	if (TreeIsEmpty(pt))
	{
		puts("No entries!");
		return;
	}
	puts("Please enter name of pet you wish to delete:");
	s_gets(temp.petname, SLEN);
	puts("Please enter pet kind:");
	s_gets(temp.petkind, SLEN);
	uppercase(temp.petname);
	uppercase(temp.petkind);
	printf("%s the %s", temp.petname, temp.petkind);
	if (DeleteItem(&temp, pt))
		printf("is dropped from the club.\n");
	else
		printf("is not a member.\n");
}



void uppercase(char* str)
{
	while (*str)
	{
		*str = toupper(*str);
		str++;
	}
}

树的思想

二叉树只有在满员时效率(或平衡)最高, 查找不平衡的树并不比查找链表快。
其中由两位俄国科学家发明的算法解决了该问题, 使用该算法创建的树称为AVL树, 然而因为要重构, 创建一个平衡的树花费的时间更多, 但这样的树确保了最大化搜索效率。

其他说明

基于windows的编译器支持windows图形接口。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值