排序即把无序的序列排成有序

排序稳定性指待排序列有两个或两个以上的相同关键字时,他们排序前后的相对顺序不变即为稳定,否则为不稳定

排序算法分类:

1、插入类:在一个已经有序的序列中,插入一个新的关键字

  例如:直接插入排序、折半插入排序、希尔排序

2、交换类:每一趟排序,通过一系列交换动作,让一个关键字排到最终位置

  例如:冒泡排序、快速排序

3、选择类:每一趟排序选出最小或最大的关键字,把他和序列第一个或最后一个关键字交换

  例如:简单选择排序、堆排序

4、归并类:将两个或两个以上的有序序列合并成一个新的有序序列

  例如:二路归并

5、基数类:基于多关键字思想,例如把扑克牌先按花色再按大小排序

——————————————————————————————————————————

插入类排序

直接插入排序

每趟直接将待排序关键字按照值大小插入到已经排号的序列中

代码:

void InsertSort(int R[],int n)
{
  int i,j;
  int temp;
  for(i=1;i<n;i++)
  {
    temp=R[i];//待插入关键字暂存temp
    j=i-1;
    while(j>=0&&temp<R[j])//找位置
    {
      R[j+1]=R[j];
      --j;
    }
    R[j+1]=temp;//插入
  }
}

 时间复杂度为O(n^2),空间复杂度为O(1)

折半插入排序

与直接插入排序的区别在于查找插入位置的方法不同

例子:

现有序列:13,38,49,65,76,97  待插入关键字:27,49

1、low=0,high=5,m=(0+5)/2=2(向下取整),下标为2的关键字是49, 27<49,high=m-1=1

2、low=0,high=1,m=(0+1)/2=0,下标为0的关键字是13,27>13,low=m+1=1

3、low=high=1,m=(1+1)/2=1,下标为1的关键字是38,27<38 ,high=m-1=0 

  因为high<low 折半查找结束,27应该插入在high索引关键字之后 即插入后序列为 13 27 38 49 65 76 97 待插入 49

折半查找时间复杂度为O(n^2) 空间复杂度为O(1)

 

希尔排序

 又称为缩小增量排序,将待排序列分为几个子序列,分别对这几个子序列进行直接插入排序

例子:

原始序列:49 38 65 97 76 13 27 49 55 04

1、以增量5分割序列,得到以下子序列

  序列1:49      13

  序列2:     38      27

  序列3:       65      49

  序列4:           97       55

  序列5:               76        04

  对以上5个子序列分别进行直接插入排序,结果如下

  13      49

       27      38

      49            65

      55      97

       04       76

  整体为13  27  49   55  04  49  38  65  97  76

  在分别以增量3和增量1来分割再排序

  序列1:13          55    38    76

  序列2:   27      04     65

  序列3:         49     49     97

     整体为13 04 49   38   27  49  55  65  97  76

  增量为1的过程省略

希尔排序时间复杂度与增量选取有关,是不稳定的排序算法(相同关键字相对位置可能变化)

增量序列的最后一个值一定取1

增量序列的值尽量没有除1之外的公因子

希尔排序空间复杂度为O(1)

——————————————————————————————————————————

交换类排序

 起泡排序又称冒泡排序,小的关键字像气泡一样逐渐向上移动

起泡排序算法结束的条件是在一趟排序过程中没有发生关键字交换

void BubbleSort(int R[],int n)

{

  int i,j,flag=0;  

  int temp  for(i=n-1;i>=1;--i)

  {
    flag=0;//记录本次是否发生交换
    for(j=1;j<=i;++j)
    {
      if(R[j-1]>R[j])
      {
        temp=R[j];
        R[j]=R[i];
        R[i]=temp;
        flag=1;
      }
    }
    if(flag==0)
    {
      return;
    }
  }

}

冒泡排序时间复杂度为O(n^2),空间复杂度为O(1)

快速排序

每一趟为当前所有子序列各选一个关键字作为枢轴,将序列中比枢轴小的移到前边,大的移到后边。

例子:

原始序列:49 38 65 97 76 13 27 49

      i          j

第一趟快速排序,以第一个数49为枢轴,j从后向前扫描,直到遇见比枢轴小的数

49 38 65 97 76 13 27 49

i         j

将27交换到i的位置上(不用担心数值覆盖丢失问题,枢轴的值已提前保存)

27 38 65 97 76 13 空 49

i         j 

使用i 变换扫描方向,从前向后扫描 直到遇见比枢轴大的数65 i停止

27 38 65 97 76 13 空 49

   i                    j

将65交换到j的位置上

27 38 空 97 76 13 65 49

           i                   j

接着使用j继续........直到i与j重合 将枢轴的关键字放在重合位置上 第一趟快速排序完成 枢轴位置上的关键字到达最终位置

下一趟将以上一趟的枢轴为边界将序列划分为两个更小的序列

两个序列同时开始快速排序......

代码:

void QuickSort(int R[],int low,int high)
{
  int temp;
  int i=low,j=high;
  if(low<high)
  {
    temp=R[low];
    while(i<j)
    {
      while(j>i&&R[j]>=temp)
      {
        --j;//从右往左扫描 找到一个小于temp的关键字
      }
      if(i<j)
      {
        R[i]=R[j];
        ++i;
      }
      while(i<j&&R[i]>=temp)//从左往右扫描,找到一个大于temp的关键字
      {
        ++i;
      }
      if(i<j)
      {
        R[j]=R[i];
        --j;
      }
    }
    R[i]=temp;
    QuickSort(R,low,i-1);//temp左面递归处理
    QuickSort(R,i+1,high); //temp右面递归处理
  }
}

