算法(入门笔记)

本文介绍了几种常见的排序算法,如选择排序、冒泡排序、插入排序以及更高效的希尔排序和归并排序。强调了算法的重要性,并指出时间复杂度在评估算法效率中的关键作用。还探讨了分治策略在快速排序和归并排序中的应用。
摘要由CSDN通过智能技术生成

一直用框架会降智,算法才是根本

特性

有穷性:能算完

确定性:能算准

可行性:钱

基于数学建模通过代码实现解决问题

时间复杂度:快

算法运行时间只依赖于数据的输入规模

编程实现,设计实现

排序

选择排序

适用数组(适用,但不是很适用)链表

A[n]

i=0,j=0,t=0;

for(i<n;i++){
	
    for(j=i+1;j<n;j++){
        if(A[j]<A[i]){
            t=A[i];
            A[i]=A[j];
            A[j]=t;
        }
    }
}

时间复杂度四舍五入是n^2,原式 n^2/2-n/2

冒泡排序

数组

A[n]

i=0,j=0,t=0;

for(i=n-1;i>0;i--){
    for(j=1;j<i;j++){
        if(A[j]<A[j+1]){
            t=A[j];
            A[j]=A[j+1];
            A[j+1]=t;
        }
    }
}

也是双循环,所以复杂度也是n^2

目前来说,影响时间复杂度的就是循环所有数据的次数,且层数大于次数。

插入排序

时间复杂度N^2

从后往前比较,每个元素和前一位比较,后边的小就放前面一位。也是相

邻的两两相比,方向和冒泡是相反的。

package com.gg.demo2; 
import java.util.Arrays; 
public class Insertion { // 交换函数 
    public static void swap(int[] array, int a, int b) { 
        array[a] = array[a] + array[b]; 
        array[b] = array[a] - array[b]; 
        array[a] = array[a] - array[b]; 
    }
    public static void main(String[] args) { 
        int [] a = {8,7,6,5,4,3,2,1}; 
        //数组有多少个位置, 就需要循环多少次每个位置最终决定放谁 
        for (int i = 0; i < a.length; i++) { 
            //j 表示当前元素索引 , j-1 当前元素前一个位置的索引 
            // 所以 j-1 临界值是0 所以j>0 
            // j--: 因为当前位置的元素 每次都是和它前一个进行比较 
            for (int j = i; j >0; j--) { 
                //如果当前位置元素 比前一个位置的元素小就交换 
                if(a[j]<a[j-1]){ 
                    swap(a, j, j-1); 
                } 
            } 
        }
        System.out.println(Arrays.toString(a)); 
    } 
}

数组链表

n^2

public static void swap(int[] array, int a, int b) { 
    array[a] = array[a] + array[b]; 
    array[b] = array[a] - array[b]; 
    array[a] = array[a] - array[b]; 
}
public static void main(String[] args) { 
    int [] a = {8,7,6,5,4,3,2,1}; 
    //数组有多少个位置, 就需要循环多少次每个位置最终决定放谁 
    for (int i = 0; i < a.length; i++) { 
        //j 表示当前元素索引 , j-1 当前元素前一个位置的索引 
    // 所以 j-1 临界值是0 所以j>0 
        // j--: 因为当前位置的元素 每次都是和它前一个进行比较 
        for (int j = i; j >0; j--) { 
            //如果当前位置元素 比前一个位置的元素小就交换 
            if(a[j]<a[j-1]){ 
                swap(a, j, j-1); 
                           } 
        } 
    }
    System.out.println(Arrays.toString(a)); 
}

每个数比较前一个,小的放前面

默认是双循环了\

希尔排序(Shell Sort)

时间复杂度: N^1.3

自定义一个增量gap , 但是这个增量是递减的, 一般取gap = len /2 除任意常数,len越大可以让常数增大一些。

