基于Kmeans算法的无线传感器网络分簇路由协议

一、内容说明

  本文所采用的基于Kmeans算法的分簇路由协议是本人所想的一个简单的改进(不是复现的具体哪一篇论文的内容),然后与Leach算法进行了指标对比。
  Kmeans算法及其复现可以参考以下内容:

https://blog.csdn.net/qq_45004419/article/details/130636784?spm=1001.2014.3001.5502

  Leach算法及其复现可以参考以下内容:

https://blog.csdn.net/qq_45004419/article/details/129584285?spm=1001.2014.3001.5502

二、算法简述

  先成簇,后选簇头
  成簇阶段采用Kmeans算法,聚类数就是簇头数,簇头数采用SEP协议中给的最佳簇数公式:
在这里插入图片描述
  成簇后,选择簇内距离聚类中心距离最短的点为聚类中心(因为只根据这个因素选簇头,所以本算法比较简单),当然,聚类中心选取的指标还可以增加关于能量、度等等指标。
  簇间簇内通信和Leach协议一样。

思考:Kmeans算法是一种无监督的聚类算法,而无线传感器网络成簇也是一种聚类的形式,所以Kmeans算法可以很好的应用于无线传感器网络成簇方面,但是它们又有不同点,Kmenas算法及其相关的改进算法,如FCM算法、AFKM算法、MKM算法等,其聚类考虑的因素都是1~2个,这很难达到无线传感器网络负载均衡的要求,无线传感器网络通常需要考虑能量、各种距离、通信代价等相对复杂的因素,所以仅仅只与Kmeans算法结合是达不到如今相关领域研究的要求的。

  但是Kemans可以作为一个改进点,聚类数的选取也是可以进行改进的,Kmenas初始化中心也可以进行改进,常用的改进点就是与启发式算法结合。

三、算法复现

  本文提出的算法

%% 清空环境变量
clear;clc;
close all;

%% 1、初始参数设定模块
%监测区域大小(单位:米)
xm = 100;
ym = 100;

%基站的x和y坐标
sink.x = 0.5 * xm;
sink.y = 0.5 * ym;

%场地中的节点数
n = 100;

%最佳簇头概率(根据最优簇头公式得来)
p=0.1;

%最大循环轮数
rmax = 2000;

%能量模型(所有值均以焦耳为单位)
Eo = 0.5;%初始能量
ETX = 50*0.000000001;%耗传输能耗
ERX = 50*0.000000001;%接收能
EDA = 5*0.000000001;%数据聚合能量

%发射放大器类型
Efs = 10*0.000000000001;%小于do
Emp = 0.0013*0.000000000001;%大于do
do = sqrt(Efs/Emp);%距离阈值

%数据包和控制包长度
packetLength = 4000;%数据包长度 
ctrPacketLength = 100;%控制包长度

%% 2、无线传感器网络模型产生模块
%构建无线传感器网络,在区域内均匀投放所有节点,并画出图形
figure;
for i = 1:n
    S(i).xd = rand(1,1)*xm;%坐标
    S(i).yd = rand(1,1)*ym;
    S(i).id = i;%节点的ID
    S(i).class = 0;%所属的聚类号
    S(i).E = Eo;%初始化节点能量
    S(i).cond = 1;% 节点的当前状态:存活   1;死亡  0
    %最初没有簇头只有簇成员
    S(i).type = 'N';%簇成员(普通节点) 
    plot(S(i).xd,S(i).yd,'o');
    hold on;
end
%加入基站信息
S(n+1).xd = sink.x;
S(n+1).yd = sink.y;
plot(S(n+1).xd,S(n+1).yd,'p');

%% 3、网络运行模块 
% 死亡节点数指标
first_dead = 0;%第一个死亡节点
teenth_dead = 0;%10%的死亡节点
all_dead = 0;%节点都死亡

%节点死亡数指标对应标记
flag_first_dead = 0;
flag_teenth_dead = 0;
flag_all_dead = 0;

%传输到基站和簇头的比特计数器
packets_TO_BS = 0;
packets_TO_CH = 0;

