认识复杂度和简单排序算法

简介: 认识复杂度和简单排序算法

选择排序


选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置。 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。 以此类推,直到全部待排序的数据元素的个数为零。


/**
 * 选择排序 时间复杂度O(n^2)
 * 从数组第一个元素开始遍历,后一个和前一个元素比较大小,将最小的元素的下标赋值给 minIndex
 * 然后执行swap方法,替换下标为i和minIndex元素的位置,这样每次选择出来的最小的数组就排在前面了
 * 最外层for循环结束以后 整个数组就是从小到大排序的了
 */
  public static void selectionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
      return;
    }
    //总共要经过N-1轮比较
    for (int i = 0; i < arr.length - 1; i++) {
      int minIndex = i;
      //每轮需要比较的此数N-i
      for (int j = i + 1; j < arr.length; j++) {
        minIndex = arr[j] < arr[minIndex] ? j : minIndex;
      }
      swap(arr, i, minIndex);
    }
  }
  public static void swap(int[] arr, int i, int j) {
      //交换方法一
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp


冒泡排序


冒泡排序的英文Bubble Sort,是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。冒泡排序算法的原理如下:

比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。


冒泡排序是稳定的排序方法。时间复杂度O(n^2), 空间复杂度O(1)。


/**
 * 冒泡排序 时间复杂度O(n^2)
 * 最外层for循环遍历 n 次,内层for循环先从第一个元素开始和下一个元素比较
 * 然后从第二个元素开始和第一个元素比较
 * 一直到倒数第二个元素和倒数第一个元素比较结束
 * 这样第一次循环就把最大的放在了最后面
 * 继续循环,倒数第二大的放在倒数第二个位置
 * 以此类推.........
 * 最外层for循环结束以后 整个数组就是从小到大排序的了
 */
 public static void bubbleSort(int[] arr) {
    if (arr == null || arr.length < 2) {
      return;
    }
    for (int e = arr.length - 1; e > 0; e--) {//在哪个范围上要完成这一轮了
      for (int i = 0; i < e; i++) {
      //具体玩轮
        if (arr[i] > arr[i + 1]) {
          swap(arr, i, i + 1);
        }
      }
    }
  }
  public static void swap(int[] arr, int i, int j) {
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
  }


插入排序


插入排序的原理: 一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增 1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 。 选择排序的基本思想是:

将未排序的元素一个一个地插入到有序的集合中,插入时把所有有序集合从后向前扫一遍,找到合适的位置插入。


插入排序是稳定的排序方法。时间复杂度O(n^2), 空间复杂度O(1)。


public static void insertionSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        //0~0有序的
        //0~i想有序
        // 最外层for循环从第二个元素开始直到最后一个元素
        for(int i = 1;i < arr.length;i++){
            //0~i做到有序最内层for循环从第一个元素开始,
            //如果j不越界 而且 j下标元素比 j+1 元素大
            for(int j = i - 1;j >= 0 && arr[j] > arr[j+1];j--){
                swap(arr,j,j + 1);
            }
        }
    }
    public static void swap(int[] arr,int i,int j){
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }


二分法


  1. 在一个有序数组中,找某个数是否存在


public static boolean exist(int[] sortedArr,int num){
        if(sortedArr == null || sortedArr.length == 0){
            return false;
        }
        int L = 0;
        int R = sortedArr.length - 1;
        int mid = 0;
        while (L < R){
            //>>是比特操作,可以看做是除2
            mid = L + ((R - L) >> 1);
            if(sortedArr[mid] == num){
                return true;
            }else if(sortedArr[mid] > num){
                R = mid -1;
            }else {
                L = mid + 1;
            }
        }
        return sortedArr[L] == num;
    }


  1. 在一个有序数组中,找>=某个数最左侧的位置


