内部排序之交换排序

1.排序的分类:


根据数据存储位置的不同,排序可分为内部排序和外部排序。

若所有需要排序的数据都存放在内存中,在内存中调整数据的存储顺序,这样的排序称为内部排序;反之,若待排序记录数据量较大,排序时只有部分数据被调用内存,排序过程中存在多次内,外存之间的交换,这样的排序称为外部排序。

内部排序可以分为:交换排序,插入排序,选择排序,归并排序和基数排序排序五大类。

外部排序中最常用的算法是多路归并排序,这种算法将源文件分解成多个能够一次性装入内存的子文件,每次把一个或多个子文件调用内存完成排序,最后对已经排序的子文件进行归并排序。

排序算法的实现离不开数据交换,在学习各类算法之前,先来实现一个简单的数据交换函数:

//简单的数据交换函数
void swap(int* a,int* b)
{
  int tmp=*a;
  *a=*b;
  *b=*a;
}

2.交换排序:


交换排序的核心思想是:根据序列中两条记录键值的比较结果,判断是否需要交换记录在序列中的位置。其特点是,将键值较大或者较小的记录向序列的前端移动,将键值较小的或者较大的记录向序列的后端移动。

使用这种方法,经过n-1轮排序,即可得到一个有序序列。下面给出这种算法的代码实现:

void Sort(int* arr,int n)
{
  int i=0;
  int j=0;
  int k=0;
  for(i=0;i1;n++)
  {
    for(j=i+1;jif(arr[i]

3.冒泡排序:


经过冒泡排序后得到的序列,较大或较小的数据会“浮”到序列的顶端或底部。冒泡排序的基本原则是:比较两两相邻的记录的关键字,使不满足序列要求的记录交换位置,直到n-1轮循环操作结束。

void BubbleSort(int* arr,int n)
{
  int i=0;
  int j=0;
  for(i=0;i1;i++)
  {
    for(j=0;j1-i;j++)
    {
      if(arr[j]1])
      swap(&arr[j],&arr[j+1]);
    }
  }
}

下面分析冒泡排序算法:在该算法中使用了双层for循环,第一层循环的时间复杂度为O(n-1),第二层for循环的时间复杂度为O(n-i-1),所以整个算法的时间复杂度为(n^2).
假设序列中有两个相同的数据,对算法进行分析,其中的判断条件是arr[j]

void BubbleSort_B(int* arr,int n)
{
  int i=0;
  int j=0;
  int flag=1;//设置标志位
  for(i=0;i1&&flag;i++)//若本轮没有进入if判断语句的执行语句,标志位就是0
  {
    flag=0;
    for(j=0;j1;j++)
    {
      if(arr[j]1])
      {
       flag=1;//若本轮进入循环,则修改标志位
       swap(&arr[j],&arr[j+1]);//符合条件则进行交换
      }
    }
  }
}

对于前面给出的数组arr[10],使用这种排序算法,明显能够降低时间损耗。而在空间上只是多了一个int型的变量flag,空间复杂度仍为一个常数。

对于优化过的冒泡排序算法,最好的情况是初始化已经满足要求的排序算法,此时时间复杂度O(n),最坏的情况是初始序列刚好为要求顺序的逆序,此时时间复杂度为O(n^2),与基础冒泡排序算法时间复杂度相同。另外,优化后的冒泡排序排序不会改变两个键值相同记录的先后关系,仍是一个稳定的算法。

5.快速排序:


快速排序是对冒泡排序的改进,其基本思想是:通过一趟排序,将序列中的数据分割为两份,其中一部分的所有数值都比另外一部分的小;然后按此方法,对两部分数据分别进行快速排序,直到参与排序的两部分都有序为止。

使用快速排序算进行排序时,为了将序列划分为如上的两部分,需要在一开始设置一个参数值,通过与参数值的比较来划分数据,通常选用序列中第一个记录的键值作为参考。

void QuickSort(int* arr,int left,int right)
{
  if(left>=right)
  {
    retur;
  }
  int i=left;
  int j=right;
  int key=arr[i];//使用key来保存作为键值的数据a,将arr[i]空出来
  //本轮排序开始,当i=j时本轮排序结束,将键值赋给arr[i]
  while(iwhile((i//不符合条件,继续向前寻找
      j--;
    }
    arr[i]=arr[j];
    //从后往前找一个小于当前键值的数据arr[j],将其赋值给arr[i]
    //赋值后arr[j]相当于一个空的,待赋值的空间
    while((i=arr[i]))
    {
      //不符合条件,继续向后寻找
      i++;
    }
    //找到或i
    arr[j]=arr[i];
  }
  arr[i]=key;
  //递归调用排序函数对键值两边的子序列进行排序操作
  QuickSort(arr,left,i-1);
  QuickSort(arr,i+1,right); 
}         

以上算法的实现,其核心是一个while循环,在循环中实现下标的移动和数据的对比调整。while循环完成之后,序列被选定的参数值划分为两个子序列,然后通过递归调用函数自身对子序列进行排序。在理想的情况下,每一次序列划分出两个等长的序列,那么需要划分log2n次,此时快速排序的时间复杂度为O(nlog2n);而最坏情况下,原始序列已经基本有序,每次划分只能减少一个元素,此时快速排序退化为冒泡排序,时间复杂度为O(n^2)。因此快速排序的平均时间复杂度为O(nlog2n)。

冒泡排序的空间复杂度仅为O(1)。当然使用递归算法时,每次递归调用需要开辟一定的栈空间,这个空间的总大小为nlog2n,所以快速排序的空间复杂度实际为O(nlog2n)。因为冒泡排序存在跳跃性的位置变换,所以关键字相同的两条记录在排序之后先后次序可能发生改变,这个算法是一个不稳定的排序算法。

你可能感兴趣的:(数据结构,面试题,排序,内部排序,交换排序)