Matlab粒子群算法搜索费马点-求到多个点距离之和最小的点

0. 前言

本文是在我之前一篇博客的基础上进行了扩展和延申,原文使用了matlab自带的优化函数,而本文采用了一个比较经典的随机优化算法——粒子群算法,对给定的目标函数进行求解。

Matlab实现搜索费马点-求到多个点距离之和最小的点_ 一只博客-CSDN博客_到多个点距离之和最小的点https://blog.csdn.net/qq_42276781/article/details/118356037

1. 粒子群算法介绍

粒子群优化算法(Particle Swarm optimization,PSO)又翻译为粒子群算法微粒群算法、或微粒群优化算法。是通过模拟鸟群觅食行为而发展起来的一种基于群体协作的随机搜索算法。通常认为它是群集智能 (Swarm intelligence, SI) 的一种。它可以被纳入多主体优化系统(Multiagent Optimization System, MAOS)。粒子群优化算法是由Eberhart博士和kennedy博士发明。                                                                                                     ——摘自百度百科

 基本步骤如下:

Step1  初始化所有粒子的位置和速度,计算其适应度,并以适应度最佳的粒子作为最佳个体,将当前这批粒子作为最佳群体。

Step2  更新各个粒子的速度,再以各个粒子的速度更新其位置,重新计算每个粒子的适应度。

Step3  在新生成的这批粒子中:如果有粒子的适应度优于最佳个体,则更新最佳个体;如果第gif.latex?i个粒子的适应度优于最佳群体中第gif.latex?i个粒子的适应度,更新最佳群体中第gif.latex?i个粒子的适应度。

Step4  重复Step2Step3,直到满足优化目标或迭代次数达到上限。

2. 粒子群算法搜索费马点

2.1 适应度函数

粒子群算法中的适应度函数就是目标函数,即对于费马点gif.latex?%28x%2Cy%29,其到其余点gif.latex?%5Cleft%20%5C%7B%28x_1%2Cy_1%29%2C%28x_2%2Cy_2%29%2C...%2C%28x_n%2Cy_n%29%20%5Cright%20%5C%7D的距离之和最小

gif.latex?Minf%28x%2Cy%29%3D%5Csum_%7Bi%3D1%7D%5E%7Bn%7D%5Csqrt%7B%28x_i-x%29%5E2+%28y_i-y%29%5E2%7D

该适应度函数写作matlab的表达式如下,对该代码有疑问的可以参考前言中提到的博客。

%% I. 导入数据,设置适应度函数
% 数据
data = [1 8;3 7;6 4;7 5;9 2;5 8];
% 适应度函数,点x到所有点的距离之和
fun = @(x) sum(sqrt(sum((data-x).^2, 2)));
% 如果出现矩阵维度报错,请使用下方的fun
%fun = @(x) sum(sqrt(sum((data-repmat(x,length(data),1)).^2, 2)));

2.2 参数配置

设置学习因子gif.latex?c_1gif.latex?c_2,最大进化次数gif.latex?evolution%5C_max,粒子群规模gif.latex?pop%5C_size, gif.latex?%5Cleft%20%5C%7B%28x_1%2Cy_1%29%2C%28x_2%2Cy_2%29%2C...%2C%28x_n%2Cy_n%29%20%5Cright%20%5C%7D中横纵坐标的最大最小值gif.latex?x_%7Bmin%7D%2C%20x_%7Bmax%7D%2Cy_%7Bmin%7D%2C%20y_%7Bmax%7Dgif.latex?xgif.latex?y方向的最大速度和最小速度。

%% II. 离散粒子群算法 参数配置 
c1 = 1.49445;        %个体学习因子 
c2 = 1.49445;        %群体学习因子 
evolution_max = 200; %进化次数
pop_size = 1000;     %种群规模
%最大最小种群位置,设置最大最小速度
x_max = max(data(:,1));
x_min = min(data(:,1));
y_max = max(data(:,2));
y_min = min(data(:,2));
v_x_max = x_max-x_min;
v_x_min = -v_x_max;
v_y_max = y_max-y_min;
v_y_min = -v_y_max;

