0
点赞
收藏
分享

微信扫一扫

计算机竞赛 深度学习+opencv+python实现车道线检测 - 自动驾驶

汤姆torn 2023-09-25 阅读 43
数据结构

目录

1.树概念及结构

1.1. 树的概念

1.2. 树的其他相关概念

1.3. 树的表示

1.3.1. 思考

1.3.2. 先描述

1.3.2. 再组织

1.4. 树在实际中的运用

2.二叉树概念及结构

2.1. 二叉树的概念

2.2. 特殊的二叉树

2.3. 二叉树的性质:

3.二叉树顺序结构及实现

3.1. 二叉树的顺序结构的概念

3.2. 堆的概念及结构 

3.3. 堆的实现框架

3.4.堆的实现具体细节

3.4.1. heap_push的实现

第一步:在堆得尾部直接插入数据

第二步:通过向上调整算法调整堆的结构,使其合法

3.4.1.1. 向上调整算法

3.4.1.2. heap_push的整体实现

3.4.2. heap_pop的实现

3.4.2.1 向下调整算法

3.4.2.2. heap_pop的完整实现 

3.4.3. heap_sort()的实现

3.4.3.1. 建堆

3.4.3.1.1. 以向下调整算法建堆

补充:证明向下调整算法建堆的时间复杂度

 3.4.3.1.2. 以向上调整算法建堆

补充:证明向上调整算法建堆的时间复杂度 

3.4.3.2. 排序

3.4.3.2. heap_sort的完整实现:

3.4.4. topK问题

3.4.4.1 分析:

3.4.4.2. 代码实现: 

4.二叉树链式结构及实现

4.2. 二叉树的遍历

4.2.1. 前序遍历(NLR)

4.2.2. 中序遍历(LNR)

4.2.3. 后序遍历(LRN)

 ​编辑

4.2.4. 求二叉树的有效节点个数

4.2.5. 求二叉树的有效叶子节点个数

4.2.6.  二叉树第k层节点个数

4.2.7. 二叉树查找值为x的节点

4.2.8. 二叉树的销毁

4.2.9. 二叉树的层序遍历

4.2.10. 判断二叉树是否是完全二叉树

4.3. 二叉树的练习题

4.3.1. 单值二叉树

4.3.2. 相同的树

4.3.3. 对称二叉树

4.3.4. 二叉树的前序遍历

4.3.5. 二叉树的中序遍历

4.3.6. 二叉树的后序遍历

 4.3.7. 另一颗树的子树

4.3.8. 二叉树的最大深度

4.3.9. 翻转二叉树


1.树概念及结构

1.1. 树的概念

 

 

1.2. 树的其他相关概念


1.3. 树的表示

1.3.1. 思考

1.3.2. 先描述

typedef int node_val_type;
// 描述树结构中的任意一个节点
struct tree_node
{
    struct tree_node* _firstchild;  // 第一个孩子节点
    struct tree_node* _nextbrother;  // 指向下一个兄弟节点
    node_val_type _val;    
};

1.3.2. 再组织

1.4. 树在实际中的运用

2.二叉树概念及结构

2.1. 二叉树的概念

2.2. 特殊的二叉树

2.3. 二叉树的性质:

3.二叉树顺序结构及实现

3.1. 二叉树的顺序结构的概念

3.2. 堆的概念及结构 

3.3. 堆的实现框架

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int heap_data_type;
typedef struct heap
{
  heap_data_type* _heap_array;
  size_t _capacity;
  size_t _size;
}heap;

void heap_init(heap* hp_ptr);
void heap_destroy(heap* hp_ptr);
// 尾插
void heap_push(heap* hp_ptr,heap_data_type val);
// 删除堆顶数据
void heap_pop(heap* hp_ptr);
bool heap_empty(heap* hp_ptr);
void heap_sort(heap* hp_ptr);
void heap_print(heap* hp_ptr);

3.4.堆的实现具体细节

3.4.1. heap_push的实现
第一步:在堆得尾部直接插入数据

void heap_push(heap* hp_ptr,heap_data_type val)
{
  //1. 堆的地址不为NULL
  assert(hp_ptr);
  //2. 检查容量
  check_capacity(hp_ptr);
  //3. push数据
  hp_ptr->_heap_array[hp_ptr->_size++] = val;
}
第二步:通过向上调整算法调整堆的结构,使其合法
3.4.1.1. 向上调整算法

