Pytorch实现图像风格迁移(一)

图像风格迁移是图像纹理迁移研究的进一步拓展,可以理解为针对一张风格图像和一张内容图像,通过将风格图像的风格添加到内容图像上,从而对内容图像进行进一步创作,获得具有不同风格的目标图像。基于深度学习网络的图像风格迁移主要有三种类型,分别为固定风格固定内容的风格迁移、固定风格任意内容的快速风格迁移和任意风格任意内容的极速风格迁移。

图像风格迁移主要任务是将图像的风格迁移到内容图像上,使得内容图像也具有一定的风格。其中风格图像通常可以是艺术家的一些作品,如画家梵高的《向日葵》《星月夜》,日本浮世绘的《神奈川冲浪里》等经典的画作,这些图像通常包含一些经典的艺术家风格。风格图像也可以是经典的具有特色的照片,如夕阳下的照片、城市的夜景等,图像具有鲜明色彩图像。而内容图像则通常来自现实世界,可以是自拍照、户外摄影等。利用图像风格迁移则可以将内容图像处理为想要的风格。

1.固定风格固定内容的普通风格迁移

固定风格固定内容的风格迁移方法,也可以称为普通图像风格迁移方法,也是最早的基于深度卷积神经网络的图像风格迁移方法。针对每张固定内容图像和风格图像,普通图像风格迁移方法都需要重新经过长时间的训练,这是最慢的方法,也是最经典的方法。固定风格固定内容的风格迁移方法思路很简单,就是把图片当作可以训练的变量,通过不断优化图片的像素值,降低其与内容图片的内容差异,并降低其与风格图片的风格差异,通过对卷积网络的多次迭代训练,能够生成一幅具有特定风格的图像,并且内容与内容图片的内容一致,生成图片风格与风格图片的风格一致。

 上图是论文《Image Style Transfer Using Convolutional Neural Networks》中,提到的基于VGG16网络中卷积层的图像风格迁移流程。在图中左边的图像 \vec{a\mathbf{}} 为输入的风格图像,右边的图像\vec{p} 为输入的内容图像。中间的图像 \vec{x} 则是表示由随机噪声生成的图像风格迁移后的图像。\iota _{content}表示图像的内容损失,\iota _{style}  表示图像的风格损失,\alpha 和 \beta 分别表示内容损失权重和风格损失权重。
针对深度卷积神经网络的研究发现,使用较深层次的卷积计算得到的特征映射能够较好地表示图像的内容,而较浅层次的卷积计算得到的特征映射能够较好地表示图像的风格。基于这样的思想就可以通过不同卷积层的特征映射来度量目标图像在风格上和风格图像的差异,以及在内容上和内容图像的差异。
两个图像的内容相似性度量主要是通过度量两张图像在通过VGG16的卷积计算后,在conv4_2层上特征映射的相似性,作为图像的内容损失,内容损失函数如下所示:

\iota _{content}=\frac{1}{2}\sum_{i,j}^{}\left ( F_{ij}^{l}-P_{ij}^{l} \right )^{2}

式中,l 表示特征映射的层数; F 和 P 分别是目标图像和内容图像在对应卷积层输出的特征映射。
图像风格的损失并不是直接通过特征映射进行比较的,而是通过计算Gram矩阵先计算出图像的风格,再进行比较图像的风格损失。计算特征映射的Gram矩阵则是先将其特征映射变换为一个列向量,而Gram矩阵则使用这个列向量乘以其转置获得,Gram矩阵可以更好地表示图像的风格。所以输入风格图像 \vec{a} 和目标图像 \vec{x} ,使用 A^{l} 和 G^{l} 分别表示它们在 l 层特征映射的风格表示(计算得到的Gram矩阵),那么图像的风格损失可以通过下面的方式进行计算:
 

 式中,w_{l} 是每个层的风格损失的权重; N_{l} 和 M_{l} 对应着特征映射的高和宽。针对固定图像固定风格的图像风格迁移,使用PyTorch很容易实现。后续小节将介绍如何使用PyTorch进行固定图像固定风格的图像风格迁移。

2.固定风格任意内容的快速风格迁移