2.3 初始化粒子

%% III. 产生初始粒子和速度,并计算其适应度
pop = zeros(pop_size, 2);
v = zeros(pop_size, 2);
fitness = zeros(1, pop_size);
pop(:,1) = x_min + (x_max-x_min) * rand(pop_size,1);
pop(:,2) = y_min + (y_max-y_min) * rand(pop_size,1);
v(:,1) = v_x_min + (v_x_max-v_x_min) * rand(pop_size,1);
v(:,2) = v_y_min + (v_y_max-v_y_min) * rand(pop_size,1);
for i = 1: pop_size
    fitness(i) = fun(pop(i,:));
end

gif.latex?pop是一个pop_size*2的矩阵,表示一群粒子的位置,第gif.latex?i行表示第gif.latex?i个粒子的位置,第一列表示粒子在gif.latex?x方向的位置,第二列表示粒子在gif.latex?y方向的位置,如gif.latex?pop_%7Bi%2C1%7D表示第gif.latex?i个粒子的横坐标。

gif.latex?v是一个pop_zize*2的矩阵,表示一群粒子的速度,第gif.latex?i行表示第gif.latex?i个粒子的速度,第一列表示粒子在gif.latex?x方向的速度,第二列表示粒子在gif.latex?y方向的速度,如gif.latex?v_%7Bi%2C2%7D表示第gif.latex?i个粒子在gif.latex?y方向的速度。

gif.latex?fitness是一维行向量,表示pop_size个粒子的适应度,如gif.latex?fitness_i表示第gif.latex?i个粒子的适应度。

这里使用了一个生成指定范围随机数的语句,用以生成区间[a,b]内的随机浮点数,返回形式是m*n的矩阵,里面的元素不完全相同。

a + (b-a) * rand(m, n)

2.4 计算最佳个体和最佳群体

%% IV. 初始化最佳个体和最佳群体
[fitness_best, index_best] = min(fitness); % 获取最佳适应度及其索引
I = pop(index_best,:);                     % 初始化最佳个体
P = pop;                                   % 初始化最佳群体
I_fitness_best = fitness_best;             % 初始化最佳个体适应度
P_fitness_best = fitness;                  % 初始化最佳群体适应度

2.3节中我们计算了所有粒子的适应度,现在需要从中选出最优的适应度gif.latex?fitness%5C_best和其索引gif.latex?index%5C_best(它是gif.latex?pop的第几个粒子)。matlab提供了min函数,可以搜索一个向量中的最小值及其位置。

接着把有最优适应度的粒子的位置保存到gif.latex?I,把当前的这群粒子暂时作为最佳群体,保存到gif.latex?P,最后分别保存gif.latex?Igif.latex?P的适应度gif.latex?I%5C_fitness%5C_bestgif.latex?P%5C_fitness%5C_best,用以之后的对比。

2.5 迭代寻优

2.5.1 速度更新

Step2中,粒子需要更新其速度,第gif.latex?i个粒子速度需要向最佳个体最佳群体中的第gif.latex?i个粒子学习。更新后其在gif.latex?x方向的速度可表示为

gif.latex?v_%7Bi%2C1%7D%3Dv_%7Bi%2C1%7D+c_1*r_1*%28I_1-pop_%7Bi%2C1%7D%29+c_2*r_2*%28P_%7Bi%2C1%7D-pop_%7Bi%2C1%7D%29

gif.latex?v_%7Bi%2C1%7D表示第gif.latex?i个粒子gif.latex?x方向的速度,gif.latex?c_1gif.latex?c_2分别表示第gif.latex?i个粒子向最佳个体最佳群体中的第gif.latex?i个粒子的学习因子,是一个常数,gif.latex?r_1gif.latex?r_2表示两个随机生成的0-1之间的随机数,gif.latex?pop表示当前所有粒子,gif.latex?pop_%7Bi%2C1%7D表示当前第gif.latex?i个粒子在gif.latex?x方向的位置,gif.latex?I_1表示最佳个体在gif.latex?x方向的位置,gif.latex?P_%7Bi%2C1%7D表示最佳群体中的第gif.latex?i个粒子在gif.latex?x方向的位置。同理,更新后其在gif.latex?y方向的速度可表示为

 gif.latex?v_%7Bi%2C2%7D%3Dv_%7Bi%2C2%7D+c_1*r_3*%28I_2-pop_%7Bi%2C2%7D%29+c_2*r_4*%28P_%7Bi%2C2%7D-pop_%7Bi%2C2%7D%29