%主循环
for r = 0:rmax 
    dead = 0;%计算死亡节点数
    energy = 0;%每轮节点剩余总能量
    for i = 1:n
        %检查有无死亡节点
        if S(i).E <= 0
            S(i).cond = 0;
            dead = dead+1;
            %第一个节点死亡时间
            if dead == 1
                if flag_first_dead == 0
                    first_dead = r+1;
                    flag_first_dead = 1;
                end
            end
            % 10%的节点死亡时间
            if dead == 0.1*n
                if flag_teenth_dead ==0
                    teenth_dead = r+1;
                    flag_teenth_dead = 1;
                end
            end
            if dead == n
                if flag_all_dead == 0
                    all_dead = r+1;
                    flag_all_dead = 1;
                end
            end
        end
    end
    X = zeros(n-dead,2);%质心坐标矩阵
    j = 0;
    for i = 1:n
        if S(i).E > 0
            energy = energy + S(i).E;%统计每轮节点总能量和
            S(i).type = 'N';
            j = j+1;
            X(j,:) =  [S(i).xd,S(i).yd];
        end
    end

    %节点死亡,退出循环,但是一般都是与其他算法比较,由于不同算法最终循环伦次不同
    %所以节点死亡并不退出循环,而是在达到最大循环次数后才结束循环
%     if flag_all_dead == 1
%         break;
%     end
    %r从0开始,但是计算机中数组从1开始,所以采用r+1的下标
    STATISTICS.DEAD(r+1) = dead;%每轮死亡节点数
    STATISTICS.alive(r+1) = n-dead;%每轮存活节点数
    STATISTICS.energy(r+1) = energy;%每轮剩余总能量
    
    %运用kmeans算法
    k = n-dead;%质心个数(存活的节点)
    C_number = round(sqrt(k/(2*pi))*2/0.765);%聚类数,该公式来源于SEP协议中的最佳簇数
    if C_number > 0
        [idx,center] = kmeans(X,C_number);%center返回聚类中心坐标,idx返回对应聚类号
        [class_number,~] = size(center);%最终的聚类数
    else
        idx = [];
        center = [];
        class_number = 0;
    end
    
    j = 0;
    %给节点标记聚类号
    for i = 1:n
        if S(i).cond == 1
            j = j+1;
            S(i).class = idx(j);
        end
    end
    if class_number >= 1
    %选簇头
        for k = 1:class_number
            min_dist = 1e6;%最小距离
            for i = 1:n
                if S(i).cond == k
                    dist = sqrt( (S(i).xd - center(k,1))^2 + (S(i).yd - center(k,2))^2 );
                    if dist<min_dist
                        min_dist = dist;
                        id = i;
                    end
                end
            end
            S(id).type = 'C';%簇头标记
            C(k).xd = S(id).xd;
            C(k).yd = S(id).yd;
            C(k).id = id;
            distance=sqrt( (S(id).xd-(S(n+1).xd))^2 + (S(id).yd-(S(n+1).yd))^2 );%距离基站的距离
            C(k).distance = distance;
            %簇头向基站发送数据包能耗
            if (distance > do)
                S(id).E = S(id).E- ( (ETX+EDA)*(packetLength) + Emp * packetLength*distance^4);
            else
                S(id).E = S(id).E- ( (ETX+EDA)*(packetLength) + Efs * packetLength*distance^2); 
            end
        end        
        STATISTICS.COUNTCHS(r+1) = class_number;
        packets_TO_BS = packets_TO_BS + class_number;
        if energy > 0%网络还剩余能量,这一步在节点都死亡后才生效
            for k = 1:class_number
                for i = 1:n
                    if ( S(i).class == k && S(i).type=='N' && S(i).E>0) %普通节点
                        dist = sqrt( (S(i).xd - C(k).xd)^2 + (S(i).yd - C(k).yd)^2 );
                        %向簇头发送数据包
                        if (dist > do)
                            S(i).E = S(i).E - ( ETX*packetLength + Emp*packetLength*dist^4); %向簇发送头数据包
                        else
                            S(i).E = S(i).E - ( ETX*packetLength + Efs*packetLength*dist^2); %向簇头发送数据包
                        end
                        %簇头接收簇成员的数据包
                        if(dist > 0)
                            S(C(k).id).E = S(C(k).id).E - ( (ERX + EDA)*packetLength ); %接受簇成员发来的数据包
                        end
                        packets_TO_CH = packets_TO_CH + 1;
                    end
                end
            end
        end
        STATISTICS.PACKETS_TO_CH(r+1) = packets_TO_CH;
        STATISTICS.PACKETS_TO_BS(r+1) = packets_TO_BS;
    end