固定风格任意内容的快速风格迁移,是在固定风格固定内容的图像风格迁移的基础上,做出的一些必要改进,即在普通图像风格迁移的基础上,添加一个可供训练的图像转换网络。针对一种风格图像进行训练后,可以将任意输入图像非常迅速地进行图像迁移学习,让该图像具有学习好的图像风格。其深度网络的框架如下:

 上图来自论文《Perceptual Losses for Real-Time Style Transfer and Super-Resolution》图示可以看作两个部分,一部分是通过输入图像 x 经过图像转换网络 f_{w},得到网络的输出 \hat{y},这部分是普通风格迁移图像框架中没有的部分,普通图像风格迁移的输入图像是随机噪声,而快速风格迁移的输入是一张图像经过转换网络 f_{w} 的输出;另一部分是使用VGG16网络中的相关卷积层去度量一张图像的内容损失和风格损失。
在图像转换网络( Image Transform Net)部分,可以分为3个阶段,分别是图像降维部分、残差连接部分和图像升维部分。
(1)图像降维部分:主要通过3个卷积层来完成,将图像的尺寸从256×256逐渐缩小到原来的1/4,即64×64,并且将通道数逐渐从3个增加到128个特征映射。
(2)残差连接部分:该部分是通过连接5个残差块,对图像进行学习,该结构用于学习如何在原图上添加少量内容,改变原图的风格。其中每个残差连接的结构如图所示:

(3)图像升维部分:该部分主要输出5个残差单元,通过3个卷积层的操作,逐渐将其通道数从128缩小到3,每个特征映射的尺寸从64×64放大到256 ×256,也可以使用转置卷积来完成网络的升维部分。

1.准备VGG19网络

从torchvision的models模块中导入预训练好的VGG19网络,预训练好的网络是在ImageNet数据集上进行训练的,所以使用时会非常方便。因为VGG19网络的作用是计算对应图像在网络中一些层输出的特征映射,在计算过程中,不需要更新VGG19的参数权重,所以导入VGG19网络后,需要将其中的权重冻结,程序如下所示:

import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import torch
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
import requests
from torchvision import models
from torchvision import transforms
import time
import hiddenlayer as hl
from skimage.io import imread

vgg19=models.vgg19(pretrained=True)
vgg=vgg19.features
for param in vgg.parameters():
    param.requires_grad_(False)

在上面的程序中,使用models.vgg19(pretrained=True)读取已经预训练的VGG19网络,参数pretrained=True表示读取的网络是已经预训练过的。预训练过的网络可以直接进行相关的应用。因不需要网络中的分类器相关的层,所以使用vgg19.features即可获取网络中由卷积和池化组成的特征提取层。在冻结网络的权重时,通过一个循环来遍历网络中所有可以训练的权重,然后通过requires_grad_(False)方法,设置权重在接下来的计算中不更新梯度,即权重不更新。

准备好VGG19网络之后,还需要对输入的图像进行相关的处理,因为网络可以接受任意尺寸的输入图像(图像的尺寸不宜过小,预防深层的卷积操作后没有特征映射输出,或特征映射尺寸太小),但是图像的尺寸越大,在进行风格迁移时,需要进行的计算量就会越多,速度就会越慢,所以需要保持图像有合适的尺寸。虽然图像风格迁移时可以使用任意尺寸的图像,而且输入的风格图像的尺寸和内容图像的尺寸大小也可以不相同(在实际应用中为了方便,通常会将风格图像的尺寸和内容图像的尺寸设置为相同),但目标图像尺寸和内容图像的尺寸需要相同,这样才能计算和比较内容损失的大小。下面定义load_image()函数用于读取图像,在读取图像的同时,控制图像的尺寸大小,程序如下所示:

def load_image(img_path,max_size=400,shape=None):
    image=Image.open(img_path).convert('RGB')
    #如果图片尺寸过大,就对图像进行尺寸变换
    if max(image.size) > max_size:
        size=max_size
    else:
        size=max(image.size)
    #如果指定了图像的尺寸,就像图像转化为shape指定的尺寸
    if shape is not None:
        size=shape
    #使用transform将图像转化为张量,并进行标准化
    in_transform=transforms.Compose([
        transforms.Resize(size),#图像尺寸变换,图像的短边匹配size
        transforms.ToTensor(),#数组转化为张量
        #图像进行标准化
        transforms.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))
    ])
    image=in_transform(image)[:3,:,:].unsqueeze(dim=0)
    return image

