本文主要讲述了数据结构中树的基本概念,二叉树,树与森林以及树与二叉树的应用。
知识框架如下图所示:
树的基本概念
树是N(N>=0)个结点的有限集合,N=0时,称为空树。
而任何一棵非空树应该满足,有且仅有一个根结点,当N>1时,其余结点又可以分为几个互不相交的有限集合,其本身又构成一个树(体现递归的定义),称为根结点的子树。需要特别说明的一个重要结论是,任何一个有N(N>=1)个结点的树都有N-1条边。
二叉树的基本概念
二叉树是一种特殊的树形结构,其特点是不存在度大于2的结点,且每个结点的子树(若有)均存在左右之分,其次序不能颠倒。
严格一点来说,二叉树是n(n>=0)个结点的有限集合。
1,n=0时,表示空二叉树。
2,n>0时,由根结点和互不相交的被称为根的左子树和右子树组成,而左子树和右子树也分别是一颗二叉树。
几种特殊的二叉树:满二叉树,完全二叉树,排序二叉树,平衡二叉树。
二叉树的存储结构
顺序存储结构,将一棵二叉树用0填充成一棵满二叉树,对于每个结点i,双亲结点为floor(i/2),左孩子为2*i,右孩子为2*i+1。
1 //二叉树的顺序存储结构 2 #define MAX_TREE_SIZE 100 3 typedef int SqBiTree[MAX_TREE_SIZE]; 4 SqBiTree bt; 5 6 //二叉树的链式存储结构 7 typedef struct BiTNode{ 8 int data; 9 struct BiTNode *lchild, *rchild; 10 }BiTNode, *BiTree;
1 #include<cstdio> 2 3 //二叉树的顺序存储结构 4 #define MAX_TREE_SIZE 100 5 typedef int SqBiTree[MAX_TREE_SIZE]; 6 SqBiTree bt; 7 8 //在顺序结构中寻找i和j的最近共同祖先 9 void Comm_Ancester(SqBiTree bt, int i, int j){ 10 int a = i, b = j; 11 if(bt[i] == 0){ 12 printf("编号为%d的结点不存在! ", i); 13 return; 14 } 15 if(bt[j] == 0){ 16 printf("编号为%d的结点不存在! ", j); 17 return; 18 } 19 20 while(i != j){//谁大谁往上跳一层再比,直到相等 21 if(i > j) 22 i /= 2; 23 else 24 j /= 2; 25 } 26 printf("编号为%d的结点和编号为%d的结点的最近公共祖先的结点编号为%d,其值为%d ", a, b, i, bt[i]); 27 } 28 29 int main() 30 { 31 int bt[] = {-1, 1, 2,3, 4,5,6,7, 8,0,10,0,0,0,0,0, 0,0,0,0};//层次序列 32 /* 33 1 34 / 35 2 3 36 / / 37 4 5 6 7 38 / / / / 39 8 0 10 0 0 0 0 0 40 / / 41 0 0 0 0 42 */ 43 //在上述顺序存储结构中寻找编号为i和j结点的最近共同祖先结点的编号和值 44 Comm_Ancester(bt, 2, 3); 45 Comm_Ancester(bt, 4, 5); 46 Comm_Ancester(bt, 4, 6); 47 Comm_Ancester(bt, 8, 10); 48 /* 49 编号为2的结点和编号为3的结点的最近公共祖先的结点编号为1,其值为1 50 编号为4的结点和编号为5的结点的最近公共祖先的结点编号为2,其值为2 51 编号为4的结点和编号为6的结点的最近公共祖先的结点编号为1,其值为1 52 编号为8的结点和编号为10的结点的最近公共祖先的结点编号为2,其值为2 53 */ 54 55 return 0; 56 }
链式存储结构,类似定义双向链表,不同的是指向左右孩子结点
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 7 //二叉树的链式存储结构 8 typedef struct BiTNode{ 9 char data; 10 struct BiTNode *lchild, *rchild; 11 }BiTNode, *BiTree; 12 13 //二叉树的顺序存储结构 14 #define MaxSize 500 15 typedef struct{ 16 BiTree data[MaxSize]; 17 int front,rear; 18 } SqQueue; 19 void InitQueue(SqQueue &Q)//初始化 20 { 21 Q.front = Q.rear = 0; 22 } 23 bool QueueEmpty(SqQueue Q)//队列是否为空 24 { 25 if(Q.front == Q.rear) 26 return true;//空为真,非空为假 27 return false; 28 } 29 bool EnQueue(SqQueue &Q, BiTree x)//若队列未满,入队 30 { 31 //if(Q.rear == MaxSize) return false;//判断条件错误,可能假溢出 32 Q.data[Q.rear++] = x;//假定不会假溢出 33 return true; 34 } 35 bool DeQueue(SqQueue &Q, BiTree &x)//若队列非空,出队 36 { 37 if(Q.front == Q.rear) 38 return false; 39 x = Q.data[Q.front++]; 40 return true; 41 } 42 bool GetHead(SqQueue Q, BiTree &x)//读取队头元素,若队列非空,将队头元素赋值给x 43 { 44 if(Q.front == Q.rear) 45 return false; 46 x = Q.data[Q.front]; 47 return true; 48 } 49 void ClearQueue(SqQueue &Q)//清空队列,并回收内存 50 { 51 Q.front = Q.rear = 0; 52 } 53 54 //初始化一棵二叉树 55 void InitBiTree(BiTree &bt){ 56 bt = (BiTree)malloc(sizeof(BiTNode)); 57 } 58 //按照先根序列创建一棵二叉树 59 const char *str = "123004507006000"; 60 void CreateBiTree(BiTree &bt){ 61 //printf("%s ", str); 62 int ch = str[0]; 63 str++; 64 if(ch == '0') 65 bt = NULL; 66 else{ 67 bt = (BiTNode*)malloc(sizeof(BiTNode)); 68 bt->data = ch; 69 CreateBiTree(bt->lchild); 70 CreateBiTree(bt->rchild); 71 } 72 } 73 //清空一棵二叉树 74 void ClearBiTree(BiTree &bt){ 75 if(bt != NULL){ 76 ClearBiTree(bt->lchild); 77 ClearBiTree(bt->lchild); 78 bt = NULL; 79 } 80 } 81 82 //判断该二叉树是否为空 83 bool BiTreeEmpty(BiTree bt){ 84 if(bt == NULL) 85 return true; 86 return false; 87 } 88 //返回该二叉树的深度 89 int BiTreeDepth(BiTree bt){ 90 if(bt != NULL){ 91 int l = 1 + BiTreeDepth(bt->lchild); 92 int r = 1 + BiTreeDepth(bt->rchild); 93 return max(l,r); 94 } 95 } 96 //先序遍历 97 void PreOrderTraverse(BiTree bt){ 98 if(bt != NULL){ 99 printf("%c", bt->data); 100 PreOrderTraverse(bt->lchild); 101 PreOrderTraverse(bt->rchild); 102 } 103 } 104 //中序遍历 105 void InOrderTraverse(BiTree bt){ 106 if(bt != NULL){ 107 InOrderTraverse(bt->lchild); 108 printf("%c", bt->data); 109 InOrderTraverse(bt->rchild); 110 } 111 } 112 //后序遍历 113 void PostOrderTraverse(BiTree bt){ 114 if(bt != NULL){ 115 PostOrderTraverse(bt->lchild); 116 PostOrderTraverse(bt->rchild); 117 printf("%c", bt->data); 118 } 119 } 120 //层次遍历 121 void levelOrderTraverse(BiTree bt){ 122 SqQueue Q; 123 InitQueue(Q); 124 BiTree p; 125 EnQueue(Q, bt); 126 while(!QueueEmpty(Q)){ 127 DeQueue(Q, p); 128 printf("%c", p->data); 129 if(p->lchild != NULL) 130 EnQueue(Q, p->lchild); 131 if(p->rchild != NULL) 132 EnQueue(Q, p->rchild); 133 } 134 } 135 136 int main() 137 { 138 /* 139 1 140 / 141 2 0 142 / 143 3 4 144 / / 145 0 0 5 6 146 / / 147 0 7 0 0 148 / 149 0 0 150 */ 151 //对于形如上图的二叉树 152 BiTree bt; 153 InitBiTree(bt); 154 //char str[] = "123004507006000";//先序次序,声明在前面的全局变量 155 CreateBiTree(bt); 156 PreOrderTraverse(bt);puts(""); 157 InOrderTraverse(bt);puts(""); 158 PostOrderTraverse(bt);puts(""); 159 levelOrderTraverse(bt);puts(""); 160 161 printf("该数的深度是%d ",BiTreeDepth(bt));//计算深度并输出 162 ClearBiTree(bt); 163 if(BiTreeEmpty(bt)) 164 printf("清空成功! "); 165 /* 166 1234576 167 3257461 168 3756421 169 1234567 170 该数的深度是5 171 清空成功! 172 */ 173 return 0; 174 }
线索二叉树
传统的链式存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱和后继。而二叉树的链式存储结构中存在大量的空指针,若能利用这些空链域存放指向其直接前驱或后继结点的指针,则可以加快查找前驱结点和后继结点的速度。
1 #include<cstdio> 2 #include<cstdlib> 3 4 //线索二叉树的链式存储结构 5 /* 6 左前右后 7 ltag :0 lchild域指向结点的左孩子 8 :1 lchild域指向结点的前驱 9 rtag :0 rchild域指向结点的右孩子 10 :1 rchild域指向结点的后继 11 */ 12 typedef struct TreadNode{ 13 char data; 14 struct TreadNode *lchild, *rchild; 15 int ltag, rtag; 16 }ThreadNode, *ThreadTree; 17 18 void InitThreadTree(ThreadTree &bt){ 19 bt = (ThreadTree)malloc(sizeof(ThreadNode)); 20 bt->lchild = bt->rchild = NULL; 21 bt->ltag = bt->rtag = 0; 22 } 23 //按照先根序列创建一棵二叉树 24 const char *str = "12040035000"; 25 void CreateThreadTree(ThreadTree &bt){ 26 //printf("%s ", str); 27 int ch = str[0]; 28 str++; 29 if(ch == '0') 30 bt = NULL; 31 else{ 32 bt = (ThreadNode*)malloc(sizeof(ThreadNode)); 33 bt->data = ch; 34 bt->lchild = bt->rchild = NULL; 35 bt->ltag = bt->rtag = 0; 36 CreateThreadTree(bt->lchild); 37 CreateThreadTree(bt->rchild); 38 } 39 } 40 41 /*线索二叉树的构造,即二叉树的线索化,实质上就是遍历一遍二叉树, 42 发现该结点的左右指针域是否为空,若为空,分别将其改造成指向前驱和后继结点即可, 43 不要忘记改变标志位的状态*/ 44 void InThread(ThreadTree &p, ThreadTree &pre){ 45 if(p != NULL){ 46 InThread(p->lchild, pre);//线索化左子树 47 48 if(p->lchild == NULL){ 49 p->lchild = pre; 50 p->ltag = 1; 51 } 52 if(pre != NULL && pre->rchild == NULL){ 53 pre->rchild = p; 54 pre->rtag = 1; 55 } 56 pre = p;//标记当前结点位刚刚访问过的结点 57 InThread(p->rchild, pre);//递归线索化右子树 58 } 59 } 60 //通过中序遍历建立中序线索二叉树的主过程如下 61 void CreateInThread(ThreadTree &T){ 62 ThreadTree pre = NULL; 63 //借助一个pre指针指向中序遍历时上一个刚刚访问过的结点,表示各节点的前后关系 64 if(T != NULL){ 65 InThread(T, pre); 66 pre->rchild = NULL;//处理遍历的最后一个结点 67 pre->rtag = 1; 68 } 69 } 70 //二叉树的中序遍历算法 71 void InOrderTraverse(ThreadTree bt){ 72 if(bt != NULL){ 73 InOrderTraverse(bt->lchild); 74 printf("%c", bt->data); 75 InOrderTraverse(bt->rchild); 76 } 77 } 78 //求中序线索二叉树中中序序列下的第一个结点 79 ThreadNode* Firstnode(ThreadNode *p){ 80 while(p->ltag == 0)//记得创建结点时,tag初始化为0,不然找不到中序第一个结点 81 p = p->lchild;//最左下结点(不一定是叶子结点) 82 83 return p; 84 } 85 //求中序线索二叉树中结点p在中序序列下的后继结点 86 ThreadNode* Nextnode(ThreadNode *p){ 87 if(p->rtag == 0) return Firstnode(p->rchild);//rtag == 0,右孩子存在,返回其右子树的第一个结点 88 else return p->rchild;//rtag == 1,直接返回其右孩子指针指向的后继结点 89 } 90 //不含头结点的中序线索二叉树的中序遍历算法 91 void Inorder(ThreadNode *T){ 92 for(ThreadNode *p = Firstnode(T); p != NULL; p = Nextnode(p)) 93 printf("%c", p->data);//记得创建结点时,tag初始化为0,不然找不到中序第一个结点 94 } 95 int main() 96 { 97 /* 98 1 99 / 100 2 3 101 / / 102 0 4 5 0 103 / / 104 0 0 0 0 105 */ 106 //对于形如上图的二叉树线索化 107 ThreadTree bt; 108 InitThreadTree(bt); 109 //char str[] = "12040035000";//先序次序,声明在前面的全局变量 110 CreateThreadTree(bt); 111 puts("二叉树中序遍历:"); 112 InOrderTraverse(bt);puts(""); 113 114 CreateInThread(bt); 115 puts("线索二叉树中序遍历:"); 116 Inorder(bt);puts(""); 117 /* 118 二叉树中序遍历: 119 24153 120 线索二叉树中序遍历: 121 24153 122 */ 123 return 0; 124 }
树的存储结构
双亲表示法
1 #include<cstdio> 2 #include<cstring> 3 //树的双亲表示法 4 #define MAX_TREE_SIZE 100 5 typedef struct{ 6 char data; 7 int parent; 8 }PTNode; 9 typedef struct{ 10 PTNode nodes[MAX_TREE_SIZE]; 11 int n = 0; 12 }PTree; 13 14 int main() 15 { 16 /* 17 对于如下的二叉树 18 0 19 1 2 3 20 4 5 6 21 7 8 9 22 */ 23 PTree pt; 24 int e[20][2]={0,-1, 1,0,2,0,3,0, 4,1,5,1, 6,3,7,6,8,6,9,6}; 25 for(int i = 0; i < 10; i++){ 26 pt.nodes[i].data = e[i][0]; 27 pt.nodes[i].parent = e[i][1]; 28 pt.n++; 29 } 30 return 0; 31 }
孩子表示法
1 /*树的孩子表示法 2 每个结点的孩子结点都用一个单链表存储其孩子结点,则N个结点就有N个单链表 3 */ 4 #define MAX_TREE_SIZE 100 5 typedef struct CTNode{//孩子结点 6 int child; 7 struct CTNode *next; 8 }*ChildPtr; 9 typedef struct{ 10 char data; 11 ChildPtr firstchild;//孩子链表头指针 12 }CTBox; 13 typedef struct{ 14 CTBox nodes[MAX_TREE_SIZE]; 15 int n, r;//结点数和根的位置 16 }CTree;
左孩子右兄弟表示法
1 /*树的左孩子右兄弟表示法 2 类似二叉树的链式存储结构,最大的优点时实现树转化为二叉树的操作,易于查找孩子结点,缺点是不利于查找双亲结点, 3 可以使用添加parent域指向其父结点的方法解决 4 */ 5 typedef struct CSNode{ 6 char data; 7 struct CSNode *firstchild, *nextsibling; 8 }CSNode, *CSTree;