如何深入理解并掌握数据结构中的二叉树(BinaryTree)原理和应用?

2026-04-11 21:192阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5658个文字,预计阅读时间需要23分钟。

如何深入理解并掌握数据结构中的二叉树(BinaryTree)原理和应用?

1. 树的概念及其结构 树是一种非线性数据结构,由n(n≥0)个有限节点组成,这些节点按层次关系连接,形成具有层次关系的集合。树被称为树,因为它看起来像一棵倒挂的树,也意味着它是根节点和子节点的关系。

1.1 树的概念 树是一种数据结构,由节点组成,每个节点包含数据和指向子节点的指针。它具有以下特点: - 非线性结构:节点之间没有固定的顺序。 - 层次关系:节点之间存在父子关系,形成层次结构。 - 根节点:树中唯一的节点,没有父节点。 - 子节点:根节点下的节点,可以有多个。 - 叶节点:没有子节点的节点。

1、树的概念及其结构

1.1树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

  • 有一个特殊的结点,称为根结点,根结点没有前驱结点
  • 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。

如下图为树的一种结构

注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

1.2树的相关概念

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为2

叶结点或终端结点:度为0的结点称为叶结点; 如上图:J、K、O、P、Q、R、S、I...等结点为叶结点

非终端结点或分支结点:度不为0的结点; 如上图:B、C、E、F、G、H...等结点为分支结点

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为2

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:J、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

如何深入理解并掌握数据结构中的二叉树(BinaryTree)原理和应用?

1.3树的表示

树的结构不仅仅要保存当前节点的数据,还要保存左右孩子节点的地址或者孩子和兄弟的地址,我们常用的表示方法是左孩子右兄弟的表示方法,接下来我简单介绍一下左孩子右兄弟的表示方法:

typedef int DataType; struct Node { struct Node* firstChild1; // 第一个孩子结点 struct Node* pNextBrother; // 指向其下一个兄弟结点 DataType data; // 结点中的数据域 };

左孩子右兄弟表示法,每次在遍历的时候先找到最左边的第一个孩子,然后通过右兄弟不断遍历找到同层的兄弟。 这样的设计模式在文件系统中,比较常见。


2、二叉树的概念和结构表示

2.1概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成

2.2特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.3 二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(h-1) 个结点.
  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h-1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0$, 度为2的分支结点个数为 n2,则有n0=n2+1
  4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log_2(n+1). (ps:是log以2为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
  1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
  2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
  3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

2.4二叉树的存储结构

2.4.1顺序存储

二叉树的顺序存储是存储到数组里面的,但是当我们把数据放到数组里面发现,像我们平常的二叉树在数组中出现了大量的内存浪费,顺序用数组存储只适合用完全二叉树和满二叉树的存储。

2.4.2链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。



2.4.2.1 顺序存储二叉树结构体表示

//数组存储 逻辑结构 还有物理结构 //堆 逻辑结构 完全二叉树 // 物理结构 数组 typedef int HeapDataType; typedef struct Heap { HeapDataType* a; int size; int capacity; }HP;

2.4.2.2 链式二叉树节点结构表示

typedef int BTDataType; // 二叉链 struct BinaryTreeNode { struct BinTreeNode* left; // 指向当前结点左孩子 struct BinTreeNode* right; // 指向当前结点右孩子 BTDataType data; // 当前结点值域 }

3、顺序存储结构的实现

3.1初始化

函数声明

void HeapInit(HP* hp);

函数实现

void HeapInit(HP* hp) { assert(hp); hp->capacity = 0; hp->size = 0; hp->a = NULL; }

3.2向上调整操作

函数声明

void AdjustUp(int* a, int n, int child);

为什么需要进行向上调整操作?

因为我们把数据存储在数组里面,物理存储是连续的,逻辑关系是树状关系,树之间的逻辑关系的维持是通过下标来找到父亲节点和孩子节点,向上调整是建堆的过程,父亲节点和孩子节点不断比较,将大数或者小数向上调整,来建成大堆或者小堆。

本段代码是向上调整建大堆 (孩子节点比父亲大,那么久调整位置)

时间复杂度:O(n*log(n))

void AdjustUp(int* a, int n, int child) //向上调整 { assert(a); int parent = (child - 1) / 2; while (child >0) { if (a[child] > a[parent]) //孩子比父亲大 那么就交换 { HeapDataType tmp = a[child]; a[child] = a[parent]; a[parent] = tmp; child = parent; parent = (child - 1) / 2; } else { break; } } }

3.3插入操作

函数声明

void HeapPush(HP* hp,HeapDataType x);

函数实现

void HeapPush(HP* hp, HeapDataType x) { assert(hp); if (hp->capacity == hp->size) { //增容 size_t newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2; HeapDataType* tmp = (HeapDataType)realloc(hp->a,sizeof(HeapDataType)*newcapacity); if (tmp == NULL) { perror("realloc fail"); return; } hp->a = tmp; hp->capacity = newcapacity; } //如何保证插入的数据之后还是大堆 那么我们就要通过访问下标来实现比较 //堆插入结点对其他结点没有影响 只可能影响从他到根节点路径上节点的关系 //向上调整的操作 和自己的父亲比较大小 hp->a[hp->size] = x; hp->size++; //向上调整 AdjustUp(hp->a, hp->size, hp->size - 1); }

我们设计的是动态增长长度的数组,所以需要进行判断是否需要扩容,将数据插入到最后的位置之后,整体可能不是大堆,那么需要进行向上调整来调整使得整个父亲节点都大于他的孩子节点,满足大堆的性质。

3.4向下调整操作

为什么要向下调整?

当child位置构成堆,但是左右子树还是大堆的时候,而且parent位置不符合大堆的性质,那么就要进行向下调整。

我们通过parent位置找到child位置,选出两个孩子中最小的,和parent比较如果比parent大,那么就交换

int child = parent * 2 + 1;

函数声明:

void HeapPop(HP* hp);

函数实现:

//除了child位置构成堆 左右子树都还是大堆 parent位置不符合大堆性质 void AdjustDown(HPDataType* a, int n,int parent) { int child = parent * 2 + 1; //和大儿子比 while (child<n) //大于n就是越界 { if (child+1<n && a[child + 1] < a[child]) //要注意防止越界问题 顺序不能乱 { ++child; } if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); //接着把child赋给parent parent = child; child = parent * 2 + 1; } else { break; } } }

3.5删除操作

实现思路:为了不使大堆或者小堆的性质发生改变,先把top元素和最后一个元素交换位置,让后再向下调整,保持原来的性质,再不断调整递归,删除一次递归调整数据个数-1,删除越多把数据放到后面的越多,那么数据进行向上调整的越少。

函数声明:

void HeapPop(HP* hp);

函数实现:

void HeapPop(HP* hp) { //删除根节点 //删除最大的那么剩下找出第二个最大的 assert(hp); assert(!HeapEmpty(hp)); Swap(&hp->a[0], &hp->a[hp->size - 1]); hp->size--; AdjustDown(hp->a, hp->size+1, &hp->a[0]); }

通过控制hp->size的长度来实现控制AdjustDown的数据范围。

3.6堆顶元素

取堆顶的元素比较简单,因为根节点是放在数组的第一位,那么直接返回根节点的值。

函数声明:

HPDataType HeapTop(HP* hp);

函数实现:

HPDataType HeapTop(HP* hp) { assert(hp); return hp->a[0]; }

3.7 堆的判空

我们在定义数组来存数据的时候,定义了一个size来记录数组的元素,那么通过如果size=0那么就为空

函数声明:

bool HeapEmpty(HP* hp);

函数实现:

bool HeapEmpty(HP* hp) { assert(hp); return hp->size == 0; }

3.8堆的元素个数

函数声明:

int HeapSize(HP* hp);

函数实现:

int HeapSize(HP* hp) { assert(hp); return hp->size; }

4、链式存储结构的实现

4.1链式二叉树的创建

前序建立二叉树,将char数组内的内容一个个读出来,再前序遍历,如果读到#那么就返回。

BTNode* BuyBTNode(BTDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); node->_left = NULL; node->_right = NULL; node->_data = x; return node; } BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi) { if (a[*pi] != '#') { BTNode* root = BuyBTNode(a[*pi]); ++(*pi); root->_left = BinaryTreeCreate(a, n, pi); ++(*pi); root->_right = BinaryTreeCreate(a, n, pi); return root; } else { return NULL; } }

4.2 链式二叉树的前序遍历

先访问根节点,再左子树,再右子树

void BinaryTreePrevOrder(BTNode* root) { if (root == NULL) return; printf("%c ", root->_data); BinaryTreePrevOrder(root->_left); //遍历左子树 BinaryTreePrevOrder(root->_right); //遍历右子树 }

4.3 链式二叉树的中序遍历

先访问左子树,再访问根节点,再访问右子树

void BinaryTreeInOrder(BTNode* root) { if (root == NULL) return; //先左子树再根结点再右子树 BinaryTreeInOrder(root->_left); printf("%c ", root->_data); BinaryTreeInOrder(root->_right); }

4.4 链式二叉树的后序遍历

先访问左子树,再访问右子树,再访问根节点

void BinaryTreePostOrder(BTNode* root) { if (root == NULL) return; BinaryTreePostOrder(root->_left); BinaryTreePostOrder(root->_right); printf("%c ", root->_data); }

4.5 链式二叉树的层序遍历

利用queue 当root进队列之后,访问后删除,将左右孩子结点入队,不断递归下去,实现了层序遍历

void BinaryTreeLevelOrder(BTNode* root) { Queue q; QueueInit(&q); if (root) QueuePush(&q, root); while (QueueEmpty(&q) != 0) { QUDataType front = QueueFront(&q); printf("%c ", front->_data); QueuePop(&q); if (front->_left) { QueuePush(&q, front->_left); } if (front->_right) { QueuePush(&q, front->_right); } } printf("\n"); }

4.6 链式二叉树的销毁

void BinaryTreeDestory(BTNode** pptree) { BTNode* root = *pptree; if (root == NULL) return; BinaryTreeDestory(&root->_left); BinaryTreeDestory(&root->_right); free(root); *pptree = NULL; }

4.7 二叉树节点个数

int BinaryTreeSize(BTNode* root) { if(root == NULL) return 0; return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right)+ 1; }

4.8 二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k) { if (root == NULL) return 0; if (k == 1) return 1; return BinaryTreeLevelKSize(root->_left, k-1) + BinaryTreeLevelKSize(root->_right, k-1); }

4.9 二叉树的高度

int BinaryTreeHeight(BTNode* root) { int leftHeight, rightHeight; if (root == NULL) { return 0; } leftHeight = BinaryTreeHeight(root->_left); rightHeight = BinaryTreeHeight(root->_right); return leftHeight > rightHeight ? leftHeight+1 : rightHeight+1; }

4.10 二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root) { if (root == NULL) return 0; if (root->_left == NULL && root->_right == NULL) return 1; return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right); }

4.11 二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) { BTNode* ret; if (root == NULL) return NULL; if (root->_data == x) return root; ret = BinaryTreeFind(root->_left, x); if (ret) return ret; ret = BinaryTreeFind(root->_right, x); if (ret) return ret; return NULL; }