load_image()函数有三个参数,第一个参数是输入需要读取图像的路径img_path,第二个参数和第三个参数用于控制图像的大小。如果指定了max_size参数,在读取图像时,若图像的尺寸过大,图像会进行相应的缩小,如果指定了图像的尺寸( shape参数),则将图像转化为shape指定的大小。读取图像后,为了方便通过卷积网络计算相关的特征输出,使用transforms的相关转换操作,对图像进行预处理,最后将输出一个可以使用的四维张量image。上述读取后的图像并不能通过matplotlib库直接进行可视化,需要定义一个im_convert()函数,该函数可以将一张图像的四维张量转化为一个可以使用matplotlib库可视化的三维数组,程序如下所示: 

def im_convert(tensor):
    """
    将[1,c,h,w]维度的张量转化为[h,w,c]的数组
    因为张量进行了标准化,所以要进行标准化逆变换
    :param tensor:
    :return:
    """
    image=tensor.data.numpy().squeeze()#去除batch维度数据
    image=image.transpose(1,2,0)#置换数组的维度[c,h,w]->[h,w,c]
    #进行标准化的逆操作
    image=image * np.array((0.229,0.224,0.225))+ np.array((0.485,0.456,0.406))
    image=image.clip(0,1)#将图像的取值剪切到0-1
    return image

下面的程序将读取需要使用的风格图像和内容图像,并将它们可视化。

content=load_image(r"C:\Users\zex\Desktop\sky.jpg",max_size=400)
print("content shape:",content.shape)
#根据内容图像的宽高来设置风格图像的宽高
style=load_image(r"C:\Users\zex\Desktop\fangao.png",shape=content.shape[-2:])
print("style shape:",style.shape)
#可视化图像,可视化内容图像和风格图像
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(12,5))
ax1.imshow(im_convert(content))
ax1.set_title("content")
ax2.imshow(im_convert(style))
ax2.set_title("style")
plt.show()

 运行上述程序得到如下所示的输出和如下图所示的图像。为了保证两张图像具有相同的大小,在程序中通过内容图像的尺寸来定义风格图像的尺寸。

 2.图像的输出特征和Gram矩阵的计算

为了更方便获取图像在VGG19网络指定层上的特征映射输出,定义一个get_features()函数,程序如下所示:

def get_features(image,model,layer=None):
    """
    将一张图像image在一个网络model中进行前向传播计算,并获取指定层layer中特征映射输出
    :param image:
    :param model:
    :param layer:
    :return:
    """
    #将PyTorch的VGGNet的完整映射层名称与论文中的名称相对应
    #layers参数指定:需要用于图像的内容和样式表示的图层
    ##如果layers没有指定、就使用默认的层
    if layer is None:
        layers={
            '0':'conv1_1',
            '5':'conv2_1',
            '10':'conv3_1',
            '19':'conv4_1',
            '21':'conv4_2',
            '28':'conv5_1'
        }
    features={}#获取的每层特征保存到字典中
    x=image#需要获取的特征图像
    #model._modules是一个字典,保存着网络model每层信息
    for name,layer in model._modules.items():
        #从第一层开始获取图像的特征
        x=layer(x)
        #如果是layers参数指定的特征,那就保存到features中
        if name in layers:
            features[layers[name]]=x
        return features
    

get_features()函数通过输人参数图像(image),使用网络(model)和指定的层参数( layers),输出图像在指定网络层上的特征映射,并将输出的结果保存在一个字典中,如指定VGG19网络,但不指定layers参数,默认情况下会输出VGG19网络的conv1_1、conv2_1、conv3_1、conv4_1、conv4_2、conv5_1层的特征映射。比较两个图像是否具有相同的风格时,可以使用Gram矩阵来评价。我们定义函数gram_matrix()对一张图像的特征映射输出计算Gram矩阵。

