对于大量的数据,链表的线性访问时间太慢,不宜使用。我们介绍一种简单的数据结构,其大部分操作的平均时间为O(log N)。
(1)学习目标:
我们将要涉及到的数据结构叫做二叉查找树(binary search tree)。
我们将要了解如下内容:
1.了解树是如何用于实现几个流行的操作系统中的文件系统的; 2.看到树如何能够用来计算算术表达式的值; 3.如何指出利用树支持以O(log N)平均时间进行的各种搜索操作,以及如何细化得到最坏情况时间界O(log N)。我们还将讨论当数据被存在磁盘上时如何来实现这些操作。
(2)树的基础知识:
树的递归定义:
一棵树由称作根(root)的结点r以及0个或多个非空的(子)树T1,T2,T3......组成,这些子树中每一颗的根都来自根r的一条有向边(Edge)所连接。
2.1树的实现:
每一个结点的儿子树不确定时,可以进行如下定义:
typedef struct TreeNode *PtrToNode; struct TreeNode { ElementType Element; PtrToNode FirstChild; PtrToNode NextSibling; }
2.2树的遍历及应用:
树有很多应用,流行的用法之一是包括UNIX,VAX,VAX/VMS和DOS在内的许多常用操作系统中的目录结构:
在Unix文件系统每个目录还有一项指该向目录本身以及另一项指向该目录的父目录。因此严格来说,UNIX文件系统不是树,而是类树。
设我们想要得到目录中所有文件的名字。我们的输出格式是:深度为di的文件的名字将被第di此跳格缩进后打印出来:
static void ListDir(DirectoryOrFile D,int Depth) { if(D is a legitimate entry) { PrintName(D,Name); if(D is a directory) for each child,c,of D ListDir(C,Depth+1); } }//算法的核心例程 void ListDirectory(DirectoryOrFile D) { ListDir(D,0); }//驱动例程
输出文件通常用树的先序遍历(preorider traversal),而树的后续遍历通常用来计算该树所有文件占用的磁盘块的总数。
计算一个目录大小的例程:
static void SizeDirectory(DirectoryFile D) { int TotalSize; TotalSize=0; if(D is a legitimate Entry) { TotalSize=FileSize(D);//当当前是文件,不是目录时 if(D is a directory) for(each child,C, of D) TotalSize+=SizeDirectory(C); } return TotalSize; }
(3)二叉树:
定义:
二叉树(binary tree)是一棵树,每一个结点都不能有多于两个的儿子。
特点:
平均二叉树的深度要比N小得多,这个性质有时很重要。分析表明,这个平均深度为O(√N),而对于特殊的二叉树,即二叉查找树(binary search tree),其深度的平均值是O(log N)。
二叉树的结点声明:
typedef struct TreeNode *PtrToNode; typedef struct PtrToNode Tree; struct TreeNode { ElementType Element; Tree left; Tree right; };
(4)二叉树的应用:(表达式树)
二叉树有许多与搜索无关的重要应用。它的主要用途之一是在编译器的设计领域。
1 /***************构造表达式树***************/ 2 /* 3 1.输入中缀表达式先转化为后缀表达式; 4 2.根据后缀表达式构造树 5 3.分别以中序遍历法和后序遍历法遍历此树 6 */ 7 #include<stdio.h> 8 #include<stdlib.h> 9 #include<string.h> 10 11 #define EmptyTOS (-1) 12 #define MinStackSize 5 13 14 struct StackRecord{ 15 int Capacity;//能存元素最大量 16 int TopOfStack;//记录新插入的元素所在数组中的位置 17 char Array[30][5];//字符串数组,每个字符串的大小最多为5 18 }; 19 typedef struct StackRecord *Stack; 20 21 void MakeEmpty(Stack s){ 22 s->TopOfStack=EmptyTOS; 23 } 24 Stack CreateStack(int MaxElement){ 25 Stack s; 26 if(MaxElement<MinStackSize){ 27 printf("要创建的栈太小,应大于5。 "); 28 exit(0); 29 }else{ 30 s=malloc(sizeof(struct StackRecord)); 31 s->Capacity=MaxElement; 32 MakeEmpty(s); 33 } 34 return s; 35 } 36 //判断栈是否为空栈 37 int IsEmpty(Stack S){ 38 return S->TopOfStack==EmptyTOS; 39 } 40 //判断是否满了,当为1是满了,为0是表示未满 41 int IsFull(Stack S){ 42 if(S->TopOfStack+1>=S->Capacity){ 43 return 1; 44 }else 45 return 0; 46 } 47 //压栈 48 void Push(char *x,Stack S){ 49 if(IsFull(S)){ 50 printf("栈已经满了! "); 51 }else{ 52 strcpy(S->Array[++S->TopOfStack],x); 53 } 54 } 55 //只获得头元素 56 char *Top(Stack S){ 57 if(IsEmpty(S)){ 58 printf("此栈为空,无法取栈头元素! "); 59 exit(0); 60 }else{ 61 return S->Array[S->TopOfStack]; 62 } 63 } 64 //只删除头元素 65 void Pop(Stack S){ 66 if(IsEmpty(S)){ 67 printf("此栈为空,无法去除栈头元素! "); 68 }else{ 69 S->TopOfStack--; 70 } 71 } 72 //获取头元素并删除 73 char *PopAndTop(Stack S){ 74 if(IsEmpty(S)){ 75 printf("此栈为空,无法执行获取栈头元素和去除栈头元素! "); 76 exit(0); 77 }else{ 78 return S->Array[S->TopOfStack--]; 79 } 80 } 81 //释放栈空间 82 void DisposeStack(Stack s){ 83 if(s!=NULL){ 84 free(s); 85 } 86 } 87 void printStack(Stack s){ 88 int i; 89 for(i=0;i<=s->TopOfStack;i++){ 90 printf("%s ",s->Array[i]); 91 } 92 } 93 //位置是从栈头开始计算,所以谁小谁离栈顶比较远 94 int LastPosition(char *p,Stack temp){ 95 int i; 96 if(exist(p,temp)){ 97 for(i=temp->TopOfStack;i>=0;i--){ 98 if(strcmp(temp->Array[i],p)){ 99 return i; 100 } 101 } 102 } 103 else{ 104 printf("临时栈没有%s这个操作符 ",p); 105 return -1; 106 } 107 } 108 int IsNumber(char p){ 109 if(p>='0'&&p<='9'){ 110 return 1; 111 }else 112 return 0; 113 } 114 //由于这里允许负数的存在,所以与之前的函数不同 115 int IsOperator(char *p){//这个函数是给turnInto函数使用的 116 //当长度不为1.肯定是数字,不是操作符 117 if(p[1]!='