关于排序算法的一点知识——实例和伪代码

本文详细介绍七种经典排序算法,包括插入排序、冒泡排序、快速排序等,涵盖算法原理、伪代码及性能分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 插入排序

       将待排序的数据分成两个区域:有序区和无序区,每次将一个无序区中的数据按其大小插入到有序区中的适当位置,直到所有无序区中的数据都插入完成为止。

      设待排序列为:12,8,10,14,6,2,排序过程如下图:

     

          伪代码如下:

procedure insertsort(n:integer);
  var i,j:integer;
  begin
    for i:=2 to n do
    begin
      a[0]:=a[i];
      j:=i-1;
      while a[j]>a[0] do                {决定运算次数和移动次数}
      begin
        a[j+1]:=a[j];
        j:=j-1;
      end;
      a[j+1]:=a[0];
    end;
end;

       算法分析:   

    (1)稳定性:稳定

    (2)时间复杂度:

             ①原始数据正序,总比较次数:n-1

             ②原始数据逆序,总比较次数:

             ③原始数据无序,第i趟平均比较次数=,总次数为:

             ④可见,原始数据越趋向正序,比较次数和移动次数越少。

     (3)空间复杂度:仅需一个单元A[O]

2. 冒泡排序

       最多进行n-1趟排序,每趟排序时,从底部向上扫描,一旦发现两个相邻的元素不符合规则,则交换。我们发现,第一趟排序后,最小值在A[1],第二趟排序后,较小值在A[2],第n-1趟排序完成后,所有元素完全按顺序排列。

       待排序数据:53  33  19  53  3  63  82  20  19  39

       第一趟排序:3  53  33  19  53  19  63  82  20  39

       第二趟排序:3  19  53  33  19  53  20  63  82  39

       第三趟排序:3  19  19  53  33  20  53  39  63  82

       第四趟排序:3  19  19  20  53  33  39  53  63  82

       第五趟排序:没有反序数据,排序结束。

       注意:也可以从上往下扫描,第一趟后,最大值在A[10],次大的值在A[9]。

       伪代码:     

 procedure Bubble(n:integer);;
   var temp,i,j:integer;
   change:boolean;
   begin
      for i:=1 to n-1 do
      begin
        change:=false;
        for j:=n-1 downto i do
          if a[j]>a[j+1] then
          begin
            change:=true;
            temp:=a[j]; a[j]:=a[j+1]; a[j+1]:=temp;
          end;
          if not(change) then exit;
      end;
   end;

      算法分析:     

    (1)稳定性:稳定

    (2)时间复杂度:

          ①原始数据正序,需一趟排序,比较次数n-1=O(n)

          ②原始数据反序,需n-1趟排序,比较次数

          ③一般情况下,虽然不一定需要n-1趟排序,但由于每次数据位置的改变需要3次移动操作,因此,总时间复杂度高于直接插入排序。

    (3)空间复杂度:仅需一个中间变量

  (4)改进方法:每趟排序时,记住最后一次发生交换的位置,则该位置之前的记录均已有序。

3. 快速排序  

    在A[1..n]中任取一个数据元素作为比较的“基准”(不妨记为X),将数据区划分为左右两个部分:A[1..i-1]A[i+1..n],且A[1..i-1]≤XA[i+1..n](1≤i≤n),当A[1..i-1]A[i+1..n]非空时,分别对它们进行上述的划分过程,直至所有数据元素均已排序为止。  

   示例如下:待排序数据:  67  67  14  52  29   9  90  54  87  71

                           X=67          i                                                     j

                           扫描j          i                                        j  

                           交换          54  67  14  52  29   9  90  67  87  71

                           扫描i                                              i     j  

                           交换          54  67  14  52  29   9  67  90  87  71

                           j=i,结束                                      ij   

             第一趟排序后:54  67  14  52  29  9  [67]  90  87  71

             第二趟排序后: 9  29  14  52  [54]  67 [67]  71  87  [90]

             第三趟排序后:[9]  29  14  52  [54  67  67  71] 87 [90]

             第四趟排序后:[9]  14 [29] 52  [54  67  67  71  87  90]

             第五趟排序后:[9  14  29  52  54  67  67  71  87  90]

   伪代码:   

  

 procedure quicksort(low,high:integer);
      var temp,x,i,j:integer;
      begin
         if low<high then
            begin
                 x:=a[low]; i:=low; j:=high;
                 while i<j do
                     begin
                         while (j>i) and (a[j]>=x) do j:=j-1; {j左移}
                         temp:=a[i]; a[i]:=a[j]; a[j]:=temp;
                         i:=i-1;
                         while (j>i) and (a[i]<=x) do i:=i+1;  {i右移}
                         temp:=a[i]; a[i]:=a[j]; a[j]:=temp;
                         j:=j-1;
                      end;
                 quicksort(low,i-1);
                 quicksort(i+1,high);
             end;
       end;

    算法分析: 

   (1)稳定性:不稳定

   (2)时间复杂度:每趟排序所需的比较次数为待排序区间的长度-1,排序趟数越多,占用时间越多。

      ①最坏情况:每次划分选取的基准恰好都是当前序列中的最小(或最大)值,划分的结果A[low..i-1]为空区间 或A[i+1..high]是空区间,且非空区间长度达到最大值。这种情况下,必须进行n-1快速排序,第i趟区间长度为n-i+1,总的比较次数达到最大值:n(n-1)/2=O(n2)

      ②最好情况:每次划分所取的基准都是当前序列中的“中值”,划分后的两个新区间长度大致相等。共需lgn趟快速排序,总的关键字比较次数:O(nlgn)

      ③基准的选择决定了算法性能。经常采用选取lowhigh之间一个随机位置作为基准的方式改善性能。

  (3)空间复杂度:快速排序在系统内部需要一个栈来实现递归,最坏情况下为O(n),最佳情况下为O(lgn)

