Siemens SCL 编程之排序算法(一)
Chen_TS
2020年12月04日 11:25

说明:以下程序的运行和编译都是基于Siemens Totally Integrated Automation Portal V16.0。

冒泡排序算法及其优化

冒泡排序原理:

        该算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同沸腾的气泡最终会上浮到顶端一样,故名“冒泡排序”。若文件的初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数C和记录移动次数M均达到最小值。Cmin = N-1, Mmin = 0.

       如果数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值。

次数比较:Cmax = N(N-1)/2 = O(N2)

移动(交换)次数:Mmax = 3N(N-1)/2 = O(N2)

举例:我们比较完全反序的一组序列:a[]={5,4,3,2,1}

第①趟需要比较4次。

1)      a[0]和a[1]比较,5>4,交换顺序。序列变为:{4,5,3,2,1}

2)      a[1]和a[2]比较,5>3,交换顺序。序列变为:{4,3,5,2,1}

3)      a[2]和a[3]比较,5>2,交换顺序。序列变为:{4,3,2,5,1}

4)      a[3]和a[4]比较,5>1,交换顺序。序列变为:{4,3,2,1,5}

经第趟比较完后,我们发现,该序列中最大的元素依次被移到右边,也就是依次 “下沉”,等到比较第二趟的时候,最后一个元素不再需要进行比较,因此第二趟比较次数会比上一趟少一次,变为3次。

第②趟需要比较3次。

1)      a[0]和a[1]比较,4>3,交换顺序。序列变为:{3,4,2,1,5}

2)      a[1]和a[2]比较,4>2,交换顺序。序列变为:{3,2,4,1,5}

3)      a[2]和a[3]比较,4>1,交换顺序。序列变为:{3,2,1,4,5}

经第②趟比较完后,我们发现,该序列中次最大的元素依次被移到右边,也就是依次 “下沉”,等到比较第三趟的时候,最后二个元素不再需要进行比较,因此第三趟比较次数会比上一趟少一次,变为2次。

第③趟需要比较2次。

1)      a[0]和a[1]比较,3>2,交换顺序。序列变为:{2,3,1,4,5}

2)      a[1]和a[2]比较,3>1,交换顺序。序列变为:{2,1,3,4,5}

第④趟需要比较1次。

1)      a[0]和a[1]比较,2>1,交换顺序。序列变为:{1,2,3,4,5}

最终该无序序列变为我们预期的升序序列,因为该序列完全反序,因此比较次数和移动次数均达到了最大。

比较次数:4+3+2+1=10.

移动(交换次数):3 × (4+3+2+1)=30.

程序示例:外层循环变量控制趟数,内层循环变量控制次数。

冒泡排序之变量定义

(****************************Bubble Sort ****************************)

 data:2020-11-10

 program function:Bubble Sort

//*****************************************************************//

//caculate array lower limit and upper limit,and array length size

#stat_First := LOWER_BOUND(ARR := #array, DIM := #​Array_dimension);

#stat_Last := UPPER_BOUND(ARR := #​array, DIM := #Array_dimension);

#stat_ArrayLength := #​stat_Last - #stat_First + 1;

IF #stat_ArrayLength < 2 THEN

    #error := true;

    #status := #​ArrayElement_Low;

END_IF;

