常用算法-Java
创始人
2024-03-20 04:15:24
0

基础概念

程序 = 数据结构 + 算法

算法特性:输入、输出、有穷性、确定性、可靠性

算法目标:正确性、可读性、健壮性、运行时间少、内存空间小

时间复杂度

时间复杂度是用来估计算法运行时间的一个单位。

一般来说,时间复杂度高的算法比时间复杂度低的算法慢。

常见时间复杂度(按效率排序):

        O(1) < O(logn) < O(n) < O(nlogn) < O(n^{2}) < O(n^{2}logn) < O(n^{3})

复杂问题的时间复杂度:

        O(n!) < O(2^{n}) < O(n^{n})

如何快速判断算法复杂度(适用绝大部分简单情况):

  • 确定问题规模n:循环次数、数组长度、列表长度
  • 循环减半过程:logn
  • k层关于n的循环:n^{k}
  • 复杂过程:根据算法执行过程判断

空间复杂度

空间复杂度是用来评估算法内存占用大小的单位。

空间复杂度的表示方式与时间复杂度完全一样:

  • 算法使用了几个变量:O(1)
  • 算法使用了长度为 n 的一维列表:O(n)
  • 算法使用了 m 行 n 列的二维列表:O(mn)

空间换时间

递归

递归的两个特点:调用自身、结束条件

汉诺塔问题

大梵天创建世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘;大梵天命令婆罗门把圆盘从下面开始按照大小顺序重新摆放在另一个柱子上;规则是:在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘;64个圆盘移动完毕之日,就是世界毁灭之时。

1个盘子:需要1步,2个盘子:需要3步,3个盘子:需要7步;那么 64个盘子:需要 2^{64}-1=18,446,744,073,709,551,615步

假设移动一次需要1s,那么移动 2^{64}-1 次大概需要 5849亿年

public class HanoiTower {public static void main(String[] args) {function(3, 'A', 'B', 'C');}public static void function(int n, char a, char b, char c) {if (n > 0) {function(n-1, a, c, b);System.out.println("Moving " + a + " to " + c);function(n-1, b, a, c);}}
}

列表查找

查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。

列表查找(线性表查找):从列表中查找指定元素;        输入:列表、待查找元素;        输出:元素下标(未找到元素一般返回None或-1);

顺序查找(时间复杂度:O(n))

也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。内置函数indexOf()。

二分查找(时间复杂度:O(logn))

循环减半查找,时间复杂度是O(logn),但是前提是必须先对列表元素进行排序,排序的时间复杂度 > O(n)。