4.希尔排序

   任取一个小于n的整数S1作为增量,把所有元素分成S1个组。所有间距为S1的元素放在同一个组中。

   第一组:{A[1]A[S1+1]A[2*S1+1],……}

   第二组:{A[2]A[S1+2]A[2*S1+2],……}

   第三组:{A[3]A[S1+3]A[2*S1+3],……}

   ……

   第s1组:{A[S1]A[2*S1]A[3*S1],……}

   先在各组内进行直接插人排序;然后,取第二个增量S2(<S1)重复上述的分组和排序,直至所取的增量St=1(St<St-1<St-2<<S2<S1),即所有记录放在同一组中进行直接插入排序为止。

   示例:

         

   伪代码:

 procedure shell(n:integer);
       var s,k,i,j:integer;
       begin
          s:=n;
          repeat
              s:=round(s/2);                     {设置增量S递减}
              for k:=1 to s do                   {对S组数据分别排序}
                  begin
                        i:=k+s;              {第二个待排序数据}
                        while i<=n do        {当i>n时,本组数据排序结束}
                          begin
                               a[0]:=a[i];
                               j:=i-s;           {约定步长为S}
                               while (a[j]>a[0]) and (j>=k) do
                                     begin
                                          a[j+s]:=a[j];
                                          j:=j-s;
                                     end;
                               a[j+s]:=a[0];
                               i:=i+s;
                          end;
                    end;
           until s=1;
      end; 

    算法分析

   (1)稳定性:不稳定

   (2)时间复杂度:

      ① Shell排序的执行时间依赖于增量序列。如实例所示,选择增量系列为5-2-1的比较次数

      ② 在直接插入排序中,数据越趋向于有序,比较和移动次数越少,Shell排序的目的则是增加这种有序趋势。虽然看起来重复次数较多,需要多次选择增量,但开始时,增量较大,分组较多,但由于各组的数据个数少,则比较次数累计值也小,当增量趋向1时,组内数据增多,而所有数据已经基本接近有序状态,因此,Shell时间性能优于直接插入排序。

   (3)空间复杂度:仅需一个单元A[O]

5. 直接选择排序

      选择排序的基本思想是:每一趟从待排序的数据中选出最小元素,顺序放在已排好序的数据最后,直到全部数据排序完毕。

      示例如下图:

     

       伪代码: 

 procedure select(n:integer);
             var temp,k,i,j:integer;
             begin
                    for i:=1 to n-1 do
                          begin
                                 k:=i;
                                 for j:=i+1 to n do
                                        if a[j]<a[k] then k:=j;
                                        if k<>i then
                                              begin
                                                    a[0]:=a[i]; a[i]:=a[k]; a[k]:=a[0];
                                               end;
                             end;
                end;  

     算法分析

  (1)稳定性:不稳定

  (2)时间复杂度:O(n2)

  (3)空间复杂度:仅需一个中间单元A[0]

