简单排序算法总结

本文深入探讨了排序算法的基础理论、实现细节及其性能对比,包括快速排序、冒泡排序、插入排序、希尔排序、直接选择排序和堆排序。详细阐述了每种排序算法的工作原理、优缺点以及在不同场景下的应用。特别强调了稳定性讨论,揭示了各种排序算法对数据顺序的影响。此外,文章还介绍了归并排序的概念和非递归算法实现,以及如何使用单链表结构进行高效排序。最后,提供了一套完整的排序算法实现代码,供读者实践与学习。

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

1.快速排序:不稳定排序,时间复杂度O(lg n)

void quicksort(int* a,int i,int j)//快排和其他排序不一样,j不是数组的大小N,而是N-//1,且因为快排是

//递归实现的,所以能像冒泡一样在函数内通过j--实现。

{

    assert(i>=0); 

if(i>=j) return ;//主要是递归的退出条件也可作为输入范围正确与否判断

int xx=i,yy=j;

   intk=a[i];

      while(xx!=yy)

   {

    while((a[yy]>=k)&&(xx<yy))

       yy--;

       a[xx]=a[yy];

      while((a[xx]<=k)&&(xx<yy))

       xx++;

       a[yy]=a[xx];

   }

   a[xx]=k;//绿色这几行不是通常理解的简单的找到比K右边比K小的和左边比K大的交换,可以自己举例试试,不要搞错了,需要仔细体会。

   quicksort(a,i,xx-1);

   quicksort(a,yy+1,j);

}

稳定性讨论:比如序列为 5 3 3 4 3 8 9 10,现在第一趟下来:中枢元素a[0]会与右边第一比它小的元素交换。就把原来元素3的顺序打乱了。所以不是稳定算法。

2.冒泡排序:稳定排序,时间复杂度:O(n^2) (最好情况O(N))

  voidbubblesort(int* a,int i,int j)

{

    assert(i>=0 &&i<=j)// 输入范围正确与否判断

    bool hasChange=false;

    j--;//不然a[y+1]会越界。

    for(int x=j;x>i;x--)

    {

        for(inty=i;y<x;y++)

       {   if(a[y]>a[y+1])

           {

               a[y]^=a[y+1]^=a[y]^=a[y+1];//y和y+1不可能是同一个变量,可放心这样交换

               hasChange=true;

           }  

       }

       If(!hasChange)//如果没有交换的,直接退出,只有里层循环循环了一次,外层循环只执行了x=j的情况。这种情况下时间复杂度//为O(N)

           return;

   }

}

上面是从左到右冒泡,也可以从右到左冒:有些公司的笔试题比较恶心,如果给你一个数组,问你最少需要排几次的话,你必须两种都要试试!!

int bubblesort(int* a,int i,int j)//返回值为需要的冒泡趟数

{
    bool hasChange=false;
    i++;//不然a[y+1]会越界。
    int cishu=0;
    for(int x=i;x<j;x++)
    {
        hasChange=false;
        for(int y=j-1;y>x;y--)
       {  
           if(a[y]<a[y-1])
            {
                a[y]^=a[y-1]^=a[y]^=a[y-1];//y和y+1不可能是同一个变量,可放心这样交换
                hasChange=true;
            } 
         
        }     
      if(!hasChange)//如果没有交换的,直接退出,只有里层循环循环了一次,外层循环只执行了x=j的情况。这种情况下时间复杂度//为O(N)
         return cishu;
        else
             cishu++;

   }

}//这是从右向左冒泡的改进版,可以返回需要的冒泡趟数

 

稳定性讨论:所有可能的交换都发生在相邻元素之间,而如果相邻元素相等我们一般不会去交换,所以相等的元素之间的相对顺序不会改变。是稳定算法。

3.插入排序:稳定算法,时间复杂度:O(N^2)(最好情况O(N))

void insertsort(int* a,int i,int j)

{  

    assert(i>=0 &&i<=j)// 输入范围正确与否判断

    int x,y;

    for(x=i;x<j;x++)

    {   

       for(y=x;y>0&&a[x]<a[y-1];y--)//如果本来数组就是排好序的,不需要//执行这个内部          循环,只需要执行外层循环,这种情况下时间复杂度为O(N)

       {   a[y]^=a[y-1]^=a[y]^=a[y-1];

       }

    }

}