希尔排序可以有效的减少 元素位置之间的 交换次数, 通过小分组组内的插入排序使其交换次数变少。

  1. 自定义增量。

    int n;

  2. 按增量的数值进行分组。

    a=b+n;a放左边,b放右边

  3. 每个组内要进行插入排序。

  4. 当gap =1 时,只分1组进行插入排序了。

    减小上面的n,不断减小到1(单位数)

  5. 当gap=0时 , 程序结束了,得到结果。

    因为在gap=1时所有相邻的元素都是大小相邻的了

    java:

    package com.gg.demo2; 
    import java.util.Arrays; 
    public class Shell { 
        // 交换函数 
        public static void swap(int[] array, int a, int b) { 
            array[a] = array[a] + array[b]; 
            array[b] = array[a] - array[b]; 
            array[a] = array[a] - array[b];
            }
        public static void main(String[] args) { 
            int [] a = {9,8,7,6,5,4,3,2,1}; 
            // gap : 初始值是 长度/2 ,每次递减一半, 循环条件 gap !=0 
            for (int gap= a.length/2; gap>0; gap/=2) { 
                //确定从哪开始的位置元素是需要进行插入排序的, 
                //因为每个小分组的第一个元素前面没元素了,没法比, 所以都需要把它抛出去 
                //发现规律, 正好需要进行比的那个元素们起始索引值就是gap 
                // a.length: 从gap值索引开始往后的一致到a.length-1都需要在其自己组内进行比 较。 
                for (int i =gap; i < a.length; i++) { 
                    //进行插入排序 //当前元素索引是j, 要比的前一个元素索引是j-gap //j=j-gap :下次进行比较的 当前元素 和 前一个元素 也是差了 gap步 
                    // j-gap是 索引 要>=0 
                    // a[j]<a[j-gap] 如果当前分组中, 当前位置比它前一个位置的小,就交换。 
                    // 前一个位置和当前位置 相差gap 步 
                    for (int j = i;j-gap>=0&&a[j]<a[j-gap] ; j=j-gap) { 					swap(a, j, j-gap); 
                                                                      } 
                } 
            }System.out.println(Arrays.toString(a)); 
        } 
    }
    

分治策略

比如: 二分查找法、快速排序、归并排序 都是 基于 分治策略的。

三步:

1.拆分大问题 。

2.把所有的小问题进行求解。

3.把所有的解进行合并。

分治策略一定会使用递归的。

递归就是 函数自身调用了自身, 有递归就一定要有出口,否则程序无法返回。

分: 分就是拆分的过程。

治: 求解过程和合并解的过程。

归并排序(Merge Sort)

时间复杂度:N*LogN

把序列进行拆分,不是真正拆解原数组, 而是根据索引范围进行拆解。

拆到元素长度为1时就停止,开始进行排序了。 拆的过程其实就是在定义合的过程。

也是使用了二分法进行拆分的。

图片在我的电脑上丢了,有空再补吧。

在治的过程中, 我们发现, 两个带子序的数组进行合并,有一个数组子序算法。

并不是两个数组在排序合并,而是用索引进行表示的所谓范围。

定义三个索引, i 指向 开头 start, j指向结尾end , k指向 了临时数组的开头。

还需要定义一个 临时数组。步骤如下:

数组子序算法:

  1. 创建一个和 原数组等大的 临时数组。

  2. 定义i,j,k . 同上。

  3. 三种情况,两个范围的数组分别称为A,B

  4. A没走完,B没走完, 才会比较, 把小的方到temp数组中。

  5. A没走完, B走完了, 把A剩下的都放到temp

  6. A走完了, B没走完, 把B剩下的都放到temp

4.把temp中排序完了的结果copy给原数组, 覆盖掉原数组中的值。

package com.gg.demo2; 
import java.util.Arrays; 
public class Merge { 
    // 交换函数 
    public static void swap(int[] array, int a, int b) {
        array[a] = array[a] + array[b]; 
        array[b] = array[a] - array[b]; 
        array[a] = array[a] - array[b]; 
    }
    public static void main(String[] args) { 
        int[] a = {8,7,6,5,4,3,2,1};
        int start = 0 ; 
        int end = a.length-1;
        split(a,start, end);
        System.out.println(Arrays.toString(a)); 
    }
    //分的过程 
    private static void split(int[] a, int start, int end) { 
        //出口 
        if(start>=end){ 
            return ; 
        }
        // TODO 自动生成的方法存根 
        int mid = (start+end)/2; 
        //拆出来的左面 
        split(a, start, mid);
        //拆出来的右面 
        split(a, mid+1, end); 
        //合并解 
        //start-------mid , mid+1----end 分别表示每次合并的是哪两个。 
        //start, mid , end都是变化的。 
        merge(a,start,mid,end); 
    }
    private static void merge(int[] a, int start, int mid, int end) { 
        // TODO 自动生成的方法存根
        //1创建 临时数组
        int [] temp= Arrays.copyOf(a, a.length); 
        //2 定义i,j,k 笔记中有 
        int i = start ; 
        //A数组头 
        int j = mid+1 ;
        //B数组头 
        int k = 0 ; 
        //临时数组头 
        //3种情况 
        while(i<=mid && j<=end){ 
            if(a[i]<a[j]){ 
                temp[k++] = a[i++]; 
            }
            else{temp[k++]=a[j++]; 
                } 
        }
        while(i>mid && j<=end){ 
            temp[k++]=a[j++]; 
        }
        while(i<=mid && j>end){
            temp[k++]=a[i++]; 
        }
        //4 覆盖原数组 
        // j=0:J就是个计数器了,k是往临时数组中放了多少个元素,没清零。j要往元数组中覆盖k次元素。
        //i=start: 元素组每次都不一样, 但是都是从start开始的。 a[i]表示的是原数组的开始元素 
        for (i=start,j=0;j<k; j++,i++) {
            a[i] = temp[j]; 
        } 
    }
}

