排序算法

1.冒泡排序

      每一次相邻的两个数比较,大数沉底,小数上升,完成一趟,可以让最小的数到达顶端,之后对剩下的【0,i-1】进行相同的排序过程,直到最后排序完成

void BetterBubble(int[] Array,int Size){
   int i,j,tmp;
   for(i=0;i<Size-1;i++){
      for(j=0;j<Size-1-i;j++){
          if(小) 交换;
      }
   }
}

    优化:双向冒泡排序,一边排最大,一边排最下,直到两个指针 low 大于 high 

    最佳情况:输入是一个正序

    最差情况:输入是一个反序

2.选择排序

    无论什么数据都是一个 n 平方的复杂度

    工作原理是:首先在未排序的序列中寻找一个最大或者最小的元素,存放到排序序列的起始位置,然后再从剩余的未排序元素中继续寻求最大或者最下的元素,然后放到已经排序好的元素的后边,直到排序完毕

void SelectSort(int Size, int[] Array){
    int i,j;
    for(i=0;i<Size-1;i++){
        for(j=i+1;j<Size){
            if(Array[i]>Array[j]) { 记住小的索引}
        }
        交换最小索引和 i 的数据;
    }
}

3.插入排序

直接插入排序是一种最简单的排序方法,基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的,记录增 1 的有序表

编程语言

假设有一组无序的序列 R0,R1,...,Rn-1

1)将 R0 视为已经排好序的序列

2)依次把 R1 到 Rn-1 插入到已经排好队的序列中,所以需要一个外部循环,从 1 扫描的 n-1

3)插入的过程。从后往前在排好序的序列中查找合适的位置,需要一个内部循环,从 i-1 到 0

核心代码

public void insertSort(int[] list){
//从第二个数开始遍历
    for(int i = 1;i<n;i++){
        int tmp = list[i];
        int j = 0;
        for( j =i-1;j>=0&&tmp<list[j];j-- ){
            list[j+1] = list[j];
        }
        list[j+1] = tmp;
    }
}

时间复杂度

数据正序时,复杂度为 O(N)

数据反序时,复杂度为O(N2)

平均为 O(N2)

空间复杂度

需要一个临时变量来存放要插入的值,所以为O(1)

算法稳定性

不需要改变相等数值元素的位置,所以是稳定的算法

4.希尔排序

又称缩小增量排序,是一种插入排序

基本思想是把记录按照步长分组,对每组记录采用直接插入排序的方法进行排序,随着步长逐渐减小,分成的组包含的信息越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,排序完成,如下为示例图

核心代码

public void shellSort(int[] list){
int gap = list.length/2;
while( 1 <= gap){
    //把距离为 gap 的元素编为一个组,扫描所有的组
    for(int i = gap,i<list.length;i++){
        int j = 0;
        int temp = list[i];
        //对距离为 gap 的元素组进行排序
        for(j=i-gap;j>=0 && temp<list[j];j=j-gap){
            list[j+gap]=list[j];}
        list[j+gap]=temp;
    }
    gap=gap/2;
}
}

时间复杂度

步长的选择影响复杂度

最好的步长是(1,5,19,41,109,...)

用这样的步长希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时,希尔排序还是比快速排序慢

直接插入排序和希尔排序比较

直接插入是稳定的,希尔是不稳定的

直接插入排序更适合原始记录是基本有序的集合

希尔排序的比较次数和移动次数都比直接插入排序少,当 N 越大时,效果越明显

希尔排序中,增量序列 gap 最后一个步长必须是 1

直接插入排序适用于链式存储结构,希尔排序不适用链式存储结构 

5.快速排序

基本思想是通过一趟排序将要排序的数据分割成独立的两部分,分割点左边是比它小的数,右边是比它大的数。然后按照这个方法对着两部分数据分别进行快速排序,递归进行。如下图所示。

核心代码

public int division(int[] list,int left,int right){
    int base = list[left];
    while(left < right){
        //序列右端开始,向左遍历,直到找到小于base的数
        while(left<right && list[right]>=base)
            right--;
        list[left] = list[right];
        while(left<right && list[left]>=base)
            left++;
        list[right] = list[left];
   }
    list[left] = base;
    return left;
}

private void quickSort(int[] list,int left,int right){
    //左下标一定小于右下标
    if(left<right){
        int base = division(list,left,right);
        quickSort(list,left,base-1);
        quickSort(list,base+1,right);
    }
}

