使用 Python 进行图像验证码识别训练及调用

news2024/11/16 9:48:24

目录

      • 1、验证码识别原理
        • 1.1 Tensorflow 介绍
        • 1.2 Tensorflow 运行原理
        • 1.3 卷积神经网络 CNN(Convolutional Neural Networks)
      • 2、验证码识别实现步骤
        • 2.1 安装第三方模块
          • 2.1.1 安装 TensorFlow 模块
          • 2.2.2 安装 cuda
          • 2.2.3 下载 cudnn
        • 2.2 读取验证码样本形成模型
          • 2.2.1 图片进行预处理
          • 2.2.2 使用预处理完成的图片训练生成模型
      • 3、使用训练出来的模型进行验证码识别
        • 3.1 Django项目
        • 3.2 .net项目
      • 4、源码及素材下载地址
      • 技术交流

博主介绍:
计算机科班人,全栈工程师,掌握C、C#、Java、Python、Android等主流编程语言,同时也熟练掌握mysql、oracle、sqlserver等主流数据库,能够为大家提供全方位的技术支持和交流。
目前工作五年,具有丰富的项目经验和开发技能。提供相关的学习资料、程序开发、技术解答、代码讲解、文档报告等专业服务。
🍅文末获取源码🍅
👇🏻 精彩专栏推荐订阅👇🏻 不然下次找不到我哟
《精品项目实战》


Python的图像识别已经有一些第三方库可以使用,但是本文将详细介绍使用Python对图像验证码进行识别训练,以及我们对训练后的model的一个使用。
首先我们需要准备一些验证码图片并把图片命名为具体的验证码结果,我会将这些素材和源码一同打包,感兴趣的可以下载。

1、验证码识别原理

机器学习是人工智能的一个子集,它是利用统计技术提供了向计算机“学习”数据的能力,而不需要复杂的编程。简单来说,机器学习可以被定义为一种科学,它使计算机像人类一样行动和学习,并通过以实际交互和观察的形式向他们提供信息和数据,以独立的方式提高他们的学习能力。

1.1 Tensorflow 介绍

tensorflow 是由谷歌开发,使用比较广泛的深度学习框架,主要用来深度学习(机器学习也用)以及其他涉及大量运算,也是 Github 上最受欢迎的深度学习;tensorflow 在图像分类、音频处理,推荐系统和自然语言处理等领域应用十分广泛,谷歌几乎涉及深度学习或者机器学习的项目,都在使用 tensorflow作为运算框架;tensorflow 之所以能收到广大开发者的青睐,离不开 tensorflow底层优秀的封装,让开发人员能在不了解底层原理的情况下快速上手并且能直接开发。

1.2 Tensorflow 运行原理

在这里插入图片描述

1.3 卷积神经网络 CNN(Convolutional Neural Networks)

CNN 是一种常用于图像识别、语音识别等领域的深度学习模型。CNN 是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。CNN 是由输入层、卷积层(convolutional layer)、池化层(pooling layer,也称为去样层)、全连接层及输出层构成。卷积层和池化层一般会取若干个,采用卷积层和池化层交替设置,即一个卷积层连接一个池化层,池化层再连接一个卷积层,以此类推。与其他深度学习结构相比,卷积神经网路在图像和语音识别方面具有很强的优势。

在这里插入图片描述

(1) 卷积层: 通过卷积操作来提取图像特征
在这里插入图片描述

通过过滤器(卷积核)来过滤图像各个小区域,从而得到小区域的特征值。

(2)池化层: 下采样,数据降维,防止过拟合

在这里插入图片描述
可以从上图中看到,原始图片 20x20 的,对其进行下采样,采样的窗口为10x10,最终将其下采样称为一个 2x2 大小的特征图。这么做的主要目的是因为有时候及时做完卷积,图像任然很大(由于卷积核比较小),为了降低数据维度,就进行了下采样。
总结:池化层相比卷积层可以更有效的降低数据维度,这么做不但可以大大减少运算量,还可以有效的避免过拟合。

