数据结构九大排序算法

一. 排序算法
首先我们先来确定一下什么是稳定性:通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在位置前,排序后Ai还是要在Aj位置前。
也就是排序前后两个相等的元素的前后顺序没有发生改变
其次我们来了解一下顺序存储和链式存储:链式存储适用于在较频繁地插入、删除、更新元素时,而顺序存储结构适用于频繁查询时使用。 顺序存储结构和链式存储结构的优缺点: 空间上 顺序比链式节约空间。是因为链式结构每一个节点都有一个指针存储域。 存储操作上: 顺序支持随机存取,方便操作

稳定性
不稳定排序:快选堆希(快速排序、选择排序、堆排序、希尔排序)
稳定排序: 插冒归计基 (简单插入排序、冒泡排序、归并排序、基数(桶)排序)

是否基于比较
比较:插入排序、选择排序、归并排序、快速排序、堆排序

非比较:基数排序、计数排序、桶排序

在这里插入图片描述

1.直接插入排序

直接插入排序当其逆序时为最坏情况
算法思路:前面有序,然后从有序后面一个元素取出在前面有序部分找到位置k,然后k后面有序元素全部后移一位。
在这里插入图片描述
2.折半插入排序

其实只是直接插入排序的优化,仅仅只是在找到k位置使用了折半查找。
主要算法:
在这里插入图片描述
3.希尔排序–缩小增量排序

希尔排序就是把他们分成一个个小段,然后每段进行直接插入排序。
在这里插入图片描述

void shellsort( Elemtype A[],int i)
{
   for(int dk=n/2;dk>=1;dk=dk/2)
   {
     for(int i=dk+1;i<=n;i++)
     {
       if(A[i]<A[i-dk]
       {
            A[0]=A[i];
          for(int j=i-dk;j>0&&A[0]<A[j];j-=dk)
             A[j+dk]=A[j]
          A[j+dk]=A[0];
       }
     }
   }
}

4. 冒泡排序
最好情况下的时间复杂度:如果元素本来就是有序的,那么一趟冒泡排序既可以完成排序工作,比较和移动元素的次数分别是n-1 和 0 ,因此最好情况的时间复杂度为O(n)。在本节代码中应该再加入一个flag才能实现提前退出程序,使得最少比较次数是n-1 ,否则就一直比较O(n^2)次

最差情况的时间复杂度:如果数据元素本来就是逆序的,进行n-1趟排序,所需比较和移动次数分别为n(n-1)/2和3n(n-1)/2。因此最坏情况子下的时间复杂度为O(n^2)。

平均时间复杂度为 O(n^2)
平均空间复杂度为 O(1)
稳定

void Bubblesort( Eletype A[],int n)
{
   for(int i=0;i<n-1;i++)
   {
         for(int j=i+1;j< n ;j++ )
         {
            if(A[j-1]>A[j])
              swap(A[j-1],A[j])
         }
   }
}

5.快速排序–挖坑
基本思路是:快速排序其实也是一种规划,初始化任意找一个基准元素pivot一般是第一个A[1]元素,将其赋值给pivot,标记 low 为划分块的第一个元素,high为最后一个元素,high由左向右移动直到找到第一个比pivot小的元素,赋值给A[1],然后改元素就空出来了,再把low右移直到遇到比pivot大的值,然后把它赋值给A[high],这样A[low]又空出来了,以此类推,可以写出代码为:

int Partition(Elemtype A[],int low ,int high)
{
  ElemType pivot = A[low];
  while(low<high)
  {
     while(low<high&& A[high]>=pivot)
        high--;
     A[low]=A[high];
     while(low<high&& A[low]<=pivot)
         low++;
     A[high]=A[low];
  }
      A[low]=pivot;
      return low;  //high也一样
}
void Quicksort(Elemtype A[], int low,int high)
{
   if(low<high)
   {
     int pivotpos = Partition(A,low,high);
     Quicksort(A,low,pivotpos-1)
     Quicksort(A,pivotpos+1,high)
   }


}

由于一次快排并不能把它全部排序完成,所以需要多次排序,每一次的时间复杂度为O(high-low+1);
并且该排序是不稳定的如:5 7 5 8 6 9 7 4 3 经过一次排序可以变成 3 4 5 1 5 9 8 6 7,其中5的相对位置发生了改变因此它是不稳定的排序方法。

1)数组已经是正序(same order)排过序的。
2)数组已经是倒序排过序的。
3)所有的元素都相同(1、2的特殊情况)
它最坏时间复杂度:
最坏时间复杂度 O(n^2),最坏空间复杂度为O(n)

最好、平均时间复杂度:O(nlog2n)
最坏、平均时间复杂度:O(log2n)
平均空间复杂度为O(logn),logn为递归的深度,因为每次递归传参right ,left会有空间占用,为logn次
6.直接选择排序

基本思想:假设前面 A[1]~A[i-1] 有序,在后面 i ~ n选择其中最小的元素与A[i] 交换,然后A[1] ~ A[ i ]有序,以此类推,该方法与直接插入排序有类似之处。