end
    

%% 绘图显示
figure;
plot(0:rmax, STATISTICS.DEAD, 'r', 'LineWidth', 2);
xlabel('轮数'); ylabel('死亡节点数');
figure;
plot(0:rmax, STATISTICS.alive, 'b', 'LineWidth', 2);
xlabel('轮数'); ylabel('活动节点数');
figure;
plot(0:length(STATISTICS.PACKETS_TO_BS)-1, STATISTICS.PACKETS_TO_BS, 'm', 'LineWidth', 2);
xlabel('轮数'); ylabel('基站收到数据包数');
figure;
plot(0:length(STATISTICS.COUNTCHS)-1, STATISTICS.COUNTCHS, 'g', 'LineWidth', 2);
xlabel('轮数'); ylabel('簇头数');
figure;
plot(0:rmax, STATISTICS.energy, 'k', 'LineWidth', 2);
xlabel('轮数'); ylabel('剩余能量');

  本文提出的算法与Leach算法对比
注:因为控制包相关的能耗小,实验没有考虑与控制包相关的功能。

%% 清空环境变量
clear;clc;
close all;

%% 1、初始参数设定模块
%监测区域大小(单位:米)
xm = 100;
ym = 100;

%基站的x和y坐标
sink.x = 0.5 * xm;
sink.y = 0.5 * ym;

%场地中的节点数
n = 100;

%最佳簇头概率(根据最优簇头公式得来)
p=0.1;

%最大循环轮数
rmax = 2000;

%能量模型(所有值均以焦耳为单位)
Eo = 0.5;%初始能量
ETX = 50*0.000000001;%耗传输能耗
ERX = 50*0.000000001;%接收能
EDA = 5*0.000000001;%数据聚合能量

%发射放大器类型
Efs = 10*0.000000000001;%小于do
Emp = 0.0013*0.000000000001;%大于do
do = sqrt(Efs/Emp);%距离阈值

%数据包和控制包长度
packetLength = 4000;%数据包长度 
ctrPacketLength = 100;%控制包长度

%% 2、无线传感器网络模型产生模块
%构建无线传感器网络,在区域内均匀投放所有节点,并画出图形
figure;
for i = 1:n
    S(i).xd = rand(1,1)*xm;%坐标
    S(i).yd = rand(1,1)*ym;
    S(i).id = i;%节点的ID
    S(i).class = 0;%所属的聚类号
    S(i).E = Eo;%初始化节点能量
    S(i).cond = 1;% 节点的当前状态:存活   1;死亡  0
    %最初没有簇头只有簇成员
    S(i).type = 'N';%簇成员(普通节点) 
    plot(S(i).xd,S(i).yd,'o');
    hold on;
    N(i).xd = rand(1,1)*xm;%坐标
    N(i).yd = rand(1,1)*ym;
    N(i).G = 0;%"G = 0"表示节点本周期内未当选过簇头,当选则改为"G = 1"
    N(i).E = Eo;%初始化节点能量
    %最初没有簇头只有簇成员
    N(i).type = 'N';%簇成员(普通节点) 
end
%加入基站信息
S(n+1).xd = sink.x;
S(n+1).yd = sink.y;
N(n+1).xd = sink.x;
N(n+1).yd = sink.y;
plot(S(n+1).xd,S(n+1).yd,'p');

%% 3、网络运行模块(Based_on_Kmeans) 
% 死亡节点数指标
first_dead = 0;%第一个死亡节点
teenth_dead = 0;%10%的死亡节点
all_dead = 0;%节点都死亡