def gram_matrix(tensor):
    """
    计算指定向量的Gram matrix,该矩阵表示图像的风格特征
    格拉姆矩阵最终能够在保证内容的情况下,进行风格传输
    tensor:是一张图像前向计算后的一层特征映射
    :param tensor:
    :return:
    """
    #获得tensor的batch_size,depth,height,width
    _,d,w,h=tensor.size()
    #改变矩阵的维度为(深度,高*宽)
    tensor=tensor.view(d,h*w)
    #计算gram matrix
    gram=torch.mm(tensor,tensor.t())
    return gram

 在上面定义的gram_matrix()函数是计算一张图像Gram矩阵,针对输入的四维特征映射,将其每一个特征映射设置为一个向量,得到一个行为d(特征映射数量),列为h* w(每个特征映射的像素数量)的矩阵,该矩阵乘以其转置即可得到需要的Gram矩阵。
在定义好两个辅助函数后,下面针对内容图像和风格图像计算特征输出,并且计算风格图像在每个特征输出上的Gram矩阵,程序如下所示:

#计算在第一次训练之前内容特征和风格特征,使用get_features函数
content_features=get_features(content,vgg)
#计算风格图像的风格表示
style_features=get_features(style,vgg)
#为风格图像的风格表示计算每层的格拉姆矩阵,使用字典保存
style_grams={layer: gram_matrix(style_features[layer]) for layer in style_features}
#使用内容图像的副本创建一个目标图像,训练时对目标图像进行调整
target=content.clone().requires_grad_(True)

3.进行图像风格迁移

在相关准备工作做好之后,下面就可以使用相关图像和网络进行图像风格迁移的学习,为了训练效果,在计算风格时,针对不同层的风格特征映射Gram矩阵,定义不同大小的权重,此处使用style_weights字典法完成,并且针对最终的损失,内容损失权重α和风格损失权重β分别定义为1和1×10^{6},程序如下所示:

style_weights={'conv1_1':1.,
               'conv2_1':0.75,
               'conv3_1':0.2,
               'conv4_1':0.2,
               'conv5_2':0.2}
alpha=1
beta=1e6
content_weight=alpha
style_weight=beta

需要注意的是,在style_weights中没有定义conv4_2层的Gram权重,这是因为该层的特征映射用于度量图像内容的相似性。
定义好权重参数后,下面使用Adam优化器进行训练,其中学习率为0.0003,并且为了监督网络在训练过程中的结果,每间隔1000次迭代输出目标图像的可视化情况,用于观察,并将迭代过程中每次相关损失值保存在列表中。用于优化目标图像的程序如下所示:

show_every=1000#每迭代1000次输出一个中间结果
#将损失保存
total_loss_all=[]
content_loss_all=[]
style_loss_all=[]
#使用Adam优化器
optimizer=optim.Adam([target],lr=0.0003)
steps=5000#优化时迭代的次数
t0=time.time()#记录需要的时间
for i in range(steps):
    #获取目标图像的特征
    target_features=get_features(target,vgg)
    #计算内容损失
    content_loss=torch.mean((target_features["conv4_2"] - content_features["conv4_2"])**2)
    #计算风格损失,并且初始化为0
    style_loss=0
    #将每层的gram_matrix损失相加
    for layer in style_weights:
        #计算要生成的图像风格表示
        target_feature = target_features[layer]
        target_gram=gram_matrix(target_feature)
        _,d,h,w=target_feature.shape
        #获取风格图像在每层的风格的gram_matrix
        style_gram=style_grams[layer]
        #计算要生成图像的风格和风格图像的风格之间的差距,每层都有一个权重
        layer_style_loss=style_weights[layer] * torch.mean((target_gram-style_gram)**2)
        #累加计算风格差异损失
        style_loss +=layer_style_loss/(d*h*w)
    #计算一次迭代的总的损失,即内容损失和风格损失的加权和
    total_loss=content_weight * content_loss + style_weight * style_loss
    #保留三种损失大小
    content_loss_all.append(content_loss.item())
    style_loss_all.append(style_loss.item())
    total_loss_all.append(total_loss.item())
    #更新需要生成的目标图像
    optimizer.zero_grad()
    total_loss.backward()
    optimizer.step()
    #输出每show_every次迭代后的生成图像
    if i % show_every==0:
        print('Total loss:',total_loss.item())
        print('Use time:',(time.time()-t0)/3600,'hour')
        newIm=im_convert(target)
        plt.imshow(newIm)
        plt.title("Iteration:"+str(i)+'times')
        plt.show()
        result=Image.fromarray((newIm * 255).astype(np.uint8))
        result.save('C:\\Users\\zex\\Desktop\\' +str(i)+'.bmp')