public class BinarySearch {public static void main(String[] args) {int[] list = new int[]{1,2,3,4,5,6,7,8,9};System.out.println(function(list, 3));}public static int function(int[] list, int val) {int left = 0;int right = list.length - 1;int mid = 0;while (left <= right) {//说明候选区还有值mid = (left + right) / 2;if (list[mid] == val) {return mid;} else if (mid > val) {//待查找的值在mid左侧right = mid - 1;} else if (mid < val) {//待查找的值在mid右侧left = mid + 1;}}return -1;}
}

列表排序

将一组无序的列表变成有序的列表;输入:列表;输出:有序列表;有升序与降序两种排序方式;内置排序函数sort()。

排序Low B三人组

冒泡排序(时间复杂度:O(n^{2}))

列表每两个相邻的数,如果前面的比后面的大,则交换这两个数;一次排序后,则无序区少一个元素,有序区多一个元素;

代码关键点:趟、无序区范围

冒泡排序优化:如果冒泡排序中的一轮排序没有发生任何位置交换,则说明当前列表已经有序,可以直接结束算法,不需要再循环排序。

public class BubbleSort {public static void main(String[] args) {int[] list = new int[]{2,5,7,36,6,7,3,33,24,8,9};System.out.println(Arrays.toString(function(list)));}public static int[] function(int[] list) {int temp;// 外层循环控制比较的轮数for (int i = 0; i < list.length-1; i++) {// 内层循环控制每轮比较的次数boolean exchange = false; // 冒泡排序优化:如果前面几轮已经对整个列表排好顺序,后面不需要再次循环for (int j = 0; j < list.length-i-1; j++) {if (list[j] > list[j+1]) {//如果前面的数比后面的数大,互换位置temp = list[j];list[j] = list[j+1];list[j+1] = temp;exchange = true; // 冒泡排序优化:如果前面几轮已经对整个列表排好顺序,后面不需要再次循环}}System.out.println(Arrays.toString(list));if (!exchange) { // 冒泡排序优化:如果前面几轮已经对整个列表排好顺序,后面不需要再次循环return list;}}return list;}
}

选择排序(时间复杂度:O(n^{2})) 

一轮排序记录最小的数,放到第一个位置;再一轮排序记录无序区列表中最小的数,放到第二个位置。

算法关键点:有序区与无序区、无序区最小数的位置;

public class SelectSort {public static void main(String[] args) {int[] list = new int[]{8,4,2,3,7,1,6,9,5};function(list);}// 简单选择排序: 循环每次拿到最小的数,然后放在一个新的列表里// 缺点:时间复杂度O(n*n),空间复杂度O(2n),所以不推荐使用// 优化选择排序:循环每次拿到最小的数,和无序区第一个数交换位置,避免创建新的空间public static void function(int[] list) {// 外层循环控制比较的轮数int temp;for (int i = 0; i < list.length-1; i++) {// 内层循环控制每轮比较的次数int min_index = i;for (int j = i+1; j < list.length; j++) {if (list[j] < list[min_index]) {min_index = j;}}temp = list[i];list[i] = list[min_index];list[min_index] = temp;System.out.println(Arrays.toString(list)); //打印每次交换的过程}//System.out.println(Arrays.toString(list));//输出最终排序结果}
}

插入排序 (时间复杂度:O(n^{2})) 

初始时手里(有序区)只有一张牌(一个元素),每次摸一张牌(从无序区取一个元素),插入到手里已有牌的正确位置;

public class InsertSort {public static void main(String[] args) {int[] list = new int[]{4,2,5,1,6,7,3,9,6,8};function(list);}public static void function(int[] list) {int temp, j;for (int i = 1; i < list.length; i++) {//摸到牌的下标temp = list[i];j = i - 1; //j指的是手里牌的下标while (j >= 0 && list[j] > temp) {//如果新摸的牌比手里的牌小,手里的牌向右移动list[j+1] = list[j];//手里的牌右移一个位置j -= 1;//比较的下标前移,下一次跟上一个位置的牌比较}list[j + 1] = temp;//比较完成后,把摸到的牌放在空出来的位置System.out.println(Arrays.toString(list));//输出每次插入的过程}//System.out.println(Arrays.toString(list));//输出算法结果}
}

排序 NB 三人组

快速排序 (时间复杂度:O(nlogn)) 

快速排序思路:

(1) 取一个元素p(第一个元素),使元素p归位;(2) 列表被p分成两部分,左边都比p小,右边都比p大;(3) 递归完成排序。

public class QuickSort {public static void main(String[] args) {int[] list = new int[]{5,1,7,2,9,3,8,6,4};sort(list, 0, list.length-1);System.out.println(Arrays.toString(list));}// 快速排序算法public static void sort(int[] list, int left, int right) {if (left < right) {int mid = partition(list, left, right);//中间数左侧列表取第一个元素p并让其归位,分成两部分,左侧比p小,右侧比p大sort(list, left, mid - 1);//中间数右侧列表取第一个元素p并让其归位,分成两部分,左侧比p小,右侧比p大sort(list, mid + 1, right);}}// 让元素p归位,返回p所在位置索引public static int partition(int[] list, int left, int right) {int temp = list[left];while (left < right) {// 从右向左找比temp小的数while (left < right && list[right] >= temp) {right -= 1; //右边指针向左走一步}if (left < right) {list[left] = list[right];//把右边数移到左边空位上left++; //下一轮从左向右:减少一次比较的过程System.out.println(Arrays.toString(list));//打印右边数向p左边移动的过程}// 从左向右找比temp大的数while (left < right && list[left] <= temp) {left += 1; //左边指针向右走一步}if (left < right) {list[right] = list[left];//把左边数移到右边空位上right--; //下一轮从右向左:减少一次比较的过程System.out.println(Arrays.toString(list));//打印左边数向p右边移动的过程}}list[left] = temp;return left;}
}

时间复杂度:每一次归位的时间复杂度是O(n),需要做logn次归位,所以时间复杂度是O(nlogn);

快速排序的问题:递归消耗系统性能、最坏情况(列表元素正好倒序排列,时间复杂为 O(n^{2})),针对最坏情况,可取随机位置数作为p;

堆排序(时间复杂度:O(nlogn)) 

树、二叉树、满二叉树、完全二叉树sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss

树:树是一种可以递归定义的数据结构,比如目录结构;

树是由n个节点组成的集合,1个节点作为根节点,其他节点可以分为m个集合,每个集合本身又是一个子树;

根节点:最顶上的节点;        叶子节点:最底层没有子节点的节点就是叶子节点;

树的深度:从根节点到叶子节点最多有多少层;        树的度:整个树中哪个节点分叉最多,分叉数量就是树的度;

二叉树:树中每个节点最多分两个叉,这种树就是二叉树;

满二叉树:每一层的节点数都达到了最大值,如右图(a);

完全二叉树:叶子节点只能出现在最下层和次下层,并且最下层的节点都集中在该层最左边的若干位置,如右图(b);

二叉树的存储方式:链式存储方式、顺序存储方式

二叉树的顺序存储方式 sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss

父节点和左子节点,下标之间有什么关系?