%节点死亡数指标对应标记
flag_first_dead = 0;
flag_teenth_dead = 0;
flag_all_dead = 0;

%传输到基站和簇头的比特计数器
packets_TO_BS = 0;
packets_TO_CH = 0;

%主循环
for r = 0:rmax 
    dead = 0;%计算死亡节点数
    energy = 0;%每轮节点剩余总能量
    for i = 1:n
        %检查有无死亡节点
        if S(i).E <= 0
            S(i).cond = 0;
            dead = dead+1;
            %第一个节点死亡时间
            if dead == 1
                if flag_first_dead == 0
                    first_dead = r+1;
                    flag_first_dead = 1;
                end
            end
            % 10%的节点死亡时间
            if dead == 0.1*n
                if flag_teenth_dead ==0
                    teenth_dead = r+1;
                    flag_teenth_dead = 1;
                end
            end
            if dead == n
                if flag_all_dead == 0
                    all_dead = r+1;
                    flag_all_dead = 1;
                end
            end
        end
    end
    X = zeros(n-dead,2);%质心坐标矩阵
    j = 0;
    for i = 1:n
        if S(i).E > 0
            energy = energy + S(i).E;%统计每轮节点总能量和
            S(i).type = 'N';
            j = j+1;
            X(j,:) =  [S(i).xd,S(i).yd];
        end
    end

    %节点死亡,退出循环,但是一般都是与其他算法比较,由于不同算法最终循环伦次不同
    %所以节点死亡并不退出循环,而是在达到最大循环次数后才结束循环
%     if flag_all_dead == 1
%         break;
%     end
    %r从0开始,但是计算机中数组从1开始,所以采用r+1的下标
    STATISTICS.DEAD(r+1) = dead;%每轮死亡节点数
    STATISTICS.alive(r+1) = n-dead;%每轮存活节点数
    STATISTICS.energy(r+1) = energy;%每轮剩余总能量
    
    %运用kmeans算法
    k = n-dead;%质心个数(存活的节点)
    C_number = round(sqrt(k/(2*pi))*2/0.765);%聚类数,该公式来源于SEP协议中的最佳簇数
    if C_number > 0
        [idx,center] = kmeans(X,C_number);%center返回聚类中心坐标,idx返回对应聚类号
        [class_number,~] = size(center);%最终的聚类数
    else
        idx = [];
        center = [];
        class_number = 0;
    end
    
    j = 0;
    %给节点标记聚类号
    for i = 1:n
        if S(i).cond == 1
            j = j+1;
            S(i).class = idx(j);
        end
    end
    if class_number >= 1
    %选簇头
        for k = 1:class_number
            min_dist = 1e6;%最小距离
            for i = 1:n
                if S(i).cond == k
                    dist = sqrt( (S(i).xd - center(k,1))^2 + (S(i).yd - center(k,2))^2 );
                    if dist<min_dist
                        min_dist = dist;
                        id = i;
                    end
                end
            end
            S(id).type = 'C';%簇头标记
            C(k).xd = S(id).xd;
            C(k).yd = S(id).yd;
            C(k).id = id;
            distance=sqrt( (S(id).xd-(S(n+1).xd))^2 + (S(id).yd-(S(n+1).yd))^2 );%距离基站的距离
            C(k).distance = distance;
            %簇头向基站发送数据包能耗
            if (distance > do)
                S(id).E = S(id).E- ( (ETX+EDA)*(packetLength) + Emp * packetLength*distance^4);
            else
                S(id).E = S(id).E- ( (ETX+EDA)*(packetLength) + Efs * packetLength*distance^2); 
            end
        end        
        STATISTICS.COUNTCHS(r+1) = class_number;
        packets_TO_BS = packets_TO_BS + class_number;
        if energy > 0%网络还剩余能量,这一步在节点都死亡后才生效
            for k = 1:class_number
                for i = 1:n
                    if ( S(i).class == k && S(i).type=='N' && S(i).E>0) %普通节点
                        dist = sqrt( (S(i).xd - C(k).xd)^2 + (S(i).yd - C(k).yd)^2 );
                        %向簇头发送数据包
                        if (dist > do)
                            S(i).E = S(i).E - ( ETX*packetLength + Emp*packetLength*dist^4); %向簇发送头数据包
                        else
                            S(i).E = S(i).E - ( ETX*packetLength + Efs*packetLength*dist^2); %向簇头发送数据包
                        end
                        %簇头接收簇成员的数据包
                        if(dist > 0)
                            S(C(k).id).E = S(C(k).id).E - ( (ERX + EDA)*packetLength ); %接受簇成员发来的数据包
                        end
                        packets_TO_CH = packets_TO_CH + 1;
                    end
                end
            end
        end
        STATISTICS.PACKETS_TO_CH(r+1) = packets_TO_CH;
        STATISTICS.PACKETS_TO_BS(r+1) = packets_TO_BS;
    end
