如何正确方便的理解双指针?力扣102 (二叉树的层序遍历)

news2024/11/14 21:23:59

双指针,顾名思义就是指针的指针。
在此之前我们需要先理解单指针 (简称为指针)。指针很简单,直接上例子:例:现有两个变量,a=10,b=20.
要求:交换他们的值,输出的结果应为a=20,b=10。

#include <bits/stdc++.h>
using namespace std;

void swap(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {
	int a = 10, b = 20;
	swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

乍一看没问题,结果:在这里插入图片描述
交换失败。失败的原因是在函数swap(int a,int b)中,接收的a和b是两个整型的值而不是指针(换句话说,这里交换的是形式参数而非实际参数)。int a=10;int b=10;定义的是两个实际参数,swap(int a,int b)中的a和b是形式参数,形参的交换对实参的交换是没有影响的。因此要将void swap(int a, int b)修改为void swap(int &a, int &b)

#include <bits/stdc++.h>
using namespace std;
void swap(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}
int main() {
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

在这里插入图片描述
成功。这里的void swap(int &a, int &b)是引用传递,当你调用这个函数时交换操作会直接修改变量a和b的值。这意味着函数外部的变量a和b的值也会发生交换。
当你使用swap(int *a, int *b)函数时得出的结果和上述的引用传参是一样的,但是原理不同:此时参数a和b是指向整型的指针,函数内部通过指针操作来交换变量的值。

void swap(int *a, int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

在这里插入图片描述

它们的运行结果相同。总结:引用方式和指针方式的交换两个变量的值的区别:
1、语法:
引用方式:使用引用作为形式参数,在函数内部直接操作引用所绑定的变量。
指针方式:使用指针作为形式参数,在函数内部通过引用指针所指向的地址来修改变量的值。因此引用方式的执行效率更高。
2、调用方式:
引用方式直接将变量作为实际参数传递给函数,无需取地址操作。
指针方式:将变量的地址作为实际参数传递给函数,在调用时取地址操作符&。
3、影响范围:
引用方式通过引用传递,函数内部的修改会直接影响到函数外部的变量
指针方式:通过指针传递,函数内部的修改只影响指针所指向的内存地址,不会修改指针外部的变量。注意:在函数内部修改指针指向的值,则会影响函数外部的变量。
4、错误处理:
**引用方式的引用必须绑定到一个已存在的对象,**所以在使用引用方式交换值时不需要考虑空指针或者野指针的可能性。
指针方式:指针可以为NULL或者指向未知的内存地址,所以在使用指针交换值时需要注意指针的有效性,避免空指针或者野指针的访问。

因此:我们明白了:引用方式更为简洁和安全;指针方式更加灵活,可以处理更多的特殊情况,但是相对复杂。

接下来就可以开始介绍:双指针

#include <bits/stdc++.h>
using namespace std;

void swap(int **a, int **b) {
	int *temp = *a;
	*a = *b;
	*b = temp;
}

int main() {
	int a = 10;
	int b = 20;
	int *str1 = &a;
	int *str2 = &b;
	swap(&str1, &str2);
	cout << *str1 << " " << *str2 << endl;
	return 0;
}

在这里插入图片描述
和单指针对比着看就很直观了!swap的形式参数的a和b各多了一颗“*”,整型变量temp多了一颗*。其余的没有变化。因此双指针就是给指针又套上了一个指针,并没有很复杂。学完了双指针,我们来做一题对应的习题:LeetCode102 二叉树的层次遍历
在这里插入图片描述
核心代码:

int **levealOrder(struct TreeNode *root, int *returnSize, int **returnColumnSizes) { //层序遍历函数
	int **ans = (int **)malloc(sizeof(int *) * 2000); //动态创建一个二维数组ans用于存储层序遍历的结果
	*returnSize = 0; //所在层数
	if (!root)
		return NULL;
	int columnSizes[2000];//存所在层的结点
	struct TreeNode *queue[2000];//用于存储结点的队列
	int rear = 0, head = 0; //表示队头和队尾的索引
	queue[rear++] = root; //将结点进入队列
	while (rear != head) {
		ans[(*returnSize)] = (int *)malloc(sizeof(int) * (rear - head)); //存储当前结点
		columnSizes[(*returnSize)] = rear - head; //存储当前层的结点数量
		int start = head; //当前层在队列中的起始位置
		head = rear; //头部索引变为尾部索引,表示将要开始遍历下一层
		for (int i = start; i < head; i++) { //遍历当前层的所有结点

			ans[(*returnSize)][i - start] = queue[i]->val;
			if (queue[i]->left)
				queue[rear++] = queue[i]->left;
			if (queue[i]->right)
				queue[rear++] = queue[i]->right;
		}
		(*returnSize)++;
	}
	*returnColumnSizes = (int *)malloc(sizeof(int) * (*returnSize));
	for (int i = 0; i < *returnSize; i++)
		(*returnColumnSizes)[i] = columnSizes[i];
	return ans;
}

该算法是一个层序遍历算法.层序遍历的过程用大白话描述就是:
if 无根结点,结束
else 有根结点
(1)先遍历根结点;
(2)若有左孩子,将左结点放入队列数组中;
(3)若有右孩子,将右结点放入队列数组中;
重复上述过程直到结点为NULL。

关于这个算法核心部分的解释:首先levealOrder函数的形式参数有3个,第一个是单指针指向(或者说引用)根结点root,
第二个是单指针指向层序遍历的初始层数,
第三个是双指针指向每层结点数量的数组。
该函数最终要返回一个存储层序遍历结果的二维数组ans[][].

int **ans指向一个动态创建的二维数组,用于存储层序遍历的结果:
行代表所在层,列代表所在层的结点位置。即ans[returnSize][rear-head].
(rear-head)是当前层的结点的位置。head和rear分别是队头和队尾的索引。
queue[]用于存储结点以方便遍历。
columnSize用于存储每层结点的数量;

完整代码:

#include <bits/stdc++.h>
using namespace std;

typedef struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
} TreeNode;

TreeNode *createNode(int val) {
	TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode));
	newNode ->val = val;
	newNode ->left = NULL;
	newNode ->right = NULL;
	return newNode;
}

int **levealOrder(struct TreeNode *root, int *returnSize, int **returnColumnSizes) { //层序遍历函数
	int **ans = (int **)malloc(sizeof(int *) * 2000); //动态创建一个二维数组ans用于存储层序遍历的结果
	*returnSize = 0; //所在层数
	if (!root)
		return NULL;
	int columnSizes[2000];//存所在层的结点
	struct TreeNode *queue[2000];//用于存储结点的队列
	int rear = 0, head = 0; //表示队头和队尾的索引
	queue[rear++] = root; //将结点进入队列
	while (rear != head) {
		ans[(*returnSize)] = (int *)malloc(sizeof(int) * (rear - head)); //存储当前结点
		columnSizes[(*returnSize)] = rear - head; //存储当前层的结点数量
		int start = head; //当前层在队列中的起始位置
		head = rear; //头部索引变为尾部索引,表示将要开始遍历下一层
		for (int i = start; i < head; i++) { //遍历当前层的所有结点

			ans[(*returnSize)][i - start] = queue[i]->val;
			if (queue[i]->left)
				queue[rear++] = queue[i]->left;
			if (queue[i]->right)
				queue[rear++] = queue[i]->right;
		}
		(*returnSize)++;
	}
	*returnColumnSizes = (int *)malloc(sizeof(int) * (*returnSize));
	for (int i = 0; i < *returnSize; i++)
		(*returnColumnSizes)[i] = columnSizes[i];
	return ans;
}

int main() {
	TreeNode *root = createNode(3);
	root->left = createNode(9);
	root->right = createNode(20);
	root->right->left = createNode(15);
	root->right->right = createNode(20);
	int returnSize;//记录当前层数
	int *returnColumnSizes;//存放当前层结点的数组

	int **result = levealOrder(root, &returnSize, &returnColumnSizes);
	for (int i = 0; i < returnSize; i++) {
		cout << "第" << (i + 1) << "层:";
		for (int j = 0; j < returnColumnSizes[i]; j++)
			cout << result[i][j] << " " ;
		cout << endl;
	}

	for (int i = 0; i < returnSize; i++)
		free(result[i]);//动态创建的空间用完之后要释放掉,避免内存泄漏的风险。
	free(result);
	free(returnColumnSizes);
	return 0;
}

结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1074767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统 —— 进程篇

文章目录 进程的概念程序的概念进程控制块 PCB进程的组成进程状态进程状态转换 进程队列进程的组织进程控制内核的两大功能进程创建进程终止进程阻塞与唤醒进程切换 进程通信 进程的概念 进程是操作系统中的基本概念&#xff0c;用于描述正在运行的程序实例。 它是计算机系统…

GG-Net: 超声图像中乳腺病变分割的全局指导网络

ATTransUNet 期刊分析摘要贡献方法整体框架1. Global Guidance Block2. Spatial-wise Global Guidance Block3. Channel-wise Global Guidance Block4. Breast Lesion Boundary Detection Module 实验1. 对比实验2. 消融实验2.1 Ablation Analysis of our GG-Net2.2 Ablation A…

maven_修改项目名_修改模块名_复制模块_导入模块

修改模块名 1、删除.idea和.iml 2、修改gav(记得修改子模块) 复制模块名&修改模块名 1、复制文件并修改artifactId

(二)Web服务器之Linux多进程

一、基础概念 Linux操作系统一般由以下四个主要部分组成&#xff1a;内核、shell、文件系统和应用程序 。 内核&#xff08;Kernel&#xff09;&#xff1a;是操作系统的核心部分&#xff0c;负责管理系统的硬件资源、进程管理、内存管理、文件系统等。它直接与硬件交互&…

Java中的 try-finally 代码块的题目

class Test4 {int i 1;public static void main(String[] args) {System.out.println("i的值&#xff1a;" new Test4().test());}int test() {try {// 当 try 代码块执行 return 语句时&#xff0c;返回值已经被确定并保存下来&#xff0c;等待方法结束后返回。尽…

网络原理必知会

前言&#xff1a; 网络初始&#xff1a;对于网络有一个直观的大体的认识 网络编程&#xff1a;让我们真正通过代码感受网络通信程序 网络原理&#xff1a;进一步的理解网络是如何工作的&#xff0c;以理论为主&#xff0c;很多比较抽象的东西&#xff0c;同时这里也包含大量的面…

ssti 前置学习

python venv环境 可以把它想象成一个容器&#xff0c;该容器供你用来存放你的Python脚本以及安装各种Python第三方模块&#xff0c;容器里的环境和本机是完全分开的 创建venv环境安装flask #apt install python3.10-venv #cd /opt #python3 -m venv flask1 #cd /opt 选…

1.1了解python_python量化实用版教程(初级)

Python 特点 Python 安装和使用的编译器选择不展开。 Python 是一种高级编程语言&#xff0c;具有以下特点&#xff1a; - 简单易学&#xff1a;Python 语法简单&#xff0c;易于学习和理解。 - 开放源代码&#xff1a;Python 是开源的&#xff0c;可以免费使用&#…

实验1机器学习之线性回归实验

一、实验目的&#xff1a; &#xff08;1&#xff09;理解一元线性回归和多元线性回归的数学原理&#xff0c;能够利用sklearn中相关库解决现实世界中的各类回归问题&#xff1b; &#xff08;2&#xff09;掌握利用matplotlib对一元线性回归模型进行可视化的方法&#xff0c…

操作系统 OS

本文章是学习《操作系统》慕课版 和 王道《2024年 操作系统 考研复习指导》后所做的笔记&#xff0c;其中一些图片来源于学习资料。 目录 概念&#xff08;定义&#xff09; 目标 方便性 有效性 可扩充性 开放性 作用 OS 作为用户与计算机硬件系统之间的接口 — 人机交…

基于WTMM算法的图像多重分形谱计算matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、WTMM算法概述 4.2、WTMM算法原理 4.2.1 二维小波变换 4.2.2 模极大值检测 4.2.3 多重分形谱计算 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部…

MinGW的安装和使用

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 参考博客 1、如何在Windows上使用GCC编译器&#xff1f; 2、MinGW安装和使用-腾讯云开发者社区-腾讯云 一、MinGW的简介 GCC 官网提供的 GCC 编译器是无法直接安装到 Windows 平台上的&#xff0c;如果我们…

React 状态管理 - Mobx 入门(上)

Mobx是另一款优秀的状态管理方案 【让我们未来多一种状态管理选型】 响应式状态管理工具 扩展学习资料 名称 链接 备注 mobx 文档 1. MobX 介绍 MobX 中文文档 mobx https://medium.com/Zwenza/how-to-persist-your-mobx-state-4b48b3834a41 英文 Mobx核心概念 M…

编译器优化等级对程序性能的影响

文章目录 前言代码示例性能差异探究原因附录 前言 GCC 有 -O0、-O1、-O2、-O3 四级优化等级&#xff0c;你知道它们对程序性能有多少影响吗&#xff1f;知道性能差异产生的根本原因是什么吗&#xff1f;今天就和大家一起研究下。 代码示例 combine4.c #include <stdio.h…

用什么工具来画UML?

2023年10月9日&#xff0c;周一晚上 目录 我的决定 关于rational rose UML工具有哪些 相关资料 我的决定 我决定用plantUML、draw.io或starUML就可以了 其实没必要在意工具&#xff0c; 重要的是能把图画出来、把图画好画规范&#xff0c; 重要的是知道怎么去画图、把意…

linux,write:xxx has messages disabled 与 Ubuntu多用户同时登录的问题 ubuntu 20.04

write&#xff1a;xxx has messages disabled 问题 被这问题折磨了好久&#xff0c;搜都搜不到&#xff0c;还是灵机一动想到的。 很多 帖子说&#xff0c;要使用 mesg y用了还是没有用&#xff0c;后面我登录了很多用户&#xff0c;发现只有root用户可以给别的用户使用write…

【深度学习实验】卷积神经网络(八):使用深度残差神经网络ResNet完成图片多分类任务

一、实验介绍 本实验实现了实现深度残差神经网络ResNet&#xff0c;并基于此完成图像分类任务。 残差网络&#xff08;ResNet&#xff09;是一种深度神经网络架构&#xff0c;用于解决深层网络训练过程中的梯度消失和梯度爆炸问题。通过引入残差连接&#xff08;residual conne…

java实验(头歌)-Java类和对象之访问限制

/** 任务&#xff1a;实现图书类&#xff0c;该类包含了图书的基本属性和信息。类名为&#xff1a;Book */ // 请在下面的Begin-End之间按照注释中给出的提示编写正确的代码 /********** Begin **********/ public class Book { // 定义四个私有变量 // 图书名称&#xff08;…

近期分享学习心得3

1、全屏组件封装 先看之前大屏端的监控部分全屏代码 整块全屏代码 常规流是下面这种 //进入全屏 function full(ele) {//if (ele.requestFullscreen) {// ele.requestFullscreen();//} else if (ele.mozRequestFullScreen) {// ele.mozRequestFullScreen();//} el…

黑马JVM总结(二十九)

&#xff08;1&#xff09;语法糖-重写桥接 &#xff08;2&#xff09;语法糖-匿名内部类 &#xff08;3&#xff09;类加载-加载 类加载可以分为3个阶段&#xff0c;加载、连接、初始化 我们知道java类编译成字节码以后&#xff0c;运行呢需要类加载器把类的字节码加载到方法…