gif.latex?v_%7Bi%2C2%7D表示第gif.latex?i个粒子gif.latex?y方向的速度,gif.latex?c_1gif.latex?c_2分别表示第gif.latex?i个粒子向最佳个体最佳群体中的第gif.latex?i个粒子的学习因子,是一个常数,gif.latex?r_3gif.latex?r_4表示两个随机生成的0-1之间的随机数(不同的下标用来示意这四个随机数gif.latex?r_1%2Cr_2%2Cr_3%2Cr_4并不一定完全相同),gif.latex?pop表示当前所有粒子,gif.latex?pop_%7Bi%2C2%7D表示当前第gif.latex?i个粒子在gif.latex?y方向的位置,gif.latex?I_2表示最佳个体在gif.latex?y方向的位置,gif.latex?P_%7Bi%2C2%7D表示最佳群体中的第gif.latex?i个粒子在gif.latex?y方向的位置。

为了提高效率,可以一次性更新所有粒子在gif.latex?x方向和gif.latex?y方向的速度

gif.latex?v_%7B%3A%2C1%7D%3Dv_%7B%3A%2C1%7D+c_1r_1*%5BI_1-v_%7B1%2C1%7D%2CI_1-v_%7B2%2C1%7D%2C...%2CI_1-v_%7Bn%2C1%7D%5D+c_2r_2*%28P_%7B%3A%2C1%7D-pop_%7B%3A%2C1%7D%29

gif.latex?v_%7B%3A%2C2%7D%3Dv_%7B%3A%2C2%7D+c_1r_3*%5BI_2-v_%7B1%2C2%7D%2CI_2-v_%7B2%2C2%7D%2C...%2CI_2-v_%7Bn%2C2%7D%5D+c_2r_4*%28P_%7B%3A%2C2%7D-pop_%7B%3A%2C2%7D%29

这里的符号意义如下

gif.latex?v_%7B%3A%2C1%7D%3D%5Bv_%7B1%2C1%7D%2Cv_%7B2%2C1%7D%2C...%2Cv_%7Bn%2C1%7D%5D            gif.latex?v_%7B%3A%2C2%7D%3D%5Bv_%7B1%2C2%7D%2Cv_%7B2%2C2%7D%2C...%2Cv_%7Bn%2C2%7D%5D

 gif.latex?P_%7B%3A%2C1%7D%3D%5BP_%7B1%2C1%7D%2CP_%7B2%2C1%7D%2C...%2CP_%7Bn%2C1%7D%5D          gif.latex?P_%7B%3A%2C2%7D%3D%5BP_%7B1%2C2%7D%2CP_%7B2%2C2%7D%2C...%2CP_%7Bn%2C2%7D%5D

gif.latex?pop_%7B%3A%2C1%7D%3D%5Bpop_%7B1%2C1%7D%2Cpop_%7B2%2C1%7D%2C...%2Cpop_%7Bn%2C1%7D%5D         gif.latex?pop_%7B%3A%2C2%7D%3D%5Bpop_%7B1%2C2%7D%2Cpop_%7B2%2C2%7D%2C...%2Cpop_%7Bn%2C2%7D%5D

 matlab相应的代码如下

%更新速度
v(:,1) = v(:,1) + c1*rand*(I(1)-pop(:,1)) + c2*rand*(P(:,1)-pop(:,1));
v(:,2) = v(:,2) + c1*rand*(I(2)-pop(:,2)) + c2*rand*(P(:,2)-pop(:,2));

2.5.2 位置更新

每一次迭代所需的时间为单位1,则

