TF使用例子-情感分类

article/2025/9/22 14:47:04
北京站 | NVIDIA DLI深度学习培训
2018年1月26日

NVIDIA 深度学习学院 带你快速进入火热的DL领域
阅读全文                           



正文共10052个字,4张图,预计阅读时间26分钟。


这次改写一下,做一个简单的分类模型和探讨一下hidden layer在聚类的应用场景下会有什么效果。为了能写的尽可能让读者理解,本文也会写一下keras来实现(就几行代码)。


01

爬取数据


网上有很多的爬虫教程,这里不具体讲了,不过强烈建议爬别人网站的时候先找找有没有现成的api(比如你想爬网易云音乐的歌词评论数据什么的o( ̄▽ ̄)d)。


我这里爬了bangumi上一些作品的评论,附上代码(crawler.py):


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import re
import time
from bs4 import BeautifulSoup
req_header = {  
 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',  
 'Accept-Language':'zh - CN, zh;q = 0.8, en;q = 0.6',    
'Connection':'keep - alive',    
'Cookie':'_hc.v = "\"4e10f82f-cdb5-4fa1-ba89-262a394be3d1.1490604667\""; PHOENIX_ID = 0a01084a - 15b1299fd35 - 163c9ff; s_ViewType = 10; JSESSIONID = F255BB7A08A17AFC8F2E3701599B3193; aburl = 1; cy = 6; cye = suzhou',  
 'Upgrade-Insecure-Requests':'1',  
 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
 history = {} #记录链接是否已经爬取过,防止重定向
for subjectid in range(1,220000):    
pageid = 1    
cur_url = "http://bgm.tv/subject/" + str(subjectid)+ "/comments"    
while(True):        

mark = 0        
cur_url = cur_url + "?page=" + str(pageid)        
if cur_url in history: # 是否爬取过            
break        
else:            
history[cur_url
 ![@H95LRLI39{5M`FA3OIY{QD.png](http://upload-images.jianshu.io/upload_images/77013-6993ddc74df9c560.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)] = 1      
 try:            
r = requests.get(cur_url, headers=req_header, timeout=10)        
except:            
print(subjectid)            
time.sleep(5)          
 try:                
r = requests.get(cur_url, headers=req_header, timeout=10)            
except:                
break
#爬两次还爬不到就放弃换下一个        
content = BeautifulSoup(r.content, 'lxml')        
base_url_index = r.url.find("?")      
 cur_url = r.url[0:base_url_index]        
try:           

 title = content.find("a",href=re.compile(r"/subject*")).text        
except:            
break      
 if title == "": #爬取到的页面有问题,比如404            
break      
 # 因为把每个词条都作为文件名,所以有些特殊字符不能作为文件名      
 title = title.replace("/","-").replace(":","-").replace("\""," ").replace("<", "(").replace(">", ")").replace("?","-").replace("*","-").replace("\\","-").replace("|","-").replace("\n","-")              
 # 把爬取到的条目下的评论都拿出来放到条目文件里      
 with open("directory/"+title, "a", encoding="utf-8")as f:          
 for item in content.find("div", {"id": "comment_box"}).find_all("div", {"class":"item clearit"}):                item = item.find("div",{"class": re.compile(r"text_main_*")})              
 try:                    
print(item.find("span")["class"])                
except:                  
break                
f.write(item.find("span")["class"][0] + '\n')                
f.write(item.text.strip())                
mark = 1              
 f.write("\n===========================\n")      
 pageid += 1        
if mark == 0:            
break

   time.sleep(0.3) # 睡一会,别爬太快


1.1 处理数据


爬下来的数据是这样的:


词条目录


每个词条里面是这样的:


词条细节


sstarsX表示的是打分,半个星表示1分,所以如果是sstars8就是4个星。每一行用“=====”分割。我们先把数据抽取出来。


#!/usr/bin/env python # -*- coding: utf-8 -*- import osimport re out = open("bangumi_sentiment", "w", encoding="utf-8")for cur_file in os.listdir("directory"):     with open("directory/"+cur_file, "r", encoding="utf-8") as f:        comments = f.read().split("===========================")         for comment in comments:            if comment == "":                 continue            start_index = comment.find("sstars")            score = comment[start_index+6:8].strip()             try:                #找到日期末尾位置                content_index = re.search("\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}  ",comment).end() except:                 continue out.write(score+" ") out.write(" ".join(comment[content_index:].split("\n"))+"\n")


(因为又爬了日期又爬了用户名以及打分,所以还可以做一些数据分析比如看看那些人容易给差评,兴趣相投什么的,再按一定规则给词条打个平均分什么的..反正有很多东西可以做,不过又不是PM操什么心..)


抽取出来的数据如下,第一列是打分:


8 Tr1对唱和列车音好评,Tr2+1星
6 “Terminal Station”6.8★
7 Tr.1的对唱真心带感,可惜量少是硬伤
8 嗯!很棒!一二曲很好听的!
4 “Forgotten Paradise”5★
7 合唱都很赞。个别几轨有点抽风,不过不影响整体的水平
7 感觉还行吧……
6 一般吧,感觉没有3好
6 听不出原曲系列……Tr.1这个5合1略虎(可惜不好听233
6 GCHM良心大大滴坏了
9 这张很魔性


用类似的方法也可以爬爬其它网站,因为现在依然有很多网站没有做防爬虫的措施。(下篇博客写一下验证码识别哈~)


label 部分,我把情感分成low, middle, high三个部分,比如打分在[1,4]为low, (4,7]为middle, (7,10]为high。只是我自己拍脑袋这么设置的,因为我想弄成分类模型而不是回归,如果你打算直接预测得分,可以把label作为数值,后面建模的时候loss选用mean_squared_error,让预测的得分和实际得分尽可能的相近。


02

用TensorFlow建简单的文本分类模型


首先要把训练语料里的字和事先训练的word2vector里的字对应起来,再构建一个统一的embedding层。


2.1 训练词向量


这里我爬取了一些萌娘百科的文章加上上面bangumi的语料加起来差不多200M,因为是基于字,所以要把字按照空格分开,然后繁简大小写转换之后作为word2vector的输入,类似于这样:


word2vec输入文件


#!/usr/bin/env python# -*- coding: utf-8 -*- import logging import multiprocessing import sysfrom gensim.models import Word2Vec from gensim.models.word2vec import LineSentencedef train(inp, model_name, size=100, word_frequency_threshold=5):    logging.basicConfig(format = '%(asctime)s : %(levelname)s : %(message)s', level = logging.INFO)    print("Begin to train model ...")    model = Word2Vec(LineSentence(inp),    size = size,    window = 10,    hs = 1,    sg = 1,  # skip-gram    min_count = word_frequency_threshold,    workers = multiprocessing.cpu_count()/2)  #CPU数量    model.save_word2vec_format(model_name, binary=False) # save a model in order to be checkedif __name__ == "__main__":    if len(sys.argv) <3:        print("please input input file and model file")    inp = sys.argv[1]    model = sys.argv[2]    word_size = 100    word_threshold = 3    if len(sys.argv) > 3:        word_size = int(sys.argv[3]) #词向量维度        word_threshold = int(sys.argv[4]) #每个字最少出现的次数    train(inp, model, word_size, word_threshold)


运行python inp out.model 200 3训练一段时间后就可以得到一个名为out.model的模型,为了方便查看所以binary设置成False,输出的模型文件如下:


word2vec 模型


第一行是模型的维度,这里表示的含义是公有37064个字,每个字的词向量为200。每一行的第一个列是字。


这里提一下,我们有的时候训练了两个词向量的模型,那怎么把一个词的词向量映射到另一个词向量的空间呢?你可以利用两个词向量模型都有的词来学习权重w(有点像auto encoder),从而可以把一个模型中的词向量映射到另一个空间。


(另外,词向量的模型可以加载一个模型后,继续加入句子来训练。不过不可以加入新词)


我们这里比较简单的,如果不出现在我们训练好的词(字)向量中的字,直接用<unk>(unknow)来代替。


2.2 embedding层


如果要使用我们训练好的词向量来代替embedding层(你也可以不用,效果可能会稍微差点),你要确保的是你的输入(句子)中的每个字的id正好是词向量矩阵的第id个。比如有一个句子:


除 了 剧 情 外 啥 都 好 的 片 子


每个字在词向量矩阵中的行号分别是:


[1 2 3 4 5 6 7 8 0 10 11]


那你的模型这句话的输入就是[1 2 3 4 5 6 7 8 0 10 11]。


请一定要确保这一点,而且如果你用keras,你的padding的值就是embedding中对应的行号,比如如果你的padding是-1,对应的就是embedding[-1] 也就是embedding的最后一个字。


2.3 建模型


这部分其实跟上次的序列标注差不多,区别就是上次是多输出,这次是一个输出判断属于哪个类。核心代码如下:


# 设置placeholderword_ids = tf.placeholder(tf.int32, shape=[None, None],name="word_ids")  # batch size, max length of sentence in batchsequence_lengths = tf.placeholder(tf.int32, shape=[None], name="sequence_length")   # shape = batch sizelabels = tf.placeholder(tf.int32, shape=[None], name="labels")   # only one dimensiondropout = tf.placeholder(dtype=tf.float32, shape=[], name="dropout") lr = tf.placeholder(dtype=tf.float32, shape=[], name="lr")

# 设置embedding层with tf.variable_scope("words"):    _word_embeddings = tf.Variable(embeddings, name="_word_embeddings", dtype=tf.float32,                                   trainable=trainable)    word_embeddings = tf.nn.embedding_lookup(_word_embeddings, word_ids,                                             name="word_embeddings")    word_embeddings = tf.nn.dropout(word_embeddings, dropout)# 设置模型with tf.variable_scope("bi-lstm"):    lstm_cell = tf.contrib.rnn.LSTMCell(hidden_size)    _, (output_state_fw, output_state_bw) = tf.nn.bidirectional_dynamic_rnn(lstm_cell,     lstm_cell, word_embeddings,     sequence_length=sequence_lengths,     dtype=tf.float32)    output = tf.concat((output_state_fw[-1], output_state_bw[-1]), axis=-1)    output = tf.nn.dropout(output, dropout) # 输出部分在双向lstm的最后一层合并后加一个全连接层,全连接层后接一个softmax层with tf.variable_scope("proj"):    W = tf.get_variable("W", shape=[2 * hidden_size, nlabels],                        dtype=tf.float32)    b = tf.get_variable("b", shape=[nlabels], dtype=tf.float32,                        initializer=tf.zeros_initializer())    output = tf.reshape(output, [-1, 2 * hidden_size])    pred = tf.matmul(output, W) + b    logits = pred labels_pred = tf.cast(tf.argmax(logits, axis=-1), tf.int32) losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels) loss = tf.reduce_mean(losses) # batch的平均losswith tf.variable_scope("train_step"):    optimizer = tf.train.AdamOptimizer(lr)    train_op = optimizer.minimize(loss)


03

用Keras建简单的文本分类模型


keras这部分的代码比较简洁,需要注意的是如果要用variable_length的句子(不同长度句子),需要多设置一个参数mask_zero=True,这个参数在embedding层设置后,所有word_id=0的字都会在后面的lstm层会忽略掉,所以我们在设置embedding的时候要在第一行插入一个全0的row,相应的word_id也都要加1,这点要注意注意。


这里我给了两个可以做这个模型的模型,区别只是在输出的时候是要预测一个分类还是一个数值。如果是分类就把label处理成categorical的,如果是预测打分值就直接用数值就行(比如半颗星是1分,5星是10分)。


# 分类模型def create_model_classify(max_features, nlabels, embeddings=None, embedding_dim=200, hidden_dim=300):    model = Sequential()    if embeddings is not None:        model.add(Embedding(input_dim=max_features,output_dim=embedding_dim, dropout=0.2, weights=[embeddings], mask_zero=True))    else:        model.add(Embedding(max_features, embedding_dim=embedding_dim, dropout=0.2))    model.add(Bidirectional(LSTM(hidden_dim, dropout_W=0.2, dropout_U=0.2, return_sequences=False)))  # try using a GRU instead, for fun    model.add(Dense(nlabels))    model.add(Activation('softmax'))    model.compile(loss='categorical_crossentropy',                  optimizer='adam',                  metrics=['accuracy',]) # availabel metrics https://keras.io/metrics/    return model# 回归模型def create_model_regress(max_features, embeddings=None, embedding_dim=200, hidden_dim=300):    model = Sequential()    if embeddings is not None:        model.add(Embedding(input_dim=max_features,output_dim=embedding_dim, dropout=0.2, weights=[embeddings], mask_zero=True))    else:        model.add(Embedding(max_features, embedding_dim=embedding_dim, dropout=0.2))    model.add(        Bidirectional(LSTM(hidden_dim, dropout_W=0.2, dropout_U=0.2),                      merge_mode='concat'))      model.compile(loss='mean_squared_error',                  optimizer='sgd',                  metrics=['mae','acc' ])  # availabel metrics https://keras.io/metrics/idden_dim= return model


04

情感模型的隐藏层聚类


利用上面训练出来的模型,抽取每一条训练数据的隐藏层,然后对其进行聚类。(saraba1st数据集,训练集准确率90%)


#keras实现from keras.models import Model org_model = load_model("result/model.weights/acc_XXX.model") layer_name = 'bidirectional_1'intermediate_layer_model = Model(input=org_model.input,   # keras 2.0  inputs  outputs output=org_model.get_layer(layer_name).output) intermediate_output = intermediate_layer_model.predict(np.asarray(word_ids))


4.1 聚类结果


我们先对原始数据用PCA降维到2维方便显示在平面上,不同的颜色表示的是其原始的label。


原始结果


从图中可以看到原始的数据分布就是很不均匀的,黑色部分的数据量非常大。


那么来看一下其隐藏层的实际聚类情况:


Kmeans k=3:

Kmeans k=3


我们可以发现原本很大的一部分黑色被纳入了灰色,很有可能就是这部分的数据很难判断是归于哪个类。检查之后发现是level_middle和level_high之间存在了混淆(看起来也是可以理解的)。比如有些句子是这样的:


level_high 因 为 天 元 , 所 以 期 望 越 大 失 望 越 大 吧 . . . 看 完 后 还 有 印 象 的 就 第 1 第 3 第 7 集 了 . . . 后 半 部 分 慢 慢 变 得 很 乏 力 , 能 记 住 的 只 有 几 个 小 点 . . . 神 展 开 也 让 人 感 觉 不 到 太 大 的 惊 喜 。
level_high  前 第 三 话 真 是 惊 艳 , 往 后 越 来 越 烂 。


这两句句子就算是人眼去看的话也真的很难区分到底应不应该标level_high。


来看一下Kmeans k=4:

Kmeans k=4


可以看到在middle和high之间确实还夹杂了一层难以判断是好还是中立的语句。


可以庆幸的是负类都很明显的区分开来了。


05

分成正负两类的结果


把三个分类的结果转换成二分类之后,验证集上的acc从0.8提升到了0.85。


训练集上hidden layer的结果如下:


原始训练集PCA降维后的分布


Kmeans聚类之后的结果

k=2


可以看到中间还是有一部分被遮盖了。


k=3


由于从原始的PCA降维后的结果其实是很容易看到两个"簇"的,所以考虑换一个聚类方法。


Agglomerative聚类:
linkage = complete


agglomerative k=3 linkage=complete


k=2 linkage=complete


看起来好多了。。。


同样的出现上面的问题,很大程度是因为我们用了现成的"标注"(用户的评分),但是这种标注有的时候是非常不准确的。比如:


训练集标注样例


(明明打了5星,硬是在评价上做出了看起来不那么positive的结果,sigh~)


所以在实际应用训练集的时候往往是需要多个"专业"人员对其共同进行打分和标注,才能尽可能的准确。(然而这样的数据非常缺乏)。


话说旅游网站的评论数据通常都十分准确。。


06

文本代码


请戳这里(https://github.com/Slyne/tf_classification_sentiment)


07

总结


本文用tensorflow和keras实现了一下文本情感分类,窥探了一下隐藏层的表述能力(还是不错的)。


原文链接:https://www.jianshu.com/p/1659ce108f55


查阅更为简洁方便的分类文章以及最新的课程、产品信息,请移步至全新呈现的“LeadAI学院官网”:

www.leadai.org


请关注人工智能LeadAI公众号,查看更多专业文章

大家都在看

LSTM模型在问答系统中的应用

基于TensorFlow的神经网络解决用户流失概览问题

最全常见算法工程师面试题目整理(一)

最全常见算法工程师面试题目整理(二)

TensorFlow从1到2 | 第三章 深度学习革命的开端:卷积神经网络

装饰器 | Python高级编程

今天不如来复习下Python基础



http://chatgpt.dhexx.cn/article/5KnkEgwk.shtml

相关文章

计算机视觉(五)

Bag of features&#xff0c;简称Bof&#xff0c;中文翻译为“词袋”&#xff0c;是一种用于图像或视频检索的技术。而检索就要进行比对。两幅不同的图像如何比对&#xff0c;比对什么&#xff0c;这就需要提炼出每幅图像中精练的东西出来进行比较。 一、Bag of features算法基…

Eastmount博客导读:专栏系统分类和博客归纳总结

为了更好地帮助博友学习作者的博客&#xff0c;方便作者自己归纳总结专栏&#xff0c;本文详细介绍了作者八年来&#xff0c;在CSDN写的各种专栏&#xff0c;各种系列文章。八年来&#xff0c;作者经历了从本科到硕士&#xff0c;到贵州教书成家&#xff0c;再到现在的博士。八…

Python编程实现用KNN算法对红酒分类功能

一、任务要求 导入红酒数据集&#xff08;load_wine&#xff09;&#xff0c;编写Python代码&#xff0c;完成以下任务&#xff1a; 1、实现计算平均酒精含量的功能&#xff1b; 2、实现对数据的标准化&#xff1b; 3、使用kNN算法实现红酒分类功能 二、代码实现 from sklearn…

文本挖掘(四万字总结篇:爬虫 - 文本预处理 - 高频词统计 - 聚类 - 情感分析)

1 爬虫 1.1 爬虫原理 这部分内容可以跳过&#xff0c;掌握与否对后面内容的阅读影响并不大&#xff0c;但有兴趣的话可以看看呐~ 实现一个爬虫&#xff0c;一般需要经过两个步骤&#xff1a;处理请求和解析源码/数据。 处理请求方面&#xff0c;我们可以使用Python程序自动发送…

python卷积神经网络代码,python卷积神经网络分类

怎样用python构建一个卷积神经网络模型 上周末利用python简单实现了一个卷积神经网络&#xff0c;只包含一个卷积层和一个maxpooling层&#xff0c;pooling层后面的多层神经网络采用了softmax形式的输出。 实验输入仍然采用MNIST图像使用10个featuremap时&#xff0c;卷积和p…

用python实现基于自媒体数据的人群聚类分析

&#x1f345;程序员小王的博客&#xff1a;程序员小王的博客 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 如有编辑错误联系作者&#xff0c;如果有比较好的文章欢迎分享给我&#xff0c;我会取其精华去其糟粕 &#x1f345;java自学的学习…

二叉树三种非递归遍历以及代码实现

二叉树三种非递归遍历 1.二叉树前序非递归遍历实现&#xff0c;&#xff08;采用栈&#xff09; 思路&#xff1a;(用一个栈) 1.首先用cur标记树的根(root),当cur非空的时候&#xff1b; 2.就直接打印根&#xff0c;并且将cur(也就是root)入栈&#xff1b; 3.接着遍历根的左子…

c++练习(5):二叉树非递归遍历

二叉树遍历 二叉树有三种遍历方法&#xff1a;前序&#xff08;跟左右&#xff09;跟节点在前面、中序&#xff08;左跟右&#xff09;跟节点在中间、后续&#xff08;左右跟&#xff09;跟节点在后面 前序(跟左右)&#xff1a;上图的二叉树&#xff0c;第一次跟左右对应ABC…

C++——二叉树OJ|二叉树非递归遍历

目录 二叉树的前序遍历 二叉树的中序遍历 二叉树的后续遍历 二叉树的前序遍历 144. 二叉树的前序遍历 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> preorderTraversal(TreeNode* root) { TreeNode* curroot; stack<TreeNode*>…

数据结构与算法_二叉树非递归遍历

记录二叉树的前序&#xff0c;中序&#xff0c;后续&#xff0c;层序等非递归遍历。 1 二叉树非递归前序遍历 用栈实现二叉树非递归前序遍历&#xff0c;按照 V L R顺序进行遍历&#xff1b;每一个都按照V L R方式进行。 上图中&#xff0c;根节点先入栈&#xff0c;出栈并访…

二叉树遍历非递归C++

二叉树遍历非递归C 题目链接二叉树的前序遍历思路分析代码实现 二叉树的中序遍历思路分析代码实现 二叉树的后序遍历思路分析代码实现 一点点题外话 题目链接 二叉树的前序遍历 144. 二叉树的前序遍历 思路分析 既然要使用非递归的方式&#xff0c;那就必须要借助栈来进行处…

二叉树的遍历(非递归)

由于二叉树的递归方法实际上是系统在使用栈进行操作&#xff0c;因此我们的迭代&#xff08;非递归&#xff09;方法也就需要使用栈进行模拟。 一、先序遍历 我们需要明白&#xff0c;进栈的元素都是树的根节点 root。 所以我们需要先访问该节点&#xff0c;再将该节点进栈。…

二叉树非递归遍历算法分析

以前没有学习过树的相关算法&#xff0c;只是了解一些皮毛&#xff0c;最近开始认真学习它。看视频或者网上查资料&#xff0c;可以知道怎么去遍历一棵树&#xff0c;但是算法为什么是这样的呢&#xff1f;少有讲到。如果有一天&#xff0c;我忘记了这个算法&#xff0c;我需要…

c语言和c++实现二叉树非递归遍历

结合栈结构来实现二叉树的非递归遍历&#xff0c;首先将根节点入栈&#xff0c;然后对栈内元素进行循环&#xff0c;弹出栈顶元素&#xff0c;根据链表结点携带的标志位flag来判断是对于结点进行打印还是后续的入栈操作。入栈顺序决定着实际的遍历方式。 main.cpp #include&l…

【数据结构】二叉树的非递归遍历(完整代码)

默认给一棵树前序遍历的结果&#xff0c;按要求创建这棵树&#xff08;#表示空&#xff09;&#xff0c;并用非递归的方法对它进行遍历。 解题思路 1.递归遍历&#xff1a; 则将二叉树的遍历看作是分治问题&#xff0c;将每一棵树都分为根-左子树-右子树三部分&#xff0c;每…

数据结构--二叉树的遍历(非递归)

我们已经对二叉树的基本概念有了一定的了解&#xff0c;也对它的递归版本的前、中、后序遍历掌握的很好了&#xff0c;那么接下来就要介绍二叉树中遍历的非递归版本 >【数据结构】二叉树详解< 先看本篇文章效果更佳哦 目录 1、非递归遍历解析2、前序遍历(非递归)3、中序…

二叉树的非递归遍历 C语言版

文章作者&#xff1a;Slyar 文章来源&#xff1a;Slyar Home (www.slyar.com) 转载请注明&#xff0c;谢谢合作。 上周数据结构课在讲二叉树的遍历&#xff0c;老师只讲递归算法&#xff0c;没有什么技术含量&#xff0c;遂自己琢磨非递归算法实现... 前序遍历:先访问根节点&…

二叉树非递归遍历

1. 遍历一棵树 前、中、后序遍历的路径实际是相同的&#xff0c;都是以如上路径去游历。 前、中、后序访问的结果不同&#xff0c;实际是访问节点的时机不同&#xff1a; 前序&#xff1a;一遇到节点就去访问 中序&#xff1a;访问完左树再去访问 后序&#xff1a;访问完右子…

C语言数据结构七:二叉树非递归遍历

二叉树的非递归遍历&#xff0c;必须借助栈的辅助。必须采用栈的这种先进后出的特性。 算法实现思路&#xff1a; 我们先将根节点压入栈中。然后往外弹出元素。直到栈中元素弹完为止。1、将根节点压入栈中。&#xff08;但是压入栈中&#xff0c;并不知简单的将根节点压入栈中…

二叉树非递归遍历(先序、中序、后序)

首先是一个二叉树结构&#xff1a; class TreeNode{public int value;public TreeNode left;public TreeNode right;public TreeNode(){}public TreeNode(int v){valuev;} 非递归的二叉树遍历需要用到栈的先进后出。 先序遍历 步骤&#xff1a; 1、首先定义一个当前正在检索…