排序算法小结

(排序类别,排序方法)
插入排序:直接插入、折半插入、希尔排序。
交换排序:冒泡排序、快速排序。
选择排序:简单选择排序、堆排序。
归并排序:
基数排序:
 冒泡排序时间复杂度O(n^2)、空间复杂度O(1)、归位性(每趟确定一个元素最终位置)

 快速排序、堆排序、归并排序:平均时间复杂度为O(nlogn)。

 下面分别回忆下冒泡排序、快速排序、堆排序、归并排序。

【冒泡排序】:(大数往下沉)

void sort(int a[], int len)
{
  int i,j;
  for(i=0;i<len-1;i++)
  {
    for(j=0;j<len-1-i;j++)
    {
      if(a[j]>a[j+1])
      {
        int tmp=a[j];
        a[j]=a[j+1];
        a[j+1]=tmp;
      }
    }
  }
}
【快速排序】:
一般选第一个元素为枢轴,根据枢轴将数组分割成两部分(前一部分关键字均比后一部分小),
每次一趟排序确定枢轴的位置。pivot的选取直接影响排序的好坏。(把pivot位置视为一个坑)

建议:可取a[0],a[(len-1)/2],a[len-1]的中值作为枢轴。

int partition(int A[],int low,int high)//low,high为数组下标
{
  int i=low,j=high;
  int pivot=a[i];  //取最左边为pivot
  while(i<j){
    while(a[j]>=pivot && i<j) j--;
    a[i]=a[j];  //找出右边第一个小于pivot的元素,即a[j],放入i下标。  
    while(a[i]<=pivot && i<j) i++;
    a[j]=a[i];  //找到左边第一个大于pivot的元素,即a[i],放入j下标。
  }
  a[i]=pivot  //此时i=j
  
  return i;
}
void quick_sort(int a[], int low, int high)
{
  if(low < high)
  {
    int i = partition(a,low,high);
    quick_sort(a,low,i-1);
    quick_sort(a,i+1,high);
  }
}
空间复杂度:递归造成的栈空间的使用。
分析:partition每次划分很均匀时,情况最优,时间复杂度为:O(nlogn);平均情况下时间复杂度也为:O(nlogn);对应空间复杂度:O(logn)
最坏情况:待排序序列已正序或逆序时,pivot取值为数组最大值或最小值,时间复杂度为:O(n^2);对应空间复杂度:O(n)

【堆排序】:
时间复杂度(平均、最好、最坏均为)nlogn,空间复杂度:O(1)
以大顶堆为例,初始的大顶堆为一个无序区,每一轮取堆顶元素放入有序区。调整新堆为大根堆。
大根堆:完全二叉树中,任意父节点值总数大于或等于子节点。
完全二叉树一般可采用顺序存储的方式。
算法思想:
1.将初始待序列{a[0],a[1]...a[n]}构建成大根堆,此堆为初始的无序区。
2.把堆顶元素a[0]与堆尾元素a[n]交换,则得到新的无序序列{a[0],a[1]...a[n-1]}及新的有序区{a[n]}
3.将新的无序序列调整为大根堆。
重复2,3,直至有序区的元素个数为n。


//将完全二叉树调整为大根堆
前提:i节点的子树已经为大根堆。(因此建立大根堆时,需要从最后一个非叶子节点往上遍历)

//a数组的长度为len,需调整的节点下标为i。

void swap(int *a, int *b)
{
   int tmp=*a;
   *a=*b;
   *b=tmp;
}
void adjust_heap(int a[], int len, int i)
{
  int lchild=2*i+1;
  int rchild=2*i+2;
  int max=i;            //max标记父节点,左右节点,三者之间最大值的下标。
  if(i <= (len/2-1)){   //最后一个非叶子节点的下标为len/2 -1
    if(lchild < len && a[max] < a[lchild])
      max=lchild;
    if(rchild < len && a[max] < a[rchild])
      max=rchild;
      
    if(max != i){      //i节点比左右某一子节点小,则需交换a[i],a[max]的取值
      swap(a+i,a+max); //交换后可能会破坏了子树的堆结构,所以仍需调整子树。swap(&a[i],&a[max]);
      adjust_heap(a,len,max);//递归调用
    }
  }
}

或者采用非递归的方式:循环