第二种方法:

package com.gg.demo; 
import java.util.Arrays; 
public class GuiBing2 { 
    public static int[] merge(int[] left, int[] right){ 
        int [] temp = new int[left.length+right.length]; 
        int i = 0 ; 
        //1 
        while(left.length>0 && right.length>0){
            if(left[0]<right[0]){
                temp[i++] = left[0];
                left = Arrays.copyOfRange(left, 1, left.length);
            }
            else{
                temp[i++] = right[0]; 
                right= Arrays.copyOfRange(right, 1, right.length);
            }
        }
        //2 
        while(right.length>0){ 
            temp[i++] = right[0]; 
            right= Arrays.copyOfRange(right, 1, right.length); 
        }
        while(left.length>0){
            temp[i++] = left[0]; 
            left = Arrays.copyOfRange(left, 1, left.length);
        }
        return temp ;
    }
    public static int [] split(int [] a){ 
        int [] temp = Arrays.copyOf(a, a.length); 
        int mid = temp.length/2 ;
        if(a.length<2){
            return temp ; 
        }
        int [] left = Arrays.copyOfRange(temp, 0, mid);
        int [] right = Arrays.copyOfRange(temp, mid, a.length); 
        //
        return merge(split(left), split(right));
    }
    public static void main(String[] args) {
        int [] a = {8,7,6,5,4,3,2,1};
        int [] res = split(a); 
        System.out.println(Arrays.toString(res));
    } 
}

桶排序

非基于比较,时间复杂度非常低。空间换时间。

1计数排序

时间复杂度:O(n+k),n是数据规模,k是最大数据元素

排序规则,

1,遍历原数组

2,求出原数组中 的 最大值。 最大值K决定了 桶数组的长度。

int [] ]helper = new int [source.lengt+1];

3, 将原数组元素的值 放入到 桶元素对应下标 ++操作。

4, 重新遍历桶数组, 完成排序。

不是基于 比较 进行排序

桶, Bucket

桶 是用来 计数的

统计 每个数值在原数组中 出现过的次数。

计算排序的时间复杂度

N 数据规模 O(N+k)

K 就是 原数组中的最大值

用空间换取时间

适用于 最大值 和数据规模,差距并不是很大的时候

package com.gg.demo2; 
import java.util.Arrays;
public class Counting { 
    public static int getMin(int [] a){
        int min = getMax(a); 
        for (int i : a) {
            if(min>i){ 
                min = i ; 
            } 
        }
        return min ; 
    }
    public static int getMax(int [] a){ 
        int max = Integer.MIN_VALUE; 
        for (int j : a) {
            if(max <j){ 
                max = j ; 
            }
        }
        return max; 
    }
    public static void main(String[] args) { 
        int [] a = {8,8,8,4,6,5};
        //1 获取原数组中的最大值
        int max = getMax(a); 
        //1.2 获取原数组中的最小值 
        int min = getMin(a);
        //2 设计我桶们
        int [] bucket = new int [max+1-min];
        //3 扫描原数组 桶开始 计数 
        for (int i = 0; i < a.length; i++) { 
            bucket[a[i]-min]++; 
        }
        //4 扫描桶们, 如果当前的编号的桶值不是0的话,说明,有值。 一直到当前编号的桶=0; 
        int k = 0 ;
        //指向了原数组
        for (int i = 0; i < bucket.length; i++) { 
            while(bucket[i]>0){ 
                a[k++] = i+min ; 
                bucket[i] -- ; 
            }
        }
        System.out.println(Arrays.toString(a));
    } 
}

