01-排序算法

1.选择排序

  思想:把第一个数据跟数组的其他数据比较一遍,选出最小数,放在第一个位置,然后第二个位置的数据跟数组的其他数据比较一遍,选出最小数,放在第二个位置。以此类推。

代码:从小到大排序 ,时间复杂度o(n^2)

for(int i = 0; i<t-1 ; i++){
    int min = i;
    for(int j=i+1; j<t; j++){
       min =  nums1[min] > nums1[j] ?  j : min;
    }
    if(min != i){
       int swap = nums1[i];
       nums1[i] =  nums1[min];
       nums1[min] = swap; 
    }
}

2.冒泡排序

思想:比较相邻的两个数,左边的数大于右边的数则交换,遍历完最大的数会在最后一个位置,接下来第二遍遍历的长度减1,依次比较相邻的两个数并交换,以此类推。

代码:从小到大排序,时间复杂度o(n^2)

for(int i = 0; i<t; i++){
   for(int j=0; j<t-i-1; j++){
      if(nums1[j] > nums1[j+1]){
          int swap = nums1[j];
          nums1[j] =  nums1[j+1];
          nums1[j+1] = swap; 
      }
   }
}

3.插入排序

思想:将一个记录插入到已经排好序的有序表中,从而行程一个完整的,记录+1的有序表。

代码:从小到大排序,时间复杂度 o(N^2)。

for(int i = 1; i<t; i++){
    for(int j=i-1; j>=0 && nums1[j+1] < nums1[j]; j--){
         int swap = nums1[j+1];
         nums1[j+1] =  nums1[j];
         nums1[j] = swap;
    }
}

4.归并排序

思想:简单的一个递归,左边排好序,右边排好序,将有序表合并成一个有序表。

代码:时间复杂度O(nlogn),空间复杂度T(n)。

//两个有序表合并
public void merge(int[] nums1, int m, int[] nums2, int n) {
       int i = 0;
       int p1 = 0;
       int p2 = 0;
       int[] help = new int[m+n];
       while(p1 <= m-1 && p2 <= n-1){
           help[i++] =  nums1[p1] <= nums2[p2] ? nums1[p1++] : nums2[p2++];
       }
       while(p1<= m-1){
           help[i++] = nums1[p1++];
       }
        while(p2 <= n-1){
           help[i++] = nums2[p2++];
       }
       for(int j = 0 ; j< m+n ; j++){
           nums1[j] = help[j];
       }
}

5.快速排序

思想:采用的分治的思想,随机选择一个数作为基准,数组中左边的部分小于基准数,右边的数据大于基准数,然后采用递归的方式对左右部分重复上述操作。

代码:时间复杂度O(NlogN)。

   public static void quickSort(int[] arr,int l, int r){
        if(l >= r){
            return;
        }
        swap(arr, r,l+(int)(Math.random()*(r-l+1)));
        int[] partition = partition(arr, l, r);
        quickSort(arr, l, partition[0]);
        quickSort(arr, partition[1], r);
    }
    public static int[] partition(int[] arr,int l, int r){
        int p1 = l-1;
        int p2 = r;
        for (int i = l; i < p2; i++) {
            if(arr[i] < arr[r]){
                swap(arr, ++p1, i);
            }else if(arr[i] > arr[r]){
                swap(arr, --p2, i--);
            }
        }
        swap(arr, p2, r);
        return new int[]{p1, p2+1};
    }
    public static void swap(int[] arr,int position1, int position2){
        if(position1 != position2){
            arr[position1] = arr[position1]^ arr[position2];
            arr[position2] = arr[position1]^ arr[position2];
            arr[position1]=  arr[position1]^ arr[position2];
        }
    }

6.堆排序