        0-1,1-3,2-5,3-7,4-9

        可以发现有一定规律:i --> 2i+1

父节点和右子节点,下标之间有什么关系?

        0-2,1-4,2-6,3-8,4-10

        可以发现有一定规律:i --> 2i+2

根据子节点下标找父节点下标:(i - 1) / 2取整,假设列表总长为n,那么最后一个子节点下标为 n-1,那么最后一个非叶子节点下标为 (n-2) / 2;

堆:一种特殊的完全二叉树结构

堆排序过程

  1. 建立堆
  2. 得到堆顶元素 ,为最大元素;
  3. 去掉堆顶,将堆的最后一个元素放到堆顶,然后通过一次向下调整重新使堆有序,把去掉的堆顶元素放到最后一个元素位置;
  4. 堆顶元素称为第二个元素;
  5. 重复步骤3,直到堆变空;
public class HeapSort {public static void main(String[] args) {int[] list = new int[]{9,2,4,6,3,0,5,8,1,7};sort(list);}public static void sort(int[] list) {// 建堆:从下往上,从子堆向父堆开始构建for (int i = (list.length-2)/2; i > -1; i--) {sift(list, i, list.length-1);}System.out.println(Arrays.toString(list)); //打印建完堆后的列表// 挨个出数:把堆顶元素和最后一个元素互换位置,然后从堆顶到倒数第一二元素重新调整结构,变成大根堆,依次重复int temp, j;for (int i = list.length-1; i > -1; i--) {temp = list[0]; //把堆顶元素和堆尾元素互换list[0] = list[i];list[i] = temp;sift(list, 0, i-1); //i-1是新的堆尾}System.out.println(Arrays.toString(list)); //打印堆排序后的列表}// 向下调整:保证始终是一个大根堆(父节点元素值比子节点大)public static void sift(int[] list, int head, int tail) {int i = head; //父节点下标int j = 2 * i + 1; //左子节点下标int temp = list[i]; //把堆顶存起来while (j <= tail) { //避免下标越界if (j + 1 <= tail && list[j + 1] > list[j]) { //存在右子节点,且比左子节点大j = j + 1; //切换为右子节点}if (list[j] > temp) { //如果当前子节点比父节点大list[i] = list[j]; //将子节点元素存在父节点位置// 有可能下一层子节点元素仍然比temp元素大,所以需要继续向下比较,直到最后一个元素i = j; //父节点下移一层j = 2 * i + 1; //找到新的父节点对应的左子节点} else { //如果父节点元素temp更大,把temp就放在原位置,跳出循环break;}}list[i] = temp; //不存在子节点,或父节点元素temp比子节点元素大,将此元素放在原位置}
}

应用场景:top k问题

现在有 n 个数,设计算法得到前 k 个大的数(k < n)。

解决思路:

  • (1) 堆排序后切片 O(nlogn);
  • (2) 排序LowB三人组O(nk);
  • (3) 堆排序优化O(nlogk);
    • a、取列表前 k 个元素建立一个小根堆,堆顶就是目前第 k 大的数;
    • b、依次向后遍历原列表,如果列表中的元素小于堆顶元素则忽略,否则将堆顶元素替换成列表元素,并对堆进行依次调整;
    • c、遍历列表所有元素后,倒序弹出堆顶;
public class TopK {public static void main(String[] args) {int[] list = new int[]{9,2,4,8,6,3,7,1,5,10};sort(list, 3);}public static void sort(int[] list, int k) {// 1、建堆:取前k个元素建立小根堆int[] heap = new int[k];for (int i = 0; i < k; i++) {heap[i] = list[i];}// 将 k 个元素构建成小根堆for (int i = (k-2)/2; i > -1; i--) {siftdown(heap, i, k-1); // i是堆的最后一个非子节点}// 2、遍历列表后续元素,与小根堆堆顶元素比较for (int i = k; i < list.length-1; i++) {if (list[i] > heap[0]) { //如果列表后续元素比小根堆中最小的元素还大,那么替换掉堆顶最小元素heap[0] = list[i]; //替换堆顶最小元素siftdown(heap, 0, k-1); //重新对堆进行调整保证是小根堆}}// 3、小根堆挨个出数进行排序int temp;for (int i = k-1; i > -1; i--) {temp = heap[0];heap[0] = heap[i];heap[i] = temp;siftdown(heap, 0, i-1); //i-1是未排序的最后一个元素下标}System.out.println(Arrays.toString(heap));}// 向下调整:构建小根堆public static void siftdown(int[] list, int head, int tail) {int i = head; //父节点下标int j = 2 * i + 1; //左子节点int temp = list[i]; //把堆顶元素存起来while (j <= tail) { //避免下标越界if (j + 1 <= tail && list[j + 1] < list[j]) { //如果右子节点比左子节点小,切换到右子节点上j = j+ 1;}if (list[j] < temp) { //子节点比父节点小list[i] = list[j]; //将子节点元素存入父节点位置// 有可能还存在下级节点元素比temp小的情况,所以需要继续向下比较,直到最后一个元素i = j; //i下标移到下一级j = 2 * i + 1;} else { //如果父节点元素temp值更小,把temp存在原位置,跳出循环break;}}list[i] = temp; //不存在子节点,或父节点元素temp比子节点元素更小,将元素存在原位置}
}

归并排序

归并:假设现在的列表分成两段,两段都有序,将其合成一个有序列表,这种操作就是一次归并;

归并过程:两个下标分别指向两段列表的第一个元素,两个下标所指的元素进行对比,谁小就拿出来放到一个新的列表,然后拿出的元素下标向后移动一位,继续和另一个列表的第一个元素进行对比,直到两个列表所有元素都取出来存入新列表为止;

使用归并进行归并排序

  1. 分解:将列表越分越小,直至分成一个元素;
  2. 终止条件:一个元素是有序的;
  3. 合并:将两个有序列表归并,依次归并所有组成新的有序列表;

public class MergeSort {public static void main(String[] args) {int[] list = new int[]{1,2,5,7,9,3,4,6,8};int [] result = sort(list, 0, list.length-1);System.out.println(Arrays.toString(result));// merge(list, 0, (list.length-1)/2, list.length-1);}// 归并过程:将一个列表分成两段有序列表,然后合并成一个有序列表public static int[] merge(int[] list, int head, int mid, int tail) {int i = head; //第一段列表首元素int j = mid + 1; //第二段列表首元素int[] tempList = new int[tail - head + 1];int x = 0;while (i <= mid && j <= tail) { //只要两段列表都还有数,进行对比if (list[i] < list[j]) {tempList[x] = list[i];i += 1;} else {tempList[x] = list[j];j += 1;}x += 1;}// while执行完,代表有一段列表已经没有元素了while (i <= mid) { //代表第一段列表还有元素tempList[x] = list[i];i += 1;x += 1;}while (j <= tail) { //代表第二段列表还有元素tempList[x] = list[j];j += 1;x += 1;}System.out.println(Arrays.toString(tempList));return tempList;}// 归并排序public static int[] sort(int[] list, int head, int tail) {if (head < tail) { //代表还有至少两个元素,递归拆分int mid = (head + tail) / 2;sort(list, head, mid); //将左边部分排好序sort(list, mid+1, tail); //将右边部分排好序return merge(list, head, mid, tail); //将两段排好序的列表归并}return list;}
}

希尔排序

计数排序

基数排序

相关内容

热门资讯

汽车油箱结构是什么(汽车油箱结... 本篇文章极速百科给大家谈谈汽车油箱结构是什么,以及汽车油箱结构原理图解对应的知识点,希望对各位有所帮...
美国2年期国债收益率上涨15个... 原标题:美国2年期国债收益率上涨15个基点 美国2年期国债收益率上涨15个基...
嵌入式 ADC使用手册完整版 ... 嵌入式 ADC使用手册完整版 (188977万字)💜&#...
重大消息战皇大厅开挂是真的吗... 您好:战皇大厅这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
盘点十款牵手跑胡子为什么一直... 您好:牵手跑胡子这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游...
senator香烟多少一盒(s... 今天给各位分享senator香烟多少一盒的知识,其中也会对sevebstars香烟进行解释,如果能碰...
终于懂了新荣耀斗牛真的有挂吗... 您好:新荣耀斗牛这款游戏可以开挂,确实是有挂的,需要了解加客服微信8435338】很多玩家在这款游戏...
盘点十款明星麻将到底有没有挂... 您好:明星麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【5848499】很多玩家在这款游戏...
总结文章“新道游棋牌有透视挂吗... 您好:新道游棋牌这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7682267】很多玩家在这款游...
终于懂了手机麻将到底有没有挂... 您好:手机麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...