end

%% 3、网络运行模块(Leach)
% 死亡节点数指标
first_dead1 = 0;%第一个死亡节点
teenth_dead1 = 0;%10%的死亡节点
all_dead1 = 0;%节点都死亡

%节点死亡数指标对应标记
flag_first_dead1 = 0;
flag_teenth_dead1 = 0;
flag_all_dead1 = 0;

%传输到基站和簇头的比特计数器
packets_TO_BS1 = 0;
packets_TO_CH1 = 0;

%主循环
for r = 0:rmax 
    %每过一个周期,重置G
    if(mod(r, round(1/p)) == 0)
        for i = 1:n
            N(i).G = 0;
        end
    end
    dead1 = 0;%计算死亡节点数
    energy1 = 0;%每轮节点剩余总能量
    for i = 1:n
        %检查有无死亡节点
        if N(i).E <= 0
            dead1 = dead1+1;
            %第一个节点死亡时间
            if dead1 == 1
                if flag_first_dead1 == 0
                    first_dead1 = r+1;
                    flag_first_dead1 = 1;
                end
            end
            % 10%的节点死亡时间
            if dead1 == 0.1*n
                if flag_teenth_dead1 ==0
                    teenth_dead1 = r+1;
                    flag_teenth_dead1 = 1;
                end
            end
            if dead1 == n
                if flag_all_dead1 == 0
                    all_dead1 = r+1;
                    flag_all_dead1 = 1;
                end
            end
        end
        if N(i).E > 0
            energy1 = energy1 + N(i).E;%统计每轮节点总能量和
            N(i).type = 'N';
        end
    end

    %节点死亡,退出循环,但是一般都是与其他算法比较,由于不同算法最终循环伦次不同
    %所以节点死亡并不退出循环,而是在达到最大循环次数后才结束循环
