前面的三篇文章已经将线性结构讲述完毕了,下面的文章将会为大家将讲点新东西:非线性结构中的树结构。萌新对这里的知识点相对陌生,建议反复观看!!
关于线性结构的三篇文章放在下面:
线性表之顺序表
线性表之链表
线性表之栈、队列
树是一种非线性的数据结构,它是由n (n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
注意:这里使用 ▲ 标注的都是比较重要的概念
这里使用孩子兄弟表示法:根节点左指针指向孩子,右指针指向兄弟。
typedef int DataType;struct Node
{
struct Node* firstChild1; //第一个孩子结点
struct Node* pNextBrother; //指向其下一个兄弟结点
DataType data; //结点中的数据域
};
实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
二叉树是一种特殊的树结构:
二叉树可以分为以下五种情况:
每一颗二叉树都可以分为三个部分:根 、左子树、右子树。而子树还可以继续往后分。
满二叉树:对于满二叉树而言,它的每一层节点个数都会达到最大值( 叶子节点只能是最后一层的节点,除叶子节点外,其余的所以节点都含有左右孩子 ) 。
完全二叉树:对于完全二叉树而言,它除了最后一层可以不为满,其他层必须为满,且最后一层节点必须是从左向右排列,且两节点之间不可有空再插入一个节点。
2^(i-1)
个结点.2^h - 1
.M = N + 1
(i - 1) / 2
; 若i = 0,则无双亲节点二叉树树的顺序存储是以数组为物理结构存储的一种结构,而画出来的是逻辑结构。
树的顺序存储是依照上面树的父子关系:
而我们看下面的图就可以知道,并不是所有的二叉树都适合使用二叉树的顺序存储。
下面非完全二叉树一共十三个空间就浪费了五个,再别说更极端的树了。
所以得出结论只有完全二叉树适合使用二叉树的顺序存储!!
如果有一个关键码的集合,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:根节点的关键码大于等于左右子树的关键码(根节点的关键码大于等于左右子树的关键码),则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
关键码(key) 是堆中每个元素的一个属性,用于比较元素的大小。
根据元素的关键码,可以决定堆的类型:
#pragma once
#include
#include
#include
#include
typedef int HPDataType;
typedef struct Heap
{
int* arr;
int size;
int capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* php, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);
使用堆的向上调整算法的前提是:在插入新元素之前,前面的元素就已经满足了堆的性质!!
堆的向上调整(heapify up)算法的思想是:(假设建大堆)
直到新元素值小于父节点为止,此时已经满足堆的性质。
void AdjustUp(int* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arr[child] > arr[parent])
{
HPDataType tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
}
child = parent;
parent = (child - 1) / 2;
}
}
使用堆的向下调整算法的前提是:根节点的左右子树都满足堆的性质
堆的向下调整(Heapify Down)算法的思想是:
//从从后往前的第一个非叶子节点开始向下调整
void AdjustDown(int* arr, int N, int parent)
{
int child = parent * 2 + 1;
//这里假设左孩子为小的/大的一个
while (child < N)
{
//假设这里建小堆
//假设有右孩子并且如果右孩子比左孩子小的
if (child + 1 < N && arr[child + 1] > arr[child])
{
child++;
}
//如果孩子比双亲小,则将双亲和孩子交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else //当孩子比双亲大时,则不需要调整了
{
break;
}
}
}
下面用两张图片来演示这两种构建堆的方法:
堆的向上调整算法建堆:
使用向上调整算法建堆需要从头开始插入元素,必须保证每次插入新元素使用向上调整算法,使得满足堆的条件。
堆的向下调整算法建堆:
向下调整算法的前提是左右子树都符合堆的条件,而下面这颗完全二叉树的左右子树并不满足这一条件,那么左右子树任何满足堆的条件呢?那就需要左右子树的左右子树满足堆的条件,这样一直想下去可以的得出想要对根节点使用向下调整算法,必须让根节点以下的所有子树都满足堆的条件,显然从头开始是不能完成的,那么只能从尾部开始向下调整,而后又发现叶子结点本就符合堆的条件,那么只需要从最后一个节点的父亲节点开始从后往前使用向下调整算法即可完成建堆。
小结:使用向下调整算法建堆,需要从最后一个节点的父亲节点开始从后往前使用向下调整算法即可完成建堆。
// 建堆--向上调整建堆 :注意此方法建堆需要申请和数组一样大的空间,空间复杂度为O(N)
// 这里只是演示一下可以有这种使用方法,不推荐!!!!!!!!
// 若是完成堆排序、TopK等操作不推荐,能够在数组上直接建堆,没有空间的消耗
void HeapCreate(Heap* php, HPDataType* a, int n)
{
assert(php);
//堆的初始化
php->capacity = 0;
php->size = 0;
php->arr = 0;
// 此操作是针对堆的,就需要使用这个
for (int i = 0; i < n; i++)
{
HeapPush(php, a[i]); //HeapPush()函数在下面讲述
}
//下面两种方法是任何情况都可以使用,只需要修改一些参数
//而上面的只能在有堆的情况才能使用
/*// 建堆--向上调整建堆 --O(N*logN) -- log以2为底N的对数
for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}
// 建堆--向下调整建堆 --O(N)
for (int i = (n-1-1)/2; i >= 0; --i)
{
AdjustDown(a, n, i);
}*/
}
当有新元素插入时,该元素需要与它的父亲进行比较。如果新元素的值大于父节点的值,则需要进行交换。 将新元素看作父节点,重复上述过程。(假设这里建大堆)
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
int* tmp = realloc(php->arr, sizeof(int) * newcapacity);
if (tmp == NULL)
{
perror("malloc");
return;
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;
php->size++;
AdjustUp(php->arr, php->size-1);
}
//从从后往前的第一个非叶子节点开始向下调整
void AdjustDown(int* arr, int N, int parent)
{
int child = parent * 2 + 1;
//这里假设左孩子为小的/大的一个
while (child < N)
{
//假设这里建小堆
//假设有右孩子并且如果右孩子比左孩子小的
if (child + 1 < N && arr[child + 1] > arr[child])
{
child++;
}
//如果孩子比双亲小,则将双亲和孩子交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else //当孩子比双亲大时,则不需要调整了
{
break;
}
}
}
// 堆的删除
void HeapPop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->arr[0], &php->arr[php->size - 1]);
php->size--;
AdjustDown(php->arr, php->size, 0);
}
#pragma once
#include
#include
#include
#include
typedef int HPDataType;
typedef struct Heap
{
int* arr;
int size;
int capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* php, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);
#include "Heap.h"
void AdjustUp(int* arr, int child);
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 建堆--向上调整建堆 :注意此方法建堆需要申请和数组一样大的空间,空间复杂度为O(N)
// 这里只是演示一下可以有这种使用方法,不推荐!!!!!!!!
// 若是完成堆排序、TopK等操作不推荐,能够在数组上直接建堆,没有空间的消耗
void HeapCreate(Heap* php, HPDataType* a, int n)
{
assert(php);
//堆的初始化
php->capacity = 0;
php->size = 0;
php->arr = 0;
// 此操作是针对堆的,就需要使用这个
for (int i = 0; i < n; i++)
{
HeapPush(php, a[i]); //HeapPush()函数在下面讲述
}
//下面两种方法是任何情况都可以使用,只需要修改一些参数
//而上面的只能在有堆的情况才能使用
/*// 建堆--向上调整建堆 --O(N*logN) -- log以2为底N的对数
for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}
// 建堆--向下调整建堆 --O(N)
for (int i = (n-1-1)/2; i >= 0; --i)
{
AdjustDown(a, n, i);
}*/
}
void HeapDestroy(Heap* php)
{
assert(php);
free(php->arr);
}
void AdjustUp(int* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arr[child] > arr[parent])
{
HPDataType tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
}
child = parent;
parent = (child - 1) / 2;
}
}
//从从后往前的第一个非叶子节点开始向下调整
void AdjustDown(int* arr, int N, int parent)
{
int child = parent * 2 + 1;
//这里假设左孩子为小的/大的一个
while (child < N)
{
//假设这里建小堆
//假设有右孩子并且如果右孩子比左孩子小的
if (child + 1 < N && arr[child + 1] > arr[child])
{
child++;
}
//如果孩子比双亲小,则将双亲和孩子交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else //当孩子比双亲大时,则不需要调整了
{
break;
}
}
}
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
int* tmp = realloc(php->arr, sizeof(int) * newcapacity);
if (tmp == NULL)
{
perror("malloc");
return;
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;
php->size++;
AdjustUp(php->arr, php->size-1);
}
// 堆的删除
void HeapPop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->arr[0], &php->arr[php->size - 1]);
php->size--;
AdjustDown(php->arr, php->size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->arr[0];
}
// 堆的数据个数
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
// 堆的判空
int HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0;
}
堆排序实现思想:
#include
#include
#include
#include
typedef int HPDataType;
typedef struct Heap
{
int* arr;
int size;
int capacity;
}Heap;
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整 O(N*logN)
void AdjustUp(int* arr, int child)
{
//将传入函数中的child向他的双亲进行对比
int parent = (child - 1) / 2;
//循环条件为
while (child > 0)
{
//若child比parent大(建大堆) / 小(建小堆) 则进行交换
if (arr[child] < arr[parent])
{
HPDataType tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
//child移到parent的位置,继续与他的parent进行对比
child = parent;
parent = (child - 1) / 2;
}
else //否则则证明该数组已经是堆,不需要再进行调整
{
break;
}
}
}
//向下调整算法 O(N)
//从从后往前的第一个非叶子节点开始向下调整
void AdjustDown(int* arr, int N, int parent)
{
int child = parent * 2 + 1;
//这里假设左孩子为小的/大的一个
while (child < N)
{
//假设这里建大堆
//假设有右孩子并且如果右孩子比左孩子大(建大堆)/小(建小堆)的
if (child + 1 < N && arr[child + 1] > arr[child])
{
child++;
}
//如果孩子比双亲大(建大堆)/小(建小堆),则将双亲和孩子交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else //当孩子比双亲大时,则不需要调整了
{
break;
}
}
}
void Print(int* arr, int N)
{
int i = 0;
for (i = 0; i < N; i++)
{
printf("%d ", arr[i]);
}
}
void HeapSort(int* arr, int n)
{
向上调整建堆
//for (int i = 0; i < n; i++)
//{
// AdjustUp(arr, i);
//}
//向下调整建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
int end = n - 1;
for (int i = 0; i < n; i++)
{
//end为数组最后一个元素的下标
Swap(&arr[0], &arr[end]);
//end也为该元素之前元素的总个数
AdjustDown(arr, end, 0);
end--;
}
}
int main()
{
int arr[] = { 3,2,9,7,6,1,8,5,4,0 };
HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
Print(arr, sizeof(arr) / sizeof(arr[0]));
return 0;
}
TopK问题有在我已知情况下有两种解决情况:(假设有N个值并且要取k个最值)
当数据量过大的时候,数据只能存储在内存中,而内存中不能建堆,只能使用第二种方法
值得注意的是在第一种情况下,若N与k相等,那么TopK就是堆排序。
#include
#include
#include
#include
#include
typedef int HPDataType;
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//从从后往前的第一个非叶子节点开始向下调整
void AdjustDown(int* arr, int N, int parent)
{
int child = parent * 2 + 1;
//这里假设左孩子为小的/大的一个
while (child < N)
{
//假设这里建小堆
//假设有右孩子并且如果右孩子比左孩子小的
if (child + 1 < N && arr[child + 1] < arr[child])
{
child++;
}
//如果孩子比双亲小,则将双亲和孩子交换
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else //当孩子比双亲大时,则不需要调整了
{
break;
}
}
}
//(1)
void TopK(int* arr,int n , int k)
{
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
//定义一个变量end记录堆最后一个元素
int end = n - 1;
//取前k个最值
for (int i = 0; i < k; i++)
{
//将堆顶的元素输出
printf("%d ", arr[0]);
//将堆顶与堆中最后一个元素交换,并减少堆元素个数
Swap(&arr[0], &arr[end]);
AdjustDown(arr, end, 0);
end--;
}
}
int main()
{
int n = 10000;
srand((unsigned int)time(0));
int* arr = (int*)malloc(sizeof(int)*n);
if (arr == NULL)
{
perror("malloc fail");
return;
}
for (int i = 0; i < n; i++)
{
arr[i] = rand() % 1000000 + 5;
}
//最大
/*arr[100] = 1000001;
arr[500] = 1000002;
arr[1000] = 1000003;
arr[5555] = 1000004;
arr[9999] = 1000005;*/
//最小
arr[100] = 1;
arr[500] = 2;
arr[1000] = 3;
arr[5555] = 4;
arr[9999] = 5;
TopK(arr, n , 5);
return 0;
}
#include
#include
#include
#include
#include
typedef int HPDataType;
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//从从后往前的第一个非叶子节点开始向下调整
void AdjustDown(int* arr, int N, int parent)
{
int child = parent * 2 + 1;
//这里假设左孩子为小的/大的一个
while (child < N)
{
//假设这里建小堆
//假设有右孩子并且如果右孩子比左孩子小的
if (child + 1 < N && arr[child + 1] < arr[child])
{
child++;
}
//如果孩子比双亲小,则将双亲和孩子交换
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else //当孩子比双亲大时,则不需要调整了
{
break;
}
}
}
//当数据量过大的时候,数据只能存储在内存中,而内存中不能建堆,只能使用第二种方法
void CreateNDate()
{
// 造数据
int n = 10000;
srand((unsigned int)time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < n; ++i)
{
int x = rand() % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
Print(int* arr, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
void PrintTopK(int k)
{
//先打开文件
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
//建立一个元素个数为k的堆
int* arr = (int*)malloc(sizeof(int) * k);
if (arr == NULL)
{
perror("malloc error");
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 val = 0;
while (!feof(fout))
{
fscanf(fout, "%d", &val);
if (val > arr[0])
{
Swap(&val, &arr[0]);
AdjustDown(arr,k, 0);
}
}
Print(arr , k);
}
int main()
{
//CreateNDate();
PrintTopK(10);
return 0;
}
二叉树中每一个节点包括:
#pragma once
#include
#include
#include
#include
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);
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BuyTreeNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->left = NULL;
newnode->right = NULL;
newnode->data = x;
return newnode;
}
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
//当a == '#'时说明当前节点为NULL
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = BuyTreeNode(a[*pi]);
(*pi)++;
root->left = BinaryTreeCreate(a, n , pi);
root->right = BinaryTreeCreate(a, n, pi);
return root;
}
// 二叉树前序遍历 : 根 左子树 右子树
void BinaryTreePrevOrder(BTNode* root)
{
//当该节点为NULL时,打印N
if (root == NULL)
{
printf("N ");
return;
}
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历: 左子树 根 右子树
void BinaryTreeInOrder(BTNode* root)
{
//当该节点为NULL时,打印N
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreeInOrder(root->left);
printf("%c ", root->data);
BinaryTreeInOrder(root->right);
}
// 二叉树后序遍历: 左子树 右子树 根
void BinaryTreePostOrder(BTNode* root)
{
//当该节点为NULL时,打印N
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->data);
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
//如果该root为NULL,则root不是节点
if (root == NULL)
return 0;
//如果root不为空,则返回他的左子树节点+右子树的节点+1 (1为他本身)
return BinaryTreeSize(root->left)
+ BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
//如果该root为NULL,则root不是节点
if (root == NULL)
return 0;
//当root的左子树和右子树都为NULL时,则说明root是叶子节点
if (root->left == NULL && root->right == NULL)
return 1;
//返回root左子树和右子树上的叶子节点个数
return BinaryTreeLeafSize(root->left)
+ BinaryTreeLeafSize(root->right);
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
//k不能为0
assert(k > 0);
//如果该root为NULL,则root不是节点
if (root == NULL)
return 0;
//当k == 1是,说明到了相对于根的第k层
if (k == 1)
return 1;
return BinaryTreeLevelKSize(root->left, k - 1)
+ BinaryTreeLevelKSize(root->right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
//如果该root为NULL,则root不是节点
if (root == NULL)
return NULL;
//当找到x后返回root
if (root->data == x)
return root;
BTNode* ret1 = BinaryTreeFind(root->left, x);
if (ret1) //当左子树返回的地址不为空,说明已经找到了x,返回ret1
{
return ret1;
}
BTNode* ret2 = BinaryTreeFind(root->right, x);
if (ret2) //当右子树返回的地址不为空,说明已经找到了x,返回ret1
{
return ret2;
}
//当前面左子树和右子树都没返回,说明没有找到,返回NULL
return NULL;
}
#pragma once
#include
#include
#include
typedef struct BinaryTreeNode* QDataType;
// 链式结构:表示队列
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front;
QNode* rear; //指向队列最后一个元素的后面
int size;
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
#include "Queue.h"
// 初始化队列
void QueueInit(Queue* q)
{
q->front = NULL;
q->rear = NULL;
q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->next = NULL;
newnode->data = data;
if (q->front == NULL) //分队列是否有元素两种情况
{
assert(q->rear == NULL);
q->front = newnode;
q->rear = newnode;
}
else
{
q->rear->next = newnode;
q->rear = newnode;
}
q->size++;//入队列,队列长度加一
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
return q->front == NULL && q->rear == NULL;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
//出队列时,队列不能为空
assert(!QueueEmpty(q));
//当队列中只有一个元素的时候,不仅仅头指针需要改变,尾指针也需要改变
//因为当删除最后一个元素时,首指针释放当前节点,并向后移动,而尾指针并没有移动
//当释放后若在插入元素时,尾指针会造成野指针的情况
if (q->front->next == NULL)
{
QNode* del = q->front;
q->front = NULL;
q->rear = NULL;
free(del);
}
else
{
QNode* del = q->front;
q->front = q->front->next;
free(del);
}
q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
//获取队列头部元素时,队列不能为空
assert(!QueueEmpty(q));
return q->front->data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
//获取队列头部元素时,队列不能为空
assert(!QueueEmpty(q));
return q->rear->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
//结构体中定义了一个size
//而这里遍历链表得到个数,效率低
/*int size = 0;
QNode* cur = q->front;
while (cur != q->rear)
{
size++;
q->front = q->front->next;
}*/
return q->size;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->front;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue qu;
QueueInit(&qu);
QueuePush(&qu, root);
while (!QueueEmpty(&qu))
{
BTNode* front = QueueFront(&qu);
printf("%c ", front->data);
QueuePop(&qu);
if(front -> left != NULL)
QueuePush(&qu, front->left);
if (front->right != NULL)
QueuePush(&qu, front->right);
}
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
if (root == NULL)
return true;
//当一个节点的左子树为NULL,右子树不为NULL,那么这棵树不是完全二叉树
if (root->left == NULL && root->right != NULL)
return false;
return BinaryTreeComplete(root->left)
&& BinaryTreeComplete(root->right);
}
二叉树作为一个新知识相比较于前面的知识点会有点点陌生,里面涉及的递归也会是新手相对于薄弱的地方,这边建议对于二叉树递归知识点反复观看,二叉树递归题目先自己思考,实在想不出来的可以参考一下解决思路,这也是学习的过程,对于二叉树中递归问题最终方法就是画递归展开图。
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!
如果这篇文章对你有用的话,请大家给一个三连支持一下!!