基数排序

时间复杂度:O(n*k)

k是最大数的位数。

基数: 位 个 、十、百、千、万…

桶: 当前的原数组中进制是多少 来决定, 比如 十进制 => 10桶, 0-9

​ 1.先确定原数组中的最大值

​ 2. 确定这个最大值的位数, 如果是4位, 那么 进行4次循环。

​ 3. 取第d位 依次放入 对应的桶中,

​ 4.遍历所有的桶,覆盖掉原数组, 直到d =最大数的位。

package com.gg.demo2; 
import java.util.Arrays;
public class Radix { 
    public static int getMax(int [] a){
        int max = Integer.MIN_VALUE; 
        for (int i : a) {
            if(max<i){
                max = i ; 
            } 
        }
        return max ; 
    }
    public static int getMaxLen(int max){ 
        return String.valueOf(max).length(); 
    }
    //按位 取某个数。 
    public static int getDigit(int number , int d){ 
        int [] array = {1,10,100,1000,10000,100000,1000000,10000000,100000000}; 
        // number / (int)Math.pow(10, d) %10 ;
        return number / array[d] %10 ; 
    }
    public static void main(String[] args) { 
        int [] a = {9999,876,456,222,22,11,67,13,9,2,1,7,4,4,4,0,0,0};
        //1 先要取到 数组中的最大值
        int max = getMax(a); 
        //2 取到最大位的位数(长度)
        int maxLen = getMaxLen(max); //3 循环MaxLen次 
        for (int i = 0; i < maxLen; i++) { 
            //制作桶 //10个元素,每个元素都是一个一位数组 , 每个一位数组长度都是3 
            //最极端情况, 按位排序,原数组中所有的元素 当前位都一样。
            Integer [][] bucket = new Integer [10][a.length];
            //3.1 遍历原数组, 按照余数放到对应的桶里。
            for (int j = 0; j < a.length; j++) {
                //将原数组当前元素 放入到对应的桶里 
                int yu =getDigit(a[j], i); 
                bucket[yu][j] = a[j] ; 
            }
            int k = 0 ;
            //指向了原数组
            //扫描桶, 覆盖原数组 
            for (Integer [] temp : bucket) {
                for (int j = 0; j < temp.length; j++) { 
                    if(temp[j]!=null){ 
                        a[k++] = temp[j];
                    }
                } 
            } 
        }
        System.out.println(Arrays.toString(a)); 
    } 
}

桶排序(哈希)

可以对小数也能排序。

计算每个桶之间的跨度

(最大值-最小值)/(桶数量-1)

桶: 表示的是 每个数值的范围

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56Ytfrkx-1678366564895)(C:\Users\zhangyunli\AppData\Roaming\Typora\typora-user-images\image-20211025140616031.png)]

​ 1. 定义使用多少个桶。 划分 每个桶的区间。

​ 2. 每个桶桶内要进行 排序 。Collections.sort() 底层使用了归并

3. 把所有的桶的数据按顺序输出即可 得到最后的排序结果。
package com.gg.demo2; 
import java.util.ArrayList;
import java.util.Arrays; 
import java.util.Collections; 
import java.util.LinkedList; 
import java.util.List; 
public class Bucket { 
    public static void main(String[] args) { 
        double[] a = { 3.14, 3.33, 1.11, 56.5, 77.8, 122.1, 8, 7, 6, 5, 4, 3, 3, 2, 1 };
        sort(a); 
        System.out.println(Arrays.toString(a));
    }
    public static double getMin(double[] a){
        double min = getMax(a);
        for (double i : a) { 
            if(min>i){ 
                min = i ; 
            }
            }
        return min ; 
    }
    public static double getMax(double[] a){
        double max = Integer.MIN_VALUE;
        for (double i : a) { 
            if(max<i){ max =i ; 
                     } 
        }
        return max ;
    }
    //(max - min )/(bucketCount -1) 求出 桶之间的区间跨度 
    private static void sort(double[] a) {
        double max = getMax(a);
        double min = getMin(a); 
        // TODO 自动生成的方法存根 
        // 我希望 使用 5个桶 。 
        int bucketCount = 5 ;
        //生产 5个 桶 ,把5个桶放到一个大集合里 
        List<LinkedList<Double>> buckets = new ArrayList<LinkedList<Double>>();
        for (int i = 0; 
             i <5; i++) { 
            buckets.add(new LinkedList<Double>());
        }
        // 开始 遍历 原数组 , 根据原数组当前元素的值, 来区分它需要落到哪个桶里。
        for (double number : a) {
            // 求出这个number 需要落到 哪个桶编号里。 
            //min 值肯定落到0号桶 里 
            // number -min 就是 一共大了多少数值 ,
            // 多出来的数值 除 每个桶数值跨度 === 一共能跨多少个桶。
            int idx = (int) ((number-min)*(bucketCount -1)/(max - min ));
            //从桶集合中按编号找到这个桶, 把元素放到这个桶里 
            buckets.get(idx).add(number); 
        }
        // 把5个桶 ,桶内自行排序
        for (LinkedList<Double> bucket : buckets) { 
            //底层归并排序
            Collections.sort(bucket); 
        }
        //遍历所有的桶, 从0号桶开始最后一个桶 , 覆盖掉原数组a
        //指向了 原数组 0索引 (开头位置)
        int i = 0 ; 
        for (LinkedList<Double> bucket : buckets) {
            for (Double d : bucket) {
                a[i++] = d;
               }
        } 
    }
} 

