链表和数组
数组:
优点:
- c直接支持
- 提供随机访问、
缺点: - 在编译时确定大小
- 插入和删除元素很费时
链表:
优点: - 运行时确定大小
- 快速插入和删除元素
缺点: - 不能随机访问
- 用户必须提供编程支持
数组可以直接使用数组下标直接访问数组中的任意元素, 这叫做随机访问(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);
}
- 考虑删除项
删除项是最复杂的任务
没有子节点的节点称为叶节点(leaf).
删除一个节点
须注意两点:
- 该程序必须标识删除节点的父节点
- 为了修改指针, 代码必须把该指针的地址传递给执行删除任务的函数
先对第二点进行分析, 假设有合适的地址可用, 函数如下:
// 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;
}
- 遍历树
树的分支特性非常适合使用分而制之的递归, 需要完成如下任务:
处理节点中的项
处理左子树 (递归调用)
处理右节点(递归调用)
可以将遍历分成两个函数来完成: 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);
}
}
- 清空树
清空树基本上和遍历树相同, 除了需要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);
}
}
- 完整的包
/* 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图形接口。