C4.5决策树生成算法完整版(Python),连续属性的离散化, 缺失样本的添加权重处理, 算法缺陷的修正, 代码等
C4.5决策树生成算法完整版(Python)
转载请注明出处:©️ Sylvan Ding
ID3算法实验
决策树从一组无次序、无规则的事例中推理出决策树表示的分类规则,采用自顶向下的递归方式,在决策树的内部节点进行属性值的比较并根据不同的属性值判断从该结点向下的分支,在决策树叶结点得到结论。
实验目的
- 理解ID3算法原理
- 理解C4.5算法原理
- 编程实现C4.5算法
决策树生成算法
决策树的生成过程是使用满足划分准则的特征不断地将数据集划分为纯度更高、不确定性更小的子集的过程。
Generate_decision_tree
输入: 训练样本samples,由离散值属性表示;候选属性的集合attribute_list。
输出: 一颗决策树
创建结点N
IF samples都在同一个类C THEN返回N作为叶结点,以类C标记;
IF attribute_list为空 THEN返回N作为叶结点,标记为samples中最普通的类;
选择attribute_list中具有最高信息增益(或增益率)的属性test_attribute;
标记结点N为test_attribute;
FOR each test_attribute中的已知值ai //划分samples由结点N长出一个条件为test_attribute=ai的分支;
设si是samples中test_attribute=ai的样本集合 //一个划分
IF si为空 THEN加上一个树叶,标记为samples中最普通的类;
ELSE 加上一个由Generate_decision_tree(si, attribute_list - test_attribute)返回的结点;
ID3算法原理
- 决策树中每一个非叶结点对应着一个非类别属性,树枝代表这个属性的值。一个叶结点代表从树根到叶结点之间的路径对应的记录所属的类别属性值。
- 采用信息增益来选择最佳划分属性。
信息增益计算
ID3总是选择具有最高信息增益(最大熵)的属性作为当前结点的测试属性(test_attribute)。
设S是s个数据样本的集合,假定类标号属性有m个不同值,定义m个不同类Ci(i=1,2,…,m)C_i(i=1,2,\dots,m)Ci(i=1,2,…,m)。设sis_isi是类CiC_iCi中的样本数。对一个给定的样本分类所需的期望信息为:
I(s1,s2,…,sm)=−∑i=1mpilog(pi)I(s_1,s2,\dots,s_m) = -\sum_{i=1}^{m}p_i\log(p_i)I(s1,s2,…,sm)=−i=1∑mpilog(pi)
其中,pip_ipi是任意样本属于CiC_iCi的概率,pi=sisp_i=\frac{s_i}{s}pi=ssi。
设属性A具有v个不同值,则可用属性A将S划分为v个子集,设sijs_{ij}sij是子集SjS_jSj中类CiC_iCi的样本数,则根据A划分子集的熵为:
E(A)=−∑j=1v∑i=1msijsI(s1j,s2j,…,smj)E(A) = -\sum_{j=1}^{v}\frac{\sum_{i=1}^{m}s_{ij}}{s}I(s_{1j}, s_{2j}, \dots, s_{mj})E(A)=−j=1∑vs∑i=1msijI(s1j,s2j,…,smj)
由期望信息和熵值可以得到对应的信息增益值:
Gain(A)=I(s1j,s2j,…,smj)−E(A)Gain(A)=I(s_{1j}, s_{2j}, \dots, s_{mj})-E(A)Gain(A)=I(s1j,s2j,…,smj)−E(A)
ID3算法分析
优点
ID3算法避免了搜索不包含目标函数的不完整假设空间的主要风险,因为有限个离散值函数可以表示某个决策树。
缺点
无法回溯爬山搜索中常见的风险,如收敛到局部最优,而不是全局最优。ID3算法只能处理离散值的属性。
当特征的取值较多时,根据此特征划分更容易得到纯度高的子集,因此划分之后的熵更低,由于划分前的熵是一定的,所以信息增益更大,ID3偏袒较多值的属性。
C4.5算法
- 用信息增益率来代替信息增益
- 合并具有连续属性的值
- 处理缺少属性值的训练样本
信息增益率
GainRatio(A)=Gain(A)SplitI(A)GainRatio(A)=\frac{Gain(A)}{SplitI(A)}GainRatio(A)=SplitI(A)Gain(A)
其中,
SplitI(A)=−∑j=1vpjlog(pj)SplitI(A)=-\sum_{j=1}^{v}p_j\log(p_j)SplitI(A)=−j=1∑vpjlog(pj)
连续属性的离散化
- 根据属性的值,对数据集排序;
- 用不同的阈值将数据集动态划分;
- 取两个实际值中点作为一个阈值;
- 取两个划分,所有样本都在这两个划分中;
- 得到所有可能的阈值和增益率;
对连续属性A进行排序,按阈值将A划分为两部分,一部分落入vjv_jvj对范围内,而另一部分则大于vjv_{j}vj,选择增益率最大的划分所对应的阈值为划分阈值进行属性离散化。注意,当前划分属性为连续属性,则该属性还可以作为其后代的划分属性。新增labelProperties表示属性为连续还是离散。
缺失值处理
选取最优划分属性
有缺失值属性的信息增益为该属性(设该属性为A)下的无缺失值样本占比×\times×无缺失值样本子集的信息增益。
Gain(A)=p×Gain(A~)Gain(A) = p\times Gain(\widetilde{A})Gain(A)=p×Gain(A)
其中,p为A属性下无缺失值样本的占比,即p=∑x∈D~wx∑x∈Dwxp=\frac{\sum_{x\in \widetilde{D}}w_x}{\sum_{x\in D}w_x}p=∑x∈Dwx∑x∈Dwx,Gain(A~)Gain(\widetilde{A})Gain(A)为A属性下无缺失值样本的信息增益,wxw_xwx是样本x的权重,D是所有样本,D~\widetilde{D}D是无缺失样本。
缺失值样本的划分
增加样本权重概念。样本权重的初始值为1,对于无缺失值样本,将其划分到子结点时,权重保持不变。而对于有缺失值样本,在划分时按无缺失值样本在每个分支中所占比重(即对于分支中无缺失值样本数/该属性下无缺失值样本总数)划分到分支中。此时,ID3中所得公式需改写:
Gain(A~)=I(S)−E(A)Gain(\widetilde{A})=I(S)-E(A)Gain(A)=I(S)−E(A)
I(S)=−∑i=1mpi~log(pi~)I(S)= -\sum_{i=1}^{m}\widetilde{p_i}\log(\widetilde{p_i})I(S)=−i=1∑mpilog(pi)
E(A)=−∑j=1vrj~I(Sj)E(A)=-\sum_{j=1}^v \widetilde{r_j}I(S^j)E(A)=−j=1∑vrjI(Sj)
Sj=(s1j,s2j,…,smj)S^j=(s_{1j},s_{2j},\dots,s_{mj})Sj=(s1j,s2j,…,smj)
S=(s1,s2,…,sm)S=(s_{1},s_{2},\dots,s_{m})S=(s1,s2,…,sm)
pi~=∑x∈Di~wx∑x∈D~wx\widetilde{p_i}=\frac{\sum_{x\in \widetilde{D_i}}w_x}{\sum_{x\in \widetilde{D}}w_x}pi=∑x∈Dwx∑x∈Diwx
rj~=∑x∈Dj~wx∑x∈D~wx\widetilde{r_j}=\frac{\sum_{x\in \widetilde{D^j}}w_x}{\sum_{x\in \widetilde{D}}w_x}rj=∑x∈Dwx∑x∈Djwx
pi~\widetilde{p_i}pi是无缺失值样本中第i类所占比例,样本个数按权重计算;rj~\widetilde{r_j}rj是无缺失值样本中属性A上取值为aja_jaj的样本所占比例,样本个数按权重计算。i是样本索引,j是属性索引。
*缺失测试样本的分类
C4.5算法的决策树构造完成后,需要对含缺失值的测试样本进行分类。在为测试样本某属性下的未知值选择分支时,考虑该缺失属性在该分支下的每个叶子结点中属于不同分类的概率,最大概率对于的分类即为所属分类。概率的计算使用该分支下每个叶子结点中不同分类的权值的加权平均。
本实验仅涉及决策树生成算法,故不考虑测试集的分类和决策树剪枝算法。
*C4.5算法的缺陷和修正
当离散属性和连续属性并存时,C4.5算法倾向于选择连续特征作为最佳树分裂点。因此要对最佳分裂点的信息增益进行修正:
Gain(A)=Gain(A)−log(N−1)∣D∣Gain(A) = Gain(A)-\frac{\log{(N-1)}}{\left| D\right|}Gain(A)=Gain(A)−∣D∣log(N−1)
其中,N为连续特征可能的分裂点个数,D是样本数目。
此外,C4.5算法的信息增益率偏向取值较少的特征。因此,并不直接选择信息增益率最大的特征,而是在候选特征中找出信息增益高于平均水平的特征,然后在这些特征中再选择信息增益率最高的特征作为最佳划分特征。
本实验编写的决策树生成算法,不考虑上述两种问题的修正。
代码
import copy
import operator
from math import log
from numpy import infNAN = 'Nan' # 缺失值定义def calcShannonEnt(dataSet: list, labelIndex: int):"""计算对应属性索引下样本的香农熵:param dataSet: 样本:param labelIndex: 属性索引:return: shannonEnt 香农熵"""numEntries = 0 # 样本数(按权重计算)labelCounts = {}# 遍历样本,计算每类的权重for featVec in dataSet:# 样本的属性不为空if featVec[labelIndex] != NAN:weight = featVec[-2]numEntries += weightcurrentLabel = featVec[-1] # 当前样本的类别# 如果样本类别不在labelCountsif currentLabel not in labelCounts.keys():# 添加该类别,令该类别权重为0labelCounts[currentLabel] = .0# 添加该类别的权重labelCounts[currentLabel] += weightshannonEnt = .0for key in labelCounts: # 计算信息熵prob = labelCounts[key] / numEntriesshannonEnt -= prob * log(prob, 2)return shannonEntdef splitDataSet(dataSet: list, axis: int, value, AttrType='N'):"""划分数据集:param dataSet: 数据集:param axis: 按第几个特征划分:param value: 划分特征的值:param AttrType: N-离散属性; L-小于等于value值; R-大于value值:return: 对应axis为value(连续情况下则为大于或小于value)的数据集dataSet的子集"""subDataSet = []# N-离散属性if AttrType == 'N':for featVec in dataSet:if featVec[axis] == value:reducedFeatVec = featVec[:axis]reducedFeatVec.extend(featVec[axis + 1:])subDataSet.append(reducedFeatVec)# L-小于等于value值elif AttrType == 'L':for featVec in dataSet:# 样本axis对应属性非空if featVec[axis] != NAN:if featVec[axis] <= value:# 无需减少该特征subDataSet.append(featVec)# R-大于value值elif AttrType == 'R':for featVec in dataSet:if featVec[axis] != NAN:if featVec[axis] > value:# 无需减少该特征subDataSet.append(featVec)else:exit(0)return subDataSetdef calcTotalWeight(dataSet: list, labelIndex: int, isContainNull: bool):"""计算样本对某个特征值的总样本数(按权重计算):param dataSet: 数据集:param labelIndex: 属性索引:param isContainNull: 是否包含空值:return: 样本的总权重"""totalWeight = .0# 遍历样本for featVec in dataSet:# 样本权重weight = featVec[-2]# 不包含空值并且该属性非空if isContainNull is False and featVec[labelIndex] != NAN:# 非空样本树,按权重计算totalWeight += weight# 包含空值if isContainNull is True:# 总样本数totalWeight += weightreturn totalWeightdef splitDataSetWithNull(dataSet: list, axis: int, value, AttrType='N'):"""划分含有缺失值的数据集:param dataSet: 数据集:param axis: 按第几个特征划分:param value: 划分特征的值:param AttrType: N-离散属性; L-小于等于value值; R-大于value值:return: 按value划分的数据集dataSet的子集"""# 属性值未缺失样本子集subDataSet = []# 属性值缺失样本子集nullDataSet = []# 计算非空样本总权重totalWeightV = calcTotalWeight(dataSet, axis, False)# N-离散属性if AttrType == 'N':for featVec in dataSet:if featVec[axis] == value:reducedFeatVec = featVec[:axis]reducedFeatVec.extend(featVec[axis + 1:])subDataSet.append(reducedFeatVec)# 样本该属性值缺失elif featVec[axis] == NAN:reducedNullVec = featVec[:axis]reducedNullVec.extend(featVec[axis + 1:])nullDataSet.append(reducedNullVec)# L-小于等于value值elif AttrType == 'L':for featVec in dataSet:# 样本该属性值未缺失if featVec[axis] != NAN:if value is None or featVec[axis] < value:subDataSet.append(featVec)# 样本该属性值缺失elif featVec[axis] == NAN:nullDataSet.append(featVec)# R-大于value值elif AttrType == 'R':for featVec in dataSet:# 样本该属性值未缺失if featVec[axis] != NAN:if featVec[axis] > value:subDataSet.append(featVec)# 样本该属性值缺失elif featVec[axis] == NAN:nullDataSet.append(featVec)# 计算此分支中非空样本的总权重totalWeightSub = calcTotalWeight(subDataSet, -1, True)# 缺失值样本按权值比例划分到分支中for nullVec in nullDataSet:nullVec[-2] = nullVec[-2] * totalWeightSub / totalWeightVsubDataSet.append(nullVec)return subDataSetdef calcGainRatio(dataSet: list, labelIndex: int, labelType: bool):"""计算信息增益率,返回信息增益率和连续属性的划分点:param dataSet: 数据集:param labelIndex: 属性索引:param labelType: 属性类型,0为离散,1为连续:return: 信息增益率和连续属性的划分点"""# 计算根节点的信息熵baseE = calcShannonEnt(dataSet, labelIndex)# 对应labelIndex的特征值向量featVec = [row[labelIndex] for row in dataSet]# featVec值的种类uniqueVals = set(featVec)newE = .0 # 新信息熵bestPivotValue = None # 最佳划分属性IV = .0 # 该变量取自西瓜书# 总样本权重totalWeight = calcTotalWeight(dataSet, labelIndex, True)# 非空样本权重totalWeightV = calcTotalWeight(dataSet, labelIndex, False)# 对离散的特征if labelType == 0:# 按属性值划分数据集,计算各子集的信息熵for value in uniqueVals:# 划分数据集subDataSet = splitDataSet(dataSet, labelIndex, value)# 计算子集总权重totalWeightSub = calcTotalWeight(subDataSet, labelIndex, True)# 过滤空属性if value != NAN:prob = totalWeightSub / totalWeightVnewE += prob * calcShannonEnt(subDataSet, labelIndex)prob1 = totalWeightSub / totalWeightIV -= prob1 * log(prob1, 2)# 对连续的特征else:uniqueValsList = list(uniqueVals)# 过滤空属性if NAN in uniqueValsList:uniqueValsList.remove(NAN)# 计算空值样本的总权重,用于计算IVdataSetNull = splitDataSet(dataSet, labelIndex, NAN)totalWeightN = calcTotalWeight(dataSetNull, labelIndex, True)probNull = totalWeightN / totalWeightif probNull > 0:IV += -1 * probNull * log(probNull, 2)# 属性值排序sortedUniqueVals = sorted(uniqueValsList)minEntropy = inf # 定义最小熵# 如果UniqueVals只有一个值,则说明只有左子集,没有右子集if len(sortedUniqueVals) == 1:totalWeightL = calcTotalWeight(dataSet, labelIndex, True)probL = totalWeightL / totalWeightVminEntropy = probL * calcShannonEnt(dataSet, labelIndex)IV = -1 * probL * log(probL, 2)# 如果UniqueVals只有多个值,则计算划分点else:for j in range(len(sortedUniqueVals) - 1):pivotValue = (sortedUniqueVals[j] + sortedUniqueVals[j + 1]) / 2# 对每个划分点,划分得左右两子集dataSetL = splitDataSet(dataSet, labelIndex, pivotValue, 'L')dataSetR = splitDataSet(dataSet, labelIndex, pivotValue, 'R')# 对每个划分点,计算左右两侧总权重totalWeightL = calcTotalWeight(dataSetL, labelIndex, True)totalWeightR = calcTotalWeight(dataSetR, labelIndex, True)probL = totalWeightL / totalWeightVprobR = totalWeightR / totalWeightVEnt = probL * calcShannonEnt(dataSetL, labelIndex) + probR * calcShannonEnt(dataSetR, labelIndex)# 取最小的信息熵if Ent < minEntropy:minEntropy = EntbestPivotValue = pivotValueprobL1 = totalWeightL / totalWeightprobR1 = totalWeightR / totalWeightIV += -1 * (probL1 * log(probL1, 2) + probR1 * log(probR1, 2))newE = minEntropygain = totalWeightV / totalWeight * (baseE - newE)# 避免IV为0(属性只有一个值的情况下)if IV == 0.0:IV = 0.0000000001gainRatio = gain / IVreturn gainRatio, bestPivotValuedef chooseBestFeatureToSplit(dataSet: list, labelProps: list):"""选择最佳数据集划分方式:param dataSet: 数据集:param labelProps: 属性类型,0离散,1连续:return: 最佳划分属性的索引和连续属性的最佳划分值"""numFeatures = len(labelProps) # 属性数bestGainRatio = -inf # 最大信息增益bestFeature = -1 # 最优划分属性索引bestPivotValue = None # 连续属性的最佳划分值for featureI in range(numFeatures): # 对每个特征循环gainRatio, bestPivotValuei = calcGainRatio(dataSet, featureI, labelProps[featureI])# 取信息益率最大的特征if gainRatio > bestGainRatio:bestGainRatio = gainRatiobestFeature = featureIbestPivotValue = bestPivotValueireturn bestFeature, bestPivotValuedef majorityCnt(classList: list, weightList: list):"""返回出现次数最多的类别(按权重计):param classList: 类别:param weightList: 权重:return: 出现次数最多的类别"""classCount = {}# 计算classCountfor cls, wei in zip(classList, weightList):if cls not in classCount.keys():classCount[cls] = .0classCount[cls] += wei# 排序sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)# 仅剩一个类别if len(sortedClassCount) == 1:return sortedClassCount[0][0], sortedClassCount[0][1]# 剩余多个类别,返回出现次数最多的类别return sortedClassCount[0][0], sortedClassCount[0][1]def isSame(dataSet: list):"""比较样本特征是否相同:param dataSet: 数据集:return: 相同True,否则False"""for j in range(len(dataSet[0])-2):for i in range(1, len(dataSet)):if not dataSet[i][j] == dataSet[0][j]:return Falsereturn Truedef createTree(dataSet: list, labels: list, labelProps: list):"""创建决策树(Decision Tree):param dataSet: 数据集:param labels: 属性集:param labelProps: 属性类型,0离散,1连续:return: 决策树"""classList = [sample[-1] for sample in dataSet] # 类别向量weightList = [sample[-2] for sample in dataSet] # 权重向量# 如果只剩一个类别,返回并退出if classList.count(classList[0]) == len(classList):totalWeight = calcTotalWeight(dataSet, 0, True)return classList[0], totalWeight# 如果所有特征都遍历完了,返回出现次数最多的类别,并退出if len(dataSet[0]) == 1:return majorityCnt(classList, weightList)# 如果剩余样本特征相同,返回出现次数最多的类别,并退出if isSame(copy.copy(dataSet)):return majorityCnt(classList, weightList)# 计算最优分类特征的索引,若为连续属性,则还返回连续属性的最优划分点bestFeat, bestPivotValue = chooseBestFeatureToSplit(dataSet, labelProps)# 对离散的特征if labelProps[bestFeat] == 0:bestFeatLabel = labels[bestFeat]myTree = {bestFeatLabel: {}}labelsNew = copy.copy(labels)labelPropertyNew = copy.copy(labelProps)# 已经选择的离散特征不再参与分类del (labelsNew[bestFeat])del (labelPropertyNew[bestFeat])featValues = [sample[bestFeat] for sample in dataSet]# 最佳花划分属性包含的所有值uniqueValue = set(featValues)# 删去缺失值uniqueValue.discard(NAN)# 遍历每个属性值,递归构建树for value in uniqueValue:subLabels = labelsNew[:]subLabelProperty = labelPropertyNew[:]myTree[bestFeatLabel][value] = createTree(splitDataSetWithNull(dataSet, bestFeat, value),subLabels, subLabelProperty)# 对连续特征,不删除该特征,分别构建左子树和右子树else:bestFeatLabel = labels[bestFeat] + '<' + str(bestPivotValue)myTree = {bestFeatLabel: {}}subLabels = labels[:]subLabelProperty = labelProps[:]# 构建左子树valueLeft = 'Y'myTree[bestFeatLabel][valueLeft] = createTree(splitDataSetWithNull(dataSet, bestFeat, bestPivotValue, 'L'),subLabels, subLabelProperty)# 构建右子树valueRight = 'N'myTree[bestFeatLabel][valueRight] = createTree(splitDataSetWithNull(dataSet, bestFeat, bestPivotValue, 'R'),subLabels, subLabelProperty)return myTreeif __name__ == '__main__':# 读取数据文件fr = open(r'data.csv')data = [row.strip().split(',') for row in fr.readlines()]labels = data[0][0:-1] # labels:属性dataset = data[1:] # dataset:数据集(初始样本)labelProperties = [0, 1, 0] # labelProperties:属性标识,0为离散,1为连续# 样本权重初始化for row in dataset:row.insert(-1, 1.0)# 按labelProperties连续化离散属性for row in dataset:for i, lp in enumerate(labelProperties):# 若标识为连续属性,则转化为float型if lp:row[i] = float(row[i])# C4.5算法生成决策树trees = createTree(copy.copy(dataset), copy.copy(labels), copy.copy(labelProperties))print(trees)
Python3.6
结果验证
在data.csv数据集上运行上述代码,得到结果如下:
{'天气': {'多云': ('玩', 3.230769230769231), '晴': {'湿度<77.5': {'Y': ('玩', 2.0), 'N': {'有雨?': {'有': ('不玩', 1.0), '无': ('不玩', 2.0)}}}}, '雨': {'有雨?': {'有': {'湿度<85.0': {'Y': ('不玩', 2.0), 'N': ('玩', 0.38461538461538464)}}, '无': ('玩', 3.0)}}}} // (结果, 权重)
附录(data.csv)
天气 | 湿度 | 有雨? | 去玩? |
---|---|---|---|
晴 | 70 | 有 | 玩 |
晴 | 90 | 有 | 不玩 |
晴 | 85 | 无 | 不玩 |
晴 | 95 | 无 | 不玩 |
晴 | 70 | 无 | 玩 |
Nan | 90 | 有 | 玩 |
多云 | 78 | 无 | 玩 |
多云 | 65 | 有 | 玩 |
多云 | 75 | 无 | 玩 |
雨 | 80 | 有 | 不玩 |
雨 | 70 | 有 | 不玩 |
雨 | 80 | 无 | 玩 |
雨 | 80 | 无 | 玩 |
雨 | 96 | 无 | 玩 |
参考
- 数据挖掘原理与算法(第3版)
- 《机器学习》周志华
- 决策树–信息增益,信息增益比,Geni指数的理解
- 机器学习笔记(5)——C4.5决策树中的连续值处理和Python实现
- 机器学习笔记(7)——C4.5决策树中的缺失值处理
C4.5决策树生成算法完整版(Python),连续属性的离散化, 缺失样本的添加权重处理, 算法缺陷的修正, 代码等相关推荐
- python教程视频完整版-Python教程视频完整版
原标题:Python教程视频完整版 Python是由 Guido van Rossum 在八十年代末和九十年代初,在荷兰国家数学和计算机科学研究所设计出来的.随着人工智能的发展,Python这门语言也 ...
- 机器学习笔记——支持向量机SMO算法完整版代码分析
机器学习笔记--支持向量机SMO算法完整版代码分析 代码大体分析 外循环 参数类 内循环 KKT条件判断 eCache参数 完整SMO代码 添加核函数代码 代码参考书籍:<机器学习实战> ...
- 全网完整版Python学习路线图(超详细适合零基础自学)
本文包含了千锋教育Python学习路线全阶段视频教程(从入门到精通),涵盖了你所需要掌握的所有前沿技术及知识点! 全网最新,史上最全Python学习路线,从基础到项目实战应有尽有,牛批卡拉斯! Pyt ...
- 用c语言实现数据结构算法将两个有序链表并为一个有序链表的算法,,(完整版)数据结构-习题集答案-(C语言版严蔚敏)...
} 2.15 已知指针ha和hb分别指向两个单链表的头结点,并且已知两个链表的长度分别为m和n.试写一算法将这两个链表连接在一起,假设指针hc指向连接后的链表的头结点,并要求算法以尽可能短的时间完成连 ...
- 数据结构与算法 完整版单链表(附GIF)
因为博主认为单链表是非常重要的数据结构,能够熟练使用单链表的话后面的数据结构会越学越轻松,所以博主就把这篇博客做的细致一点,不是很好懂的地方做成 gif 动画,希望大家能理解期中代码的含义 学习链表的 ...
- python入门教程完整版-Python入门教程完整版(懂中文就能学会)
隐藏内容,您需要满足以下条件方可查看 目录大纲: 本套教程15天 学前环境搭建 1-3 天内容为Linux基础命令 4-13 天内容为Python基础教程 14-15 天内容为 飞机大战项目演练 视频 ...
- python做一个单项选择题系统_(完整版)python选择题word打印版
Python 单选题库 一. python 语法基础 1 . Python 3.x 版本的保留字总数是 A.27 B.29 C.33 D.16 2. 语言保留字的是 Python 不是 , 以下选项中 ...
- 数据结构与算法 完整版双链表
上一篇单链表博主已经讲的很详细了,相信掌握单链表的朋友搞定双链表并不困难,所以博主就之给出代码了,如果实在感觉理解困难可以留言,博主会加gif的 #include<stdio.h> #in ...
- 2023最新完整版python安装教程
2.1 环境安装 2.1.1 下载解释器: py2.7.16 (2020年官方不再维护) py3.6.8 (推荐安装) 1.下载解释器一定去官网下载,https://www.python.org 2. ...
最新文章
- 2022-2028年中国电容器电子薄膜行业市场研究及前瞻分析报告
- 784.字母大小写全排列
- 对DbRuleAuthorizationProvider的修改
- 华为正式宣布鸿蒙,空欢喜一场?华为正式宣布,鸿蒙系统用作他用
- 深度linux安装好上不了网,Deepin Linux 无法上网
- linux基础_centos安装与网络配置
- LoRa VS NB-IoT,一场物联网时代 C 位争夺战
- UNIX/Linux系统取证之信息采集案例
- VS2019正式版注册码秘钥
- YOLOv7全文翻译
- Yield Guild Games:播客专题
- 2021-2027全球及中国运动营养和体重管理食品行业研究及十四五规划分析报告
- python高级练习题:转换所有的案件!【难度:3级】--景越Python编程实例训练营,不同难度Python习题,适合自学Python的新手进阶
- win7桌面背景变黑且不能更换壁纸
- macbook air 卸载java,macbook air如何卸载软件 macbook air卸载软件的方法
- 模拟复杂红绿灯交通指示程序编程显示黄灯闪烁箭头指示
- Java job interview:WinForm界面特效设计案例导航图分享
- 【特征检测】HOG特征算法
- css关于:hover的使用
- Wireshark malformed packet
热门文章
- LeetCode 1227. 飞机座位分配概率(DP+数学归纳法)
- 程序员面试金典 - 面试题 08.11. 硬币(背包DP)
- 程序员面试金典 - 面试题 01.03. URL化(字符串)
- LeetCode 1289. 下降路径最小和 II(DP)
- LeetCode 126. 单词接龙 II(图的BFS)
- matlab提取图像中的一部分并移动,在Matlab中从图像中提取对象
- 简单阻容降压电路图_升压降压芯片电路
- zeal刷新不出来_饥荒:游戏中的这些事物都是无中生有,几乎可以无限制刷新!...
- Opencv visual studio c++ 环境搭建
- flutter 图解_【Flutter 专题】83 图解自定义 ACEWave 波浪 Widget (一)