稳定性讨论:同上面冒泡算法。

4.shell排序(改进版插入排序):不稳定,时间复杂度:O(N^2)

void shellsort(int*a,int i,int j)
{
    int k=j/2;
    int x=0;
    while (k>0)
    {
        for (int y=k;y<j;y++)
        {
            x=y-k;
            if (a[y]<a[x])
            {
                a[y]^=a[x]^=a[y]^=a[x];
            }
        }
        k/=2;
    }
}

5.直接选择排序:不稳定排序,时间复杂度:O(N^2)

  void selectsort(int*a,int i,int j)

{   assert(i>=0 &&i<=j)// 输入范围正确与否判断

for(intx=i;x<j;x++)

    {   int minnum=x;

       bool needChange=false;

       for(int y=x;y<j;y++)

       {   if(a[y]<a[minnum])

           {   minnum=y;

              needChange=true;

           }

       }

       if(needChange)

       a[x]^=a[minnum]^=a[x]^=a[minnum]; 

    }

}

稳定性讨论:比如序列为 5 3 3 4 3 5 2 10,现在左边第一个5会和右边的2交换,就把原来元素5的顺序打乱了。所以不是稳定算法。

6.堆排序时间复杂度O(nlgn),不稳定。
 3 (1)用大根堆排序的基本思想
 4 ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
 5 ② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,
 6 由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
 7 ③ 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
 8 然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,
 9 由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n- 2].keys≤R[n-1..n].keys,
10 同样要将R[1..n-2]调整为堆。
11 ……
12 直到无序区只有一个元素为止。
13 (2)大根堆排序算法的基本操作:
14 ① 初始化操作:将R[1..n]构造为初始堆;
15 ② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
16 注意:
17 ①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
18 ②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。
19 堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,
20 且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。 
 */
 //生成大根堆
 void HeapAdjust(int SortData[],int StartIndex, int Length)
 {
     while(2*StartIndex+1 < Length)
     {
         int MinChildrenIndex = 2*StartIndex+1 ;
         if((2*StartIndex+2 < Length )&&(SortData[2*StartIndex+1]<SortData[2*StartIndex+2]))


               MinChildrenIndex = 2*StartIndex+2//比较左子树和右子树,记录最大值的Index                                   
         if(SortData[StartIndex] < SortData[MinChildrenIndex])
         {
             //交换i与MinChildrenIndex的数据
             int tmpData =SortData[StartIndex];
             SortData[StartIndex] =SortData[MinChildrenIndex];
             SortData[MinChildrenIndex] =tmpData;
            //堆被破坏,需要重新调整
             StartIndex = MinChildrenIndex ;
         }
         else
           break;//比较左右孩子均大则堆未破坏,不再需要调整
     }
     return;
 }
 //堆排序
 void HeapSortData(int SortData[], int Length)
 