时间复杂度

数据越随机分布,快速排序性能越好,数据越接近有序,快速排序性能越差

平均时间复杂度为 Nlog2(N)

空间复杂度

每次分割时需要一个空间存储基准值,大概需要 Nlog2(N)次的分割,所以需要相同的空间

6.堆排序

堆是一颗顺序存储的完全二叉树

其中每一个节点的关键字都不大于其孩子节点的关键字,叫小根堆

每个节点的关键字都不小于其孩子节点的关键字,叫大根堆

设一个元素在数组中以 R[i] 表示,那么它的左孩子节点为 R[ 2 * i + 1 ],右孩子节点为 R [ 2 * i + 2 ] ,父 节点是 R[ (i-1)/2]

一个完全二叉树的最后一个非终端节点是第 n/2 个元素

思想:根据初始数组构造初始堆(构造一个完全二叉树,所有的父节点都比孩子节点值大),每次交换第一个和最后一个元素(最大值),然后把剩下的元素重新调整为大根堆

核心代码

public void HeapAdjust(int[] array,int parent,int length){
    int temp = array[parent];//temp 保存当前父节点
    int child = 2*parent + 1;// 先获得左孩子
    while(child<length){
        // 如果有右孩子节点,并且右孩子节点比左孩子大,则选择右孩子节点
        if(child + 1 < lendth && array[child]<array[child + 1])
            child ++;
        // 如果父节点的值大于孩子节点,直接结束
        if( temp > array[child])
            break;
        // 把孩子节点的值赋给父节点
        array[parent] = array[child];
        // 选取孩子节点的左孩子节点,继续向下筛选
        parent = child;
        child = 2*parent + 1 ;
    }
    array[parent] = temp;
}

public void heapSort(int[] list){
    //循环建立初始堆
    for(i = length/2 ;i>=0 ; i-- ){
        HeapAdjust(list,i,list.length);
    }    
    //进行 n-1 次循环,完成排序
    for(int i = list.length -1 ; i>0; i--){
        //最后一个元素和第一个元素进行交换
        int temp = list[i];
        list[i] = list[0];
        list[0] = temp;
        //筛选 R[0] 节点,得到 i-1 个节点的堆
        HeadAdjust(list,0,i); 
    }
}    

时间复杂度

堆的存储是顺序的。因为堆所对应的二叉树是完全二叉树,完全二叉树通常采用顺序存储方式。

当想得到一个序列中第 k 个最小元素之前的部分排序序列,最好采用堆排序

平均情况为 nlog2(n)

7.归并排序

基本思想是将数组分成两组,依次类推,当分出来的小组只有一个数据时,可以认为这个小组达到有序,然后再依次合并相邻的两个小组就可以了。

时间复杂度为 O(nlogn)

class MergeSort:
    #auxarray 用来中间排序的存储
    def sort2(self,array,length):
        auxarray = [0]*length
        self.mergesort(array,auxarray,0,length-1)
    #进行归并排序
    def mergesort(self,array,auxarray,begin,end):
        if begin < end:
            mid = (begin+end) // 2
            #对左边递归进行归并排序
            self.mergesort(array,auxarray,begin,mid)
            #对右边递归进行归并排序
            self.mergesort(array,auxarray,mid+1,end)
            #归并左边和右边的排好序的数组
            self.merge(array,auxarray,begin,end,mid)
    #合并两个排序数组
    def merge(self,array,auxarray,begin,end,mid):
        i = begin 
        j = mid + 1 
        k = 0
        while i <= mid and j <=end:
            if array[i] <= array[j]:
                auxarray[k] = array[i]
                i = i + 1
                k = k + 1 
            else:
                auxarray[k] = array[j]
                j = j + 1
                k = k + 1
        while i <= mid:
            auxarray[k] = array[i]
            k = k + 1
            i = i + 1
        while j <= end:    
            auxarray[k] = array[j]
            k = k + 1 
            j = j + 1
        i = 0
        j = begin 
        #将合并后的数组复制到之前的数组中
        while j != (end+1):
            array[j] = auxarray[i] 
            i = i + 1
            j = j + 1  

if __name__ == '__main__':
    sort1 = MergeSort()
    sort1.sort2([1,7,3,2,4,7,3],7)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值