(3)全连接层: 输出结果
经过卷积层和池化层处理过后的数据输入到全连接层,得到最终想要的结果。经过卷积层和池化层降维过的数据,全连接层才能“跑得动”。


2、验证码识别实现步骤

环境:Windows 10
开发工具: PyCharm

2.1 安装第三方模块
2.1.1 安装 TensorFlow 模块

根据 python 版本选择合适的 TensorFlow 版本,可按如下版本匹配:

PythonTensorFlowCUDA
3.6.3(64 位,TensorFlow 只支持 64 位)tensorflow-gpu1.10.0 9.0
# 普通的模块安装:
pip install tensorflow-gpu 
# 使用镜像地址进行模块安装:
pip install tensorflow-gpu -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple 
# 卸载模块:
pip uninstall tensorflow-gpu 
# 指定模块版本进行安装:
pip install torch==1.6.0+cu101
# 使用以下下载好的 whl 进行安装:
pip install G:\whl\torch-1.6.0+cu101-cp36-cp36m-win_amd64.whl
2.2.2 安装 cuda

下载地址:https://developer.nvidia.com/cuda-toolkit-archive
在这里插入图片描述

安装完成运行 python 程序如果报如下错误:

ImportError: Could not find 'cudnn64_7.dll'. TensorFlow requires that this DLL be installed in a directory that is named in your

请继续下面的操作。

2.2.3 下载 cudnn

下载地址:https://developer.nvidia.com/rdp/cudnn-archive
选择合适的版本:
在这里插入图片描述

解压后把 bin\cudnn64_7.dll 文件拷贝到 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\bin 目录下,运行程序,如果报错:ImportError: Matplotlib requires numpy>=1.15; you have 1.14.5,则升级 numpy。


2.2 读取验证码样本形成模型
2.2.1 图片进行预处理

一张原始的图片,并不一定每个点或者每个特征都是神经网络所必须 的。例如,一张彩色图片做文字识别,可能彩色的信息就不重要,但是为了存储颜色一般需要花费 3 倍的数据空间。或者在图像上有很多小小的噪音点, 这些点对图像的识别没有用处,反而有反效果。这种在正式处理运算之前对图像的操作,叫做图像数据的预处理。

灰度化:
将灰度图变为黑白图,黑白图上只有纯黑色和纯白色两种颜色。这种情况有助于过滤掉无关信息,而关注于主要的区域,或者图像区域的边界。

二值化:
二值化一般使用阈值法,即设置一个阈值,如果灰度大于阈值设为 255, 小于阈值设为 0,这时阈值的选择就成了关键。python 中二值化可以使用opencv 模块提供了threshold 方法对图片进行二值化,需要先安装 opencv 。

实现代码如下:

import os
import tensorflow as tf
import cv2
# 原始图片路径
img_path = 'F:\\train\\img\\'
# 灰度化的图片路径
gray_path = 'F:\\train\\gray\\'

def rgb_to_gray_scale():
    # 读取immg下的图片
    i = 0
    for name in os.listdir(img_path):
        i = i+1
        if i > 500:
            with tf.Session() as sess:

                img = tf.read_file(img_path+name)
                # 灰度化
                img_data = tf.image.decode_jpeg(img, channels=3)             # 解码
                gray_img = sess.run(tf.image.rgb_to_grayscale(img_data.eval()))  # 灰度化

                # 二值化一般使用阈值法,即设置一个阈值
                retval, im_at_fixed = cv2.threshold(gray_img, 185, 255, cv2.THRESH_BINARY)
                cv2.imwrite(gray_path + name, im_at_fixed)  # 保存图片
    print("灰度化以及二值化完成")

rgb_to_gray_scale()

如下彩色图像
在这里插入图片描述

经过处理得到
在这里插入图片描述


2.2.2 使用预处理完成的图片训练生成模型

(1) 导入包并定于根据实际情况设置常量

import tensorflow as tf
import numpy as np
from PIL import Image
import os
import random
import cv2

train_data_dir = r'F:\train\gray'  # 根据实际情况替换
test_data_dir = r'F:\train\img'