5、顺序存储二叉树的完整代码

//Heap.h #pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> #include<time.h> //堆 小堆那么树中所有的父亲都小于或等于孩子 大堆树所有的父亲都大于或等于孩子 typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }HP; //初始化 void HeapInit(HP* hp); void HeapPush(HP* hp, HPDataType x); void HeapDesory(HP* hp); void HeapPop(HP* hp); HPDataType HeapTop(HP* hp); bool HeapEmpty(HP* hp); int HeapSize(HP* hp); void AdjustDown(HPDataType* a, int n, int parent); void AdjustUp(HPDataType* a, int child); void Swap(HPDataType* p1, HPDataType* p2);

//Heap.c #include"Heap.h" //大堆 void HeapInitArray(HP* hp) { assert(hp); hp->a = (HPDataType*)malloc(sizeof(HPDataType) * 4); if (hp->a == NULL) { perror("malloc fail"); } hp->capacity = 4; hp->size = 0; //向下建堆 for (int i = (hp->size - 1 - 1) / 2; i >= 0; --i) { AdjustDown(hp->a, hp->size, i); //调整叶子节点的父亲节点 如果父亲节点比孩子节点小那么调整 不断遍历到所有父亲节点 } } void Swap(HPDataType* p1, HPDataType* p2) { HPDataType x = *p1; *p1 = *p2; *p2 = x; } //向上调整 void AdjustUp(HPDataType* a, int child) { int parent = (child - 1) / 2; while (child > 0) { if (a[child] < a[parent]) { /*HPDataType tmp = a[child]; a[child] = a[parent]; a[parent] = tmp;*/ Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void HeapPush(HP* hp, HPDataType x) { //位置都可以放 assert(hp); if (hp->capacity == hp->size) { //扩容 HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * hp->capacity * 2); if (tmp == NULL) { perror("realloc fail"); } hp->a = tmp; hp->capacity *= 2; } //插入数据 hp->a[hp->size] = x; hp->size++; //判断一下需不需要向上调整 AdjustUp(hp->a, hp->size - 1); } void HeapDesory(HP* hp) { assert(hp); free(hp->a); hp->a = NULL; hp->capacity = hp->size = 0; } //除了child位置构成堆 左右子树都还是大堆 parent位置不符合大堆性质 void AdjustDown(HPDataType* a, int n,int parent) { int child = parent * 2 + 1; //和大儿子比 while (child<n) //大于n就是越界 { if (child+1<n && a[child + 1] < a[child]) //要注意防止越界问题 顺序不能乱 { ++child; } if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); //接着把child赋给parent parent = child; child = parent * 2 + 1; } else { break; } } } void HeapPop(HP* hp) { //删除根节点 //删除最大的那么剩下找出第二个最大的 assert(hp); assert(!HeapEmpty(hp)); Swap(&hp->a[0], &hp->a[hp->size - 1]); hp->size--; AdjustDown(hp->a, hp->size+1, &hp->a[0]); } HPDataType HeapTop(HP* hp) { assert(hp); return hp->a[0]; } bool HeapEmpty(HP* hp) { assert(hp); return hp->size == 0; } int HeapSize(HP* hp) { assert(hp); return hp->size; }

//test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"Heap.h" //逻辑结构: 想象出来的 //物理结构: 实实在在在内存种如何存储的 //数组种层序存储 那么孩子如何找到自己的父亲位置 那么就要从通过孩子算父亲位置parent=(child-1)/2 // 通过父亲找左右孩子位置 leftchild=parent*2+1 rightchild=parent*2+2 //难度就是很抽象 数组存储表示二叉树只适合完全二叉树 //int main() //{ // HP hp; // // HeapInit(&hp); // HeapPush(&hp, 4); // HeapPush(&hp, 10); // HeapPush(&hp,20); // HeapPush(&hp, 50); // HeapPush(&hp, 10); // HeapPush(&hp, 3); // // while (!HeapEmpty(&hp)) // { // printf("%d ", HeapTop(&hp)); // HeapPop(&hp); // } // printf("\n"); // // // return 0; //} //升序是建大堆 降序是建小堆 void HeapSort(int* a, int n) { //建堆 向上模拟建堆 时间复杂度 O(N*logN) 如果叶子节点多那么调整次数就多 (双多) //for (int i = 1; i < n; ++i) //{ // AdjustUp(a, i); //第二个要传 i就是child 现在已经是大堆了 模拟插入建堆 大堆 //} //向下调整建堆 时间复杂度O(N) 最后一层不用调整 最后一层占一半的节点 提高了效率 节点次数少调整次数多 节点次数多调整次数少 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); //调整叶子节点的父亲节点 如果父亲节点比孩子节点小那么调整 不断遍历到所有父亲节点 } //排升序建大堆 每次取出最大的 和最后一个叶节点交换 把前面的看作堆 int i = 1; //时间复杂度O(N*logN) while (n-i!=0) { Swap(&a[0], &a[n - i]); AdjustDown(a, n - i, 0); i++; } //int end = n - 1; //while (end > 0) //{ // Swap(&a[end], &a[0]); // AdjustDown(a, end, 0); // --end; //} }

