经典算法案例1 排序--快速排序 &归并排序

快速排序 :

先划分好了 再合并 而快速排序的划分很讲究 所以合并很简单(就是不需要合并就已经有序了的地步) 这点与归元正好相反

我理解的原理:

找到一个中间值 把数组划分为左边是小于等于他的 右边是大于他的

然后再左边递归 右边递归 最后就会变成一个有序的数组 这就是我认为的合并很简单

代码大概是:

 private static void quickHelper(int[] array, int left, int right) {
        if(left >= right){
            //此时数组为空,或者只有一个元素不用排序
            return;
        }
        int index = partition(array,left,right);//第一次划分 返回的index为第一个元码
        quickHelper(array,left,index-1);//左边划分
        quickHelper(array,index+1,right);//右边划分
    }

 

一遍单向扫描法(主动动的指针只有一个):

 

 

 

以第一个元素为元码 然后 探索指针为 left 下标为1 bigger指针为right 下标为array.length-1

若探索指针比 元码小 则 left++;

若探索指针比元码大 则 先交换left与right 然后right--;

终点就是两个指针交错 如下图:

 

这时再把right与元码交换即可 这样元码在划分点了 他左边<=他    右边>他

再递归下去即可

left为探索指针 right为bigger指针

/**
  * @arr 待排序的数组
  * @begin 需要partition的起始下标
  * @end 需要partition的末尾下标
  * @return 返回pivot所在位置下标
  */
int partition2(int arr[], int begin, int end){
    int pivotIndex = begin;
    int pivot = arr[pivotIndex];
    swap(arr, pivotIndex, end);
   
    int big = begin - 1; // index of smaller element
    for (int small = begin; small <= end - 1; small++){
        // 遇到一个元素小于pivot
        if (arr[small] <= pivot){
            big++;
            swap(arr, big, small);
        }
    }
    swap(arr, big + 1, end);
    return big + 1;
}

 

双指针扫描法(两个指针一起动):

 

 

 

同样的3个指针分布

单指针只是左边走 而右边只用来交换

而双指针则是一起走的 left<=主元时 一直++  直到停下来

          right<主元时 一直++   直到停下来

停下来的进行交换

 

 

代码如下:
public class selectK {
    public static void main(String[] args) {
        int[] a = {4,2,3,5};
        quicksort(a,0,3);
        for(int x:a){
            System.out.println(x);
        }
       int b = select(a,0,a.length-1,2);

        System.out.println(b);

    }
   

    public static void quicksort(int[] a,int p,int r){

        if(p<r){
            int q =partition2(a,p,r);
            quicksort(a,p,q-1);
            quicksort(a,q+1,r);
        }
    }

    //双指针遍历交换 分区
    public static int partition2(int[] a,int p,int r){
        int pivot = a[p];
        int left = p+1;//扫描指针
        int right = r;//右侧指针
        while (left <= right){
            while (left <= right && a[left] <= pivot) left++;
            while (left <= right && a[left] > pivot)  right--;
            if (left < right){
                swap(a,left,right);
            }
        }
        swap(a,p,right);
        return right;
    }

    //交换函数
    public static void swap(int[] a,int p,int r){
        int temp = a[p];
        a[p]=a[r];
        a[r]=temp;

    }
}

 

 

 算法优化

  1. 在递归到规模比较小时,使用选择排序/插入排序代替。
  2. 选取一个比较靠谱的pivot。(取first,取last,取middle,取三者的中)
  3. 使用循环代替递归。

 

归并排序 :

    /**
     * 归并排序:
     * 把数组分成两部分。每两部分再分成两部分排序,递归。
     * 一直分成每部分只有一个元素。
     * 再合并。就变成整体有序了。
     *
     *
     * 时间复杂度:O(nlogn)
     * 空间复杂度:O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序
     * 稳定性:归并排序是稳定的排序算法,temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
     * 这行代码可以保证当左右两部分的值相等的时候,先复制左边的值,这样可以保证值相等的时候两个元素的相对位置不变。
     * 
     * @param arr
     */
    public static void guibingSort(int[] arr) {
        sort(arr, 0, arr.length-1);
    }
 
    public static void sort(int[] arr, int left, int right){
        if(left == right){
            return;
        }
        int mid = left + ((right - left) >> 1);
        sort(arr,left,mid);
        sort(arr,mid+1,right);
        merge(arr,left,mid,right);
    }
    /**
     * 将两个分离开数组合并
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] tmp = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        //        比较左右两边的数据,小的放在tmp里面
        while (p1 <= mid && p2 <= right) {
            if (arr[p1] < arr[p2]) {
                tmp[i++] = arr[p1++];
            } else {
                tmp[i++] = arr[p2++];
            }
            //            人家的写法,也就是用了一个三目运算
//                        tmp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        //        循环结束后。吧另外一个剩下的全部插入到临时数组后面
        while (p1 <= mid) {
            tmp[i++] = arr[p1++];
        }
        while (p2 <= right) {
            tmp[i++] = arr[p2++];
        }
//        把合并好的数组放回原来的数组
        for (int j = 0; j < tmp.length; j++) {
            arr[left + j] = tmp[j];
        }
    }

 

 

算法案例

➢调整数组顺序使奇数位于偶数前面:输入-个整数数组,调整数组 中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。要求时间复杂度为O(n).

 快排思路 -》以及这里使用了双指针

package algorithm.foroffer.top20;

import java.util.Arrays;

/**
 * Created by liyazhou on 2017/5/26.
 * 面试题14:调整数组顺序使奇数位于偶数前面
 * 题目:
 *      输入一个整数数组,实现一个函数来调整该数组中数字的顺序,
 *      使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
 *
 * 问题:
 *      1. 排序
 *
 * 思路:
 *      1. 快速排序的思想
 *      2. 用 A、B 两个指针分别指向数组的开始和末尾
 *          A 指针往右移动,若发现偶数则停止移动
 *          B 指针往左移动,若发现奇数则停止移动
 *          若 A 指向偶数,B 指向奇数。交换两者的位置
 *          直到 A、B 指针相遇
 *
 */