# 图片高度
IMAGE_HEIGHT = 23
# 图片宽度
IMAGE_WIDTH = 68
# 验证码的长度
MAX_CAPTCHA = 4
# 验证码字符集个数(如本次只有纯数字,所以字符集格式为10[0-9])
CHAR_SET_LEN = 10

X = tf.placeholder(tf.float32, [None, IMAGE_HEIGHT * IMAGE_WIDTH])
Y = tf.placeholder(tf.float32, [None, MAX_CAPTCHA * CHAR_SET_LEN])
keep_prob = tf.placeholder(tf.float32)  # dropout

(2) 灰度处理函数和标签处理函数

def conver2gray(img):
    if len(img.shape)>2:
        gray=np.mean(img,-1)
        return gray
    else:return img

def text2vec(text):
    try:
        text_len=len(text)
        if text_len>MAX_CAPTCHA:
            raise ValueError('longer than 4')
        vector=np.zeros(MAX_CAPTCHA*CHAR_SET_LEN)

        for i,c in enumerate(text):
            idx=i*CHAR_SET_LEN+int(c) #[1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0....]一共四十个元素 里面只有四个元素为1
            vector[idx]=1
        return vector
    except Exception as e:
        print(text)

(3) 获取验证码图片的缩影路径

#获取所有验证码图片
def _get_filenames_and_classes(dataset_dir):
    photo_filenames = []
    for filename in os.listdir(dataset_dir):
        #获取文件路径
        path = os.path.join(dataset_dir, filename)
        photo_filenames.append(path)
    return photo_filenames

(4) 由于 cnn 计算每次需要随机选择一批量的照片,所以需要批次提取照片函数

def gen_train_data(batch_size=32):
    # 生成训练数据
    train_file_name_list = os.listdir(train_data_dir)
    selected_train_file_name_list = random.sample(train_file_name_list, batch_size)

    batch_x=np.zeros([batch_size,IMAGE_HEIGHT*IMAGE_WIDTH])
    batch_y=np.zeros([batch_size,MAX_CAPTCHA*CHAR_SET_LEN])
    i = 0
    for selected_train_file_name in selected_train_file_name_list:
        captcha_image = Image.open(os.path.join(train_data_dir, selected_train_file_name))
        captcha_image = np.array(captcha_image)
        image = conver2gray(captcha_image)

        # 获取label
        labels = selected_train_file_name.split('\\')[-1][0:4]

        batch_x[i, :] = image.flatten() / 255
        batch_y[i, :] = text2vec(labels)
        i = i+1
    return batch_x, batch_y

(5) cnn 训练算法