6、链式二叉树的完整代码

test.c代码可以根据提供的头文件和函数实现文件进行测试

//BinaryTree.h #pragma once #include <stdio.h> #include <malloc.h> #include <assert.h> #include <stdlib.h> typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType _data; struct BinaryTreeNode* _left; struct BinaryTreeNode* _right; }BTNode; // a是一个前序遍历的数组 BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi); void BinaryTreeDestory(BTNode** root); int BinaryTreeSize(BTNode* root); int BinaryTreeLeafSize(BTNode* root); int BinaryTreeLevelKSize(BTNode* root, int k); BTNode* BinaryTreeFind(BTNode* root, BTDataType x); // 遍历 递归&非递归 void BinaryTreePrevOrder(BTNode* root); void BinaryTreeInOrder(BTNode* root); void BinaryTreePostOrder(BTNode* root); void BinaryTreeLevelOrder(BTNode* root); int BinaryTreeComplete(BTNode* root); void BinaryTreePrevOrderNonR(BTNode* root); void BinaryTreeInOrderNonR(BTNode* root); void BinaryTreePostOrderNonR(BTNode* root); void TestBinaryTree();

//BinaryTree.c #pragma once #include<stdio.h> #include<assert.h> #include<string.h> #include<stdlib.h> typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType _data; struct BinaryTreeNode* _left; struct BinaryTreeNode* _right; }BTNode; // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi); // 二叉树销毁 void BinaryTreeDestory(BTNode** root); // 二叉树节点个数 int BinaryTreeSize(BTNode* root); // 二叉树叶子节点个数 int BinaryTreeLeafSize(BTNode* root); // 二叉树第k层节点个数 int BinaryTreeLevelKSize(BTNode* root, int k); // 二叉树查找值为x的节点 BTNode* BinaryTreeFind(BTNode* root, BTDataType x); // 二叉树前序遍历 void BinaryTreePrevOrder(BTNode* root); // 二叉树中序遍历 void BinaryTreeInOrder(BTNode* root); // 二叉树后序遍历 void BinaryTreePostOrder(BTNode* root); // 层序遍历 void BinaryTreeLevelOrder(BTNode* root); // 判断二叉树是否是完全二叉树 int BinaryTreeComplete(BTNode* root);


//Queue.h #pragma once #include <stdio.h> #include <malloc.h> #include <assert.h> struct BinaryTreeNode; typedef struct BinaryTreeNode* QUDataType; typedef struct QueueNode { struct QueueNode* _next; QUDataType _data; }QueueNode; typedef struct Queue { QueueNode* _front; // 队头 QueueNode* _back; // 队尾 }Queue; void QueueInit(Queue* pq); void QueueDestory(Queue* pq); QueueNode* BuyQueueNode(QUDataType x); void QueuePush(Queue* pq, QUDataType x); void QueuePop(Queue* pq); QUDataType QueueFront(Queue* pq); QUDataType QueueBack(Queue* pq); int QueueEmpty(Queue* pq); int QueueSize(Queue* pq); void TestQueue();

//Queue.c #pragma once #include "Queue.h" void QueueInit(Queue* pq) { assert(pq); pq->_front = NULL; pq->_back = NULL; } void QueueDestory(Queue* pq) { QueueNode* cur; assert(pq); cur = pq->_front; while (cur != NULL) { QueueNode* next = cur->_next; free(cur); cur = next; } pq->_front = pq->_back = NULL; } QueueNode* BuyQueueNode(QUDataType x) { QueueNode* node = (QueueNode*) malloc(sizeof(QueueNode)); assert(node); node->_next =NULL; node->_data = x; return node; } void QueuePush(Queue* pq, QUDataType x) { assert(pq); if (pq->_back == NULL) { pq->_front = pq->_back = BuyQueueNode(x); } else { QueueNode* back = BuyQueueNode(x); pq->_back->_next = back; pq->_back = back; } } void QueuePop(Queue* pq) { QueueNode* next; assert(pq); next = pq->_front->_next; free(pq->_front); pq->_front = next; if (next == NULL) { pq->_back = NULL; } } QUDataType QueueFront(Queue* pq) { assert(pq); return pq->_front->_data; } // 空 0 // 非空 1 int QueueEmpty(Queue* pq) { assert(pq); return pq->_front == NULL ? 0 : 1; } int QueueSize(Queue* pq) { int size = 0; QueueNode* cur = pq->_front; while (cur) { size++; cur = cur->_next; } return size; } QUDataType QueueBack(Queue* pq) { return pq->_back->_data; } //void TestQueue() //{ // Queue q; // QueueInit(&q); // // QueuePush(&q, 1); // QueuePush(&q, 2); // QueuePush(&q, 3); // QueuePush(&q, 4); // // while (QueueEmpty(&q)) // { // printf("%d ", QueueFront(&q)); // QueuePop(&q); // } // // printf("\n"); // // QueueDestory(&q); //}