gif.latex?pop_%7Bi%2C%3A%7D%3Dpop_%7Bi%2C%3A%7D+V_%7Bi%2C%3A%7D

这里gif.latex?pop_%7Bi%2C%3A%7D%3D%5Bpop_%7Bi%2C1%7D%2C%20pop_%7Bi%2C2%7D%5Dgif.latex?v_%7Bi%2C%3A%7D%3D%5Bv_%7Bi%2C1%7D%2Cv_%7Bi%2C2%7D%5D

更新后的粒子可能会超出预设的边界,需要对超出边界的粒子进行修正。

如图,对于费马点的搜索,费马点的横坐标必定不会小于gif.latex?%5Cleft%20%5C%7B%28x_1%2Cy_1%29%2C%28x_2%2Cy_2%29%2C...%2C%28x_n%2Cy_n%29%20%5Cright%20%5C%7D中的任何点的横坐标,必定不会大于任何点的横坐标,即

gif.latex?min%28x_1%2Cx_2%2C...%2Cx_n%29%5Cleq%20x%5Cleq%20max%28x_1%2Cx_2%2C...%2Cx_n%29

纵坐标必定不会小于gif.latex?%5Cleft%20%5C%7B%28x_1%2Cy_1%29%2C%28x_2%2Cy_2%29%2C...%2C%28x_n%2Cy_n%29%20%5Cright%20%5C%7D中的任何点的纵坐标,必定不会大于任何点的纵坐标,即

gif.latex?min%28y_1%2Cy_2%2C...%2Cy_n%29%5Cleq%20y%5Cleq%20max%28y_1%2Cy_2%2C...%2Cy_n%29

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVG9ibGVyb25lX1dpbmQ=,size_17,color_FFFFFF,t_70,g_se,x_16

 找出gif.latex?%5Cleft%20%5C%7B%28x_1%2Cy_1%29%2C%28x_2%2Cy_2%29%2C...%2C%28x_n%2Cy_n%29%20%5Cright%20%5C%7D中横纵坐标的最大最小值,组合后得到4个点,将其连接得到上述矩形。不难发现,如果费马点gif.latex?P在红色矩形框外,记距离gif.latex?P最近的矩形框上的点为gif.latex?A,显然gif.latex?A到6个蓝色点的距离之和小于gif.latex?P到6个蓝色点的距离之和。

故我们只需要使用构成这个矩形框的gif.latex?%5Bx_%7Bmin%7D%2C%20x_%7Bmax%7D%5Dgif.latex?%5By_%7Bmin%7D%2C%20y_%7Bmax%7D%5D对超出边界的粒子进行修正即可。单个粒子gif.latex?%28x%2Cy%29的修正逻辑如下

 gif.latex?x%3D%5Cbegin%7Bcases%7D%20x_%7Bmin%7D%20%26%20%5Ctext%7B%20if%20%7D%20x%3Cx_%7Bmin%7D%20%5C%5C%20x_%7Bmax%7D%26%20%5Ctext%7B%20if%20%7D%20x%3Ex_%7Bmax%7D%20%5C%5C%20x%20%26%20%5Ctext%7B%20if%20%7D%20x_%7Bmin%7D%20%5Cleq%20x%5Cleq%20x_%7Bmax%7D%20%5Cend%7Bcases%7D

 gif.latex?y%3D%5Cbegin%7Bcases%7D%20y_%7Bmin%7D%20%26%20%5Ctext%7B%20if%20%7D%20y%3Cy_%7Bmin%7D%20%5C%5C%20y_%7Bmax%7D%26%20%5Ctext%7B%20if%20%7D%20y%3Ey_%7Bmax%7D%20%5C%5C%20y%20%26%20%5Ctext%7B%20if%20%7D%20y_%7Bmin%7D%20%5Cleq%20y%20%5Cleq%20y_%7Bmax%7D%20%5Cend%7Bcases%7D

同样,仿照2.5.1节,我们也同时更新所有粒子的位置