def cnn(w_alpha=0.01, b_alpha=0.1):
    x=tf.reshape(X,shape=[-1,IMAGE_HEIGHT, IMAGE_WIDTH, 1])
    w_c1=tf.Variable(w_alpha*tf.random_normal([3,3,1,32]))
    b_c1=tf.Variable(b_alpha*tf.random_normal([32]))
    conv1=tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x,w_c1,strides=[1,1,1,1],padding='SAME'),b_c1))
    conv1=tf.nn.max_pool(conv1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
    conv1 = tf.nn.dropout(conv1, keep_prob)

    w_c2=tf.Variable(w_alpha*tf.random_normal([3,3,32,64]))
    b_c2=tf.Variable(b_alpha*tf.random_normal([64]))
    conv2=tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv1,w_c2,strides=[1,1,1,1],padding='SAME'),b_c2))
    conv2=tf.nn.max_pool(conv2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
    conv2 = tf.nn.dropout(conv2, keep_prob)

    w_c3=tf.Variable(w_alpha*tf.random_normal([3,3,64,64]))
    b_c3=tf.Variable(b_alpha*tf.random_normal([64]))
    conv3=tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv2,w_c3,strides=[1,1,1,1],padding='SAME'),b_c3))
    conv3=tf.nn.max_pool(conv3,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
    conv3 = tf.nn.dropout(conv3, keep_prob)

    # 全连接层
    w_d=tf.Variable(w_alpha*tf.random_normal([3*9*64, 1024]))
    b_d=tf.Variable(w_alpha*tf.random_normal([1024]))
    dense = tf.reshape(conv3, shape=[-1, w_d.get_shape().as_list()[0]])
    dense = tf.nn.relu(tf.add(tf.matmul(dense, w_d), b_d))
    dense = tf.nn.dropout(dense, keep_prob)

    w_out = tf.Variable(w_alpha * tf.random_normal([1024, MAX_CAPTCHA * CHAR_SET_LEN]))
    b_out = tf.Variable(b_alpha * tf.random_normal([MAX_CAPTCHA * CHAR_SET_LEN]))
    out = tf.add(tf.matmul(dense, w_out), b_out)
    return out

(6) 训练函数

def train_cnn():
    output = cnn()
    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=output, labels=Y))
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)
    predict = tf.reshape(output, [-1, MAX_CAPTCHA, CHAR_SET_LEN])

    max_idx_p = tf.argmax(predict, 2)
    max_idx_l = tf.argmax(tf.reshape(Y, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)
    correct_pred = tf.equal(max_idx_p, max_idx_l)
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

    saver = tf.train.Saver()
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())

        step = 0
        while True:
            batch_x, batch_y = gen_train_data(64)
            _, loss_ = sess.run([optimizer, loss], feed_dict={X: batch_x, Y: batch_y, keep_prob: 0.75})
            print('step+loss', step, loss_)

            # 每100 step计算一次准确率
            if step % 10 == 0:
                batch_x_test, batch_y_test = gen_train_data(100)
                acc = sess.run(accuracy, feed_dict={X: batch_x_test, Y: batch_y_test, keep_prob: 1.})
                print('step+acc', step, acc)
                # 如果准确率大于50%,保存模型,完成训练
                if loss_ < 0.0001:
                    saver.save(sess, "./model/crack_capcha.model", global_step=step)
                    break

            step += 1

执行训练

在这里插入图片描述

最终生成了我们需要的model,如下

在这里插入图片描述


3、使用训练出来的模型进行验证码识别

这里分别利用Python 的Django框架和.net 分别创建项目来进行调用,源码也一同打包,感兴趣的下载。

3.1 Django项目

在这里插入图片描述

yzm.py

from django.http import HttpResponse
from django.shortcuts import render
import os
import uuid
from yzmweb.cnn import deeper_model


def index(request):
    if request.method == "GET":
        return render(request, "up_file.html")
    else:
        try:
            file_obj = request.FILES.get("myfile", None)
            print(file_obj.name)
            test=request.FILES.get("test",None)
            print(test)
            if file_obj.name.split('.')[-1] not in ['jpeg', 'jpg', 'png']:
                return HttpResponse('输入文件有误')
            if not file_obj:
                return HttpResponse("no files for upload")
            file_id = uuid.uuid1()
            name = file_obj.name
            file_name = os.path.join("D:\\train\\upload", str(file_id)+'-'+name)
            print(file_name)
            with open(file_name, 'wb+') as f:
                f.write(file_obj.read())
            cnn_model = deeper_model()  # 实例化
            str22 = cnn_model.predict(file_name)  # 调用模型,输出识别结果

            return HttpResponse(str22)
        except Exception as e:
            print(e)
            return HttpResponse("upload error!")

up_file.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form enctype="multipart/form-data" method="post">
   <input type="file" name="myfile" id="avatar_file" />
   <br/>
   <input type="submit" value="upload">
    {% csrf_token %}
</form>
</body>
</html>

启动项目

在这里插入图片描述

获取验证码接口:http://ip:端口/yzm/,如果请求方式为GET,弹出如下页面使用。

在这里插入图片描述

识别结果如下

在这里插入图片描述

3.2 .net项目

在这里插入图片描述

注:如果你的服务器使用CPU来处理图像,CPU和GPU图像处理能力有差别,所以如果连续快速识别可能会出现异常。


4、源码及素材下载地址