快速排序时间复杂度为O(nlogn),空间复杂度为O(logn)

——————————————————————————————————————————

选择类排序

简单选择排序:从头到尾扫描序列,找出最小的一个关键字,和第一个关键字交换

代码:

void SelectSort(int R[],int n)
{
  int i,j,k;
  int temp;
  for(int i=0;i<n;i++)
  {
    k=i;
    for(j=i+1;j<n;j++)//从无序序列中挑选出最小关键字
    {
      if(R[k]>R[j])
      {
        k=j;
      }
    }
    temp=R[i];
    R[i]=R[k];
    R[k]=temp;
  }
}

简单选择排序时间复杂度为O(n^2),空间复杂度为O(1)

堆排序

可以把堆看成一颗完全二叉树,且满足任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。

若父亲大孩子小,则这样的堆叫做大顶堆;若父亲小孩子大,则这样的堆叫做小顶堆。

执行过程例子:

原始序列:49 38 65 97 76 13 27 49

  建堆:先将序列调整为一个大顶堆

  76 13 27 49 是叶子结点 没有左右孩子满足堆的定义

  从97开始,按 97  65 38 49的顺序依次调整

  97>49满足定义不需要调整

  65>13,65>27满足定义

  38<97,38<76,不满足定义和孩子中大的交换,38和97交换,但38<49,38和49交换

  49<97,49<65,49和97交换,49<76 继续交换

此时已建立好大顶堆,序列为97 76 65 49 49 13 27 38,将堆顶关键字97与最后一个关键字38交换,第一趟排序完成。

97到达最终位置 对除97以外的序列重新调整为大顶堆即可。

 

 

 代码:

void Sift(int R[],int low,int high)
{
  int i=low,j=2*i;
  int temp=R[i];
  while(j<=high)
  {
    if(j<high&&R[j]<R[j+1]){//若右孩子较大 则把j指向右孩子
      ++j;
    }
    if(temp<R[j]){
      R[i]=R[j];//继续向下调整
      i=j;
      j=2*i;
    }
    else{
      break;
    }
  }
  R[i]=temp; //被调整结点放入最终位置
}
void heapSort(int R[],int n)
{
  int i;
  int temp;
  for(i=n/2;i>=1;--i)
  {
    Sift(R,i,n);//建立初始堆
  }
  for(i=n;i>=2;--i)
  {
    temp=R[1];//以下三句 换出根节点 放入最终位置
    R[1]=R[i];
    R[i]=temp;
    Sift(R,1,i-1); //在减少一个关键字的序列中进行调整
  }
}

堆排序时间复杂度为O(nlogn)(最坏情况下也是),空间复杂度为O(1),堆排序适用关键字很多的情况 比如从10000个关键字中找10个最小的

——————————————————————————————————————————————————————————————

二路归并排序

过程:

1、将原始序列看成n个只含有一个关键字的子序列

2、两两归并

3、归并后的有序二元组子序列继续归并

4、最后归并成一个序列

二路归并排序时间复杂度为O(nlogn)空间复杂度为O(n)

——————————————————————————————————————————————————————————

基数排序

第一种实现方式是最高位优先,先按最高位排成若干子序列,再对每个子序列按次高位排序。例如对扑克牌先按花色再按大小排序。

第二种是实现方式是最低位优先,每次排序所有关键字参与,例子如下:

原始序列: 247 109 547 024

因为数字的范围是0~9,准备十个队列存放关键字(如果是英语单词就是26个队列)

1、按最低位排序结果如下

队列4:024

队列9:109

队列7:247、547

其他队列为空

按0到9的顺序对队列进行收集 结果为024、247、547、109

2、按次低位进行排序

队列0:109

队列2:024

队列4:247、547

其他队列为空

按0到9的顺序对队列进行收集 结果为109、024、247、547

3、按首位进行排序

队列0:024

队列1:109

队列2:247

队列5:547

其他队列为空

按0到9的顺序对队列进行收集 结果为:024、109、247、547

基数排序的时间复杂度为O(d(n+rd)),空间复杂度为O(rd) 

n为序列的关键字数,d为关键字位数,rd为关键字基的个数(比如数字为10,字母为26)

————————————————————————————————————————————————————

外部排序:将内存作为工作空间来辅助外存数据的排序

 外部排序常用归并排序

 

 

重要子算法:

置换-选择算法:用于产生上边的m个有序记录段

 

 

最佳归并树:将当前m组(每组k个有序记录段)记录归并成m个有序记录段的过程称为一趟归并,为了使得归并次数少,需要使用最佳归并树

 

 

败者树:归并排序中需要从当前k个值选出最值,使用败者树可提高效率

 

 

——————————————————————————————————————————————————————————

排序小结:

1、时间复杂度

  平均情况下(快些归队),快速排序,希尔排序,归并排序、堆排序均为O(nlogn),其他均为O(n^2),基数排序为O(d(n+rd))

  最坏情况下,快速排序为O(n^2),其他与平均情况下相同

2、空间复杂度

  快速排序O(logn),归并排序O(n),基数排序O(rd),其他皆是O(1)

3、稳定性

  (快些选堆)快速排序、希尔排序、简单选择排序、堆排序都是不稳定的排序算法

4、其他

 1、经过一趟排序能够保证一个关键字到达最终位置,这样的排序是交换类的两种(冒泡、快速)和选择类的两种(简单选择、堆)

 2、排序算法的关键字比较次数和原始序列无关——简单选择排序和折半插入排序

 3、排序算法的排序趟数和原始序列有关——交换类排序