排序复习总结

1 插入排序

直接插入排序

是一种最简单的排序方法,它的基本操作是将一个记录插入到一个有序表的合适位置

得到一个新的有序的、记录数增1的有序表。

代码如下:

Void insertsort(int L[],int &M)    //M是比L长度多1的同类型数组

{

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

{

   M[i+1]=L[i];

}

For(int i=2;i<=length;i++)

{

   If(M[i]<M[i-1])

{

   M[0]=M[i];                 //复制为哨兵

   M[i]=M[i-1];

}

For(int j=i-2;M[0]<M[j];j--)

{

   M[j+1]=M[j];

}

//当退出上个循环时1是说明j=0,2是说明第j个记录是小于等于M[0]

M[j+1]=M[0];

}

}

     注:哨兵的作用:用来保存待插入的数;防止数组下标出界,省去了每次循环判断是否出界,降低了时间复杂度。

     稳定性分析:

由上述代码可以看出当遇到关键字相等时是退出了循环,得到了插入位置j+1,即如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

时间复杂度分析:

原始序列是正序时,则整个排序过程只需要进行关键字的比较n-1次,不需要移动位置

原始序列为逆序时,则比较次数为n-1+n*(n-1)/2=(n+2)(n-1)/2,移动次数:3+4+5+…+n+1=(n-4)(n+1)/2.

 

希尔排序

基本思想:先将整个序列分割成若干子序列分别进行直接插入排序,重复若干次,待整个序列基本有序时再对全体序列进行一次插入排序。

其中子序列的构成不是简单的逐段分割,而是将相隔某个增量的记录组成一个子序列,如51 ,22,35,46,89,26,75,第一趟:以增量4进行分割子序列(51,46),(22,89),(35,26),(75)。第二趟以增量3进行分割…。由于前两趟插入比较中同一子序列中关键字较小的记录不是一步一步的往前移动,而是跳跃式的移动,重复若干次后序列已基本有序,从而在最后一次增量为1的插入时可以移动较少就完成排序,因此希尔排序的时间复杂度比直接插入排序时间复杂度要低。

但需注意应使增量序列中的值没有除1以外的公因子,并且最后一次插入排序的增量为1。

稳定性分析:相等的关键字的顺序可能发生变化的,所以希尔排序是不稳定的

2.交换排序                                          

冒泡排序

判别冒泡排序结束的条件时:在一趟排序中没有进行过交换记录的操作,就意味这记录已是有序的,可以通过立个flag标志是否有交换记录。

代码如下:

Int sort(int &L)

{

  for( int i=0;i<n-1;i++)       //共n-1次循环排序

{

   For(int j=0;j<n-i-1;j++)   //第i次循环进行n-i次比较

   {

     If(L[j]>L[j+1])

      {

         Int temp=L[j+1];

         L[j+1]=L[j];

         L[j]=temp;

      }

     }

  }

}

稳定性分析:相等关键字的前后顺序不会发生变化,所以冒泡排序是稳定的

快速排序

快排是对冒泡排序的改进,基本思想:通过一趟排序将待排记录分割成两个独立的部分,其中一部分记录的关键字均比另外一部分记录的关键字小,分别对这两部分再进行快排,以达到整体有序。

一趟快速排序具体过程:

1.首先选取一个记录作为枢轴,通常选取第一个记录,附设两个指针Low和High,分别指向第一个元素和最后一个元素。

2.然后从High所指的位置向下搜索找到第一个小于枢轴元素的记录,将其与枢轴记录交换(此时Low指向枢轴记录),继续从Low所指的位置向上搜索找到第一个大于枢轴元素的记录,将其与枢轴记录交换(此时High指向枢轴记录)。这种操作实质上是枢轴的交换,Low和High总有一个指针指向枢轴位置,直到Low=High退出循环,得到枢轴位置,

3.重复2过程,直到Low=High。

至此一趟快速排序完成。

参考代码:

Int quicksort_one(int &L,int low,int high)

{

  Int pivot=L[low];

  While(low<high)

  {

    While(low<high&&L[high]>=pivot)   high--;

       Int temp=L[high];

       L[high]=L[low];

       L[low]=temp;

    While(low<high&&L[low]<=pivot)   low++;

       temp=L[high];

       L[high]=L[low];

       L[low]=temp;

}

Return low;

}

递归实现整个排序:

Void quicksort(int &L,int low,int high)

{

   If(low<high)

{

  Int pivot=quicksort_one(L,low,high);

  Quicksort(L,pivot+1,high);   //对高子表递归排序

  Quicksort(L,low,pivot+1);   //对低子表递归排序

}

}

稳定性分析:两个相等的关键字快排之后的位置前后可能会发生变化的,所以快排方法是不稳定的

均时间复杂度:nlogn

 

3 选择排序

简单选择排序

基本思想:在第i趟选出最大或最小的记录作为表中第i个记录。

时间复杂度:O(n^2)

稳定性分析:选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

推排序

由堆的含义可知完全二叉树中所有非终端结点的值均不大于或不小于其左右孩子节点的值,由此可得堆序列中堆顶元素(完全二叉树的根)必是序列中最大或最小的。

若在输出堆顶的最小值或最大值后,使得剩余n-1个元素的序列重新建成一个新堆,输出整个序列中的次小值或次大值,如此反复执行,得到一个有序序列,这个过程就是堆排序。

两个问题:

  1. 从无序堆到有序堆
  2. 输出堆顶元素后重新调整为新堆。

堆要求完全二叉树中所有非终端结点的值均不大于或不小于其左右孩子节点的值,所以只要从最后一个非终端节点到根结点依次递减的调整即可,完全二叉树的最后一个非终端节点是序列的第n/2(取整)个元素。

代码:

for (int i =length/2; i >0; i--)

{

    oneway_sdjust(arr, i, length);

}

for (int i = length; i >1; i--)

{

    int temp = arr[1];

    arr[1] = arr[i];

    arr[i] = temp;                //输出最大元素并放在最后一个位置上

oneway_sdjust(arr,1,i-1);     //将输出后的堆调整为新堆,因为左右均为堆,所以只需要对第一个位置进行调整即可

}

 

void  oneway_sdjust(int *arr, int n, int length//从无序序列建推序列是从下往上

{

    int temp=arr[n];

    for (int i = 2*n; i <= length; i=2*i)

    {

        if (i < length && arr[i] < arr[i+1])  i++;

        if (arr[n] >= arr[i])  break;

        arr[n] = arr[i];

        arr[i] = temp;

        n = i;

        temp =arr[n];

    }

}

 

堆排序不是稳定的排序算法。

平均时间复杂度:nlogn

猜你喜欢

转载自blog.csdn.net/qq_23913079/article/details/81132770