下载地址:https://download.csdn.net/download/xch_yang/89306923

在这里插入图片描述


技术交流

大家点赞、收藏、关注、评论啦!
精彩专栏推荐订阅:下方专栏👇🏻👇🏻👇🏻👇🏻
《精品项目实战》



更多技术干货,请持续关注程序员大佬超。
原创不易,转载请务必注明出处。

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

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

相关文章

智慧公厕的核心技术详解:物联网、云计算、大数据、自动化控制

公共厕所是城市的重要组成部分&#xff0c;而智慧公厕的建设和管理正成为城市发展的重要方向。智慧公厕的核心技术即是物联网、云计算、大数据和自动化控制。下面将以智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案例项目现场实景实图实例&#xff0c;详细介…

【微命令】git config如何配置全局的用户和邮箱?(--global user.name、user.email;git config --help)

虽然经常用&#xff0c;也经常忘记&#xff0c;特此记录。 命令 git config --global user.name "myname" git config --global user.email test163.com另外一种方式 help git config --help |grep email | grep name直接help查看

Redis的集群模式——Java全栈知识(20)

1、主从模式 Redis 支持主从模式的集群搭建&#xff0c;这是 Redis 提供的最简单的集群模式搭建方案&#xff0c;目的是解决单点服务器宕机的问题。当单点服务器发生故障的时候保证 Redis 正常运行。 主从模式主要是将集群中的 Redis 节点分为主节点和从节点。然后读和写发生在…

C++初阶学习第六弹——string(1)——标准库中的string类

前言&#xff1a; 在前面&#xff0c;我们学习了C的类与对象&#xff0c;认识到了C与C语言的一些不同&#xff0c;今天&#xff0c;我们将进入C的 关键部分——STL&#xff0c;学习完这部分之后&#xff0c;我们就可以清楚的认识到C相比于C语言的快捷与便利 目录 一、为什么有s…

springboot房屋租赁系统

摘要 房屋租赁系统&#xff1b;为用户提供了一个房屋租赁系统平台&#xff0c;方便管理员查看及维护&#xff0c;并且可以通过需求进行设备信息内容的编辑及维护等&#xff1b;对于用户而言&#xff0c;可以随时进行查看房屋信息和合同信息&#xff0c;并且可以进行报修、评价…

面试集中营—rocketmq架构篇

一、基本定义 Apache RocketMQ 是一款低延迟、高并发、高可用、高可靠的分布式消息中间件。消息队列 RocketMQ 可为分布式应用系统提供异步解耦和削峰填谷的能力&#xff0c;同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。 Topic&#xff1a;消息主题&…

基于yolov2深度学习网络的单人口罩佩戴检测和人脸定位算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022A 3.部分核心程序 ..............................................................I0 imresize…

详述进程的地址空间

进程的地址空间 合法的地址 (可读或可写) 代码 (main, %rip 会从此处取出待执行的指令)&#xff0c;只读数据 (static int x)&#xff0c;读写堆栈 (int y)&#xff0c;读写运行时分配的内存 (???)&#xff0c;读写动态链接库 (???) 非法的地址 NULL&#xff0c;导致 se…

Arduino-ILI9341驱动-SPI接口TFTLCD实现触摸功能系列之触控开关二

Arduino-ILI9341驱动-SPI接口TFTLCD实现触摸功能系列之触控开关二 1.概述 这篇文章在触摸屏上绘制一个开关&#xff0c;通过点击开关实现控制灯的开关功能。 2.硬件 硬件连接参考第一篇文章介绍 Arduino-ILI9341驱动-SPI接口TFTLCD实现触摸功能系列之获取触控坐标一 3.实现…

使用Caché管理工具

Cach通过一个web工具来对其进行系统管理和完成管理任务,该方法的一个好处是不必将Cach安装到用于管理的系统上。目前,通过网络远程管理和控制对站点的访问,这些都比较容易。因为数据及其格式信息都直接来自被管理的系统,因此,这也可以最小化跨版本的兼容问题。 本文将描述…

【知识碎片】2024_05_14