void adjust_heap(int a[], int len, int i)
{
  int lchild=2*i+1;
  int rchild=2*i+2;
  int max=i;            //max标记父节点,左右节点,三者之间最大值的下标。
  while(i <= (len/2-1)){   //最后一个非叶子节点的下标为len/2 -1
    if(lchild < len && a[max] < a[lchild])
      max=lchild;
    if(rchild < len && a[max] < a[rchild])
      max=rchild;
      
    if(max != i){      //i节点比左右某一子节点小,则需交换a[i],a[max]的取值
      swap(a+i,a+max); //交换后可能会破坏了子树的堆结构,所以仍需调整子树。swap(&a[i],&a[max]);     
      i=max;lchild=2*i+1;rchild=2*i+2;//检查子树
    }
    else{
      break;          //i节点本身就比两个子节点取值大
    }
  }
}

建立大根堆函数:

void build_heap(int a[], int len)  //从下往上建立堆
{
  int begin=len/2-1; //最后一个非叶子节点的下标为len/2 -1
  for(int i=begin; i>=0; i--)
  {
    adjust_heap(a,len,i);
  }
}

堆排序函数:

void heap_sort(int a[], int len)
{
  build_heap(a,len);         //步骤1:建立大根堆
  for(int i=len-1;i>0;i--)
  {
    swap(&a[i],&a[0]);       //步骤2:交换堆顶和堆尾元素
    adjust_heap(a,i,0);      //步骤3:调整剩余的堆为大根堆
    
  }
}


【归并排序】
分治法的典型应用
算法思路:将数组分为2个子数组a,b。如果这两个数组组内数据都为有序,则问题转化为
合并两个有序的数列。至于如何让a,b两个数组组内都为有序,则将a,b各自继续进行划分,
类推,直到组内只有一个元素时,可视为组内有序,再合并相邻两个小组。

先递归分解数列,再合并数列,即为归并排序。

//合并有序a[],b[]到c[]中。

void merge(int a[],int len1,int b[],int len2,int c[])
{
  int i,j,k;           //i,j,k分别对应a,b,c的下标
  i=j=k=0;
  while(i<len1 && j<len2)
  {
    if(a[i] <= b[j])
      c[k++]=a[i++];
    else
      c[k++]=b[j++];
  }
  while(i<len1) {c[k++]=a[i++];} //只剩下a数组未结束
  while(j<len2) {c[k++]=b[j++];} //只剩下b数组未结束
}
//合并两个有序数列{a[first]...,a[mid]}、{a[mid+1]...a[last]}

//first,mid,last均为a数组的下标。注意:要将排序后的数组元素从辅助数组copy至原数组a中。

void mergearry(int a[],int first,int mid,int last,int c[])  
{
   int i=fist,j=mid+1;k=0;
   while(i<=mid && j<=last)
   {
     if(a[i]<=b[j])
       c[k++]=a[i++];
     else
       c[k++]=b[j++];
   }
   while(i<=mid) {c[k++]=a[i++];}
   while(j<=last) {c[k++]=b[j++];}
   //此时数组c的长度为k
   for(i=0;i<k;i++)
   {
     a[first+i]=c[i];
   }
}

归并排序函数:

void mergesort(int a[],int first,int last,int tmp[])
{
  if(first<last){
    int mid=(first+last)/2;
    mergesort(a,fisrt,mid,tmp);
    mergesort(a,mid+1,last,tmp);
    mergearry(a,first,mid,last,tmp);
  }
  
}

//应该避免在多次调用的函数中临时new分配数组,因此不在mergearray,mergesort中分配临时辅助数组。
  或者初始化一个静态全局变量。
bool mymergesort(int a[],int len)
{
  int *p=new int[len];
  if(p==NULL)
    return false;
  mergesort(a,0,len-1,p);
  delete[] p;
  return true;
}
时间复杂度:
 公式:T[n]=2T[n/2]+O(n) ==> T[n]=O(nlogn) 即该算法的最优、最差、平均时间复杂度均为:O(nlogn)
空间复杂度:
 关于递归:递归实际上是在执行到递归函数时,将现有的函数中现场保存入栈,执行完函数后,恢复现场。
           即需要注意哪些数据是必须要存入栈的。例如:mergesort函数中的mid的每次取值,都需入栈。
 递归层数决定了栈的深度,与递归时压入栈的数据占用的空间相对应。
 归并的空间复杂度为临时数组加上递归压入栈的深度:n+logn ==> O(n)
 
[非递归实现归并排序](待补充...)

【c、c++关于排序的库函数】(待补充)


附:

【二分查找】(顺序存储结构、关键字大小有序)
int binarySearch(int array[], int len, int key)//返回对应下标
{
  int low=0,high=len-1;
  int mid;
  while(low <= high)
  {
    mid=(low+high)/2;
    if(key==mid) {return mid;}
    if(key<mid) {high=mid-1;}
    if(key>mid) {low=mid+1;}
  }
  return -1;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值