这套代码对用户屏蔽了复杂的二叉树的旋转操作,不区分用户的数据类型,任何数
据都可以用有序平衡二叉树来存放。我还对平衡二叉树进行了一点点扩展,在树结构里
面增加了保持线行递增(或递减)顺序的双向链表,方便使用者能够快速按序遍历所有
树节点。
这套代码需要用户直接参与的部分都用注册函数来实现,让使用者完全不需要了解
有序平衡二叉树的添加、删除或查找的过程,能够做到傻瓜式的使用。至于性能,我添
加过300万个树节点之后,树高只有22层,查找任意一个节点最多只需要22次匹配操作。
比HASH表快了很多。
这套代码还针对vxworks操作系统做了一点点扩展,其实就是判断一下当前操作系统
是否是vxworks,如果是的话就创建一个二进制信号量,如果不是就什么信号量也不创建。
目前此版本提供了如下通用接口:
1、数据结构定义
1.1、树的结构定义
typedef struct _AVL_TREE_HEADER
{
TREE_NODE *pTreeHeader
#ifdef ORDER_LIST_WANTED
TREE_NODE *pListHeader
TREE_NODE *pListTail
#endif
unsigned int count/*AVL树里的节点总数*/
int (*keyCompare)(TREE_NODE * , TREE_NODE *)
int (*free)(TREE_NODE *)
#if OS==3||OS==4 /*#if OS == VXWORKS || OS == VXWORKS_M*/
SEM_ID sem
#endif
} tAVLTree
1.2、树节点的结构定义
typedef struct _AVL_TREE_NODE
{
#ifdef ORDER_LIST_WANTED
struct _AVL_TREE_NODE *prev
struct _AVL_TREE_NODE *next
#endif
struct _AVL_TREE_NODE *tree_root
struct _AVL_TREE_NODE *left_child
struct _AVL_TREE_NODE *right_child
int bf /*平衡因子;当平衡因子的绝对值大于或等于2的时候就表示树不平衡(balance_factor)*/
}TREE_NODE
2、通用函数接口定义
2.1、avlTreeCreate
函数原型:tAVLTree *avlTreeCreate(int *keyCompareFunc,int *freeFunc)
参数描述:keyCompareFunc - 节点大小比较函数的指针,此函数需要用户自己提供,
比较函数的返回值应该是-1、0、1,(*keyCompareFunc)函数有两个参数,
分别是两个树节点,如果第二个参数所指向的树节点的值比第一个的小,
那么比较函数就应该返回-1,如果相等就是返回0,否则就是1。具体的比
较规则由用户根据所存储的数据里面的关键字来指定(当然,关键字有可
能有多个)。
freeFunc - (*freeFunc)只有一个参数,就是需要释放内存的节点的指
针,填写这个注册函数的目的是为了实现avlTreeDestroy和avlTreeFlush
这两个函数。
返回值 :成功 - 返回指向一个树的指针
失败 - NULL
函数描述:创建一棵空的平衡二叉树,如果是vxworks操作系统的话还创建一个跟树
相关的一个二进制信号量。初始化所有指针为空指针。
2.2、avlTreeDestroy
函数原型:int avlTreeDestroy(tAVLTree *pTree)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 1
失败 - 0
函数描述:摧毁一颗平衡二叉树,释放所有树节点的内存,并且释放信号量(如果是
vxworks操作系统的话),释放pTree所指向的二叉树所占用的内存。
2.3、avlTreeFlush
函数原型:int avlTreeFlush(tAVLTree *pTree)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 1
失败 - 0
函数描述:清空一颗平衡二叉树,释放所有树节点的内存,但是并不删除平衡二叉树
2.4、avlTreeAdd
函数原型:int avlTreeAdd(tAVLTree *pTree , TREE_NODE *pInsertNode)
参数描述:pTree - 指向一棵平衡二叉树的指针
pInsertNode - 待插入的节点的指针
返回值 :成功 - 1
失败 - 0
函数描述:往平衡二叉树中添加一个成员节点
2.5、avlTreeDel
函数原型:int avlTreeAdd(tAVLTree *pTree , TREE_NODE *pDelNode)
参数描述:pTree - 指向一棵平衡二叉树的指针
pDelNode - 待插入的节点的指针
返回值 :成功 - 1
失败 - 0
函数描述:从平衡二叉树中删除一个树节点
2.6、avlTreeFind
函数原型:TREE_NODE *avlTreeFind(tAVLTree *pTree,TREE_NODE *pKeyNode)
参数描述:pTree - 指向一棵平衡二叉树的指针
pKeyNode - 待查找的节点的关键字
返回值 :成功 - 查找到的节点指针
失败 - NULL
函数描述:查找一个节点
2.7、avlTreeCount
函数原型:int avlTreeCount(tAVLTree *pTree)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 当前平衡二叉树里的节点总数
失败 - 0
函数描述:获取当前树里面的所有成员节点总数
2.8、avlTreeFirst
函数原型:TREE_NODE *avlTreeFirst(tAVLTree *pTree)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 当前平衡二叉树里面的最小的成员节点的指针
失败 - NULL,只有树是空的时候才会返回NULL
函数描述:获取当前平衡二叉树里面第一个成员节点,即最小的成员节点
2.9、avlTreeLast
函数原型:TREE_NODE *avlTreeLast(tAVLTree *pTree)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 当前平衡二叉树里面的最大的成员节点的指针
失败 - NULL,只有树是空的时候才会返回NULL
函数描述:获取当前平衡二叉树里面最后一个成员节点,即最大的成员节点
2.10、avlTreeNext
函数原型:TREE_NODE *avlTreeNext(TREE_NODE *pNode)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 下一个成员节点的指针
失败 - NULL
函数描述:获取当前节点的下一个节点
2.11、avlTreePrev
函数原型:TREE_NODE *avlTreePrev(TREE_NODE *pNode)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :成功 - 前一个成员节点的指针
失败 - NULL
函数描述:获取当前节点的前一个节点
2.12、AVL_TREE_LOCK
函数原型:void AVL_TREE_LOCK(tAVLTree *pTree,int timeout)
参数描述:pTree - 指向一棵平衡二叉树的指针
timeout - 等待时间
返回值 :N/A
函数描述:此函数只有是vxworks系统才有效,目的是对树进行互斥操作,防止
多任务同时操作链表。
2.13、AVL_TREE_UNLOCK
函数原型:void AVL_TREE_UNLOCK(tAVLTree *pTree)
参数描述:pTree - 指向一棵平衡二叉树的指针
返回值 :N/A
函数描述:此函数只有是vxworks系统才有效,目的是对树进行解除互斥操作
2.13、AVL_TREENODE_FREE
函数原型:void AVL_TREENODE_FREE(tAVLTree *pTree,TREE_NODE *pNode)
参数描述:pTree - 指向一棵平衡二叉树的指针
pNode - 需要释放的节点的指针
返回值 :N/A
函数描述:此函数释放内存的过程采用的是注册的释放函数,释放不仅仅
只有free函数,对于一些复杂的结构设计,可能需要释放多个
不同的内存。
3、应用举例
> typedef struct _testStruct{
> TREE_NODE node /*树节点的结构一定要放在用户自定义结构的最前面,注意!*/
> int keyA /*关键字A*/
> int keyB /*关键字B*/
> int keyC /*关键字C,比方说此结构有3个关键字*/
> int userData[200]/*用户的实际数据区*/
> }tTestStruct
>
> int keyCompareFunc(TREE_NODE *p , TREE_NODE *p1)
> {
> tTestStruct *T1=NULL,*T2=NULL
>
> T1=(tTestStruct *)p
> T2=(tTestStruct *)p1
>
> if(T1->keyA <T2->keyA) return 1
> if(T1->keyA >T2->keyA) return -1
>
> if(T1->keyB <T2->keyB) return 1
> if(T1->keyB >T2->keyB) return -1
>
> if(T1->keyC <T2->keyC) return 1
> if(T1->keyC >T2->keyC) return -1
>
> return 0
> }
>
> int freeFunc(TREE_NODE *pNode)
> {
> free((void *)pNode)
> return 1
> }
>
> tAVLTree *pTree = NULL
> int main()
> {
> tTestStruct *pTest = NULL
> tTestStruct key
> int count = 0
> pTree = (tAVLTree *)avlTreeCreate
> (
> (void*)keyCompareFunc ,
> (void *)freeFunc
> )
> if(!pTree)
> {
> printf("\r\n 创建平衡二叉树失败")
> return 0
> }
>
> /*添加一个节点*/
> pTest = (tTestStruct *)malloc(sizeof(tTestStruct))
> if(!pTest)
> {
> printf("\r\n 创建树节点失败")
> return 0
> }
>
> pTest->keyA = 1
> pTest->keyB = 2
> pTest->keyC = 3
> if(!avlTreeAdd(pTree , (TREE_NODE *)pTest))
> {
> printf("\r\n 已经存在相同节点,添加失败!关键字完全匹配就表示节点完全相同")
> return 1
> }
>
> /*再添加一个节点*/
> pTest = (tTestStruct *)malloc(sizeof(tTestStruct))
> if(!pTest)
> {
> printf("\r\n 创建树节点又失败")
> return 0
> }
>
> pTest->keyA = 1
> pTest->keyB = 1
> pTest->keyC = 3 /*第二次添加的节点的关键字比较大家可以算一算*/
> if(!avlTreeAdd(pTree , (TREE_NODE *)pTest))
> {
> printf("\r\n 已经存在相同节点,添加失败!关键字完全匹配就表示节点完全相同")
> return 1
> }
>
> /*遍历有序平衡二叉树 -- 从小到大*/
> pTest = (tTestStruct *)avlTreeFirst(pTree)
> while(pTest)
> {
> /**************************
> *这里你想干嘛干嘛
> *处理pTest->userData?
> ***************************/
> pTest = (tTestStruct *)avlTreeNext(pTree)
> }
>
> /*遍历有序平衡二叉树 -- 从大到小*/
> pTest = (tTestStruct *)avlTreeLast(pTree)
> while(pTest)
> {
> /**************************
> *这里你想干嘛干嘛
> *处理pTest->userData?
> ***************************/
> pTest = (tTestStruct *)avlTreePrev(pTree)
> }
>
> /*查找某个节点*/
> key->keyA = 1
> key->keyB = 1
> key->keyC = 3
> pTest = (tTestStruct *)avlTreeFind(pTree , (TREE_NODE *)&key)
> if(pTest)
> printf("\r\n 这里应该可以查找到一条记录,就是第二个插入的节点")
>
> /*删除一个节点,我们就将上面查找到的节点删除*/
> if(!avlTreeCount(pTree , (TREE_NODE *)pTest))
> {
> printf("\r\n 如果删除失败,只能说明一个问题,树里面不存在这个节点")
> return 0
> }
>
> /*获取树的总的节点数*/
> count = avlTreeCount(pTree)
> printf("\r\n 我想现在count应该等于1,刚才我们删掉了一个节点")
>
> /*清空整棵树*/
> avlTreeFlush(pTree)
>
> /*删除整棵树,其实现在只有一颗裸树了,因为树节点都被flush掉了*/
> avlTreeDestroy(pTree)
> pTree = NULL
>
> return 1
> }
操作系统可能包含许多关于系统当前状态的信息。当系统发生变化时,这些数据结构必须做相应的改变以反映这些情况。例如,当用户登录进系统时将产生一个新的进程。核心必须创建表示新进程的数据结构,同时 将它和系统中其他进程的数据结构连接在一起。 大多数数据结构存在于物理内存中并只能由核心或者其子系统来访问。数据结构包括数据和指针;还有其他数据结构的地址或者子程序的地址。它们混在一起让Linux核心数据结构看上去非常混乱。尽管可能被几个核心子系统同时用到,每个数据结构都有其专门的用途。理解Linux核心的关键是理解它的数据结构以及Linux核心中操纵这些数据结构的各种函数。本书把Linux核心的 描叙重点放在数据结构上,主要讨论每个核心子系统的算法,完成任务的途径以及对核心数据结构的使用。2.3.1 连接列表
Linux使用的许多软件工程的技术来连接它的数据结构。在许多场合下,它使用linked或者chained数据结构。 每个数据结构描叙某一事物,比如某个进程或网络设备,核心必须能够访问到所有这些结构。在链表结构中,个根节点指针包含第一个结构的地址,而在每个结构中又包含表中下一个结构的指针。表的最后一项必须是0或者NULL,以表明这是表的尾部。在双向链表中,每个结构包含着指向表中前一结构和后一结构的指针。使用双向链表的好处在于更容易在表的中部添加与删除节点,但需要更多的内存操作。这是一种典型的操作系统开销与CPU循环之间的折中。
2.3.2 散列表
链表用来连接数据结构比较方便,但链表的操作效率不高。如果要搜寻某个特定内容,我们可能不得不遍历整个链表。Linux使用另外一种技术:散列表来提高效率。散列表是指针的数组或向量,指向内存中连续的相邻数据集合。散列表中每个指针元素指向一个独立链表。如果你使用数据结构来描叙村子里的人,则你可以使用年龄作为索引。为了找到某个人的数据,可以在人口散列表中使用年龄作为索引,找到包含此人特定数据的数据结构。但是在村子里有很多人的年龄相同,这样散列表指针变成了一个指向具有相同年龄的人数据链表的指针。搜索这个小链表的速度显然要比搜索整个数据链表快得多。 由于散列表加快了对数据结构的访问速度,Linux经常使用它来实现Caches。Caches是保存经常访问的信息的子集。经常被核心使用的数据结构将被放入Cache中保存。Caches的缺点是比使用和维护单一链表和散列表更复杂。寻找某个数据结构时,如果在Cache中能够找到(这种情况称为cache 命中),这的确很不错。但是如果没有找到,则必须找出它,并且添加到Cache中去。如果Cache空间已经用完则Linux必须决定哪一个结构将从其中抛弃,但是有可能这个要抛弃的数据就是Linux下次要使用的数据。
2.3.3 抽象接口
Linux核心常将其接口抽象出来。接口指一组以特定方式执行的子程序和数据结构的集合。例如,所有的网络设备驱动必须提供对某些特定数据结构进行操作的子程序。通用代码可能会使用底层的某些代码。例如网络层代码是通用的,它得到遵循标准接口的特定设备相关代码的支持。 通常在系统启动时,底层接口向更高层接口注册(Register)自身。这些注册操作包括向链表中加入结构节点。例如,构造进核心的每个文件系统在系统启动时将其自身向核心注册。文件/proc/filesysems中可以看到已经向核心注册过的文件系统。注册数据结构通常包括指向函数的指针,以文件系统注册为例,它向Linux核心注册时必须将那些mount文件系统连接时使用的一些相关函数的地址传入。
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)