树是一种基本的层次结构,相比线性结构,在动态查找上,效率更高。
在说树以前,咱们先说一下线性结构中两种经常使用的查找方式。node
对于一个线性表方便的查找方式是,从后向前查找且将被查找的值放在最前面做为哨兵,只须要判断下标位置的元素是否为查找元素,而不用考虑下标是否走到边界(由于边界元素正好是咱们的哨兵,走到了这里自动退出)算法
代码实现:数组
#include<stdio.h> #include<stdlib.h> typedef struct LNode* List; #define MAXSIZE 100 #define ElementType int struct LNode { ElementType Data[MAXSIZE]; int length; }; int sequentailSearch(List L,ElementType key) { int i = 0; L->Data[0] = key;//创建哨兵 for (i = L->length; L->Data[i] != key; i--); return i;//找到返回对应下标,没找到返回0 }
想对一个线性表使用二分查找,他必须是有序的数据结构
例若有一个线性表从小到大排列,设置两个变量(left和right)分别设为线性表的两端下标,经过比较中间位置的值于被查找元素的值,改变2个变量的指向,若是中间的值大于被查到元素,说明被查找元素应该在中间的右侧,咱们将right设为中间的位置下标减1,同理....post
代码实现:ui
#include<stdio.h> #include<stdlib.h> typedef struct LNode* List; #define MAXSIZE 100 #define ElementType int struct LNode { ElementType Data[MAXSIZE]; int length; }; //从小到大排序的状况 int binarySearch(List L, ElementType key) { int left = 1, right = L->length; while (left <= right) { int mid = (left + right) / 2; //若是mid下标的元素大于要找的,说明在left-mid之间,顾使right = mid-1。 if (L->Data[mid] > key) { right = mid - 1; } else if (L->Data[mid] < key) { left = mid + 1; } else { return mid; } } return -1;//查找失败返回-1 }
树是一种抽象数据类型或是实现这种抽象数据类型的数据结构,用来模拟具备树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具备层次关系的集合。n = 0时为空树。this
1.只含有一个根节点
2.其他的节点分红多个不相交的节点哪一个节点又是原树的子树
3.子树互不相交,除根节点外全部节点都有一个父节点
4.N个节点的数有N-1条边spa
1.结点的度(Degree):结点的子树个数
2.树的度:树的全部结点中最大的度数
3.叶结点(Leaf):度为0的结点
4.父结点(Parent):有子树的结点是其子树的根结点的父结点
5.子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点。
6.兄弟结点(Sibling):具备同一父结点的各结点彼此是兄弟结点。
7.路径和路径长度:从结点\(n_1\)到\(n_k\)的路径为一个结点序列\(n_1\) , \(n_2\),… , \(n_k\), \(n_i\)是 \(n_{i+1}\)的父结点。路径所包含边的个数为路径的长度。
9.祖先结点(Ancestor):沿树根到某一结点路径上的全部结点都是这个结点的祖先结点。
10.子孙结点(Descendant):某一结点的子树中的全部结点是这个结点的子孙。
11.结点的层次(Level):规定根结点在1层,其它任一结点的层数是其父结点的层数加1。
12.树的深度(Depth):树中全部结点中的最大层次是这棵树的深度。3d
对于一个二叉树,他的任何节点最多有两个子树,分别为左子树和右子树,二叉树的分支具备左右次序,不能随意颠倒。code
1.第i层最多有\(2^{i-1}\)个节点
2.深度为k的树最多有\(2_k-1\)个节点
3.对于一个非空二叉树用\(n\)k(k \(\in\) 0,1,2)表示有几个儿子的节点。有等式\(n_0\) = \(n_2\) + 1 恒成立。
证: 树的总边数 = \(n_0\) + \(n_1\) + \(n_2\) - 1(除了根节点外每一个节点都有父节点)
树的总边数 = 0 * \(n_0\) + 1 * \(n_1\) + 2 * \(n_2\)(节点的下标表明了他有几个儿子也就是几条边)
联立可得\(n_0\) = \(n_2\) + 1
若是采用线性的储存方法,保存在数组中,根据上面的二分查找咱们能够知道,二分查找顺序固定,能够行的通,但对于不是完美二叉树,会在空间上形成浪费,并不推荐。
经常使用的作法是采用链式储存下面给出树节点代码实现
struct TreeNode { ElementType Data; BinTree Left;//指向左子树 BinTree Right;//指向右子树 };
对于一颗二叉树,咱们常常但愿访问树中的每个结点而且查看它的值。有不少常见的顺序来访问全部的结点,并且每一种都有有用的性质。
这里介绍基于深度的前序中序后序遍历
对一棵树前序遍历,就是先访问它的根节点,再访问他的左子树,再访问他的右子树。
对一棵树中序遍历,就是先访问他的左子树,再访问它的根节点,再访问他的右子树。
对一棵树后序遍历,就是先访问他的左子树,再访问他的右子树,再访问它的根节点。
不难看出这三种遍历方式均可以用递归来实现,下面给出代码实现
#include<stdio.h> #include<stdlib.h> typedef struct TreeNode* BinTree; #define ElementType int struct TreeNode { ElementType Data; BinTree Left; BinTree Right; }; //先序遍历 void PreOrderTraversal(BinTree T) { //若是是空树就不遍历 if (T) { printf("%d ", T->Data); PreOrderTraversal(T->Left); PreOrderTraversal(T->Right); } } //中序遍历 void InOrderTraversal(BinTree T) { //若是是空树就不遍历 if (T) { InOrderTraversal(T->Left); printf("%d ", T->Data); InOrderTraversal(T->Right); } } //后序遍历 void PostOrderTraversal(BinTree T) { //若是是空树就不遍历 if (T) { PostOrderTraversal(T->Left); PostOrderTraversal(T->Right); printf("%d ", T->Data); } }
一般状况下,用递归实现的方法均可以用循环来实现,对于三种遍历方法也是,仔细观察树遍历的路线图,不难发现,所走的路线是同样的,只是抛出的时机不一样,前序遇到就抛出,中序遇到第2次掏出,后续第三次抛出,根据这种特性咱们能够经过堆栈来实现。
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef struct SNode* Stack; typedef struct TreeNode* BinTree; #define ElementType1 int #define ElementType2 BinTree struct TreeNode { ElementType1 Data; BinTree Left; BinTree Right; }; struct SNode { ElementType2 Data; Stack Next; }; //get link stack length,the empty stack has a header, the default length is 0 int length(Stack ptrs) { Stack temp = ptrs; int len = 0; while (temp->Next) { temp = temp->Next; len++; } return len; } //make an empty linked Stack,generate an empty header(length is 1) Stack makeEmpty() { Stack ptrs; ptrs = (Stack)malloc(sizeof(struct SNode)); ptrs->Next = NULL; return ptrs; } //check the linklist is empty bool isEmpty(Stack ptrs) { if (length(ptrs) == 0) { return true; } else { return false; } } //push Stack push(ElementType2 value, Stack ptrs) { Stack newLNode = (Stack)malloc(sizeof(struct SNode)); newLNode->Data = value; newLNode->Next = ptrs->Next; ptrs->Next = newLNode; return ptrs; } //pop ElementType2 pop(Stack ptrs) { if (isEmpty(ptrs)) { printf("the Stack has been empty."); return NULL; } Stack temp = ptrs->Next; ElementType2 value = temp->Data; ptrs->Next = temp->Next; free(temp); return value; } //先序遍历 void PreOrderTraversal(BinTree T) { Stack S = makeEmpty(); BinTree BT = T; while (BT || !isEmpty(S)) { while (BT) { printf("%d ", BT->Data); S = push(BT, S); BT = BT->Left; } if (!isEmpty(S)) { BT = pop(S); BT = BT->Right; } } } //中序遍历 void InOrderTraversal(BinTree T) { Stack S = makeEmpty(); BinTree BT = T; while (BT || !isEmpty(S)) { while (BT) { S = push(BT, S); BT = BT->Left; } if (!isEmpty(S)) { BT = pop(S); printf("%d ", BT->Data); BT = BT->Right; } } } //后序遍历 void PostOrderTraversal(BinTree T) { Stack S = makeEmpty(); BinTree BT = T; while (BT || !isEmpty(S)) { while (BT) { S = push(BT, S); BT = BT->Left; } if (!isEmpty(S)) { BT = pop(S); if (BT->Right) { S = push(BT, S); } else { printf("%d ", BT->Data); } BT = BT->Right; } } }
还有广度优先遍历,和深度优先遍历不一样,广度优先遍历会先访问离根节点最近的节点。二叉树的广度优先遍历又称按层次遍历。算法借助队列实现。
基本思路为,先把根节点入队,再让根节点出队,而后将根结点的左右子树入队,一直到队列空就遍历完成整个树。
代码实现:
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef struct QNode* Queue; typedef struct TreeNode* BinTree; #define ElementType1 int #define ElementType2 BinTree struct TreeNode { ElementType1 Data; BinTree Left; BinTree Right; }; struct QNode { ElementType2 Data; Queue Next; }; Queue makeEmpty() { Queue Q = (Queue)malloc(sizeof(struct QNode)); Q->Next = NULL; return Q; } void QueueAdd(ElementType2 value, Queue Q) { Queue temp = Q->Next; Queue newNode = (Queue)malloc(sizeof(struct QNode)); newNode->Data = value; newNode->Next = temp; Q->Next = newNode; } bool isEmpty(Queue Q) { if (Q->Next == NULL) { return true; } return false; } ElementType2 QueueDelete(Queue Q) { Queue ptrq = Q; if (!ptrq->Next) { printf("The queue has been empty."); return NULL; } while (ptrq->Next->Next) { ptrq = ptrq->Next; } Queue temp = ptrq->Next; ptrq->Next = NULL; ElementType2 value = temp->Data; free(temp); return value; } //层序遍历 void LevelOrderTraversal(BinTree T) { //空树直接退出 if (!T) { return; } Queue Q = makeEmpty(); QueueAdd(T, Q);//先把根节点压入队列 while (!isEmpty(Q)) { BinTree BT = QueueDelete(Q);//出队 printf("%d ", BT->Data); //若是该节点左右还有节点就压入队列 if (BT->Left) { QueueAdd(BT->Left, Q); } if (BT->Right) { QueueAdd(BT->Right, Q); } }
给定两棵树T1和T2。若是T1能够经过若干次左右孩子互换就变成T2,则咱们称两棵树是“同构”的。例如图1给出的两棵树就是同构的,由于咱们把其中一棵树的结点A、B、G的左右孩子互换后,就获得另一棵树。而图2就不是同构的。
现给定两棵树,请你判断它们是不是同构的。
输入格式:
输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号);随后N行,第i行对应编号第i个结点,给出该结点中存储的1个英文大写字母、其左孩子结点的编号、右孩子结点的编号。若是孩子结点为空,则在相应位置上给出“-”。给出的数据间用一个空格分隔。注意:题目保证每一个结点中存储的字母是不一样的。
输出格式:
若是两棵树是同构的,输出“Yes”,不然输出“No”。
输入样例1(对应图1):
8
A 1 2
B 3 4
C 5 -
D - -
E 6 -
G 7 -
F - -
H - -
8
G - 4
B 7 6
F - -
A 5 1
H - -
C 0 -
D - -
E 2 -
输出样例1:
Yes
输入样例2(对应图2):
8
B 5 7
F - -
A 0 3
C 6 -
H - -
D - -
G 4 -
E 1 -
8
D 6 -
B 5 -
E - -
H - -
C 0 2
G - 3
F - -
A 1 4
输出样例2:
No
代码实现:
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define ElemrnntType char #define MAXSIZE 10 #define Null -1 struct TreeNode { ElemrnntType Data; int Left; int Right; }T1[MAXSIZE],T2[MAXSIZE]; int buildTree(struct TreeNode T[]) { int N = 0; int root = -1; //注意看重点,必定要在这里读取加\n,要不会自闭的 scanf("%d\n", &N); //用来检查哪一个元素是根,默认都是0,在读入节点时,有指向的位置就不是 int check[MAXSIZE] = {0}; for (int i = 0; i < N;i++) { char left, right ,data; scanf("%c %c %c\n", &data, &left, &right); T[i].Data = data; T[i].Left = (left == '-') ? Null : (check[left - '0'] = 1, left - '0'); T[i].Right = (right == '-') ? Null : (check[right - '0'] = 1, right - '0'); } for (int i = 0; i < N;i++) { if (check[i] == 0) { root = i; break; } } //返回根节点 return root; } //采用递归算法,每次传入一个树的子树做为新树 bool IsomorphicTree(int root1,int root2) { //若是都是空的返回true if (root1==Null&&root2==Null) { return true; } //若是只有一颗空树返回false if ((root1 == Null && root2 != Null)||(root1 != Null && root2 == Null)) { return false; } //若是根节点不一样返回false, 相同则进行下一次递归判别 if (T1[root1].Data != T2[root2].Data){ return false; } else { //判断左右交换和没换只要有一种成立就返回true return (IsomorphicTree(T1[root1].Left, T2[root2].Left) && IsomorphicTree(T1[root1].Right, T2[root2].Right)) || (IsomorphicTree(T1[root1].Left, T2[root2].Right) && IsomorphicTree(T1[root1].Right, T2[root2].Left)); } } int main() { //读取2颗树 int root1 = buildTree(T1); int root2 = buildTree(T2); //判别是否为异构树 if (IsomorphicTree(root1, root2)) { printf("Yes\n"); } else { printf("No\n"); } }
Given a tree, you are supposed to list all the leaves in the order of top down, and left to right.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤10) which is the total number of nodes in the tree -- and hence the nodes are numbered from 0 to N−1. Then N lines follow, each corresponds to a node, and gives the indices of the left and right children of the node. If the child does not exist, a "-" will be put at the position. Any pair of children are separated by a space.
Output Specification:
For each test case, print in one line all the leaves' indices in the order of top down, and left to right. There must be exactly one space between any adjacent numbers, and no extra space at the end of the line.
Sample Input:
8 1 - - - 0 - 2 7 - - - - 5 - 4 6
Sample Output:
4 1 5
题目的意思就是传入一个树,要对该树层序遍历的顺序,打印出他的全部叶节点,与上一题同样,注意找出根节点。
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef struct QNode* Queue; #define MAXSIZE 10 #define Null -1 struct TreeNode { int Left; int Right; }T[MAXSIZE]; struct QNode { int index; Queue Next; }; Queue makeEmpty() { Queue Q = (Queue)malloc(sizeof(struct QNode)); Q->Next = NULL; return Q; } void QueueAdd(int value, Queue Q) { Queue temp = Q->Next; Queue newNode = (Queue)malloc(sizeof(struct QNode)); newNode->index = value; newNode->Next = temp; Q->Next = newNode; } bool isEmpty(Queue Q) { if (Q->Next == NULL) { return true; } return false; } int QueueDelete(Queue Q) { Queue ptrq = Q; if (!ptrq->Next) { printf("The queue has been empty."); return -1; } while (ptrq->Next->Next) { ptrq = ptrq->Next; } Queue temp = ptrq->Next; ptrq->Next = NULL; int value = temp->index; free(temp); return value; } int buildTree(struct TreeNode T[]) { int N = 0; int root = -1; //注意看重点,必定要在这里读取加\n,要不会自闭的 scanf("%d\n", &N); //用来检查哪一个元素是根,默认都是0,在读入节点时,有指向的位置就不是 int check[MAXSIZE] = { 0 }; for (int i = 0; i < N; i++) { char left, right, data; scanf("%c %c\n", &left, &right); T[i].Left = (left == '-') ? Null : (check[left - '0'] = 1, left - '0'); T[i].Right = (right == '-') ? Null : (check[right - '0'] = 1, right - '0'); } for (int i = 0; i < N; i++) { if (check[i] == 0) { root = i; break; } } //返回根节点 return root; } //层序遍历出叶节点 void LevelOrderTraversal(int root) { //空树直接退出 if (root==-1) { return; } bool isfrist = true; Queue Q = makeEmpty(); QueueAdd(root, Q);//先把根节点压入队列 while (!isEmpty(Q)) { root = QueueDelete(Q);//出队 //叶节点就抛出 if ((T[root].Left == -1)&& (T[root].Right == -1)) { if (isfrist) { printf("%d", root); isfrist = false; } else { printf(" %d", root); } } //若是该节点左右还有节点就压入队列 if (T[root].Left !=-1) { QueueAdd(T[root].Left, Q); } if (T[root].Right !=-1) { QueueAdd(T[root].Right, Q); } } } int main() { //读取树 int root = buildTree(T); //层序遍历叶节点 LevelOrderTraversal(root); }
An inorder binary tree traversal can be implemented in a non-recursive way with a stack. For example, suppose that when a 6-node binary tree (with the keys numbered from 1 to 6) is traversed, the stack operations are: push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); pop(); pop(). Then a unique binary tree (shown in Figure 1) can be generated from this sequence of operations. Your task is to give the postorder traversal sequence of this tree.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤30) which is the total number of nodes in a tree (and hence the nodes are numbered from 1 to N). Then 2N lines follow, each describes a stack operation in the format: "Push X" where X is the index of the node being pushed onto the stack; or "Pop" meaning to pop one node from the stack.
Output Specification:
For each test case, print the postorder traversal sequence of the corresponding tree in one line. A solution is guaranteed to exist. All the numbers must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop
Sample Output:
3 4 2 6 5 1
题目的大意根据堆栈的顺序获得树,获得他的后序遍历,仔细观察不难发现,push的顺序对应了前序遍历,pop的顺序对应了中序遍历,因此对于该题的解法为,先将前序遍历顺序和中序遍历顺序保存在数组中,再根据前序遍历和中序遍历推导出后序遍历。由于后序遍历为左右根,因此能够采用堆栈的方法的,先将树的根堆入,再堆入右子树,再堆入左子树,递归完成。
代码实现:
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define MAXSIZE 30 #define ElementType char typedef struct SNode* Stack; #define ElementType char struct SNode { ElementType Data; Stack Next; }; char PreOrderTraversal[MAXSIZE]; char InOrderTraversal[MAXSIZE]; //get link stack length,the empty stack has a header, the default length is 0 int length(Stack ptrs) { Stack temp = ptrs; int len = 0; while (temp->Next) { temp = temp->Next; len++; } return len; } //make an empty linked Stack,generate an empty header(length is 1) Stack makeEmpty() { Stack ptrs; ptrs = (Stack)malloc(sizeof(struct SNode)); ptrs->Next = NULL; return ptrs; } //check the linklist is empty bool isEmpty(Stack ptrs) { if (length(ptrs) == 0) { return true; } else { return false; } } //push Stack push(ElementType value, Stack ptrs) { Stack newLNode = (Stack)malloc(sizeof(struct SNode)); newLNode->Data = value; newLNode->Next = ptrs->Next; ptrs->Next = newLNode; return ptrs; } //pop ElementType pop(Stack ptrs) { if (isEmpty(ptrs)) { printf("the Stack has been empty."); return -1; } Stack temp = ptrs->Next; ElementType value = temp->Data; ptrs->Next = temp->Next; free(temp); return value; } //读入并返回读取几个节点 int read() { int N = 0; scanf("%d\n", &N); Stack S = makeEmpty(); // int preindex = 0; int inindex = 0; for (int i = 0; i < 2 * N; i++) { char operation[10]; gets(operation); //若是读取的操做(第二个字母是o),说明是pop,不然为push if (operation[1]=='o') { InOrderTraversal[inindex++] = pop(S); } else { int end = 0; while (operation[end] != '\0')end++; char dest[3] = {""}; strncpy(dest, operation + 5, end-4); dest[2] = '\0'; int num = atoi(dest); PreOrderTraversal[preindex++] = num; push(num, S); } } return N; } //left 和 right 分别为传入树的在中序遍历的左右下标,root为当前树的根 void getpost(Stack S,int left,int right,int root) { if (left > right) { return; } else { int index = left; while (index < right && InOrderTraversal[index] != PreOrderTraversal[root]) { index++; } push(PreOrderTraversal[root], S); getpost(S,index+1,right,root+1+index-left); getpost(S,left,index-1,root+1); } } int main() { /* * 分析题目能够看出,push的顺序对应了先序遍历,push和pop出的序列是中序 * 因此题目无需创建树,只须要经过先序和中序还原后序遍历 */ //读取先序序列和中序序列 int num = read(); //根据先序序列和中序序列生成后序遍历 Stack PostOrderTraversal = makeEmpty(); getpost(PostOrderTraversal, 0, num-1, 0); //打印后序遍历 bool isfirst = true; while (!isEmpty(PostOrderTraversal)) { if (isfirst) { printf("%d", pop(PostOrderTraversal)); isfirst = false; } else { printf(" %d", pop(PostOrderTraversal)); } } }