快速排序(Quick Sort)

基于 分支策略

是一个不稳定的排序方法。

时间复杂度是 LogN*N ~ N^2

Java的API 都是使用了快速排序。

快速排序 对于 偏向于随机序列 有 优势。

定义一个主元 pivot

所有的数 都和 主元 进行比较

定义了 2个哨兵 指针

i => 当前阶段 比主元小的序列的最后一个位置。

j=> 指向了每次的元素

(1)如果a[j] >=a[pivot] j++的操作。

(2)如果a[j]<a[pivot]

swap(a, i+1,j);

i++;

j++;

一旦 j 走到头, 操作: swap(a, i+1,pivot)

使用三数取中法 解决在选取主元时 避开最大或者最小。

package com.gg.demo; 
import java.util.Arrays; 
public class KuaiPai { 
    public static void getPivot(int [] a , int start , int end){ 
        System.out.println(Arrays.toString(a));
        int number1 = a[start]; 
        int number2 = a[(start+end)/2] ;
        int number3 = a[end]; 
        int [] temp = {number1, number2, number3}; 
        for (int i = 0; i < temp.length; i++) {
            for (int j = i+1; j < temp.length; j++) {
                if(a[i]>a[j]){ swap(temp, i, j); 
                             } 
            }
        }
        a[start] = temp[0];
        a[(start+end)/2] = temp[1]; 
        a[end] = temp[2]; 
        //
        swap(a, (start+end)/2, end-1);
        System.out.println(Arrays.toString(a));
    }
    public static void swap(int []array, int a, int b){
        int temp = array[a]; 
        array[a] = array[b]; 
        array[b] = temp ; 
    }
    public static void sort(int[] a, int start , int end ){
        //当前分支长度==1 递归出口 结束
        if(start>=end){ return ; 
                      }
        getPivot(a, start, end);
        //1 确定 pivot 
        int pivot = end -1 ; 
        //
        int i = start - 1;   
        // 遍历当前start => end所有的元素,和主元比较 
        for (int j = start; j <end-1; j++) {
            //如果 当前位置元素比 主元 小, 
            if(a[j]<a[pivot]){ 
                //就把 当前元素和 i+1位置的元素 交换
                swap(a, j, i+1); 
                // i指针 指向最新的 比主元小的那个数,确保 循环结束时刻和 主元交换。 
                i++ ; 
            }
        }
        //把主元和i+1 位置的元素 互换, 保证左侧都比主元小, 右侧都比主元大。 
        swap(a, pivot, i+1); 
        //左分支 
        sort(a, start, i+1); 
        //右有分支
        sort(a, i+2, end);
    }
    public static void main(String[] args) {
        int [] a = {8,7,6,5,4,3,2,1}; 
        int start = 0 ; 
        int end = a.length-1;
        sort(a,start,end); 
        System.out.println(Arrays.toString(a)); 
    } 
}

堆排序

时间复杂度: N*LogN

堆: 堆积,含义是一定是按照数值的大小进行排列。

大堆:双亲要比孩子数值大。

小堆:双亲要比孩子数值小。

堆被建立出来之后,一定是一个完全二叉树的结构。

往往都是基于大堆的, 最后得到的排序结果是从小到大的。

反之小堆排列的,是从大到小的。

**大顶堆:**arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

**小顶堆:**arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值