void adjust_up(heap* hp_ptr)
{
  //1. 堆得地址不为NULL
  assert(hp_ptr);
  //2. 确定父节点和子节点的位置
  //a. 子节点就是最后一个节点
  //b. 父节点根据父子关系求得
  int child = hp_ptr->_size-1;
  int parent = (child-1)/2;
  //3. 在这里我们建小堆
  //注意:当子节点到了根节点就结束循环
  while(child > 0)
  {
    // a. 如果孩子小于父亲,就交换父子的值,并更新父子位置
    
    if(hp_ptr->_heap_array[child] < hp_ptr->_heap_array[parent])
    {
      swap(&hp_ptr->_heap_array[child],&hp_ptr->_heap_array[parent]);
      child = parent;
      // 注意:子节点到了根节点后,父节点就不要再更新了
      if(child != 0)
        parent = (child-1)/2;
    }
    else
    {
      break;
    }
  }
}
3.4.1.2. heap_push的整体实现
void heap_push(heap* hp_ptr,heap_data_type val)
{
  //1. 堆的地址不为NULL
  assert(hp_ptr);
  //2. 检查容量
  check_capacity(hp_ptr);
  //3. push数据
  hp_ptr->_heap_array[hp_ptr->_size++] = val;
  //4. 调整堆(我们在这里是小堆)
  adjust_up(hp_ptr);
}

3.4.2. heap_pop的实现

3.4.2.1 向下调整算法

// n代表有效元素的个数
// root代表父节点(相对的)
void adjust_down(heap* hp_ptr,int root,int n)
{
  //1. 堆不为NULl
  assert(hp_ptr);
  //2. 确定孩子和父亲的初始位置
  int parent = root;
  //3. 默认孩子为左孩子
  int child = 2 * parent + 1;  
  //4. 建小堆
  while(child < n)
  {
     //5. 确定小孩子,如果右孩子更小,就++child
     // 注意:小心右孩子越界
     if(child + 1 < n && hp_ptr->_heap_array[child] > hp_ptr->_heap_array[child + 1])
     {
       ++child;
     }
     //6. 如果孩子小于父亲,则交换,并更新父子的位置
     if(hp_ptr->_heap_array[child] < hp_ptr->_heap_array[parent])
     {
       swap(&hp_ptr->_heap_array[child],&hp_ptr->_heap_array[parent]);
       parent = child;
       child = 2 * parent + 1;
     }
     else
     {
       break;
     }
  }
}
3.4.2.2. heap_pop的完整实现 
void heap_pop(heap* hp_ptr)
{
  //1. 堆的地址不为NULL
  assert(hp_ptr);
  //2. 堆不可以为NULL
  assert(!heap_empty(hp_ptr));
  //3. 删除数据
  //a. 第一步:交换堆顶和最后一个数据
  swap(&hp_ptr->_heap_array[0],&hp_ptr->_heap_array[hp_ptr->_size-1]);
  //b. 第二步:更新_size
  hp_ptr->_size--;
  //c. 第三步:向下调整堆,且只需要调整堆顶,因为堆顶的左右子树结构没发生改变
  adjust_down(hp_ptr,0,hp_ptr->_size);
}

3.4.3. heap_sort()的实现

3.4.3.1. 建堆
3.4.3.1.1. 以向下调整算法建堆

//1. 最后一个非叶子节点 = 最后一个叶子节点的父亲
//a. 最后一个节点: _size-1
//b. parent = (child-1)/2;
int cur = (hp_ptr->_size-1-1)/2;
//2. 从最后一个非叶子节点开始调,调到根节点(包含根节点)
while(cur >= 0)
{
  adjust_down(hp_ptr,cur,hp_ptr->_size);
  --cur;
}
补充:证明向下调整算法建堆的时间复杂度

 3.4.3.1.2. 以向上调整算法建堆