在上面的程序中还需要注意以下几点:
(1)优化器的使用方式为optim.Adam([target], lr=0.0003),表明在优化器中,最终要优化的参数是目标图像的像素值,不会优化VGG网络中的权重等参数。
(2)获取目标图像在相关层的特征输出时使用get_features(target, vgg)函数,并且因为内容图像的特征映射在conv4_2层,所以内容损失计算时,需提取指定层的输出,即使用target_features['conv4_2']获得目标图像的内容表示,以及使用content__features['conv4_2']获得内容图像的内容表示。
(3)由于图像的风格表示的损失是通过多个层来表示,所以需要通过for循环来逐层计算相关的Gram矩阵和风格损失。
(4)最终的损失是风格损失和内容损失的加权和。
(5)为了观察和保留图像风格在迁移过程中的结果,将图像每间隔1000次迭代计算后的结果进行可视化并保存到指定的文件中。
由于以上程序训练时间十分漫长,这也是普通图像风格迁移方法的最大缺点,因此最终结果这里就不展示了。下一节课介绍快速图像风格迁移方法。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode热题HOT100:76. 最小覆盖子串,84.柱状图中最大的矩形、96. 不同的二叉搜索树

LeetCode 热题 HOT 100 76. 最小覆盖子串 题目:给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。 注意: 对于 t 中重复字符,我们寻找的子字…

ADManager Plus:简化 Active Directory 管理的完美工具

在企业中,Active Directory(AD)是一个非常重要的组件,用于管理和控制所有计算机和用户的访问权限。然而,AD的管理和维护需要一定的技术能力和时间成本。为了简化这个过程,ManageEngine 推出了 ADManager Pl…

Leetcode-二叉树

1.中序-后序构建二叉树 106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode) 1. 首先根据后序(左右中)确定顶点元素; 2. 根据顶点元素划分中序序列; 3. 根据划分中序序列中-左子树的长度,进…

数据类型及变量的定义、使用和注意事项

数据类型 计算机存储单元 变量的定义格式: 数据类型 变量名数据值; 我们知道计算机是可以用来存储数据的,但是无论是内存还是硬盘,计算机存储设备的最小信息单元叫“位( bit ) ",我们又称之为“比特位”,通常用…

除了Java,还可以培训学习哪些IT技术?

除了Java,还可以培训学习哪些IT技术? 转行IT学Java似乎已经成为很多人的首选,原因无非是开发技术含量高、开发有前景、开发是一个互联网企业的核心岗位,最重要的是开发薪资待遇高。但其实只单纯因为薪资选择Java的话,小…

百万赞同:网络安全为什么缺人? 缺什么样的人?

1.网络安全为什么缺人? 缺人的原因是有了新的需求 以前的时候,所有企业是以产品为核心的,管你有啥漏洞,管你用户信息泄露不泄露,我只要做出来的产品火爆就行。 这一切随着《网络安全法》、《数据安全法》、《网络安全审查办法》…

什么是机器学习?

目录 简介 机器学习可以做什么 机器学习未来的趋势 总结 简介 机器学习是一种人工智能领域中的技术,其主要目的是让计算机能够自动进行模式识别、数据分析和预测。 机器学习的起源可以追溯到20世纪50年代,当时美国的Arthur Samuel在一篇论文中提出了相关…

静态时序分析Static Timing Analysis4——多时钟域和多时钟时序检查

文章目录 前言一、多时钟域时序分析1、慢时钟域到快时钟域1.1 建立时间检查1.2 保持时间检查1.3 多周期检查 2、快时钟域到慢时钟域2.1 建立时间检查2.2 保持时间检查2.3 合理的约束 3、总结 二、多时钟1、整数倍关系2、非整数倍关系 三、相位移动 前言 2023.4.12 这里讲的多时…