本文共计5658个文字,预计阅读时间需要23分钟。

如何深入理解并掌握数据结构中的二叉树(BinaryTree)原理和应用?

1. 树的概念及其结构 树是一种非线性数据结构,由n(n≥0)个有限节点组成,这些节点按层次关系连接,形成具有层次关系的集合。树被称为树,因为它看起来像一棵倒挂的树,也意味着它是根节点和子节点的关系。

1.1 树的概念 树是一种数据结构,由节点组成,每个节点包含数据和指向子节点的指针。它具有以下特点: - 非线性结构:节点之间没有固定的顺序。 - 层次关系:节点之间存在父子关系,形成层次结构。 - 根节点:树中唯一的节点,没有父节点。 - 子节点:根节点下的节点,可以有多个。 - 叶节点:没有子节点的节点。

1、树的概念及其结构

1.1树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

  • 有一个特殊的结点,称为根结点,根结点没有前驱结点
  • 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。

如下图为树的一种结构

注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

1.2树的相关概念

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为2

叶结点或终端结点:度为0的结点称为叶结点; 如上图:J、K、O、P、Q、R、S、I...等结点为叶结点

非终端结点或分支结点:度不为0的结点; 如上图:B、C、E、F、G、H...等结点为分支结点

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为2

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:J、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

如何深入理解并掌握数据结构中的二叉树(BinaryTree)原理和应用?

1.3树的表示

树的结构不仅仅要保存当前节点的数据,还要保存左右孩子节点的地址或者孩子和兄弟的地址,我们常用的表示方法是左孩子右兄弟的表示方法,接下来我简单介绍一下左孩子右兄弟的表示方法:

typedef int DataType; struct Node { struct Node* firstChild1; // 第一个孩子结点 struct Node* pNextBrother; // 指向其下一个兄弟结点 DataType data; // 结点中的数据域 };

左孩子右兄弟表示法,每次在遍历的时候先找到最左边的第一个孩子,然后通过右兄弟不断遍历找到同层的兄弟。 这样的设计模式在文件系统中,比较常见。


2、二叉树的概念和结构表示

2.1概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成

2.2特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.3 二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(h-1) 个结点.
  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h-1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0$, 度为2的分支结点个数为 n2,则有n0=n2+1
  4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log_2(n+1). (ps:是log以2为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
  1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
  2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
  3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

2.4二叉树的存储结构

2.4.1顺序存储

二叉树的顺序存储是存储到数组里面的,但是当我们把数据放到数组里面发现,像我们平常的二叉树在数组中出现了大量的内存浪费,顺序用数组存储只适合用完全二叉树和满二叉树的存储。

2.4.2链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。



2.4.2.1 顺序存储二叉树结构体表示

//数组存储 逻辑结构 还有物理结构 //堆 逻辑结构 完全二叉树 // 物理结构 数组 typedef int HeapDataType; typedef struct Heap { HeapDataType* a; int size; int capacity; }HP;

2.4.2.2 链式二叉树节点结构表示

typedef int BTDataType; // 二叉链 struct BinaryTreeNode { struct BinTreeNode* left; // 指向当前结点左孩子 struct BinTreeNode* right; // 指向当前结点右孩子 BTDataType data; // 当前结点值域 }

3、顺序存储结构的实现

3.1初始化

函数声明

void HeapInit(HP* hp);

函数实现

void HeapInit(HP* hp) { assert(hp); hp->capacity = 0; hp->size = 0; hp->a = NULL; }

3.2向上调整操作

函数声明

void AdjustUp(int* a, int n, int child);

为什么需要进行向上调整操作?

因为我们把数据存储在数组里面,物理存储是连续的,逻辑关系是树状关系,树之间的逻辑关系的维持是通过下标来找到父亲节点和孩子节点,向上调整是建堆的过程,父亲节点和孩子节点不断比较,将大数或者小数向上调整,来建成大堆或者小堆。

本段代码是向上调整建大堆 (孩子节点比父亲大,那么久调整位置)

时间复杂度:O(n*log(n))

void AdjustUp(int* a, int n, int child) //向上调整 { assert(a); int parent = (child - 1) / 2; while (child >0) { if (a[child] > a[parent]) //孩子比父亲大 那么就交换 { HeapDataType tmp = a[child]; a[child] = a[parent]; a[parent] = tmp; child = parent; parent = (child - 1) / 2; } else { break; } } }

3.3插入操作

函数声明

void HeapPush(HP* hp,HeapDataType x);

函数实现

void HeapPush(HP* hp, HeapDataType x) { assert(hp); if (hp->capacity == hp->size) { //增容 size_t newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2; HeapDataType* tmp = (HeapDataType)realloc(hp->a,sizeof(HeapDataType)*newcapacity); if (tmp == NULL) { perror("realloc fail"); return; } hp->a = tmp; hp->capacity = newcapacity; } //如何保证插入的数据之后还是大堆 那么我们就要通过访问下标来实现比较 //堆插入结点对其他结点没有影响 只可能影响从他到根节点路径上节点的关系 //向上调整的操作 和自己的父亲比较大小 hp->a[hp->size] = x; hp->size++; //向上调整 AdjustUp(hp->a, hp->size, hp->size - 1); }

我们设计的是动态增长长度的数组,所以需要进行判断是否需要扩容,将数据插入到最后的位置之后,整体可能不是大堆,那么需要进行向上调整来调整使得整个父亲节点都大于他的孩子节点,满足大堆的性质。

3.4向下调整操作

为什么要向下调整?

当child位置构成堆,但是左右子树还是大堆的时候,而且parent位置不符合大堆的性质,那么就要进行向下调整。

我们通过parent位置找到child位置,选出两个孩子中最小的,和parent比较如果比parent大,那么就交换

int child = parent * 2 + 1;

函数声明:

void HeapPop(HP* hp);

函数实现:

//除了child位置构成堆 左右子树都还是大堆 parent位置不符合大堆性质 void AdjustDown(HPDataType* a, int n,int parent) { int child = parent * 2 + 1; //和大儿子比 while (child<n) //大于n就是越界 { if (child+1<n && a[child + 1] < a[child]) //要注意防止越界问题 顺序不能乱 { ++child; } if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); //接着把child赋给parent parent = child; child = parent * 2 + 1; } else { break; } } }

3.5删除操作

实现思路:为了不使大堆或者小堆的性质发生改变,先把top元素和最后一个元素交换位置,让后再向下调整,保持原来的性质,再不断调整递归,删除一次递归调整数据个数-1,删除越多把数据放到后面的越多,那么数据进行向上调整的越少。

函数声明:

void HeapPop(HP* hp);

函数实现:

void HeapPop(HP* hp) { //删除根节点 //删除最大的那么剩下找出第二个最大的 assert(hp); assert(!HeapEmpty(hp)); Swap(&hp->a[0], &hp->a[hp->size - 1]); hp->size--; AdjustDown(hp->a, hp->size+1, &hp->a[0]); }

通过控制hp->size的长度来实现控制AdjustDown的数据范围。

3.6堆顶元素

取堆顶的元素比较简单,因为根节点是放在数组的第一位,那么直接返回根节点的值。

函数声明:

HPDataType HeapTop(HP* hp);

函数实现:

HPDataType HeapTop(HP* hp) { assert(hp); return hp->a[0]; }

3.7 堆的判空

我们在定义数组来存数据的时候,定义了一个size来记录数组的元素,那么通过如果size=0那么就为空

函数声明:

bool HeapEmpty(HP* hp);

函数实现:

bool HeapEmpty(HP* hp) { assert(hp); return hp->size == 0; }

3.8堆的元素个数

函数声明:

int HeapSize(HP* hp);

函数实现:

int HeapSize(HP* hp) { assert(hp); return hp->size; }

4、链式存储结构的实现

4.1链式二叉树的创建

前序建立二叉树,将char数组内的内容一个个读出来,再前序遍历,如果读到#那么就返回。

BTNode* BuyBTNode(BTDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); node->_left = NULL; node->_right = NULL; node->_data = x; return node; } BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi) { if (a[*pi] != '#') { BTNode* root = BuyBTNode(a[*pi]); ++(*pi); root->_left = BinaryTreeCreate(a, n, pi); ++(*pi); root->_right = BinaryTreeCreate(a, n, pi); return root; } else { return NULL; } }

4.2 链式二叉树的前序遍历

先访问根节点,再左子树,再右子树

void BinaryTreePrevOrder(BTNode* root) { if (root == NULL) return; printf("%c ", root->_data); BinaryTreePrevOrder(root->_left); //遍历左子树 BinaryTreePrevOrder(root->_right); //遍历右子树 }

4.3 链式二叉树的中序遍历

先访问左子树,再访问根节点,再访问右子树

void BinaryTreeInOrder(BTNode* root) { if (root == NULL) return; //先左子树再根结点再右子树 BinaryTreeInOrder(root->_left); printf("%c ", root->_data); BinaryTreeInOrder(root->_right); }

4.4 链式二叉树的后序遍历

先访问左子树,再访问右子树,再访问根节点

void BinaryTreePostOrder(BTNode* root) { if (root == NULL) return; BinaryTreePostOrder(root->_left); BinaryTreePostOrder(root->_right); printf("%c ", root->_data); }

4.5 链式二叉树的层序遍历

利用queue 当root进队列之后,访问后删除,将左右孩子结点入队,不断递归下去,实现了层序遍历

void BinaryTreeLevelOrder(BTNode* root) { Queue q; QueueInit(&q); if (root) QueuePush(&q, root); while (QueueEmpty(&q) != 0) { QUDataType front = QueueFront(&q); printf("%c ", front->_data); QueuePop(&q); if (front->_left) { QueuePush(&q, front->_left); } if (front->_right) { QueuePush(&q, front->_right); } } printf("\n"); }

4.6 链式二叉树的销毁

void BinaryTreeDestory(BTNode** pptree) { BTNode* root = *pptree; if (root == NULL) return; BinaryTreeDestory(&root->_left); BinaryTreeDestory(&root->_right); free(root); *pptree = NULL; }

4.7 二叉树节点个数

int BinaryTreeSize(BTNode* root) { if(root == NULL) return 0; return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right)+ 1; }

4.8 二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k) { if (root == NULL) return 0; if (k == 1) return 1; return BinaryTreeLevelKSize(root->_left, k-1) + BinaryTreeLevelKSize(root->_right, k-1); }

4.9 二叉树的高度

int BinaryTreeHeight(BTNode* root) { int leftHeight, rightHeight; if (root == NULL) { return 0; } leftHeight = BinaryTreeHeight(root->_left); rightHeight = BinaryTreeHeight(root->_right); return leftHeight > rightHeight ? leftHeight+1 : rightHeight+1; }

4.10 二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root) { if (root == NULL) return 0; if (root->_left == NULL && root->_right == NULL) return 1; return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right); }

4.11 二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) { BTNode* ret; if (root == NULL) return NULL; if (root->_data == x) return root; ret = BinaryTreeFind(root->_left, x); if (ret) return ret; ret = BinaryTreeFind(root->_right, x); if (ret) return ret; return NULL; }