void Selectsort(Elemtype A[],int n)
{
   for(int i=0;i<n-1;i++)
   {
       int min=i;
       for(int j=i+1;j<n;j++)
       {
       if(A[j]<A[min]
       min=j;
       }
       if(min!=j)
       swap(A[i],A[min]);
   }

}

7.堆排序
总的时间复杂度为 O(nlog2n),不稳定
如 2 1 one ,建立堆后 为 2 1 one,然后输出2,2与one交换位置,one 1 2 .由于one = 1 所以顺序不变然后输出one,最后输出1,所以输出的顺序为 2 one 1 ,不稳定。
适用于顺序和链式存储。
叙述如下:
在这里插入图片描述
初始建堆时间复杂度为O(n),下面为建堆以及向下调整的过程代码:

//代码从A[1]开始排序,A[0]当作哨兵。计算长度时,也得把A[0]计算进去
void BuildHeap(int A[],int len)
{
   for(int i=len/2;i>0;i--) //注意千万不能等于零,否则会无限循环!
   {
   Adjust(A,i,len);
   }
}
void Adjust(int A[],int k,int len)
{
  A[0]=A[k]; //A[0]存储了父节点的值,然后根据该值确定该值应该放置的位置。
  for(int i=2*k;i<len;i*=2)
  {
     if(A[i]<A[i+1])
       i++;
     if(A[0]>=A[i])
        break; //如果根节点大于子节点就直接退出,否则还要循环直到找到叶节点或者大于子节点
     else
        {  
          A[k]=A[i];
          k=i;
        }
  }
  A[k]=A[0];//将父节点的值赋值回去,由于子节点可能已经覆盖了父节点所以再用原本父节点的值覆盖子节点完成交换。
}

那么建立完了堆就开始根据堆输出正确的排序吧。
主要的思想为:输出堆顶元素,然后将堆顶元素与末尾元素进行交换,再次调用调整函数,调整堆,但是这次调整的元素数量应该减一,因为原来的堆顶元素已经被输出了并且置于末尾不需要再对其进行调整(向下调整)。
下面为输出堆代码:

void Heapsort(int A[],int k,int len)
{
   BulidHeap(int A[],int len);
   for(int i=len;i>1;i--)
   {
      printf("%d",A[1]);//输出堆定元素
      swap(A[1],A[i]);
      Adjust(A,1,i-1);  //重新调整堆
   }

}

堆的插入节点:直接将其插在最后节点,然后向上进行调整。代码如下:

void Adjustup(int A[],int len)//向上调整
{
    A[0]=A[k];  //其中k为末尾序号
    for(int i=k; i>0;i=i/2)
    {
       if(A[0]>A[i/2])
          A[i]=A[i/2];
       else 
       {
          A[i]=A[0];
          break;
       }
    }
}

8.归并排序
归并排序有两种算法
第一种: 递归算法:

void Merge(int left,int mid,int right,int A[],int B[])
{
   `/* 把已经排好序的A[left] ~ A[mid] 和 A[mid+1] ~ A[right]合并为一个有序数组B*/
   int i=left,k=left,j=mid+1;
   while(i<=mid&&j<=right)
   {
      B[k++]=(A[i]<=A[j])?A[i++]:A[j++];    
   }
   while(i<=mid)
      B[k++]=A[i++];
   while(j<=right)
      B[k++]=A[j++];
}
void MergeSort(int A[],int B[],int left,int right )
{
     int mid=(right+left)/2;
     if(left<right)
     {
        MergeSort(A,B,left,mid);
        MergeSort(A,B,mid+1,right);
        Merge(left,mid,right,A,B);
     }
}

**第二种:**迭代算法
迭代算法主要思路就是:把原始数组A[] 一对一对进行比较组成新的有序对,然后赋值给B[],之后又把B[]的有序队之间进行比较赋值给A[]最终全体赋值完毕。

void Part(int n,int len,int A[],int B[])
{
   int i=1;
   while(i<=n-2*len+1)
   {
     int mid=i+len-1;
     Merge(i,mid,i+2*len-1,A,B);
     i=i+2*len;
   }
   if(i+len-1<n)//代表仅有一队和不完全的一队,让他们组合
   {
     i=i-2*len;
     Merge(i,i+len-1,n,A,B)
   }
   else   //仅有不完全的一队,直接赋值给B[]
     for(int t=i;t<=n;t++)
       B[t]=A[t];
}
void Mergesort(int n,int A[],int B[])
{
  int len=1;
  while(len<n)
  {
     Part(n,len,A,B);//把A归并赋值给B
     len=len*2;//即使len在此时已经大于n了那么接下来的函数将不会执行其他语句,而是直接执行最后的赋值语句
     Part(n,len,B,A);//把B归并给A
     len=len*2;
  }
}

9.基数排序—桶排序

10.最终总结
在这里插入图片描述
选择排序算法:

在这里插入图片描述
二. 外部排序
如图,总共四个归并段,所以4*Tis,由一个无序序列读写到最终有序需要读写三次,归并两次。
在这里插入图片描述
最佳归并树,败者树都可以实现外部排序。
在这里插入图片描述
m叉归并树应该补充的虚段为 m-(n-1)%(m-1)- 1个

外部排序详细解说可以看笔者另外一篇文章
外部排序专题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值