助力研发效能变革,第七届Techo TVP 开发者峰会圆满落下帷幕

引言 在互联网数字企业结束“野蛮扩张”、追求高质量增长的今天,研发效能已然成为企业关注的核心命题。伴随着云原生概念在软件领域的落地生根,云原生正驱动软件应用设计、实现、部署及运维方式的巨变,为研发效能治理带来了新的挑战与机遇&am…

vue-router3.0处理页面滚动部分源码分析

在使用vue-router3.0时候,会发现不同的路由之间来回切换,会滚动到上次浏览的位置,今天就来看看这部分的vue-router中的源码实现。 无论是基于hash还是history的路由切换,都对滚动进行了处理,这里分析其中一种即可。 无…

SpringBoot

文章目录 创建SpringBoot项目快速入门创建Controller启动项目 打包项目创建工件 SpringBoot概述SpringBoot优点起步依赖切换Web服务器 配置文件配置文件application.propertiesapplication.ymlapplication.yaml 三种配置文件优先级yaml格式读取配置数据(yml为例&…

windows系统管理_Windows server 2016 组管理与授权

组账户的概述 在 windows 服务器中,当我们需要为多个用户设置相同的权限时,一个一个的逐一设置会比较 麻烦,这个时候我们就需要用到另一种模式,组账户,使用此账户来进行简化操作。 在以后的职场中,每家公司…

Windows环境下调试DAB-DETR与Deformable-DETR

先前都是在服务器上运行DETR的相关程序,服务器使用的是Linux,所以运行较为简单,但如果想要简单的debug的话就没必要使用服务器了,今天便来在Winodws环境下调试DETR类项目,这里以Deformable-DETR与DAB-DETR为例。 首先是…

I.MX6U开发板使用OTG烧写系统

1.系统烧写 在实际的产品开发中肯定不可能通过网络来运行,否则没网的时候产品岂不 是就歇菜了。因此我们需要将 uboot、linux kernel、.dtb(设备树)和 rootfs 这四个文件烧写到板子 上的 EMMC、NAND 或 QSPI Flash 等其他存储设备上,这样不管有没有网络我…

R语言ggplot2 | 绘制随机森林重要性+相关性热图

📋文章目录 原图复现准备数据集及数据处理构建不同分类随机森林模型的并行计算绘制随机森林变量重要性柱状图计算数据集的相关性热图可视化合并随机森林重要性和热图 附上所有代码 在文献中,我们经常遇到随机森林和相关性热图的组合图片(下图)&#xff0…

Vue3——一文入门Vue3

Vue3的优势 1. 性能的提升 打包大小减少41% 初次渲染快55%,更新渲染快133% 内存减少54% … 2. 源码的升级 使用Proxy代替defineProperty实现响应式 重写虚拟DOM的实现和Tree-Shaking … 3. 拥抱TypeScript Vue3可以更好的支持TypeScript 4. 新的特性 1.C…

什么是文件共享软件?文件传输软件如何共享?

它是一个文件共享软件应用程序,可让强大的数据保护层下将任何大小的文件发送到世界上的任何地方。以光速发送和共享无限数量的文件。可以提交门户并使用语言,品牌,存储等自定义门户。可以选择一个存储点,例如文件传输软件&#xf…

零基础可以学习数据分析吗,有没有好的培训机构推荐?

数据分析从沿海火到了中西部的软件园,从传统互联网企业火到了新经济领域,火到了第一二产业。数字化成为这个时代的标签,而数据也成为了最有价值的资源,更多企业重视数据;因为有了真实数据的支撑,所有的决策…

【软考备战·希赛网每日一练】2023年4月19日

文章目录 一、今日成绩二、错题总结第一题第二题第三题 三、知识查缺 题目及解析来源:2023年04月19日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析: 第二题 解析: server-side n.服务器端 enterprise n.企业 client n.客户 d…

常见排序算法

目录 一、插入排序 1、直接插入排序 2、希尔排序(缩小增量插入排序) 二、选择排序 三、堆排序 四、冒泡排序 五、快速排序(递归) 1、交换法 2、挖坑法 3、前后指针法(推荐) 4、快排再优化 六、快速排序&…
最新文章