%     if flag_all_dead == 1
%         break;
%     end
    %r从0开始,但是计算机中数组从1开始,所以采用r+1的下标
    STATISTICS1.DEAD(r+1) = dead1;%每轮死亡节点数
    STATISTICS1.alive(r+1) = n-dead1;%每轮存活节点数
    STATISTICS1.energy(r+1) = energy1;%每轮剩余总能量
    cluster=0;%计数簇头数
    
    %次循环(选簇头)
    if energy1 > 0%网络还剩余能量,这一步在节点都死亡后才生效
        for i=1:n
            if(N(i).E>0)%节点必须有能量
                temp_rand=rand;     
                if ( (N(i).G)<=0) %该节点在候选集合中
                    %选簇头
                    if( temp_rand <= (p/(1-p*mod(r,round(1/p)))))
                        cluster = cluster+1;
                        N(i).type = 'C'; %簇头
                        N(i).G = round(1/p)-1;%换成"S(i).G = 1"也行,只要不是0
                        C(cluster).xd = N(i).xd;
                        C(cluster).yd = N(i).yd;
                        distance=sqrt( (N(i).xd-(N(n+1).xd))^2 + (N(i).yd-(N(n+1).yd))^2 );%距离基站的距离
                        C(cluster).distance = distance;
                        C(cluster).id = i;
                        %簇头向基站发送数据包能耗
                        if (distance > do)
                            N(i).E = N(i).E- ( (ETX+EDA)*(packetLength) + Emp * packetLength*distance^4);
                        else
                            N(i).E = N(i).E- ( (ETX+EDA)*(packetLength) + Efs * packetLength*distance^2); 
                        end
                        packets_TO_BS1 = packets_TO_BS1+1;
                    end     
                end
            end 
        end
    end
    STATISTICS1.COUNTCHS(r+1) = cluster;

    %次循环(成簇)
    if energy1 > 0%网络还剩余能量,这一步在节点都死亡后才生效
        for i = 1:n
            if ( N(i).type=='N' && N(i).E>0 ) %普通节点
                %如果有簇头存在
                if(cluster>= 1)
                    length = zeros(cluster,1);
                    %加入最近的簇头
                    for c = 1:cluster
                        length(c) = sqrt( (N(i).xd - C(c).xd)^2 + (N(i).yd - C(c).yd)^2 );
                    end
                    [min_dis, min_dis_cluster] = min(length);%min_dis返回最短距离,min_dis_cluster返回最短距离对应的簇号
                    %向簇头发送加入消息、向簇头发送数据包
                    if (min_dis > do)
                        N(i).E = N(i).E - ( ETX*packetLength + Emp*packetLength*min_dis^4); %向簇发送头数据包
                    else
                        N(i).E = N(i).E - ( ETX*packetLength + Efs*packetLength*min_dis^2); %向簇头发送数据包
                    end
                    %簇头接收簇成员的数据包
                    if(min_dis > 0)
                        N(C(min_dis_cluster).id).E = N(C(min_dis_cluster).id).E - ( (ERX + EDA)*packetLength ); %接受簇成员发来的数据包
                    end
                    N(i).min_dis = min_dis;
                    N(i).min_dis_cluster = min_dis_cluster;
                    packets_TO_CH1 = packets_TO_CH1 + 1;
                %如果本轮没有簇头,则普通节点直接将数据直接发送给基站
                else
                    min_dis = sqrt((N(i).xd-N(n+1).xd)^2 + (N(i).yd-N(n+1).yd)^2);
                    if min_dis > do
                        N(i).E = N(i).E- (ETX*packetLength + Emp*packetLength*min_dis^4);
                    else
                        N(i).E = N(i).E- (ETX*packetLength + Efs*packetLength*min_dis^2);
                    end
                    packets_TO_BS1 = packets_TO_BS1+1;
                end
            end
        end
    end
    STATISTICS1.PACKETS_TO_CH(r+1) = packets_TO_CH1;
    STATISTICS1.PACKETS_TO_BS(r+1) = packets_TO_BS1;
end


%% 绘图显示
figure;
plot(0:rmax, STATISTICS.DEAD, 'r',0:rmax, STATISTICS1.DEAD, 'g', 'LineWidth', 2);
xlabel('轮数'); ylabel('死亡节点数');
legend('Based on Kmeans','Leach')
figure;
plot(0:rmax, STATISTICS.alive, 'r',0:rmax, STATISTICS1.alive, 'g', 'LineWidth', 2);
xlabel('轮数'); ylabel('活动节点数');
legend('Based on Kmeans','Leach')
figure;
plot(0:rmax, STATISTICS.energy, 'r',0:rmax, STATISTICS1.energy, 'g', 'LineWidth', 2);
xlabel('轮数'); ylabel('剩余能量');
legend('Based on Kmeans','Leach')
% 网络在没有能量后,循环还在进行,代码优化了这一点,导致部分数据在网络死亡后的轮数不更新了,所以维度不是0:rmax
% 用numel函数计算了实际数组长度,所以下面的做图代码不同于上面的代码
figure;
plot(0:numel(STATISTICS.COUNTCHS)-1, STATISTICS.COUNTCHS, 'r',0:numel(STATISTICS.COUNTCHS)-1, STATISTICS1.COUNTCHS(1:numel(STATISTICS.COUNTCHS)), 'g', 'LineWidth', 2);
xlabel('轮数'); ylabel('簇头数');
legend('Based on Kmeans','Leach')

四、运行截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

御息无

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

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

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

打赏作者

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

抵扣说明:

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

余额充值