gif.latex?pop_%7B%3A%2C1%7D%3Dpop_%7B%3A%2C1%7D+v_%7B%3A%2C1%7D         gif.latex?pop_%7B%3A%2C2%7D%3Dpop_%7B%3A%2C2%7D+v_%7B%3A%2C2%7D

更新所有粒子位置以及修正越界粒子位置的代码如下

pop(:,1) = pop(:,1) + v(:,1);
pop(:,2) = pop(:,2) + v(:,2);
%调整越界的粒子
pop(pop(:,1) > x_max, 1) = x_max;
pop(pop(:,1) < x_min, 1) = x_min;
pop(pop(:,2) > y_max, 2) = y_max;
pop(pop(:,2) < y_min, 2) = y_min;

在修正越界粒子的代码中,嵌套了matlab的索引代码,如果有不理解之处可以阅读我的这篇博客,或在评论区讨论。

matlab核心知识点-索引index运用_ 一只博客-CSDN博客_matlab中index索引https://blog.csdn.net/qq_42276781/article/details/121715378

2.5.3 迭代逻辑

%% V. 迭代寻优
fitness_evolution = zeros(evolution_max, 1);
position_evolution = zeros(evolution_max, 2);
for i = 1: evolution_max
    %记录最佳个体
    fitness_evolution(i) = I_fitness_best;
    position_evolution(i,:) = I;
    %更新速度
    v(:,1) = v(:,1) + c1*rand*(I(1)-pop(:,1)) + c2*rand*(P(:,1)-pop(:,1));
	v(:,2) = v(:,2) + c1*rand*(I(2)-pop(:,2)) + c2*rand*(P(:,2)-pop(:,2));
    %更新位置
    pop(:,1) = pop(:,1) + v(:,1);
    pop(:,2) = pop(:,2) + v(:,2);
    %调整越界的粒子
    pop(pop(:,1) > x_max, 1) = x_max;
    pop(pop(:,1) < x_min, 1) = x_min;
    pop(pop(:,2) > y_max, 2) = y_max;
    pop(pop(:,2) < y_min, 2) = y_min;
    %更新适应度
    for j = 1: pop_size
        fitness(j) = fun(pop(j,:));
    end
    %更新最佳群体
    index = fitness < P_fitness_best;
    P_fitness_best(index) = fitness(index);
    P(index,:) = pop(index,:);
    %更新最佳个体
    [fitness_best, index_best] = min(fitness);
    if fitness_best < I_fitness_best
        I_fitness_best = fitness_best; %更新最佳个体适应度
        I = pop(index_best,:);    %更新最佳个体
    end
end

2.6 输出结果

%% VI.输出结果
figure, plot(fitness_evolution), title('最优个体适应度')
xlabel('进化代数'), ylabel('适应度');
fprintf('最佳费马点为(%.2f, %.2f)\n',I(1), I(2))
fprintf('最小距离和为%.2f\n', I_fitness_best)

绘制每一次迭代的最优适应度gif.latex?fitness%5C_evolution,输出最后求解得到的费马点gif.latex?I和最小距离和gif.latex?I%5C_fitness%5C_best

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVG9ibGVyb25lX1dpbmQ=,size_19,color_FFFFFF,t_70,g_se,x_16

2.7 模拟动画

通过动画来模拟每一次迭代得到的费马点,即费马点的搜索过程。因为本文使用的数据量较少,费马点很快就搜索到了,第一次迭代的费马点和最终的费马点相距较近(第一次迭代得到的最短距离之和为18.736左右,最终的距离之和为18.724左右),难以发现明显的移动。如果点较多,就可以发现明显移动。

figure
for i = 1: evolution_max
    plot(data(:,1), data(:,2), 'bo');
    hold on
    plot(position_evolution(i,1), position_evolution(i,2), 'rp');
    hold off
    m(:,i) =getframe;
end

3. 完整代码