public class Test14 {

    public static void reorderOddEven(int[] array){
        if (array == null) return;
        int first = 0;
        int second = array.length-1;

        while (first < second){
            // 从左往右,找到第一个偶数
            while(((array[first] & 1) == 1) && first < second) first ++;
            while(((array[second] & 1) == 0) && second > first) second --;
            // 从右往左,找到第一个奇数
            if (first < second){
                int tmp = array[first];
                array[first] = array[second];
                array[second] = tmp;
            }
        }
    }

    public static void main(String[] args){
        int[] array = {1, 2 ,3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(Arrays.toString(array));
        Test14.reorderOddEven(array);
        System.out.println(Arrays.toString(array));
    }

}

 

➢第k个元素;以尽量高的效率求出一个乱序数组中按数值顺序的第K个元素值

13495  k=3 则输出5

快排划分思路  ——》快排划分了会这样          小于元码有3个  元码 大于元码有5个  那么第4个元素就是元码 那假如我要找第2个呢 那2<元码下标+1 我是不是要在左边找 要找第5个就在右边找 这样递归下去

 public static int select(int[] a,int p,int r,int k)就是了 调用了快排的划分
public class selectK {
    public static void main(String[] args) {
        int[] a = {4,2,3,5};
        quicksort(a,0,3);
        for(int x:a){
            System.out.println(x);
        }
       int b = select(a,0,a.length-1,2);

        System.out.println(b);

    }
    /*
    @param arr数组 p探索指针 r最右侧指针 k按顺序的第几个数
    //第k个元素;以尽量高的效率求出一个乱序数组中按数值顺序的第
    //K个元素值
     */
    public static int select(int[] a,int p,int r,int k){
        int q = partition2(a,p,r);//主元下标 用快排
        int qK = q - p + 1;//主元是第几(k)个元素
        if (qK ==k) return a[q];
        else if(k<qK)return select( a, p,q-1, k);
        else return select(a,q+1,r,k-qK);//这里的k是重新算的
    }

//这个是快排
public static void quicksort(int[] a,int p,int r){ if(p<r){ int q =partition2(a,p,r); quicksort(a,p,q-1); quicksort(a,q+1,r); } } //双指针遍历交换 分区 public static int partition2(int[] a,int p,int r){ int pivot = a[p]; int left = p+1;//扫描指针 int right = r;//右侧指针 while (left <= right){ while (left <= right && a[left] <= pivot) left++; while (left <= right && a[left] > pivot) right--; if (left < right){ swap(a,left,right); } } swap(a,p,right); return right; } //交换函数 public static void swap(int[] a,int p,int r){ int temp = a[p]; a[p]=a[r]; a[r]=temp; } }

 

 

➢超过一半的数字:数组中有一个数字出现的次数超过了数组长度的-半,找出这个数字。

 

➢最小可用ID :在非负数组(乱序)中找到最小的可分配的id (从1开始编号) ,数据量1000000

 

2道逆序对的题目

➢合并有序数组∶给定两个排序后的数组A和B,其中A的末端有足够

的缓冲空间容纳B。编写一个方法,将B合并入A并排序

 思路:3个指针  过龙门的方式即可  A12389指针1- - -门current指针3

                B234指针2

➢逆序对个数︰一个数列,如果左边的数大,右边的数小,则称这

两个数位一个逆序对。求出一个数列中有多少个逆序对。

 归并

posted @ 2021-01-04 16:21  数码暴农  阅读(437)  评论(0)    收藏  举报
TOP