目录
1、树的概念和结构
树是一种非线性的数据结构,是由n个有限节点组成的具有层次关系的集合,它倒起来像一棵树,所以称为树,树的根朝上,叶朝下。
有一个特殊的节点:根节点,其余结点被分成M(M>0)个互不相交的集合,树是递归定义的(简称“套娃”)。任何一棵树有根和子树(N>=0)组成。
下面有几个定义需要知道:
- 节点的度:一个节点含有的子树的个数称为该节点的度。
- 叶节点或终端节点:度为0的节点称为叶节点
- 双亲节点:若这个节点有子节点,则这个节点为双亲节点。
- 孩子节点或子节点:一个节点含有的子树的跟节称为该节点的子节点。
- 兄弟节点:共有双亲节点的节点称为兄弟节点。
- 节点的层次:根为一层,其子节点为二层,以此类推。
- 树的高度或深度:树中节点的最大层次。
- 森林:有多棵互不相交树的集合称为森林
树的表示:
struct TreeNode
{
int val;
struct TreeNode* leftchild;//无论一个双亲节点有多少个孩子,child指向左边开始的第一个孩子
struct TreeNode* rightBrother;
};//左孩子右兄弟表示
2、二叉树概念和结构
一棵二叉树是结点的一个有限集合,该集合: ①或者为空,②由一个根结点加上两棵别称为左子树和右子树的二叉树组成。
二叉树不存在度大于二的节点,二叉树的节点有左右之分,不能颠倒,所以二叉树又叫做有序树。
二叉树有两个特殊的二叉树:
- 满二叉树:就是每一层都是满的。如果层数为k,则节点总数为2^k-1.
- 完全二叉树:就是前K-1层是满的,最后一层不满,最后一次是从左到右必须是连续的。
- 满二叉树是特殊的完全二叉树。
对任何一棵二叉树, 如果度为0其叶结点个数为n , 度为2的分支结点个数为 m,则有n=m+1。
3、二叉树的存储结构
二叉树一般用顺序结构和链式结构来存储。
1、顺序结构
顺序结构存储是用数组来进行存储的,数组存储只适用于完全二叉树,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2、链式结构
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。
typedef int BTDataType;
//二叉链
struct BinaryTreeNode
{
BTDataType val;
struct BinaryTreeNode*left;
struct BinaryTreeNode*right;
};
//三叉链
struct BinaryTreeNode
{
BTDataType val;
struct BinaryTreeNode*parent;
struct BinaryTreeNode*left;
struct BinaryTreeNode*right;
};
4、堆
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
堆相当于完全二叉树,分为大堆(任何一个双亲>=孩子,根为最大),小堆(任何一个双亲<=孩子,根为最小)。
以数组的形式存储,知道双亲在数组的下标就知道左右孩子的下标(设双亲下标为i,左孩子:2*i+1,右孩子:2*i+2),知道孩子的下标也能知道双亲的下标(设孩子下标为j,双亲:(j-1)/2)。
堆的实现:
Heap.h:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* arr;
int size;
int capacity;
}Heap;
//初始化
void HeapInit(Heap*ph);
//销毁
void HeapDestroy(Heap* ph);
//插入
void HeapPush(Heap* ph,HPDataType x);
//堆顶的删除
void HeapPop(Heap* ph);
HPDataType HeapTop(Heap* ph);
bool HeapEmpty(Heap* ph);
void Swap(HPDataType* a, HPDataType* b);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
Heap.c:
void HeapInit(Heap* ph)
{
assert(ph);
ph->arr = NULL;
ph->capacity = ph->size = 0;
}
//销毁
void HeapDestroy(Heap* ph)
{
assert(ph);
free(ph->arr);
ph->arr = NULL;
ph->capacity = ph->size = 0;
}
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
void AdjustUp(HPDataType*a,int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//插入
void HeapPush(Heap* ph,HPDataType x)
{
assert(ph);
if (ph->size == ph->capacity)
{
int newcapacity = ph->capacity == 0 ? 4 : ph->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(ph->arr, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ph->arr = tmp;
ph->capacity = newcapacity;
}
ph->arr[ph->size++] = x;
AdjustUp(ph->arr, ph->size - 1);
}
//向下调整算法
void AdjustDown(HPDataType*a,int n,int parent)
{
//先假设左孩子小
int child = parent * 2 + 1;
//找小的孩子
while(child < n)//child>=说明孩子已经到叶子了
{
if (child+1<n && a[child + 1] < a[child])
{
++child;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* ph)
{
assert(ph);
assert(ph->size > 0);
Swap(&ph->arr[0], &ph->arr[ph->size - 1]);
ph->size--;
AdjustDown(ph->arr,ph->size,0);
}
HPDataType HeapTop(Heap* ph)
{
assert(ph);
assert(ph->size > 0);
return ph->arr[0];
}
bool HeapEmpty(Heap* ph)
{
assert(ph);
return ph->size == 0;
}
test.c
void TestHeap01()
{
int a[] = { 4,2,8,1,5,6,9,7 };
Heap h;
HeapInit(&h);
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
HeapPush(&h, a[i]);
}
while (!HeapEmpty(&h))
{
printf("%d ", HeapTop(&h));
HeapPop(&h);
}
printf("\n");
//找最小的前k个
/*int k;
scanf("%d", &k);
while (k--)
{
printf("%d ", HeapTop(&h));
HeapPop(&h);
}*/
HeapDestroy(&h);
}
int main()
{
TestHeap01();
return 0;
}
堆的插入:
上述代码的向上调整函数和向下调整函数为什么不传Heap*ph。是为了接下来堆排序做准备的。
堆排序:
void HeapSort(int* a, int n)
{
//建堆
//降序,建大堆的代价太大,堆的顺序会被打乱
//所以,降序,建小堆;升序,建大堆
for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
void HeapSort(int*a,int n)
{
for(int i=(n-1-1)/2;i>=0;i--)
{
AdjustDown(a,n,i);
}
int end=n-1;
while(end>0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
向上调整建堆,这个算法时间复杂度为O(n*log(n)),但向下调整建堆的时间复杂度为O(n)。
向上调整建堆:T(h)=2^1*1+2^2*2···2^(h-1)*(h-1),T(N)=-N+(N+1)*(log(N+1)-1)+1约等于n*log(n).
向下调整建堆:T(h)=2^0*(h-1)+2^1*(h-2)+···+2^(h-2)*1
N=2^h-1,h=log(N+1),可得T(N)=N-log(N+1),所以时间复杂度为O(n)。
top k:
void CreateData()
{
int n = 100000;
srand(time(0));
FILE* fin = fopen("text.txt", "w");
if (fin == NULL)
{
perror("fopen fail");
return;
}
for (int i = 0; i < n; i++)
{
int ret = (rand() + i) % n;
fprintf(fin, "%d\n", ret);
}
fclose(fin);
}
void test()
{
FILE* fout = fopen("text.txt", "r");
if (fout == NULL)
{
perror("fopen fail");
return;
}
int k;
printf("请输入k的值:>");
scanf("%d",&k);
int* arr = (int*)malloc(sizeof(int) * k);
if (arr == NULL)
{
perror("malloc fail");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &arr[i]);
}
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, k, i);
}
int x = 0;
while (fscanf(fout, "%d", &x) > 0)
{
if (x > arr[0])
{
arr[0] = x;
AdjustDown(arr, k, 0);
}
}
printf("最大的前%d个数\n", k);
for (int i = 0; i < k; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
5、二叉树
遍历分为四种:前序,中序,后序,层序。
遍历二叉树:前、中、后序:
前序遍历:访问根结点的操作发生在遍历其左右子树之前。
中序遍历:访问根结点的操作发生在遍历其左右子树之中(间)。
后序遍历:访问根结点的操作发生在遍历其左右子树之后。
前序:根,左子树,右子树
中序:左子树,根,右子树
后序:左子树,右子树,根
前序:上图前序顺序为:1->2->3->N->N->N->4->5->N->N->6->N->N(N为NULL),从根开始,有左子树就走,然后左子树的第一个为根,等没有左子树就看看右子树有没有,没有就看之前的有没有右子树。
中序:顺序为N->3->N->2->N->1->N->5->N->4->N->6->N.
后序:顺序为N->N->3->N->2->N->N->5->N->N->6->4->1.
程序实现:
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc,fail");
return;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
void PrevOrder(BTNode*root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ",root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
int main()
{
BTNode* root = CreatBinaryTree();
PrevOrder(root);
printf("\n");
InOrder(root);
printf("\n");
PostOrder(root);
return 0;
}
与上面的顺序一样。
层序:
层序我们以利用队列的形式的来实现,这里需要把队列中typedef int QDataType改为
typedef struct BinaryTreeNode QDataType。
//层序遍历(上一层带下一层)
void TreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
}
节点个数以及高度:
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left) + TreeSize(root->right) + 1;
}
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL || root->right == NULL)
return 1;
return TreeHeight(root->left) > TreeHeight(root->right) ?
TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
}
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if(root == NULL)
return NULL;
if(root->data == x)
return root;
BTNode* ret1 = TreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2 = TreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
int main()
{
/*size = 0;
printf("size:%d\n", TreeSize(root));
size = 0;
printf("size:%d\n", TreeSize(root));*/
printf("TreeSize:%d\n", TreeSize(root));
printf("TreeLeafSize:%d\n", TreeLeafSize(root));
printf("TreeHeight:%d\n", TreeHeight(root));
printf("TreeLevelKSize:%d\n",TreeLevelKSize(root, 3));
return 0;
}
二叉树的建立与销毁
BTNode* TreeCreate(char*a,int*pi)
{
if(a[*pi]=='#')
{
(*pi)++;
return NULL;
}
BTNode*tmp=(BTNode*)malloc(sizeof(BTNode));
tmp->data=a[(*pi)++];
tmp->left=TreeCreate(a,pi);
tmp->right=TreeCreate(a,pi);
return tmp;
}//给数组来创建树
void TreeDestroy(BTNode* root)
{
if (root == NULL)
return;
TreeDestroy(root->left);
TreeDestroy(root->right);
free(root);
}
判断是不是完全二叉树
这里还是利用队列的方法来实现,完全二叉树的特点就是最后一排从左到右有序,那么第一次遇到NULL就返回,看看队列里面是否为空,空为完全二叉树,反之,不是。
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
while(!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
完!谢谢大家观看。