5、顺序存储二叉树的完整代码

//Heap.h #pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> #include<time.h> //堆 小堆那么树中所有的父亲都小于或等于孩子 大堆树所有的父亲都大于或等于孩子 typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }HP; //初始化 void HeapInit(HP* hp); void HeapPush(HP* hp, HPDataType x); void HeapDesory(HP* hp); void HeapPop(HP* hp); HPDataType HeapTop(HP* hp); bool HeapEmpty(HP* hp); int HeapSize(HP* hp); void AdjustDown(HPDataType* a, int n, int parent); void AdjustUp(HPDataType* a, int child); void Swap(HPDataType* p1, HPDataType* p2);

//Heap.c #include"Heap.h" //大堆 void HeapInitArray(HP* hp) { assert(hp); hp->a = (HPDataType*)malloc(sizeof(HPDataType) * 4); if (hp->a == NULL) { perror("malloc fail"); } hp->capacity = 4; hp->size = 0; //向下建堆 for (int i = (hp->size - 1 - 1) / 2; i >= 0; --i) { AdjustDown(hp->a, hp->size, i); //调整叶子节点的父亲节点 如果父亲节点比孩子节点小那么调整 不断遍历到所有父亲节点 } } void Swap(HPDataType* p1, HPDataType* p2) { HPDataType x = *p1; *p1 = *p2; *p2 = x; } //向上调整 void AdjustUp(HPDataType* a, int child) { int parent = (child - 1) / 2; while (child > 0) { if (a[child] < a[parent]) { /*HPDataType tmp = a[child]; a[child] = a[parent]; a[parent] = tmp;*/ Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void HeapPush(HP* hp, HPDataType x) { //位置都可以放 assert(hp); if (hp->capacity == hp->size) { //扩容 HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * hp->capacity * 2); if (tmp == NULL) { perror("realloc fail"); } hp->a = tmp; hp->capacity *= 2; } //插入数据 hp->a[hp->size] = x; hp->size++; //判断一下需不需要向上调整 AdjustUp(hp->a, hp->size - 1); } void HeapDesory(HP* hp) { assert(hp); free(hp->a); hp->a = NULL; hp->capacity = hp->size = 0; } //除了child位置构成堆 左右子树都还是大堆 parent位置不符合大堆性质 void AdjustDown(HPDataType* a, int n,int parent) { int child = parent * 2 + 1; //和大儿子比 while (child<n) //大于n就是越界 { if (child+1<n && a[child + 1] < a[child]) //要注意防止越界问题 顺序不能乱 { ++child; } if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); //接着把child赋给parent parent = child; child = parent * 2 + 1; } else { break; } } } void HeapPop(HP* hp) { //删除根节点 //删除最大的那么剩下找出第二个最大的 assert(hp); assert(!HeapEmpty(hp)); Swap(&hp->a[0], &hp->a[hp->size - 1]); hp->size--; AdjustDown(hp->a, hp->size+1, &hp->a[0]); } HPDataType HeapTop(HP* hp) { assert(hp); return hp->a[0]; } bool HeapEmpty(HP* hp) { assert(hp); return hp->size == 0; } int HeapSize(HP* hp) { assert(hp); return hp->size; }

//test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"Heap.h" //逻辑结构: 想象出来的 //物理结构: 实实在在在内存种如何存储的 //数组种层序存储 那么孩子如何找到自己的父亲位置 那么就要从通过孩子算父亲位置parent=(child-1)/2 // 通过父亲找左右孩子位置 leftchild=parent*2+1 rightchild=parent*2+2 //难度就是很抽象 数组存储表示二叉树只适合完全二叉树 //int main() //{ // HP hp; // // HeapInit(&hp); // HeapPush(&hp, 4); // HeapPush(&hp, 10); // HeapPush(&hp,20); // HeapPush(&hp, 50); // HeapPush(&hp, 10); // HeapPush(&hp, 3); // // while (!HeapEmpty(&hp)) // { // printf("%d ", HeapTop(&hp)); // HeapPop(&hp); // } // printf("\n"); // // // return 0; //} //升序是建大堆 降序是建小堆 void HeapSort(int* a, int n) { //建堆 向上模拟建堆 时间复杂度 O(N*logN) 如果叶子节点多那么调整次数就多 (双多) //for (int i = 1; i < n; ++i) //{ // AdjustUp(a, i); //第二个要传 i就是child 现在已经是大堆了 模拟插入建堆 大堆 //} //向下调整建堆 时间复杂度O(N) 最后一层不用调整 最后一层占一半的节点 提高了效率 节点次数少调整次数多 节点次数多调整次数少 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); //调整叶子节点的父亲节点 如果父亲节点比孩子节点小那么调整 不断遍历到所有父亲节点 } //排升序建大堆 每次取出最大的 和最后一个叶节点交换 把前面的看作堆 int i = 1; //时间复杂度O(N*logN) while (n-i!=0) { Swap(&a[0], &a[n - i]); AdjustDown(a, n - i, 0); i++; } //int end = n - 1; //while (end > 0) //{ // Swap(&a[end], &a[0]); // AdjustDown(a, end, 0); // --end; //} }