public static int nearestIndex(int[] arr,int value){
        int L = 0;
        int R = arr.length -1;
        int index = -1;
        while (L < R){
            int mid = L + ((R- L >> 1);
            if(arr[mid] >= value){
                index = mid;
                R = mid -1;
            }else {
                L = mid + 1;
            }
        }
        return index;
    }


  1. 局部最小值问题

局部最小值定义:

  1. 最左边边界处,如果最左边的数小于左边第二个数,则最左边为局部最小值。
  2. 最右边边界处,如果最右边的数小于右边第二个数,则最右边为局部最小值。
  3. 中间处,如果一个数小于它两边的数,则这个数就是局部最小值。
  1. 首先,先观察最左边的第一个和第二个数,判断是否是局部最小值。
  2. 然后,再观察最右边第一个和第二个数,判断是否是局部最小值。
  3. 如果最左边和最右边有一个是局部最小值,则此题结束。
  4. 否则,说明一个什么问题?说明左边的曲线是向下递减的,右边的曲线也是向上递增的,对吧?那么,中间是必定有局部最小值的,这是在高数最大值最小值中有类似的定义,对吧?
  5. 然后,直接用二分法,选取中间的数,判断其是不是局部最小值?如果不是的话,观察他左右趋势,假如,它左边一个数比他小,说明什么?说明它左边是递增的,而此时最左边的递减的,通过上面分析4的判断,说明它左边存在局部最小值,然后,再其左边区间,再次二分法,直到找到局部最小值。


package paixu.class01;
public class Code09_FindOneLessValueIndex {
    public static void main(String[] args) {
        int[] arr = { 6, 5, 3, 4, 6, 7, 8 };
        int[] arr1 = {1,2,3,4,5,6,7,8};
        int[] arr2 = {2,1,3,4,5,6,8,7};
        System.out.println("(中间情况)arr局部最小索引为:" + getLessIndex(arr) + "\t值为" + arr[getLessIndex(arr)]);
        System.out.println("(左边情况)arr1局部最小索引为:" + getLessIndex(arr1) + "\t值为" + arr1[getLessIndex(arr1)]);
        System.out.println("(右边情况)arr2局部最小索引为:" + getLessIndex(arr2) + "\t值为" + arr2[getLessIndex(arr2)]);
    }
    public static int getLessIndex(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1; // no exist
        }
        //1.先观察最左边的第一个和第二个数,判断是否是局部最小值
        if (arr.length == 1 || arr[0] < arr[1]) {
            return 0;
        }
        //2.再观察最右边第一个和第二个数,判断是否是局部最小值
        if (arr[arr.length - 1] < arr[arr.length - 2]) {
            return arr.length - 1;
        }
        //3.局部最小值在中间
        int L = 1;
        int R = arr.length - 2;
        while (L <= R) {
            int mid = L + ((R-L) >> 1);
            if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid+1]) {
                return mid;
            }
            if (arr[mid] < arr[mid+1]) {
                R = mid - 1;
            }else {
                L = mid + 1;
            }
        }
        return -1;
    }
}


异或


异或运算:相同为0,不同为1


1687268432008.png


或者:(异或也可以理解为无进位相加)


1687268438197.png


性质:0和任何数异或都等于这个数

4)交换两个数的值(不用额外变量)

前提,a和b在内存是2块独立的区域


1687268446960.png


力扣T136


1687268453681.png


题解:


class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int a=0;
        for(int i=0;i<nums.size();i++){
            a=a^nums[i];
        }
        return a;
    }
};


力扣T260


1687268416310.png


题解:


public static  void printOddTimeNum1(int[] arr){
        int eor = 0;
        //假设a和b是两个出现奇数次的数字
        //先把每个数 都异或起来  最后 eor = a^b (a 异或 b)
        //先从头异或到尾
        for(int cur : arr){
            eor ^= cur;
        }
       //eor = a ^ b
        //eor != 0
        //eor必然有一个位置上是1,选哪个位置是1,选最右侧的为1
        //因为 a ≠ b ,所以 eor 肯定有一位是 1 ,异或相异为 1
        //那么 取出这一位 1
        // ~ 取反 +1  再和 eor 与运算
        int rightOne = eor & (~eor + 1);//提取出最右的1
        //~eor+1,eor取反加1
        int onlyOne = 0; //eor'
        //再遍历数组  所有数字 只要第几位是 0  那么最后  onlyOne 就是 a或者b
        for(int cur : arr){
            //只要1侧
            if ((cur & rightOne)== 0){
                onlyOne ^= cur;
            }
        }
        // eor ^ onlyOne 就是剩下那个数字了
        System.out.println(onlyOne+" "+(eor ^ onlyOne));
    }


a&(~a+1)  获取最右的1


1687268408903.png



相关文章
|
7月前
|
机器学习/深度学习 存储 算法
【算法与数据结构】复杂度深度解析(超详解)
【算法与数据结构】复杂度深度解析(超详解)
【算法与数据结构】复杂度深度解析(超详解)
|
7月前
|
存储 算法
数据结构与算法:复杂度
数据结构: 数据结构是用于存储和组织数据的方式,以便可以有效地访问和修改数据。不同的数据结构适用于不同类型的应用,并且具体的数据结构可以大幅影响程序的性能。数据结构分为两大类:线性数据结构和非线性数据结构。 算法: 算法是完成特定任务的一系列操作步骤,是解决问题的明确规范。算法的效率通常通过时间复杂度和空间复杂度来评估,即算法执行所需的时间和空间资源。
|
7月前
|
机器学习/深度学习 存储 算法
如何评判算法好坏?复杂度深度解析
如何评判算法好坏?复杂度深度解析
126 0
|
6月前
|
机器学习/深度学习 存储 算法
1 .算法的复杂度(超全)
1 .算法的复杂度(超全)
|
2月前
|
移动开发 算法 前端开发
前端常用算法全解:特征梳理、复杂度比较、分类解读与示例展示
前端常用算法全解:特征梳理、复杂度比较、分类解读与示例展示
32 0
|
3月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
70 4
|
3月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
61 1
|
4月前
|
算法
【初阶数据结构】复杂度算法题篇
该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 kmodn 个元素会移动至数组头部,其余元素向后移动 kmodn 个位置。
30 1
|
5月前
|
机器学习/深度学习 存储 算法
【数据结构】算法的复杂度
算法的时间复杂度和空间复杂度
87 1
【数据结构】算法的复杂度