{
     int i=0
;
      //将Hr[0,Lenght-1]建成大根堆

     for (i=Length/2-1; i>=0; i--)
 
    {
 
        HeapAdjust(SortData, i, Length);
 
    }
    for (i=Length-1; i>0; i--
)
    {
        //与最后一个记录交换

        int tmpData =SortData[0];
        SortData[0=
SortData[i];
         SortData[i] =
tmpData;
         //将H.r[0..i]重新调整为大根堆

         HeapAdjust(SortData, 0, i);
    }
    return;
 }

7.1归并排序,时间复杂度是Θ(nlgn)。稳定的排序插入排序算法采取增量式(Incremental)的策略解决问题,每次添一个元素到已排序的子序列中,逐渐将整个数组排序完毕,它的时间复杂度是O(n2)。下面介绍另一种典型的排序算法--归并排序,它采取分而治之(Divide-and-Conquer)的策略,。

归并排序的步骤如下:

  1. Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。

  2. Conquer: 对这两个子序列分别采用归并排序。

  3. Combine: 将两个排序好的子序列合并成一个最终的排序序列。

在描述归并排序的步骤时又调用了归并排序本身,可见这是一个递归的过程。

#define LEN 8
int a[LEN] = { 5, 2, 4, 7, 1, 3, 2, 6 };
void merge(int start, int mid, int end)
{
     int n1 = mid - start + 1;
     int n2 = end - mid;
     int left[n1], right[n2];
     int i, j, k;
     for (i = 0; i < n1; i++) /* left holds a[start..mid] */
    left[i] = a[start+i];
    for (j = 0; j < n2; j++) /* right holds a[mid+1..end] */
    right[j] = a[mid+1+j];
    i = j = 0;
    k = start;
   while (i < n1 && j < n2)
   if (left[i] < right[j])
         a[k++] = left[i++];
   else
         a[k++] = right[j++];

      while (i < n1) /* left[] is not exhausted */
          a[k++] = left[i++];
      while (j < n2) /* right[] is not exhausted */
          a[k++] = right[j++];
}

void sort(int start, int end)
{
      int mid;
     if (start < end)

     {
         mid = (start + end) / 2;
        printf("sort (%d-%d, %d-%d) %d %d %d %d %d %d %d %d\n", start, mid, mid+1, end, a[0], a[1], a[2], a[3], a[4], [5], a[6], a[7]);
        sort(start, mid);
        sort(mid+1, end);
       merge(start, mid, end);
       printf("merge (%d-%d, %d-%d) to %d %d %d %d %d %d %d %d\n", start, mid, mid+1, end, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
     }
}

 

7.2,归并排序的非递归算法:

设单链表结构为 struct ListNode {
int data ;
ListNode * link ;
};
下面的程序是以单链表为存储结构, 实现二路归并排序的算法, 要求链表不另外占用存储空间, 排序过程中不移动结点中的元素, 只改各链结点中的指针, 排序后r仍指示结果链表的第一个结点.在初始状态下, 所有待排序记录链接在一个以r为头指针的单链表中.例如,在算法实现时,利用了一个队列做为辅助存储, 存储各有序链表构成的归并段的链头指针.初始时, 各初始归并段为只有一个结点的有序链表.队列的数据类型为Queue, 其可直接使用的相关操作有置空队列操作:makeEmpty ( );将指针x加入到队列的队尾操作:EnQueue ( ListNode * x );退出队头元素, 其值由函数返回的操作:ListNode *DlQueue ( );判队列空否的函数, 空则返回1, 不空则返回0:int IsEmpty( ). 
解决方法提示:
程序首先对待排序的单链表进行一次扫描, 将它划分为若干有序的子链表, 其表头 指针存放在一个指针队列中.当队列不空时, 从队列中退出两个有序子链表, 对它们进行二路归并, 结果链表的表头指针存放到队列中.如果队列中退出一个有序子链表后变成空队列, 则算法结束.这个有序子链表即为所求.

(1) 两路归并算法

void merge ( ListNode * ha, ListNode * hb,, ListNode *& hc )
 {
      ListNode *pa, *pb, *pc ; 
     if ( ha→data <= hb→data )
      {

            hc = ha; pa = ha→link; pb = hb;

      }
    else
      {

           hc = hb; pb = hb→link; pa = ha ;

        }
   pc = hc;
   while ( pa && pb ) 
      if (pa→data <= pb→data) 
      {

          pc→link = pa; pc = pa; 
          pa = pa→link;
      }
     else
    {  
        pc→link = pb; pc = pb;
         pb = pb→link;
    }
    if ( pa ) 
         pc→link = pa;
    else

        pc→link = pb;
};
(2) 归并排序主程序
void mergesort ( ListNode * r ) 
{
     ListNode * s, t;

     Queue Q ;
     if ( ! r )

           return; 
     s = r; 
     Q.EnQueue( r );
     while ( s )
    { 
         t=s→link;
       while(t!=0&&s→data<=t→data) 
       { 
            s=t; 
            t=t→link; 
       }//划分为若干有序的子链表

      if(t)
         { 
             s→link = 0; //有序子链表结束
             s = t;
             Q.EnQueue(s);//
         }
   }
   while (!Q.IsEmpty())
   {
        r = Q.DlQueue( );
       if ( Q.IsEmpty( ) ) break;
       s = Q.DlQueue( );
       merge( r, s, t );
       Q.EnQueue( t );
   }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值