REGION Ascending

    IF (NOT #SortDirection) AND (#​stat_ArrayLength >= 2) THEN          FOR #i := 0  TO  #​stat_ArrayLength - 2 DO    

  FOR #j:=#​stat_First  TO  #stat_Last-1-#​i DO                  ;

                IF (#array[#​j] > #array[#​j + 1]) THEN

                    #stat_exchange := #​array[#j];

                    #array[#​j] := #array[#​j + 1];

                    #array[#​j + 1] := #stat_exchange;

                END_IF;

            END_FOR;

        END_FOR;

    END_IF;

END_REGION

REGION Descending            //降序,从大到小

    IF #SortDirection AND ((#​stat_ArrayLength >= 2))THEN

        FOR #i := 0 TO (#​stat_ArrayLength-2) DO

            FOR #j := #​stat_First TO (#stat_Last -1 - #​i)  DO

                IF (#array[#​j] < #array[#​j + 1]) THEN

                    #stat_exchange := #​array[#j];

                    #array[#​j] := #array[#​j + 1];

                    #array[#​j + 1] := #stat_exchange;

                END_IF;

            END_FOR;

        END_FOR;

    END_IF;

END_REGION

运行结果示例:

主程序中调用

排序结果(升序)

优化(一)

对于一组已经有顺序排列的数组或者序列,经第一趟比较完之后,如果没有发生任何的数据交换,那么说明这组序列已经是有序的,不需要在进行第二趟、第三趟...,第n趟的扫描比较。假设我们现在排序a[]={1,2,3,4,5,6,7,8,10,9}这组数据,按照上面的排序方式,第一趟排序后将10和9交换已经有序,接下来的8趟排序就是多余的,什么也没做。所以我们可以在交换的地方插个旗子(立flag),如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续扫描比较下去。先输出一遍未优化的程序,具体排序过程如下(为了更加直观比较,在VC++环境下测试):

比较次数和趟数(轮数)

接着再输出一遍经过优化(一)的结果:

优化(一)输出结果

#include<iostream>

using namespace std;

int main()

{

    int i,j,k,temp,flag,count=0;

int num[]={9,0,1,2,3,4,5,6,7,8};

int len = sizeof(num)/sizeof(int);

int cishu=1;

int Total_cishu=0;

cout<<"-----输出原始序列-----&#​34;<<endl;

cout<<"    &#​34;;

for(int m=0;m<len;m++)

cout<<num[m]<<",&#​34;;

    cout<<endl;

cout<<"-----输出排序过程-----&#​34;<<endl;

for(i=0;i<len;i++)

{

flag=0;   //每轮都默认没有发生交换

for(j=0;j<len-i-1;j++)

{

if(num[j]>num[j+1])

{

temp=num[j];

num[j]=num[j+1];

num[j+1]=temp;

flag=1;  //发生交换

}

  Total_cishu++;

  

    if(cishu<10)     //10之前的数为了对齐10以上的数,特意在前面输出一个0

cout<<0<<cishu++<<": &#​34;;

else

            cout<<cishu++<<": &#​34;; //10以上的数,直接输出即可

for(k=0;k<len;k++)

if(k<len-1)  //每组序列最后一个元素不带逗号

{

    cout<<num[k]<<",&#​34;;

}

else

{  

cout<<num[k];

    cout<<endl;

}

}

count++;   //记录交换轮次

if(flag==0)//当没有发生交换时就退出循环

break;

}

cout<<"比较轮数:&#​34;<<count<<endl;

  cout<<"比较次数:&#​34;<<Total_cishu<<endl;

return 0;

}

可见,经过第一种优化过的程序在进行比较的时候,次数明显减少。只进行了2轮排序,总共9+8=17次。但是我们会发现一个问题,前9次比较此序列已经有序,从第10次开始每次都是重复性的工作。因此我们希望能够一轮比较(9次)就可以得出结果。为此我们继续第二种优化(缩小轮数优化)。

优化(二)

优化(一)仅仅适用于连片有序而整体无序的数据(例如:1,2,3 ,4 ,7,6,5)。但是对于前面大部分是无序而后边小半部分有序(片段有序)的数据{1,2,5,7,4,3,6,8,9,10}排序效率也不可观,对于这种类型序列,我们可以继续优化。即我们可以记下最后一次交换的位置,后边没有交换,必然是有序的,然后下一次排序从第一个比较到上次记录的位置结束即可。

优化(三)轮次和比较次数

#include<iostream>

using namespace std;

int main()

{

int num[]={1,2,5,7,4,3,6,8,9,10};

int len = sizeof(num)/sizeof(int);

int j,k,temp,count=0;

int Total_cishu=0;

int flag=len;    //默认比较轮次为N 轮

while(flag>0)

{

k=flag;    //用k记录每一轮的比较次数

flag=0;    

for(j=1;j<k;j++)

{

if(num[j-1]>num[j])

{

temp=num[j];

num[j]=num[j-1];

num[j-1]=temp;

flag=j;   //记录符合交换条件的最终位置

Total_cishu++;

            for(int i=0;i<len;i++)

               if(i<len-1)  //每组序列最后一个元素不带逗

   {

                cout<<num[i]<<",&#​34;;

   }

               else

   {  

                cout<<num[i];

                cout<<endl;

   }

}      

}

if(flag)

count++;       //记录比较轮次

}

cout<<"比较轮数count=&#​34;<<count<<endl;

cout<<"比较次数Total_cishu=&#​34;<<Total_cishu<<endl;

return 0;

}