堆排序是指利用堆这种数据结构所涉及的排序算法。(二叉)堆是一个数组,它可以被看成是一个近似的完全二叉树,二叉堆分为两种形式,最大堆和最小堆,最大堆是指父节点大于等于子节点,最大堆的最大元素在根节点。最小堆是父节点小于等于子节点,最小堆的最小元素在根节点。堆排序算法中用的是最大堆,最小堆通常用于构造优先队列。

6.1维护堆的性质
public static void heapify(int[] data, int i){
    int max = i;
    int l = 2*i+1;
    int r = 2*i+2;
    if(l<= data.length-1 && data[l] > data[i]){
        max = l;
    }
    if(r<= data.length-1 && data[r] > data[max]){
        max = r;
    }
    if(max != i){
        data[max] = data[i] ^ data[max];
        data[i] = data[i] ^ data[max];
        data[max] = data[i] ^ data[max];
        heapify(data, max);
    }
}

上个方法是对于一棵以i为节点,大小为n的数,除了i节点以外的节点都是符合最大堆,把i节点和其他节点变成最大堆。上面方法的时间代价包括:调整A[i]和A[max]为\Theta(1),和以i为根节点的子树的方法运行时间。每个孩子的子树最大为2/3n(最坏情况在树的叶子节点半满时),所以可以用递归来刻画运行时间:T(n) \leq T(2/3n) + \Theta(1)。根据master公式得出 O(lgn)。

6.2建堆

存在一个数组,可以用heapify方法,把除了叶子节点的每一个节点自右而左,自下而上遍历。使其成为最大堆。

public static void buildMaxHead(int[] data){
    for (int i = data.length/2; i>=0; i--){
        heapify(data, i);
    }
}

每次调用heapify的时间复杂度是O(lgn),需要调用O(n)次,所以总的时间复杂度为O(nlgn)。这个上界虽然正确但不是渐近紧确的。

可以观察到不同节点运行heapify的时间与该节点的树高有关系,而且大部分节点的高度很小。利用下面性质可以得到更紧确的界:包含n个元素的堆的高度为[lgn],高度为h的堆最多包含[n/2^{h+1}]个节点。

在一个高度为h的节点上运行heapify的代价是O(h),可以将buildMaxHead的总代价表示为:

\sum_{h=0}^{lgn}\left [ \frac{n}{2^{h+1}} \right ]O(h) = O(1/2*n\sum_{h=0}^{lgn}\left [ \frac{h}{2^{h}} \right ]) = O(n)。下面是详细计算过程。

对于x\neq1,\sum_{k=0}^{n}x^{k} =  1+ x^{1}+x^{2}+...+x^{n}  =  \frac{x^{n+1}-1}{x-1}

当n是无限的,且|x|<1时,x_{n+1}几乎为0,所以\sum_{k=0}^{\infty }x^{k} =  \frac{1}{1-x}

对上面公式进行微分,可以得到\sum_{k=0}^{\infty }kx^{k} =  \frac{x}{(1-x)^{2}}。下面是详细计算。

O(1/2*n\sum_{h=0}^{lgn}\left [ \frac{h}{2^{h}} \right ])是以x=\frac{1}{2}代入上面公式计算得出\sum_{h=0}^{\infty }\left [ \frac{h}{2^{h}} \right ] = 2。所以得出O(n)。

6.3堆排序算法

堆排序算法的思想是:构建一个最大堆,然后把最大的元素跟最后一个元素互换后,把堆的节点数量减1,然后剩下的节点执行heapfipy,重复上面过程完成排序。因为构建最大堆的时间复杂度为O(n),然后互换元素后heapfipy的时间复杂度为O(lgn),然后会调用n-1次heapfipy。所以时间复杂度为O(nlgn)。

以上几种算法的对比。

算法时间复杂度空间复杂度稳定性
选择排序O(n2)O(1)不稳定
冒泡排序O(n2)O(1)稳定
插入排序O(n2)O(1)稳定
归并排序O(nlgn)O(n)稳定
快速排序O(nlgn)O(lgn)不稳定
堆排序O(nlgn)O(1)不稳定

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值