6. 堆排序

        堆排序是一种树形选择排序,在排序过程中,将A[1..n]看成是完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。根据堆中根节点的关键字大小,分为大根堆和小根堆。

               

        主要是有个步骤:(1)调整堆;(2)选择、交换、调整。

        例子:

                                

                               (1.1) 原始数组                                   (1.2)原始堆

                    

                                (2.1)交换                                (2.2)调整后的堆

                         

                              (3.1)交换后                              (3.2)当前堆

                  

                            (4.1)交换                                (4.2)调整后的堆

                 

                        (5.1)交换后                                (5.2)当前堆

                   

                           (6.1)交换                               (6.2)调整后的堆

                       

                         (7.1)交换后                                  (7.2)当前堆

                       

                              (8.1)交换                            (8.2)调整后的堆

                      

                              (9.1)交换后                             (9.2)当前堆

                            

                               (10.1)交换                           (10.2)调整后的堆

              

                      (11.1)交换后,得到结果

       伪代码:

       

 procedure editheap(i:integer; s:integer); {堆的调整}
                var j:integer;
               begin
                       if 2*i<=s then                  {如果该结点为叶子,则不再继续调整}
                             begin
                                    a[0]:=a[i]; j:=i;
                                    if a[2*i]>a[0] then begin a[0]:=a[2*i]; j:=2*i; end;
                                    if (2*i+1<=s) and (a[2*i+1]>a[0]) then
                                             begin a[0]:=a[2*i+1]; j:=2*i+1; end; {获取最大值}
                                    if j<>i then                  
                                           begin
                                                   a[j]:=a[i]; a[i]:=a[0]; {更新子树结点}
                                                   editheap(j,s);        {调整左右孩子}
                                           end;
                            end;
            end;
 procedure buildheap;         {堆的建立}
            var i,j:integer;
             begin
                     for i:=n div 2 downto 1 do          {从后往前,依次调整}
                             begin
                                       a[0]:=a[i]; j:=i;
                                        if a[2*i]>a[0] then begin a[0]:=a[2*i]; j:=2*i; end;
                                         if (2*i+1<=n) and (a[2*i+1]>a[0]) then
                                                begin a[0]:=a[2*i+1]; j:=2*i+1; end;
                                          if j<>i then
                                                 begin
                                                         a[j]:=a[i]; a[i]:=a[0];
                                                          editheap(j,n);
                                                  end;
                               end;
               end;
 procedure heapsort(n:integer);          {堆排序}
                var k,i,j:integer;
                          begin
                                 buildheap;
                                  for i:=n downto 2 do
                                          begin
                                                   a[0]:=a[i];  a[i]:=a[1]; a[1]:=a[0];
                                                    editheap(1,i-1);
                                           end;
                            end;

        算法分析:   

      (1)稳定性:不稳定。

      (2)时间复杂度:堆排序的时间,主要由建立初始堆和反复调整堆这两部分的时间开销构成,它们均是通过调用editheap函数实现的。堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

      (3)空间复杂度:O(1)

7. 归并排序  

        归并是指将若干个已排序的子文件合并成一个有序的文件。这里只讲二路归并,即两两合并。

        设有两个有序(升序)序列存储在同一数组中相邻的位置上,不妨设为A[l..m]A[m+1..h]将它们归并为一个有序数列,并存储在A[l..h]为了减少数据移动次数,不妨采用一个临时工作数组C,将中间排序结果暂时保存在C数组中,等归并结束后,再将C数组值复制给A

    第1趟归并排序时,将数列A[1..n]看作是n个长度为1的有序序列,将这些序列两两归并,若n为偶数,则得到[n/2]个长度为2的有序序列;若n为奇数,则最后一个子序列不参与归并。第2趟归并则是将第1趟归并所得到的有序序列两两归并。如此反复,直到最后得到一个长度为n的有序文件为止。      

      伪代码:   

      

 procedure merge(l,m,h:integer);
  var p1,p2,p3,j:integer;
  begin
  if m<n then
  begin 
     if h>n then h:=n;
        p1:=l; p2:=m+1;
        p3:=l;
        while (p1<=m) and (p2<=h) do
        begin
          if a[p1]<=a[p2] then
          begin
            c[p3]:=a[p1]; p1:=p1+1;
          end
          else begin
            c[p3]:=a[p2]; p2:=p2+1;
          end;
          p3:=p3+1;
       end;
       if p1>m then
          for j:=p2 to h do begin c[p3]:=a[j]; p3:=p3+1; 
          end;
       if p2>h then
          for j:=p1 to m do begin c[p3]:=a[j]; p3:=p3+1;
           end;
       for j:=l to h do a[j]:=c[j];
       end;
 end;
procedure mergesort;
  var i,s,k:integer;
  begin
     s:=1;
     while s<n do</span>
     begin
         i:=1;
         repeat
          merge(s*(i-1)+1,s*i,s*(i+1));
          i:=i+2;
         until i>n;
         s:=s*2;
    end;
end;

     算法分析:

  (1)稳定性:稳定。

  (2)存储结构要求:用顺序存储结构,也易于在链表上实现。

  (3时间复杂度:长度为n数列,需进行[log2n]趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlog2n)

  (4)空间复杂度:需要一个辅助数组来暂存两有序序列归并的结果,故其辅助空间复杂度为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值