6、链式二叉树的完整代码

test.c代码可以根据提供的头文件和函数实现文件进行测试

//BinaryTree.h #pragma once #include <stdio.h> #include <malloc.h> #include <assert.h> #include <stdlib.h> typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType _data; struct BinaryTreeNode* _left; struct BinaryTreeNode* _right; }BTNode; // a是一个前序遍历的数组 BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi); void BinaryTreeDestory(BTNode** root); int BinaryTreeSize(BTNode* root); int BinaryTreeLeafSize(BTNode* root); int BinaryTreeLevelKSize(BTNode* root, int k); BTNode* BinaryTreeFind(BTNode* root, BTDataType x); // 遍历 递归&非递归 void BinaryTreePrevOrder(BTNode* root); void BinaryTreeInOrder(BTNode* root); void BinaryTreePostOrder(BTNode* root); void BinaryTreeLevelOrder(BTNode* root); int BinaryTreeComplete(BTNode* root); void BinaryTreePrevOrderNonR(BTNode* root); void BinaryTreeInOrderNonR(BTNode* root); void BinaryTreePostOrderNonR(BTNode* root); void TestBinaryTree();

//BinaryTree.c #pragma once #include<stdio.h> #include<assert.h> #include<string.h> #include<stdlib.h> typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType _data; struct BinaryTreeNode* _left; struct BinaryTreeNode* _right; }BTNode; // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi); // 二叉树销毁 void BinaryTreeDestory(BTNode** root); // 二叉树节点个数 int BinaryTreeSize(BTNode* root); // 二叉树叶子节点个数 int BinaryTreeLeafSize(BTNode* root); // 二叉树第k层节点个数 int BinaryTreeLevelKSize(BTNode* root, int k); // 二叉树查找值为x的节点 BTNode* BinaryTreeFind(BTNode* root, BTDataType x); // 二叉树前序遍历 void BinaryTreePrevOrder(BTNode* root); // 二叉树中序遍历 void BinaryTreeInOrder(BTNode* root); // 二叉树后序遍历 void BinaryTreePostOrder(BTNode* root); // 层序遍历 void BinaryTreeLevelOrder(BTNode* root); // 判断二叉树是否是完全二叉树 int BinaryTreeComplete(BTNode* root);


//Queue.h #pragma once #include <stdio.h> #include <malloc.h> #include <assert.h> struct BinaryTreeNode; typedef struct BinaryTreeNode* QUDataType; typedef struct QueueNode { struct QueueNode* _next; QUDataType _data; }QueueNode; typedef struct Queue { QueueNode* _front; // 队头 QueueNode* _back; // 队尾 }Queue; void QueueInit(Queue* pq); void QueueDestory(Queue* pq); QueueNode* BuyQueueNode(QUDataType x); void QueuePush(Queue* pq, QUDataType x); void QueuePop(Queue* pq); QUDataType QueueFront(Queue* pq); QUDataType QueueBack(Queue* pq); int QueueEmpty(Queue* pq); int QueueSize(Queue* pq); void TestQueue();

//Queue.c #pragma once #include "Queue.h" void QueueInit(Queue* pq) { assert(pq); pq->_front = NULL; pq->_back = NULL; } void QueueDestory(Queue* pq) { QueueNode* cur; assert(pq); cur = pq->_front; while (cur != NULL) { QueueNode* next = cur->_next; free(cur); cur = next; } pq->_front = pq->_back = NULL; } QueueNode* BuyQueueNode(QUDataType x) { QueueNode* node = (QueueNode*) malloc(sizeof(QueueNode)); assert(node); node->_next =NULL; node->_data = x; return node; } void QueuePush(Queue* pq, QUDataType x) { assert(pq); if (pq->_back == NULL) { pq->_front = pq->_back = BuyQueueNode(x); } else { QueueNode* back = BuyQueueNode(x); pq->_back->_next = back; pq->_back = back; } } void QueuePop(Queue* pq) { QueueNode* next; assert(pq); next = pq->_front->_next; free(pq->_front); pq->_front = next; if (next == NULL) { pq->_back = NULL; } } QUDataType QueueFront(Queue* pq) { assert(pq); return pq->_front->_data; } // 空 0 // 非空 1 int QueueEmpty(Queue* pq) { assert(pq); return pq->_front == NULL ? 0 : 1; } int QueueSize(Queue* pq) { int size = 0; QueueNode* cur = pq->_front; while (cur) { size++; cur = cur->_next; } return size; } QUDataType QueueBack(Queue* pq) { return pq->_back->_data; } //void TestQueue() //{ // Queue q; // QueueInit(&q); // // QueuePush(&q, 1); // QueuePush(&q, 2); // QueuePush(&q, 3); // QueuePush(&q, 4); // // while (QueueEmpty(&q)) // { // printf("%d ", QueueFront(&q)); // QueuePop(&q); // } // // printf("\n"); // // QueueDestory(&q); //}