void adjust_up(int* ptr, int pos)
{
	//1. 数组的地址不为NULL
	assert(ptr);
	//2. 找父亲
	int child = pos;
	int parent = (child - 1) / 2;
	//3. 在这里我们建小堆
	while (child > 0)
	{
		// a. 如果孩子小于父亲,就交换父子的值,并更新父子位置
		// 注意:子节点到了根节点后,父节点就不要再更新了
		if (ptr[child] < ptr[parent])
		{
			swap(&ptr[child],&ptr[parent]);
			child = parent;
			if (child != 0)
				parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void build_heap(int* ptr,int n)
{
	// 1.建堆
	assert(ptr);
	// 2.只有一个节点,就不用建堆了
	if (n <= 1)
		return;
    // 3. 从根节点的下一个节点开始调整
	int cur = 1;
	while (cur < n)
	{
		adjust_up(ptr, cur);
		++cur;
	}
}
补充:证明向上调整算法建堆的时间复杂度 

3.4.3.2. 排序
//3. 排序, 小堆,排降序
int end = hp_ptr->_size - 1;
while(end > 0)
{
  //a. 交换堆顶和end所处位置的数据
  swap(&hp_ptr->_heap_array[0],&hp_ptr->_heap_array[end]);
  //b. 更新end
  --end;
  //c. 重新建堆
  //注意,每次排序后,向下调整的数组的大小要更新,在这里就是end+1
  adjust_down(hp_ptr,0,end+1);
}
3.4.3.2. heap_sort的完整实现:
void heap_sort(heap* hp_ptr)
{
  // 堆的地址不可以为NULL
  assert(hp_ptr);
  // 堆不可以为NULL
  assert(!heap_empty(hp_ptr));
  //1. 最后一个非叶子节点 = 最后一个叶子节点的父亲
  int cur = (hp_ptr->_size-1-1)/2;
  //2. 从最后一个非叶子节点开始调,调到根节点(包含根节点)
  while(cur >= 0)
  {
    adjust_down(hp_ptr,cur,hp_ptr->_size);
    --cur;
  }
  //3. 排序, 小堆,排降序
  int end = hp_ptr->_size - 1;
  while(end > 0)
  {
    //a. 交换堆顶和end所处位置的数据
    swap(&hp_ptr->_heap_array[0],&hp_ptr->_heap_array[end]);
    //b. 更新end
    --end;
    //c. 重新建堆
    //注意,每次排序后,向下调整的数组的大小要更新,在这里就是end+1
    adjust_down(hp_ptr,0,end+1);
  }
}

3.4.4. topK问题

3.4.4.1 分析:

3.4.4.2. 代码实现: 
// 将数据存到文件中
void creat_data()
{
	int n = 100000;
	srand((size_t)time(NULL));
	int *arr = (int*)malloc(sizeof(int)* n);
	assert(arr);
	for (int i = 0; i < n; ++i)
	{
		// 每个数在0-99
		arr[i] = rand() % 100;
	}
	// 特殊处理:前k个大的数据
	arr[888] = 101;
	arr[6123] = 102;
	arr[83] = 103;
	arr[8623] = 106;
	arr[723] = 105;
	arr[124] = 104;
	arr[5215] = 109;
	arr[7437] = 108;
	arr[8965] = 110;
	arr[4241] = 107;
	FILE* fp = fopen("./data.txt", "w");
	assert(fp);

	for (int i = 0; i < n; ++i)
	{
		fprintf(fp, "%d\n", arr[i]);
	}
	free(arr);
	fclose(fp);
}
// 模拟TopK问题
void print_top_k(int K)
{
	FILE* fp = fopen("./data.txt", "r");
	assert(fp);

	int k = K;
	// 最后一个非叶子节点
	int *tmp_arr = (int*)malloc(sizeof(int)* k);
	assert(tmp_arr);
	// 先读10个数据
	for (size_t i = 0; i < 10; ++i)
	{
		fscanf(fp, "%d", &tmp_arr[i]);
	}
	int end = (k - 1 - 1) / 2;
	while (end >= 0)
	{
		// 建立k个数据的堆
		adjust_down(tmp_arr, end, 10);
		--end;
	}
	int tmp = 0;
    //从剩余数据中读取存到tmp里,与堆顶数据进行比较
	while (fscanf(fp, "%d", &tmp) != EOF)
	{
        // 如果比堆顶大,就交换
		if (tmp > tmp_arr[0])
		{
			swap(&tmp, &tmp_arr[0]);
			adjust_down(tmp_arr, 0, 10);
		}
	}
	for (int i = 0; i < k; ++i)
	{
		printf("%d ", tmp_arr[i]);
	}
	free(tmp_arr);
	fclose(fp);
}

4.二叉树链式结构及实现

4.2. 二叉树的遍历

4.2.1. 二叉树的前序、中序、后序遍历

4.2.1. 前序遍历(NLR)

void preorder_traversal(bt_node* root)
{
    // 节点为NULL就返回
	if (root == NULL)
    {
		printf("NULL->");
		return;
    }
    // 前序遍历(NLR)
	printf("%d->", root->_val);
	preorder_traversal(root->_left);
	preorder_traversal(root->_right);
}

4.2.2. 中序遍历(LNR)

void inorder_traversal(bt_node* root)
{
    // 节点为NULL,直接return当前函数
	if (root == NULL)
	{
		printf("NULL->");
		return;
	}
    // 中序遍历左根右(LNR)
	inorder_traversal(root->_left);
	printf("%d->", root->_val);
	inorder_traversal(root->_right);
}

4.2.3. 后序遍历(LRN)

 


void postorder_traversal(bt_node* root)
{
    // 如果是空节点,回到上级栈帧
	if (root == NULL)
	{
		printf("NULL->");
		return;
	}
    // 后序遍历左右根(LRN):
	postorder_traversal(root->_left);
	postorder_traversal(root->_right);
	printf("%d->", root->_val);
}

4.2.4. 求二叉树的有效节点个数

  

int get_binary_tree_node_size(bt_node* root)
{
	int count = 0;
	if (root == NULL)
		return 0;
	else
	{
		++count;
		get_binary_tree_node_size(root->_left);
		get_binary_tree_node_size(root->_right);
		return count;
	}
}

int main()
{
    int node_size = get_binary_tree_node_size(n1);
	printf("node_size: %d\n", node_size);
    return 0;
}
int get_binary_tree_node_size(bt_node* root)
{
	static int count = 0;
	if (root == NULL)
		return 0;
	else
	{
		++count;
		get_binary_tree_node_size(root->_left);
		get_binary_tree_node_size(root->_right);
		return count;
	}
}
int main()
{
    // 第一次调用
    int node_size = get_binary_tree_node_size(n1);
	printf("node_size: %d\n", node_size);
    // 第二次调用
    node_size = get_binary_tree_node_size(n1);
	printf("node_size: %d\n", node_size);
    return 0;
}
int get_binary_tree_node_size(bt_node* root)
{
    // 如果是空树(空节点),那么返回0
	if (root == NULL)
		return 0;
    // 如果是!空树,递归调用得到它的左子树和右子树并+1
	else
		return get_binary_tree_node_size(root->_left) + get_binary_tree_node_size(root->_right) + 1;
}

int main()
{
    // 第一次调用
    int node_size = get_binary_tree_node_size(n1);
	printf("node_size: %d\n", node_size);
    // 第二次调用
    node_size = get_binary_tree_node_size(n1);
	printf("node_size: %d\n", node_size);
    return 0;
}

4.2.5. 求二叉树的有效叶子节点个数

int get_leaf_node_size(bt_node* root)
{
    // 当走到空节点或者空树时,返回0
	if (root == NULL)
		return 0;
    // 如果是叶子节点,就+1
	else if (root->_left == NULL && root->_right == NULL)
		return 1;
    // 如果没走到空且不是叶子节点,就继续遍历这个节点的左子树和右子树
	else
		return get_leaf_node_size(root->_left) + get_leaf_node_size(root->_right);
}

 

int main()
{
    bt_node* n1 = build_node(5);
	bt_node* n2 = build_node(2);
	bt_node* n3 = build_node(6);
	bt_node* n4 = build_node(4);
	bt_node* n5 = build_node(1);
	n1->_left = n2;
	n1->_right = n3;
	n2->_left = n4;
	n2->_right = n5;
    int ret = get_leaf_node_size(n1);
    printf("ret = %d\n", ret);
    free(n1);
	free(n2);
	free(n3);
	free(n4);
	free(n5);
    return 0;
}

4.2.6.  二叉树第k层节点个数

int get_tree_level_node(bt_node* root, int k)
{
	//二叉树的高度必须大与0
	assert(k > 0);
	if (root == NULL)
		return 0;
	// 如果到了k层的上一层,那么节点个数++,并返回上级栈帧
	else if (k == 1)
		return 1;
	// 如果没有到k的上一层,那么遍历该节点的左子树,和右子树,k-1代表往下一层走
	else
		return get_tree_level_node(root->_left, k - 1) 
			+  get_tree_level_node(root->_right, k - 1);
}

4.2.7. 二叉树查找值为x的节点

bt_node* get_tree_special_node(bt_node* root, int val)
{
	// 如果是空树或者走到空节点了,直接返回NULL
	if (root == NULL)
		return NULL;
	// 如果找到了这个特殊节点,就返回,不要再递归了
	if (equal_val_node(root, val))
		return root;
	else
	{
		// 走到这里,说明没有找到特殊节点,那么继续递归当前节点的左子树和右子树
		// 注意: 在这里我们要接受左子树的返回结果,如果左子树返回不是NULL,那么右子树就不用再递归了
		bt_node* ret_node = get_tree_special_node(root->_left, val);
		// 如果左子树返回NULL,那么说明没找到,继续递归它的右子树
		if (ret_node == NULL)
			ret_node = get_tree_special_node(root->_right, val);
		return ret_node;
	}
}

 

4.2.8. 二叉树的销毁

void tree_destroy(bt_node* root)
{
	if (root == NULL)
		return;
	else
	{
		// 依据我们的后续思想(LRN)
		// 先找当前节点的左子树
		tree_destroy(root->_left);
		// 再找当前节点的右子树
		tree_destroy(root->_right);
		// 释放当前节点
		free(root);
	}
}

4.2.9. 二叉树的层序遍历

void level_order(bt_node* root)
{
	Queue Qu;
	QueueInit(&Qu);
	if (root != NULL)
	{
		QueuePush(&Qu,root);
	}
	while (!QueueEmpty(&Qu))
	{
		// 1. 队列中的队头节点作为当前节点。
		bt_node* front = QueueFront(&Qu);      
        // 2. 只要左子树和右子树不为NULL,就入左子树、右子树
		if (front->_left != NULL)
			QueuePush(&Qu, front->_left);
		if(front->_right != NULL)
			QueuePush(&Qu, front->_right);
        //3.打印队头数据
		printf("%d ",front->_val);
		//4.出队头
        QueuePop(&Qu);
	}
	QueueDestroy(&Qu);
}

4.2.10. 判断二叉树是否是完全二叉树

bool is_complete_binary_tree(bt_node* root)
{
	bt_node* cur = root;
	Queue Qu;
	QueueInit(&Qu);
	if (cur != NULL)
		QueuePush(&Qu, cur);
	while (!QueueEmpty(&Qu))
	{
		//1.取对头数据
		bt_node* front = QueueFront(&Qu);
		//2. 如果遇到空节点就退出循环
		if (front == NULL)
			break;
		//3. 入队头数据的左孩子和右孩子(包括空节点)
		QueuePush(&Qu, front->_left);
		QueuePush(&Qu, front->_right);
		//4. 出对头数据
		QueuePop(&Qu);
	}
	// 走到这里说明遇到NULL了
	while (!QueueEmpty(&Qu))
	{
		//继续取队列的剩余数据
		// 如果还有非空数据那么就说明不是完全二叉树,return false
		bt_node* front = QueueFront(&Qu);
		if (front != NULL)
		{
			QueueDestroy(&Qu);
			return false;
		}
		QueuePop(&Qu);
	}
	// 走到这里说明没有在队列里遇到非空数据,那么就说明是完全二叉树,return true
	QueueDestroy(&Qu);
	return true;
}

4.3. 二叉树的练习题

4.3.1. 单值二叉树

965. 单值二叉树 - 力扣(LeetCode)

class Solution {
public:
    // 两个节点的比较函数
    bool equal_val_node(TreeNode* node1,TreeNode* node2)
    {
        // 只要其中一个节点为nullptr,那么就可以认为是单值节点
        if(node1 == nullptr || node2 == nullptr)
            return true;
        // 否则只有当两个有效节点的val相等,返回true
        else
        return node1->val == node2->val;
    }
    bool isUnivalTree(TreeNode* root) {
        // 空节点返回true
        if(root == NULL)
            return true;
        // 只要根节点和它的左节点不相等或者根节点和它的右节点不相等就不是单值二叉树
        if(!(equal_val_node(root,root->right) && equal_val_node(root,root->left)))
        {
            return false;
        }
        // 如果符合前面的条件,继续判断它的左子树和右子树
        else
        {
            return isUnivalTree(root->right) && isUnivalTree(root->left);   
        } 
    }
};

4.3.2. 相同的树

 100. 相同的树 - 力扣(LeetCode)

class Solution {
public:
    // 判断节点是否为nullptr
    inline bool is_empty_node(TreeNode* root)
    {
        return root == nullptr;
    }
    // 判断两个节点是否相等
    bool is_equal_node(TreeNode* left, TreeNode* right)
    {
        if(is_empty_node(left) && is_empty_node(right))
            return true;
        if(is_empty_node(left) || is_empty_node(right))
            return false;
        if(left->val == right->val)
            return true;
        else
            return false;
    }
    bool isSameTree(TreeNode* rootA, TreeNode* rootB) {
        // 都为nullptr,认为节点相等,返回true
        if(is_empty_node(rootA) && is_empty_node(rootB))
            return true;
        // 其中一个为nullptr,认为节点不相等,返回false
        if(is_empty_node(rootA) || is_empty_node(rootB))
            return false;
        // 只要满足上面的条件,就开始分别判断对应的根、左子树、右子树
        bool ret = is_equal_node(rootA,rootB)
        && is_equal_node(rootA->left,rootB->left)
        && is_equal_node(rootA->right,rootB->right);
        // 只要有一个不相等即返回值ret == 0,那么就不用继续递归左子树和右子树了
        // 如果前面的判断 ret != 0,那么就继续递归左子树和右子树
        if(ret == 1)
            return isSameTree(rootA->left,rootB->left)
        && isSameTree(rootA->right,rootB->right);
        return ret;
    }
};

4.3.3. 对称二叉树

101. 对称二叉树 - 力扣(LeetCode)

// 判断节点是否相等
inline bool is_empty_node(TreeNode* root)
{
    return root == nullptr;
}

// 仿函数 --- 判断两个节点是否同时为nullptr
class two_node_all_empty
{
public:
    bool operator()(TreeNode* rootA, TreeNode* rootB)
    {
        if(is_empty_node(rootA) && is_empty_node(rootB))
            return true;
        else
            return false;
    }
};

// 仿函数 --- 判断两个节点是否有一个为nullptr
class two_node_one_empty
{
public:
    bool operator()(TreeNode* rootA, TreeNode* rootB)
    {
        if(is_empty_node(rootA) || is_empty_node(rootB))
            return true;
        else
            return false;
    }
};

class Solution {
public:
    // 判断两个节点是否相等
    bool is_equal_val_node(TreeNode* rootA,TreeNode* rootB)
    {
        two_node_all_empty all;
        if(all(rootA,rootB))
            return true;
        two_node_one_empty one;
        if(one(rootA,rootB))
            return false;
        if(rootA->val == rootB->val)
            return true;
        else
            return false;
    }
    bool is_equal_tree(TreeNode* rootA,TreeNode* rootB)
    {
        // 如果同时为nullptr,返回true
        two_node_all_empty all;
        if(all(rootA,rootB))
            return true;
        // 只有其中一个为nullptr,返回false
        two_node_one_empty one;
        if(one(rootA,rootB))
            return false;
        // 分别比较左子树和右子树的根
        // 左子树的左节点和右子树的右节点
        // 左子树的右节点和右子树的左节点
        // 全部相等返回true
        bool ret = is_equal_val_node(rootA,rootB)
        && is_equal_val_node(rootA->left,rootB->right)
        && is_equal_val_node(rootA->right,rootB->left);
        // 如果上面的判断为true,继续递归左子树和右子树
        // 如果上面的判断为false,直接return false
        if(ret == 1)
            return is_equal_tree(rootA->left,rootB->right)
        && is_equal_tree(rootA->right,rootB->left);
        return ret;
    }
    bool isSymmetric(TreeNode* root) {
        // 空树直接返回true
        if(is_empty_node(root))
            return true;
        // 只有一个节点,返回true
        two_node_all_empty all;
        if(all(root->left,root->right))
            return true;
        // 只有两个节点,返回false,不对称
        two_node_one_empty one;
        if(one(root->left,root->right))
            return false;
        // 将该二叉树分为左子树和右子树
        // 如果左子树和右子树对称,那么原树就是对称的
        return is_equal_tree(root->left,root->right);
    }
};

4.3.4. 二叉树的前序遍历

// 确立节点个数,为了具体的malloc的空间大小
int get_tree_node_size(struct TreeNode* root)
{
    if(root == NULL)
        return 0;
    else 
        return get_tree_node_size(root->left) + get_tree_node_size(root->right) + 1;
}

int* _pre_order(struct TreeNode* root,int* arr,int* pi)
{
    if(root == NULL)
        return arr;
    // 根据前序的遍历规则根左右(NLR):
    else
    {
        //注意:这里较好的方式:传递下标的地址
        arr[(*pi)++] = root->val;
        _pre_order(root->left,arr,pi);
        _pre_order(root->right,arr,pi);
        return arr;
    }
} 

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    *returnSize = get_tree_node_size(root);
    int* ret_arr = (int*)malloc(sizeof(int)*(*returnSize));
    assert(ret_arr);
    int i = 0;
    ret_arr = _pre_order(root,ret_arr,&i);
    return ret_arr;
}
class Solution {
public:
    // 由于这里的ret出了函数作用域,不会被销毁,因此可以用传引用返回
    vector<int>& _pre_order(vector<int>& ret,TreeNode* root)
    {
        if(root == nullptr)
            return ret;
        else
        {
            // 根据左根右的规则(NLR)
            ret.push_back(root->val);
            _pre_order(ret,root->left);
            _pre_order(ret,root->right);
            return ret;
        }
    }
    vector<int> preorderTraversal(TreeNode* root) {
        std::vector<int> ret;
        _pre_order(ret,root);
        return ret;
    }
};

4.3.5. 二叉树的中序遍历

class Solution {
public:
    // 因为ret出了函数作用域不会被销毁,因此可以传引用返回
    vector<int>& _in_order(TreeNode* root,vector<int>& ret)
    {
        if(root == nullptr)
            return ret;
        else
        {
            //根据中序的遍历规则左根右(LNR)
            _in_order(root->left,ret);
            ret.push_back(root->val);
            _in_order(root->right,ret);
            return ret;
        }
    }

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        _in_order(root,ret);
        return ret;
    }
};

4.3.6. 二叉树的后序遍历

class Solution {
public:
    // 由于这里的ret出了函数作用域,不会被销毁,因此可以用传引用返回
    vector<int>& _post_order(TreeNode* root,vector<int>& ret)
    {
        if(nullptr == root)
            return ret;
        else
        {
            //根据后序的遍历规则左右根(LRN)
            _post_order(root->left,ret);
            _post_order(root->right,ret);
            ret.push_back(root->val);
            return ret;
        }
    }
    vector<int> postorderTraversal(TreeNode* root) {
        std::vector<int> ret;
        _post_order(root,ret);
        return ret;
    }
};

 4.3.7. 另一颗树的子树

572. 另一棵树的子树 - 力扣(LeetCode)

// 判断节点是否为nullptr
inline bool is_empty_node(TreeNode* root)
{
	return root == nullptr;
}
// 判断两个节点是否相等
bool is_equal_node(TreeNode* left, TreeNode* right)
{
	if (is_empty_node(left) && is_empty_node(right))
		return true;
	if (is_empty_node(left) || is_empty_node(right))
		return false;
	if (left->val == right->val)
		return true;
	else
		return false;
}
// 判断两棵树是否相等
bool isSameTree(TreeNode* rootA, TreeNode* rootB) {
	if (is_empty_node(rootA) && is_empty_node(rootB))
		return true;
	if (is_empty_node(rootA) || is_empty_node(rootB))
		return false;
	// 只要满足上面的条件,就开始分别判断对应的根、左子树、右子树
	bool ret = is_equal_node(rootA, rootB)
		&& is_equal_node(rootA->left, rootB->left)
		&& is_equal_node(rootA->right, rootB->right);
	// 只要有一个不相等即返回值ret == 0,那么就不用继续递归左子树和右子树了
	// 如果前面的判断 ret != 0,那么就继续递归左子树和右子树
	if (ret == 1)
		return isSameTree(rootA->left, rootB->left)
		&& isSameTree(rootA->right, rootB->right);
	return ret;
}

class Solution {
public:
    bool isSubtree(TreeNode* root, TreeNode* subRoot) {
        if(root == nullptr)
            return false;
        bool ret = 0;
        // 如果当前根节点 == 目标根节点,在判断是否是相同的树结构
        if(is_equal_node(root,subRoot))
        {
            //  分别比较两个数的根、左子树、右子树
            ret = isSameTree(root,subRoot)
            || isSameTree(root->left,subRoot)
            || isSameTree(root->right,subRoot);
        }
        // 如果上面不相等,继续递归它的左子树、右子树
        if(ret == 0)
            ret = isSubtree(root->left,subRoot) 
            || isSubtree(root->right,subRoot);
        return ret;
    }
};

4.3.8. 二叉树的最大深度

104. 二叉树的最大深度 - 力扣(LeetCode)

# 方案一: 中途包括了大量的重复计算,不好
// class Solution {
// public:
//     int maxDepth(TreeNode* root) {
//         if(root == nullptr)
//             return 0;
//         else
//            // 虽然结果正确,但最后会时间超限
//            // 原因:三目运算符们没有保存左右调用的结果
//            // 导致最后处理时又进行了一次递归,产生了大量的重复计算
//         {
//             return maxDepth(root->left) > maxDepth(root->right) ?
//             maxDepth(root->left) + 1 : maxDepth(root->right) + 1;
//         }
//     }
// };

# 方案二: 通过用变量保存递归的结果,避免了重复计算
// class Solution {
// public:
//     int maxDepth(TreeNode* root) {
//         if(root == nullptr)
//             return 0;
//         else
//         {
                //通过保存左右数递归的结果,避免了大量的重复计算
//             int left_high = maxDepth(root->left);
//             int right_high = maxDepth(root->right);
//             return left_high > right_high ? left_high + 1 : right_high + 1;
//         }
//     }
// };

# 方案三:通过函数的返回值避免了重复计算
class Solution {
public:
    inline int get_max_two_sum(const int& left,const int& right)
    {
        if(left < right)    return right;
        else return left;
    }
    int maxDepth(TreeNode* root) {
        if(root == nullptr)
            return 0;
        else
        {
            // 通过函数的返回值处理,避免了重复计算
            return get_max_two_sum(maxDepth(root->left),maxDepth(root->right)) + 1;
        }
    }
};

4.3.9. 翻转二叉树

226. 翻转二叉树 - 力扣(LeetCode)

class Solution {
public:
    // 判断节点是否为nullptr
    inline bool is_empty_node(TreeNode* node)
    {
        return node == nullptr;
    }
    // 交换两个有效节点
    void swap_two_node(TreeNode*& left,TreeNode*& right)
    {
        TreeNode* tmp = left;
        left = right;
        right = tmp;
    }

    void reverse_tree(TreeNode* tree_node)
    {
        //如果遇到nullptr,直接返回
        if(tree_node == nullptr)
            return ;
        else
        {
            // 结合后续的思想:左右根(LRN)
            reverse_tree(tree_node->left);
            reverse_tree(tree_node->right);
            swap_two_node(tree_node->left,tree_node->right);
        }
    }

    TreeNode* invertTree(TreeNode* root) {
        // 如果是空树,直接返回nullptr
        if(is_empty_node(root))
            return nullptr;
        // 如果只有根节点一个节点,直接返回root
        if(root->left == nullptr && root->right == nullptr)
            return root;
        // 如果有两个及两个以上的节点,进行翻转处理
        else
            reverse_tree(root);
        return root;
    }
};

 

 

4.3.10. 二叉树遍历

二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

 

#include <iostream>
template<class T>
struct binary_tree_node
{
    binary_tree_node<T>* _left;
    binary_tree_node<T>* _right;
    T _key;
    binary_tree_node(const T& key)
    : _left(nullptr)
    ,_right(nullptr)
    ,_key(key)
    {}
};

binary_tree_node<char>* build_binary_tree(const std::string& str,int& i)
{
    //如果遇到'#',就返回到上级栈帧
    if(str[i] == '#'){
        ++i;
        return nullptr;
    }
    //构建新节点
    binary_tree_node<char>* root = new binary_tree_node(str[i++]);
    // 通过前序的思想根左右(NLR):
    // 递归子树,并通过返回值构成链接关系
    root->_left = build_binary_tree(str,i);
    root->_right = build_binary_tree(str,i);
    return root;
}

template<class T>
void in_order(binary_tree_node<T>* root)
{
    if(root == nullptr)
        return ;
    else
    {
        in_order(root->_left);
        std::cout << root->_key << " ";
        in_order(root->_right);
    }
}

int main()
{
    std::string st;
    std::cin>>st;
    int i = 0;
    // 构建二叉树
    binary_tree_node<char>* node = build_binary_tree(st,i);
    // 中序遍历
    in_order(node);
    return 0;
}
举报

相关推荐

0 条评论