clear, close all
%% I. 导入数据,设置适应度函数
%数据
data = [1 8;3 7;6 4;7 5;9 2;5 8];
%适应度函数,点x到所有点的距离之和
fun = @(x) sum(sqrt(sum((data-x).^2, 2)));
%如果出现矩阵维度报错,请使用下方的fun
%fun = @(x) sum(sqrt(sum((data-repmat(x,length(data),1)).^2, 2)));
%% II. 离散粒子群算法 参数配置 
c1 = 1.49445;        %个体学习因子 
c2 = 1.49445;        %群体学习因子 
evolution_max = 200; %进化次数
pop_size = 1000;     %种群规模
%最大最小种群位置,设置最大最小速度
x_max = max(data(:,1));
x_min = min(data(:,1));
y_max = max(data(:,2));
y_min = min(data(:,2));
v_x_max = x_max-x_min;
v_x_min = -v_x_max;
v_y_max = y_max-y_min;
v_y_min = -v_y_max;
%% III. 产生初始粒子和速度,并计算其适应度
pop = zeros(pop_size, 2);
v = zeros(pop_size, 2);
fitness = zeros(1, pop_size);
pop(:,1) = x_min + (x_max-x_min) * rand(pop_size,1);
pop(:,2) = y_min + (y_max-y_min) * rand(pop_size,1);
v(:,1) = v_x_min + (v_x_max-v_x_min) * rand(pop_size,1);
v(:,2) = v_y_min + (v_y_max-v_y_min) * rand(pop_size,1);
for i = 1: pop_size
    fitness(i) = fun(pop(i,:));
end
%% IV. 初始化最佳个体和最佳群体
[fitness_best, index_best] = min(fitness); %获取最佳适应度及其索引
I = pop(index_best,:);                     %初始化最佳个体
P = pop;                                   %初始化最佳群体
I_fitness_best = fitness_best;             %初始化最佳个体适应度
P_fitness_best = fitness;                  %初始化最佳群体适应度
%% V. 迭代寻优
fitness_evolution = zeros(evolution_max, 1);
position_evolution = zeros(evolution_max, 2);
for i = 1: evolution_max
    %记录最佳个体
    fitness_evolution(i) = I_fitness_best;
    position_evolution(i,:) = I;
    %更新速度
    v(:,1) = v(:,1) + c1*rand*(I(1)-pop(:,1)) + c2*rand*(P(:,1)-pop(:,1));
	v(:,2) = v(:,2) + c1*rand*(I(2)-pop(:,2)) + c2*rand*(P(:,2)-pop(:,2));
    %更新位置
    pop(:,1) = pop(:,1) + v(:,1);
    pop(:,2) = pop(:,2) + v(:,2);
    %调整越界的粒子
    pop(pop(:,1) > x_max, 1) = x_max;
    pop(pop(:,1) < x_min, 1) = x_min;
    pop(pop(:,2) > y_max, 2) = y_max;
    pop(pop(:,2) < y_min, 2) = y_min;
    %更新适应度
    for j = 1: pop_size
        fitness(j) = fun(pop(j,:));
    end
    %更新最佳群体
    index = fitness < P_fitness_best;
    P_fitness_best(index) = fitness(index);
    P(index,:) = pop(index,:);
    %更新最佳个体
    [fitness_best, index_best] = min(fitness);
    if fitness_best < I_fitness_best
        I_fitness_best = fitness_best; %更新最佳个体适应度
        I = pop(index_best,:);         %更新最佳个体
    end
end
%% VI.输出结果
figure, plot(fitness_evolution), title('最优个体适应度')
xlabel('进化代数'), ylabel('适应度');
fprintf('最佳费马点为(%.2f, %.2f)\n',I(1), I(2))
fprintf('最小距离和为%.2f\n', I_fitness_best)
%% VII.模拟动画
figure
for i = 1: evolution_max
    plot(data(:,1), data(:,2), 'bo');
    hold on
    plot(position_evolution(i,1), position_evolution(i,2), 'rp');
    hold off
    m(:,i) =getframe;
end

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVG9ibGVyb25lX1dpbmQ=,size_17,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVG9ibGVyb25lX1dpbmQ=,size_18,color_FFFFFF,t_70,g_se,x_16

 有问题可以进群交流讨论

  • 10
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Toblerone_Wind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值