本篇记录了两道关于位运算的选择题&#xff0c;和一道有点思维的代码题。 C语言碎片知识 求函数返回值&#xff0c;传入 -1 &#xff0c;则在64位机器上函数返回&#xff08; &#xff09; int func(int x) {int count 0;while (x){count;x x&(x - 1);//与运算} return c…

Java医院绩效核算系统与his对接所需数据有哪些?java+springboot+MySQL医院绩效管理系统-构建智慧医疗生态

Java医院绩效核算系统与his对接所需数据有哪些&#xff1f;javaspringbootMySQL医院绩效管理系统-构建智慧医疗生态 医院绩效核算系统与his对接所需数据 1、诊察工作量绩效&#xff1a;信息系统-财务权限-统计报表-报表浏览-财务常用报表-门诊医生工作量报表 2、判读及操作工…

微信小程序 - - - - - 使用TDesign库(微信小程序UI库)

使用TDesign库 1. 初始化依赖2. 安装TDesgin3. npm构建3. 修改 app.json 1. 初始化依赖 npm init -y2. 安装TDesgin yarn add tdesign-miniprogram -S --productionor npm install tdesign-miniprogram -S --production3. npm构建 3. 修改 app.json 将 app.json 中的 “styl…

CTF如何学习?

CTF如何学习&#xff1f;打CTF有什么用 CTF本身有几个常见的领域 MISC WEB [逆向 密码学](https://www.zhihu.com/search?q逆向 密码学&search_sourceEntity&hybrid_search_sourceEntity&hybrid_search_extra{“sourceType”%3A"answer"%2C"sourc…

ubuntu 22.04 安装 RTX 4090 显卡驱动 GPU Driver(PyTorch准备)

文章目录 1. 参考文章2. 检查GPU是Nvidia3. 卸载已有驱动3.1. 命令删除3.2. 老驱动包 4. 官网下载驱动5. 运行5.1. 远程安装关闭交互界面5.2. 运行5.3. 打开交互界面 6. 检测与后续安装 1. 参考文章 https://blog.csdn.net/JineD/article/details/129432308 2. 检查GPU是Nvid…

【MySQL】Mysql——安装指南(Linux)

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以; Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 3. 上传MySQL安装包 4. 创建目录,并解压 mkdir mysqltar -xvf mysql-8.0.26-1.el7.x86_64.rpm-bundle.tar -C mysql5. 安装mysql的安装包 …

CAPL入门之使用CAPL记录测试Logging

0 前言 以往测试的log都是直接从trace导出&#xff0c;但是最近发现trace中能导出的数据是有限的&#xff0c;如果测试的时间过长&#xff0c;新的数据就会把之前的数据全部覆盖&#xff0c;并且对于长时间的测试&#xff0c;直接导出trace的内容也会造成查找效率低下的问题。因…

iOS 创建pch文件

1.参考链接&#xff08;xcode8添加方法&#xff0c;之前的跟这个差不多&#xff09;&#xff1a; 参考链接 2.自我总结&#xff1a; &#xff08;1&#xff09;创建pch文件: 注意点&#xff1a;1&#xff09;注意选中所有的targets&#xff08;看图明义&#xff09; 2&…

关于链表相关的OJ题

✨✨✨专栏&#xff1a;数据结构 &#x1f9d1;‍&#x1f393;个人主页&#xff1a;SWsunlight 一、 OJ题 返回倒数第K个节点&#xff1a; 1、遍历链表一遍:用2个指针&#xff0c;phead和ptail先让ptail先走k步&#xff0c;然后让2个指针一起走&#xff0c;快的走到NULL即…

计算机发展史故事【13】

微电脑先锋 与第一台电子计算机埃历阿克的命运相似&#xff0c;1974 年面世的“牛郎星”能否作为世界上第一台微电脑被载入史册&#xff0c;人们似乎也存在着分歧。 拥有微处理器发明权的英特尔公司&#xff0c;难道自己不会组装微电脑&#xff0c;非得罗伯茨来越俎代庖吗&…